深入解析Pod對(duì)象(一):基本概念

現(xiàn)在,你已經(jīng)非常清楚:Kubernetes 項(xiàng)目中的最小編排單位是 Pod,而不是容器。將這個(gè)設(shè)計(jì)落實(shí)到 API 對(duì)象上,容(Container)就成了 Pod 屬性里的一個(gè)普通的字段。那么,一個(gè)很自然的問(wèn)題就是:到底哪些屬性屬于 Pod 對(duì)象,而又有哪些屬性屬于 Container 呢?

要徹底理解這個(gè)問(wèn)題,你就一定要牢記我在上一篇文章中提到的一個(gè)結(jié)論:Pod 扮演的是傳統(tǒng)部署環(huán)境里“虛擬機(jī)”的角色。這樣的設(shè)計(jì),是為了使用戶從傳統(tǒng)環(huán)境(虛擬機(jī)環(huán)境)向 Kubernetes(容器環(huán)境)的遷移,更加平滑。

而如果你能把 Pod 看成傳統(tǒng)環(huán)境里的“機(jī)器”、把容器看作是運(yùn)行在這個(gè)“機(jī)器”里的“用戶程序”,那么很多關(guān)于 Pod 對(duì)象的設(shè)計(jì)就非常容易理解了。

比如,凡是調(diào)度、網(wǎng)絡(luò)、存儲(chǔ),以及安全相關(guān)的屬性,基本上是 Pod 級(jí)別的。

這些屬性的共同特征是,它們描述的是“機(jī)器”這個(gè)整體,而不是里面運(yùn)行的“程序”。比如,配置這個(gè)“機(jī)器”的網(wǎng)卡(即:Pod 的網(wǎng)絡(luò)定義),配置這個(gè)“機(jī)器”的磁盤(即:Pod 的存儲(chǔ)定義),配置這個(gè)“機(jī)器”的防火墻(即:Pod 的安全定義)。更不用說(shuō),這臺(tái)“機(jī)器”運(yùn)行在哪個(gè)服務(wù)器之上(即:Pod 的調(diào)度)。
接下來(lái),我就先為你介紹 Pod 中幾個(gè)重要字段的含義和用法。

NodeSelector:是一個(gè)供用戶將 Pod 與 Node 進(jìn)行綁定的字段,用法如下所示:

apiVersion: v1
kind: Pod
...
spec:
 nodeSelector:
   disktype: ssd

這樣的一個(gè)配置,意味著這個(gè) Pod 永遠(yuǎn)只能運(yùn)行在攜帶了“disktype: ssd”標(biāo)簽(Label)的節(jié)點(diǎn)上;否則,它將調(diào)度失敗。

NodeName:一旦 Pod 的這個(gè)字段被賦值,Kubernetes 項(xiàng)目就會(huì)被認(rèn)為這個(gè) Pod 已經(jīng)經(jīng)過(guò)了調(diào)度,調(diào)度的結(jié)果就是賦值的節(jié)點(diǎn)名字。所以,這個(gè)字段一般由調(diào)度器負(fù)責(zé)設(shè)置,但用戶也可以設(shè)置它來(lái)“騙過(guò)”調(diào)度器,當(dāng)然這個(gè)做法一般是在測(cè)試或者調(diào)試的時(shí)候才會(huì)用到。

HostAliases:定義了 Pod 的 hosts 文件(比如 /etc/hosts)里的內(nèi)容,用法如下:

apiVersion: v1
kind: Pod
...
spec:
  hostAliases:
  - ip: "10.1.2.3"
    hostnames:
    - "foo.remote"
    - "bar.remote"
...

在這個(gè) Pod 的 YAML 文件中,我設(shè)置了一組 IP 和 hostname 的數(shù)據(jù)。這樣,這個(gè) Pod 啟動(dòng)后,/etc/hosts 文件的內(nèi)容將如下所示:

cat /etc/hosts
# Kubernetes-managed hosts file.
127.0.0.1 localhost
...
10.244.135.10 hostaliases-pod
10.1.2.3 foo.remote
10.1.2.3 bar.remote

其中,最下面兩行記錄,就是我通過(guò) HostAliases 字段為 Pod 設(shè)置的。需要指出的是,在 Kubernetes 項(xiàng)目中,如果要設(shè)置 hosts 文件里的內(nèi)容,一定要通過(guò)這種方法。否則,如果直接修改了 hosts 文件的話,在 Pod 被刪除重建之后,kubelet 會(huì)自動(dòng)覆蓋掉被修改的內(nèi)容。
除了上述跟“機(jī)器”相關(guān)的配置外,你可能也會(huì)發(fā)現(xiàn):
凡是跟容器的 Linux Namespace 相關(guān)的屬性,也一定是 Pod 級(jí)別的。
這個(gè)原因也很容易理解:Pod 的設(shè)計(jì),就是要讓它里面的容器盡可能多地共享 Linux Namespace,僅保留必要的隔離和限制能力。這樣,Pod 模擬出的效果,就跟虛擬機(jī)里程序間的關(guān)系非常類似了。
舉個(gè)例子,在下面這個(gè) Pod 的 YAML 文件中,我定義了 shareProcessNamespace=true:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  shareProcessNamespace: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    stdin: true
    tty: true

這就意味著這個(gè) Pod 里的容器要共享 PID Namespace。
而在這個(gè) YAML 文件中,我還定義了兩個(gè)容器:一個(gè)是 nginx 容器,一個(gè)是開(kāi)啟了 tty 和 stdin 的 shell 容器。
在 Pod 的 YAML 文件里聲明開(kāi)啟它們倆,其實(shí)等同于設(shè)置了 docker run 里的 -it(-i 即 stdin,-t 即 tty)參數(shù)。
如果還是不太理解它們倆的作用的話,可以直接認(rèn)為 tty 就是 Linux 給用戶提供的一個(gè)常駐小程序,用于接收用戶的標(biāo)準(zhǔn)輸入,返回操作系統(tǒng)的標(biāo)準(zhǔn)輸出。當(dāng)然,為了能夠在 tty 中輸入信息,你還需要同時(shí)開(kāi)啟 stdin(標(biāo)準(zhǔn)輸入流)。
于是,這個(gè) Pod 被創(chuàng)建后,你就可以使用 shell 容器的 tty 跟這個(gè)容器進(jìn)行交互了。我們一起實(shí)踐一下:

$ kubectl create -f nginx.yaml

接下來(lái),我們使用 kubectl attach 命令,連接到 shell 容器的 tty 上:

kubectl attach -it nginx -c shell

這樣,我們就可以在 shell 容器里執(zhí)行 ps 指令,查看所有正在運(yùn)行的進(jìn)程:

$ kubectl attach -it nginx -c shell
/ # ps ax
PID   USER     TIME  COMMAND
    1 root      0:00 /pause
    8 root      0:00 nginx: master process nginx -g daemon off;
   14 101       0:00 nginx: worker process
   15 root      0:00 sh
   21 root      0:00 ps ax

可以看到,在這個(gè)容器里,我們不僅可以看到它本身的 ps ax 指令,還可以看到 nginx 容器的進(jìn)程,以及 Infra 容器的 /pause 進(jìn)程。這就意味著,整個(gè) Pod 里的每個(gè)容器的進(jìn)程,對(duì)于所有容器來(lái)說(shuō)都是可見(jiàn)的:它們共享了同一個(gè) PID Namespace。

類似地,凡是 Pod 中的容器要共享宿主機(jī)的 Namespace,也一定是 Pod 級(jí)別的定義,比如:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  hostNetwork: true
  hostIPC: true
  hostPID: true
  containers:
  - name: nginx
    image: nginx
  - name: shell
    image: busybox
    stdin: true
    tty: true

在這個(gè) Pod 中,我定義了共享宿主機(jī)的 Network、IPC 和 PID Namespace。這就意味著,這個(gè) Pod 里的所有容器,會(huì)直接使用宿主機(jī)的網(wǎng)絡(luò)、直接與宿主機(jī)進(jìn)行 IPC 通信、看到宿主機(jī)里正在運(yùn)行的所有進(jìn)程。

當(dāng)然,除了這些屬性,Pod 里最重要的字段當(dāng)屬“Containers”了。而在上一篇文章中,我還介紹過(guò)“Init Containers”。其實(shí),這兩個(gè)字段都屬于 Pod 對(duì)容器的定義,內(nèi)容也完全相同,只是 Init Containers 的生命周期,會(huì)先于所有的 Containers,并且嚴(yán)格按照定義的順序執(zhí)行。

Kubernetes 項(xiàng)目中對(duì) Container 的定義,和 Docker 相比并沒(méi)有什么太大區(qū)別。我在前面的容器技術(shù)概念入門系列文章中,和你分享的 Image(鏡像)、Command(啟動(dòng)命令)、workingDir(容器的工作目錄)、Ports(容器要開(kāi)發(fā)的端口),以及 volumeMounts(容器要掛載的 Volume)都是構(gòu)成 Kubernetes 項(xiàng)目中 Container 的主要字段。不過(guò)在這里,還有這么幾個(gè)屬性值得你額外關(guān)注。

首先,是 ImagePullPolicy 字段。它定義了鏡像拉取的策略。而它之所以是一個(gè) Container 級(jí)別的屬性,是因?yàn)槿萜麋R像本來(lái)就是 Container 定義中的一部分。

ImagePullPolicy 的值默認(rèn)是 Always,即每次創(chuàng)建 Pod 都重新拉取一次鏡像。另外,當(dāng)容器的鏡像是類似于 nginx 或者 nginx:latest 這樣的名字時(shí),ImagePullPolicy 也會(huì)被認(rèn)為 Always。
最新版本 v1.16 默認(rèn)值是 IfNotPresent

https://kubernetes.io/docs/concepts/containers/images/

而如果它的值被定義為 Never 或者 IfNotPresent,則意味著 Pod 永遠(yuǎn)不會(huì)主動(dòng)拉取這個(gè)鏡像,或者只在宿主機(jī)上不存在這個(gè)鏡像時(shí)才拉取。

其次,是 Lifecycle 字段。它定義的是 Container Lifecycle Hooks。顧名思義,Container Lifecycle Hooks 的作用,是在容器狀態(tài)發(fā)生變化時(shí)觸發(fā)一系列“鉤子”。我們來(lái)看這樣一個(gè)例子:

apiVersion: v1
kind: Pod
metadata:
  name: lifecycle-demo
spec:
  containers:
  - name: lifecycle-demo-container
    image: nginx
    lifecycle:
      postStart:
        exec:
          command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
      
    preStop:
        exec:
          command: ["/usr/sbin/nginx","-s","quit"]

這是一個(gè)來(lái)自 Kubernetes 官方文檔的 Pod 的 YAML 文件。它其實(shí)非常簡(jiǎn)單,只是定義了一個(gè) nginx 鏡像的容器。不過(guò),在這個(gè) YAML 文件的容器(Containers)部分,你會(huì)看到這個(gè)容器分別設(shè)置了一個(gè) postStart 和 preStop 參數(shù)。這是什么意思呢?

先說(shuō) postStart 吧。它指的是,在容器啟動(dòng)后,立刻執(zhí)行一個(gè)指定的操作。需要明確的是,postStart 定義的操作,雖然是在 Docker 容器 ENTRYPOINT 執(zhí)行之后,但它并不嚴(yán)格保證順序。也就是說(shuō),在 postStart 啟動(dòng)時(shí),ENTRYPOINT 有可能還沒(méi)有結(jié)束。

當(dāng)然,如果 postStart 執(zhí)行超時(shí)或者錯(cuò)誤,Kubernetes 會(huì)在該 Pod 的 Events 中報(bào)出該容器啟動(dòng)失敗的錯(cuò)誤信息,導(dǎo)致 Pod 也處于失敗的狀態(tài)。

而類似地,preStop 發(fā)生的時(shí)機(jī),則是容器被殺死之前(比如,收到了 SIGKILL 信號(hào))。而需要明確的是,preStop 操作的執(zhí)行,是同步的。所以,它會(huì)阻塞當(dāng)前的容器殺死流程,直到這個(gè) Hook 定義操作完成之后,才允許容器被殺死,這跟 postStart 不一樣。

所以,在這個(gè)例子中,我們?cè)谌萜鞒晒?dòng)之后,在 /usr/share/message 里寫入了一句“歡迎信息”(即 postStart 定義的操作)。而在這個(gè)容器被刪除之前,我們則先調(diào)用了 nginx 的退出指令(即 preStop 定義的操作),從而實(shí)現(xiàn)了容器的“優(yōu)雅退出”。

在熟悉了 Pod 以及它的 Container 部分的主要字段之后,我再和你分享一下這樣一個(gè)的 Pod 對(duì)象在 Kubernetes 中的生命周期。

Pod 生命周期的變化,主要體現(xiàn)在 Pod API 對(duì)象的 Status 部分,這是它除了 Metadata 和 Spec 之外的第三個(gè)重要字段。其中,pod.status.phase,就是 Pod 的當(dāng)前狀態(tài),它有如下幾種可能的情況:
1
Pending。這個(gè)狀態(tài)意味著,Pod 的 YAML 文件已經(jīng)提交給了 Kubernetes,API 對(duì)象已經(jīng)被創(chuàng)建并保存在 Etcd 當(dāng)中。但是,這個(gè) Pod 里有些容器因?yàn)槟撤N原因而不能被順利創(chuàng)建。比如,調(diào)度不成功。
2
Running。這個(gè)狀態(tài)下,Pod 已經(jīng)調(diào)度成功,跟一個(gè)具體的節(jié)點(diǎn)綁定。它包含的容器都已經(jīng)創(chuàng)建成功,并且至少有一個(gè)正在運(yùn)行中。
3
Succeeded。這個(gè)狀態(tài)意味著,Pod 里的所有容器都正常運(yùn)行完畢,并且已經(jīng)退出了。這種情況在運(yùn)行一次性任務(wù)時(shí)最為常見(jiàn)。
4
Failed。這個(gè)狀態(tài)下,Pod 里至少有一個(gè)容器以不正常的狀態(tài)(非 0 的返回碼)退出。這個(gè)狀態(tài)的出現(xiàn),意味著你得想辦法 Debug 這個(gè)容器的應(yīng)用,比如查看 Pod 的 Events 和日志。
5
Unknown。這是一個(gè)異常狀態(tài),意味著 Pod 的狀態(tài)不能持續(xù)地被 kubelet 匯報(bào)給 kube-apiserver,這很有可能是主從節(jié)點(diǎn)(Master 和 Kubelet)間的通信出現(xiàn)了問(wèn)題。

更進(jìn)一步地,Pod 對(duì)象的 Status 字段,還可以再細(xì)分出一組 Conditions。這些細(xì)分狀態(tài)的值包括:PodScheduled、Ready、Initialized,以及 Unschedulable。它們主要用于描述造成當(dāng)前 Status 的具體原因是什么。

比如,Pod 當(dāng)前的 Status 是 Pending,對(duì)應(yīng)的 Condition 是 Unschedulable,這就意味著它的調(diào)度出現(xiàn)了問(wèn)題。
而其中,Ready 這個(gè)細(xì)分狀態(tài)非常值得我們關(guān)注:它意味著 Pod 不僅已經(jīng)正常啟動(dòng)(Running 狀態(tài)),而且已經(jīng)可以對(duì)外提供服務(wù)了。這兩者之間(Running 和 Ready)是有區(qū)別的,你不妨仔細(xì)思考一下。

Pod 的這些狀態(tài)信息,是我們判斷應(yīng)用運(yùn)行情況的重要標(biāo)準(zhǔn),尤其是 Pod 進(jìn)入了非“Running”狀態(tài)后,你一定要能迅速做出反應(yīng),根據(jù)它所代表的異常情況開(kāi)始跟蹤和定位,而不是去手忙腳亂地查閱文檔。
總結(jié)

在今天這篇文章中,我詳細(xì)講解了 Pod API 對(duì)象,介紹了 Pod 的核心使用方法,并分析了 Pod 和 Container 在字段上的異同。希望這些講解能夠幫你更好地理解和記憶 Pod YAML 中的核心字段,以及這些字段的準(zhǔn)確含義。

實(shí)際上,Pod API 對(duì)象是整個(gè) Kubernetes 體系中最核心的一個(gè)概念,也是后面講解各種控制器時(shí)都要用到的。

在學(xué)習(xí)完這篇文章后,我希望你能仔細(xì)閱讀$GOPATH/src/k8s.io/kubernetes/vendor/k8s.io/api/core/v1/types.go 里,type Pod struct ,尤其是 PodSpec 部分的內(nèi)容。爭(zhēng)取做到下次看到一個(gè) Pod 的 YAML 文件時(shí),不再需要查閱文檔,就能做到把常用字段及其作用信手拈來(lái)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一. Pod介紹與容器分類 1.Pod的特性 ? 最小部署單元? 一組容器的集合,緊密關(guān)聯(lián)的容器考慮放到一個(gè)pod...
    六弦極品閱讀 919評(píng)論 0 0
  • 一 概念 Pod 的中文意為: 豆莢,從字面意思不難理解,它就像一個(gè)豆莢,里面包含許多豆子,這些豆子就可以類比為 ...
    Java程序員YY閱讀 555評(píng)論 1 0
  • Pod 為什么需要Pod Pod,是Kubernetes項(xiàng)目中的原子調(diào)度單位。容器就是未來(lái)云計(jì)算系統(tǒng)中的進(jìn)程;容器...
    12點(diǎn)前睡覺(jué)hhh閱讀 1,538評(píng)論 0 0
  • 現(xiàn)在是2017.9.20,晚上10.58,記錄今天的一天。 今日聽(tīng)課 今天聽(tīng)了一位據(jù)說(shuō)很棒的老師,上課中,我發(fā)現(xiàn)紀(jì)...
    飯飯_bms閱讀 238評(píng)論 0 0
  • 自由被徹底的放飛了 靜默的等候 斯人何往 心設(shè)的圍籠依然 那只是舊去的風(fēng)景 鳥(niǎo)兒在藍(lán)天歌唱 ……
    陽(yáng)光小城閱讀 421評(píng)論 0 1

友情鏈接更多精彩內(nèi)容