深入解析Pod對象

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

要徹底理解這個(gè)問題,你就一定要牢記我在上一篇文章中提到的一個(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 對象的設(shè)計(jì)就非常容易理解了。

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

這些屬性的共同特征是,它們描述的是“機(jī)器”這個(gè)整體,而不是里面運(yùn)行的“程序”。比如,配置這個(gè)“機(jī)器”的網(wǎng)卡(即:Pod 的網(wǎng)絡(luò)定義),配置這個(gè)“機(jī)器”的磁盤(即:Pod 的存儲(chǔ)定義),配置這個(gè)“機(jī)器”的防火墻(即:Pod 的安全定義)。更不用說,這臺“機(jī)器”運(yùn)行在哪個(gè)服務(wù)器之上(即:Pod 的調(diào)度)。
接下來,我就先為你介紹 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)過了調(diào)度,調(diào)度的結(jié)果就是賦值的節(jié)點(diǎn)名字。所以,這個(gè)字段一般由調(diào)度器負(fù)責(zé)設(shè)置,但用戶也可以設(shè)置它來“騙過”調(diào)度器,當(dāng)然這個(gè)做法一般是在測試或者調(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

其中,最下面兩行記錄,就是我通過 HostAliases 字段為 Pod 設(shè)置的。需要指出的是,在 Kubernetes 項(xiàng)目中,如果要設(shè)置 hosts 文件里的內(nèi)容,一定要通過這種方法。否則,如果直接修改了 hosts 文件的話,在 Pod 被刪除重建之后,kubelet 會(huì)自動(dòng)覆蓋掉被修改的內(nèi)容。
除了上述跟“機(jī)器”相關(guān)的配置外,你可能也會(huì)發(fā)現(xiàn):
凡是跟容器的 Linux Namespace 相關(guān)的屬性,也一定是 Pod 級別的。
這個(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è)是開啟了 tty 和 stdin 的 shell 容器。
在 Pod 的 YAML 文件里聲明開啟它們倆,其實(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í)開啟 stdin(標(biāo)準(zhǔn)輸入流)。
于是,這個(gè) Pod 被創(chuàng)建后,你就可以使用 shell 容器的 tty 跟這個(gè)容器進(jìn)行交互了。我們一起實(shí)踐一下:

$ kubectl create -f nginx.yaml

接下來,我們使用 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)程,對于所有容器來說都是可見的:它們共享了同一個(gè) PID Namespace。

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

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”了。而在上一篇文章中,我還介紹過“Init Containers”。其實(shí),這兩個(gè)字段都屬于 Pod 對容器的定義,內(nèi)容也完全相同,只是 Init Containers 的生命周期,會(huì)先于所有的 Containers,并且嚴(yán)格按照定義的順序執(zhí)行。

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

首先,是 ImagePullPolicy 字段。它定義了鏡像拉取的策略。而它之所以是一個(gè) Container 級別的屬性,是因?yàn)槿萜麋R像本來就是 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ā)一系列“鉤子”。我們來看這樣一個(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è)來自 Kubernetes 官方文檔的 Pod 的 YAML 文件。它其實(shí)非常簡單,只是定義了一個(gè) nginx 鏡像的容器。不過,在這個(gè) YAML 文件的容器(Containers)部分,你會(huì)看到這個(gè)容器分別設(shè)置了一個(gè) postStart 和 preStop 參數(shù)。這是什么意思呢?

先說 postStart 吧。它指的是,在容器啟動(dòng)后,立刻執(zhí)行一個(gè)指定的操作。需要明確的是,postStart 定義的操作,雖然是在 Docker 容器 ENTRYPOINT 執(zhí)行之后,但它并不嚴(yán)格保證順序。也就是說,在 postStart 啟動(dòng)時(shí),ENTRYPOINT 有可能還沒有結(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 信號)。而需要明確的是,preStop 操作的執(zhí)行,是同步的。所以,它會(huì)阻塞當(dāng)前的容器殺死流程,直到這個(gè) Hook 定義操作完成之后,才允許容器被殺死,這跟 postStart 不一樣。

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

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

Pod 生命周期的變化,主要體現(xiàn)在 Pod API 對象的 Status 部分,這是它除了 Metadata 和 Spec 之外的第三個(gè)重要字段。其中,pod.status.phase,就是 Pod 的當(dāng)前狀態(tài),它有如下幾種可能的情況:
1
Pending。這個(gè)狀態(tài)意味著,Pod 的 YAML 文件已經(jīng)提交給了 Kubernetes,API 對象已經(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í)最為常見。
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)了問題。

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

比如,Pod 當(dāng)前的 Status 是 Pending,對應(yīng)的 Condition 是 Unschedulable,這就意味著它的調(diào)度出現(xiàn)了問題。
而其中,Ready 這個(gè)細(xì)分狀態(tài)非常值得我們關(guān)注:它意味著 Pod 不僅已經(jīng)正常啟動(dòng)(Running 狀態(tài)),而且已經(jīng)可以對外提供服務(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ù)它所代表的異常情況開始跟蹤和定位,而不是去手忙腳亂地查閱文檔。

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

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

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

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