現(xiàn)在戒幔,你已經(jīng)非常清楚:Kubernetes 項(xiàng)目中的最小編排單位是 Pod拌喉,而不是容器派诬。將這個設(shè)計落實(shí)到 API 對象上盟萨,容(Container)就成了 Pod 屬性里的一個普通的字段吝羞。那么兰伤,一個很自然的問題就是:到底哪些屬性屬于 Pod 對象,而又有哪些屬性屬于 Container 呢钧排?
要徹底理解這個問題敦腔,你就一定要牢記我在上一篇文章中提到的一個結(jié)論:Pod 扮演的是傳統(tǒng)部署環(huán)境里“虛擬機(jī)”的角色。這樣的設(shè)計恨溜,是為了使用戶從傳統(tǒng)環(huán)境(虛擬機(jī)環(huán)境)向 Kubernetes(容器環(huán)境)的遷移符衔,更加平滑。
而如果你能把 Pod 看成傳統(tǒng)環(huán)境里的“機(jī)器”糟袁、把容器看作是運(yùn)行在這個“機(jī)器”里的“用戶程序”判族,那么很多關(guān)于 Pod 對象的設(shè)計就非常容易理解了。
比如项戴,凡是調(diào)度五嫂、網(wǎng)絡(luò)、存儲肯尺,以及安全相關(guān)的屬性沃缘,基本上是 Pod 級別的。
這些屬性的共同特征是则吟,它們描述的是“機(jī)器”這個整體槐臀,而不是里面運(yùn)行的“程序”。比如氓仲,配置這個“機(jī)器”的網(wǎng)卡(即:Pod 的網(wǎng)絡(luò)定義)水慨,配置這個“機(jī)器”的磁盤(即:Pod 的存儲定義),配置這個“機(jī)器”的防火墻(即:Pod 的安全定義)敬扛。更不用說晰洒,這臺“機(jī)器”運(yùn)行在哪個服務(wù)器之上(即:Pod 的調(diào)度)。
接下來啥箭,我就先為你介紹 Pod 中幾個重要字段的含義和用法谍珊。
NodeSelector:是一個供用戶將 Pod 與 Node 進(jìn)行綁定的字段,用法如下所示:
apiVersion: v1
kind: Pod
...
spec:
nodeSelector:
disktype: ssd
這樣的一個配置急侥,意味著這個 Pod 永遠(yuǎn)只能運(yùn)行在攜帶了“disktype: ssd”標(biāo)簽(Label)的節(jié)點(diǎn)上砌滞;否則侮邀,它將調(diào)度失敗。
NodeName:一旦 Pod 的這個字段被賦值贝润,Kubernetes 項(xiàng)目就會被認(rèn)為這個 Pod 已經(jīng)經(jīng)過了調(diào)度绊茧,調(diào)度的結(jié)果就是賦值的節(jié)點(diǎn)名字。所以打掘,這個字段一般由調(diào)度器負(fù)責(zé)設(shè)置华畏,但用戶也可以設(shè)置它來“騙過”調(diào)度器,當(dāng)然這個做法一般是在測試或者調(diào)試的時候才會用到尊蚁。
HostAliases:定義了 Pod 的 hosts 文件(比如 /etc/hosts)里的內(nèi)容唯绍,用法如下:
apiVersion: v1
kind: Pod
...
spec:
hostAliases:
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
...
在這個 Pod 的 YAML 文件中,我設(shè)置了一組 IP 和 hostname 的數(shù)據(jù)枝誊。這樣,這個 Pod 啟動后惜纸,/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 會自動覆蓋掉被修改的內(nèi)容。
除了上述跟“機(jī)器”相關(guān)的配置外亭引,你可能也會發(fā)現(xiàn):
凡是跟容器的 Linux Namespace 相關(guān)的屬性绎速,也一定是 Pod 級別的。
這個原因也很容易理解:Pod 的設(shè)計焙蚓,就是要讓它里面的容器盡可能多地共享 Linux Namespace纹冤,僅保留必要的隔離和限制能力。這樣购公,Pod 模擬出的效果萌京,就跟虛擬機(jī)里程序間的關(guān)系非常類似了。
舉個例子宏浩,在下面這個 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
這就意味著這個 Pod 里的容器要共享 PID Namespace。
而在這個 YAML 文件中比庄,我還定義了兩個容器:一個是 nginx 容器橡庞,一個是開啟了 tty 和 stdin 的 shell 容器较坛。
在 Pod 的 YAML 文件里聲明開啟它們倆,其實(shí)等同于設(shè)置了 docker run 里的 -it(-i 即 stdin扒最,-t 即 tty)參數(shù)丑勤。
如果還是不太理解它們倆的作用的話,可以直接認(rèn)為 tty 就是 Linux 給用戶提供的一個常駐小程序吧趣,用于接收用戶的標(biāo)準(zhǔn)輸入法竞,返回操作系統(tǒng)的標(biāo)準(zhǔn)輸出。當(dāng)然强挫,為了能夠在 tty 中輸入信息岔霸,你還需要同時開啟 stdin(標(biāo)準(zhǔn)輸入流)。
于是俯渤,這個 Pod 被創(chuàng)建后呆细,你就可以使用 shell 容器的 tty 跟這個容器進(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
可以看到坑夯,在這個容器里,我們不僅可以看到它本身的 ps ax 指令抡四,還可以看到 nginx 容器的進(jìn)程柜蜈,以及 Infra 容器的 /pause 進(jìn)程。這就意味著指巡,整個 Pod 里的每個容器的進(jìn)程淑履,對于所有容器來說都是可見的:它們共享了同一個 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
在這個 Pod 中阔涉,我定義了共享宿主機(jī)的 Network缆娃、IPC 和 PID Namespace。這就意味著瑰排,這個 Pod 里的所有容器贯要,會直接使用宿主機(jī)的網(wǎng)絡(luò)、直接與宿主機(jī)進(jìn)行 IPC 通信椭住、看到宿主機(jī)里正在運(yùn)行的所有進(jìn)程崇渗。
當(dāng)然,除了這些屬性,Pod 里最重要的字段當(dāng)屬“Containers”了宅广。而在上一篇文章中葫掉,我還介紹過“Init Containers”。其實(shí)跟狱,這兩個字段都屬于 Pod 對容器的定義俭厚,內(nèi)容也完全相同,只是 Init Containers 的生命周期驶臊,會先于所有的 Containers挪挤,并且嚴(yán)格按照定義的順序執(zhí)行。
Kubernetes 項(xiàng)目中對 Container 的定義关翎,和 Docker 相比并沒有什么太大區(qū)別扛门。我在前面的容器技術(shù)概念入門系列文章中,和你分享的 Image(鏡像)纵寝、Command(啟動命令)论寨、workingDir(容器的工作目錄)、Ports(容器要開發(fā)的端口)爽茴,以及 volumeMounts(容器要掛載的 Volume)都是構(gòu)成 Kubernetes 項(xiàng)目中 Container 的主要字段葬凳。不過在這里,還有這么幾個屬性值得你額外關(guān)注闹啦。
首先,是 ImagePullPolicy 字段辕坝。它定義了鏡像拉取的策略窍奋。而它之所以是一個 Container 級別的屬性,是因?yàn)槿萜麋R像本來就是 Container 定義中的一部分酱畅。
ImagePullPolicy 的值默認(rèn)是 Always琳袄,即每次創(chuàng)建 Pod 都重新拉取一次鏡像。另外纺酸,當(dāng)容器的鏡像是類似于 nginx 或者 nginx:latest 這樣的名字時窖逗,ImagePullPolicy 也會被認(rèn)為 Always。
最新版本 v1.16 默認(rèn)值是 IfNotPresent
而如果它的值被定義為 Never 或者 IfNotPresent餐蔬,則意味著 Pod 永遠(yuǎn)不會主動拉取這個鏡像碎紊,或者只在宿主機(jī)上不存在這個鏡像時才拉取。
其次樊诺,是 Lifecycle 字段仗考。它定義的是 Container Lifecycle Hooks。顧名思義词爬,Container Lifecycle Hooks 的作用秃嗜,是在容器狀態(tài)發(fā)生變化時觸發(fā)一系列“鉤子”。我們來看這樣一個例子:
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"]
這是一個來自 Kubernetes 官方文檔的 Pod 的 YAML 文件。它其實(shí)非常簡單锅锨,只是定義了一個 nginx 鏡像的容器叽赊。不過,在這個 YAML 文件的容器(Containers)部分必搞,你會看到這個容器分別設(shè)置了一個 postStart 和 preStop 參數(shù)必指。這是什么意思呢?
先說 postStart 吧顾画。它指的是取劫,在容器啟動后,立刻執(zhí)行一個指定的操作研侣。需要明確的是谱邪,postStart 定義的操作,雖然是在 Docker 容器 ENTRYPOINT 執(zhí)行之后庶诡,但它并不嚴(yán)格保證順序惦银。也就是說末誓,在 postStart 啟動時扯俱,ENTRYPOINT 有可能還沒有結(jié)束。
當(dāng)然喇澡,如果 postStart 執(zhí)行超時或者錯誤迅栅,Kubernetes 會在該 Pod 的 Events 中報出該容器啟動失敗的錯誤信息,導(dǎo)致 Pod 也處于失敗的狀態(tài)晴玖。
而類似地读存,preStop 發(fā)生的時機(jī),則是容器被殺死之前(比如呕屎,收到了 SIGKILL 信號)让簿。而需要明確的是,preStop 操作的執(zhí)行秀睛,是同步的尔当。所以,它會阻塞當(dāng)前的容器殺死流程蹂安,直到這個 Hook 定義操作完成之后椭迎,才允許容器被殺死,這跟 postStart 不一樣田盈。
所以侠碧,在這個例子中,我們在容器成功啟動之后缠黍,在 /usr/share/message 里寫入了一句“歡迎信息”(即 postStart 定義的操作)弄兜。而在這個容器被刪除之前,我們則先調(diào)用了 nginx 的退出指令(即 preStop 定義的操作),從而實(shí)現(xiàn)了容器的“優(yōu)雅退出”替饿。
在熟悉了 Pod 以及它的 Container 部分的主要字段之后语泽,我再和你分享一下這樣一個的 Pod 對象在 Kubernetes 中的生命周期。
Pod 生命周期的變化视卢,主要體現(xiàn)在 Pod API 對象的 Status 部分踱卵,這是它除了 Metadata 和 Spec 之外的第三個重要字段。其中据过,pod.status.phase惋砂,就是 Pod 的當(dāng)前狀態(tài),它有如下幾種可能的情況:
1
Pending绳锅。這個狀態(tài)意味著西饵,Pod 的 YAML 文件已經(jīng)提交給了 Kubernetes,API 對象已經(jīng)被創(chuàng)建并保存在 Etcd 當(dāng)中鳞芙。但是眷柔,這個 Pod 里有些容器因?yàn)槟撤N原因而不能被順利創(chuàng)建。比如原朝,調(diào)度不成功驯嘱。
2
Running。這個狀態(tài)下喳坠,Pod 已經(jīng)調(diào)度成功鞠评,跟一個具體的節(jié)點(diǎn)綁定。它包含的容器都已經(jīng)創(chuàng)建成功壕鹉,并且至少有一個正在運(yùn)行中剃幌。
3
Succeeded。這個狀態(tài)意味著御板,Pod 里的所有容器都正常運(yùn)行完畢锥忿,并且已經(jīng)退出了牛郑。這種情況在運(yùn)行一次性任務(wù)時最為常見怠肋。
4
Failed。這個狀態(tài)下淹朋,Pod 里至少有一個容器以不正常的狀態(tài)(非 0 的返回碼)退出笙各。這個狀態(tài)的出現(xiàn),意味著你得想辦法 Debug 這個容器的應(yīng)用础芍,比如查看 Pod 的 Events 和日志杈抢。
5
Unknown。這是一個異常狀態(tài)仑性,意味著 Pod 的狀態(tài)不能持續(xù)地被 kubelet 匯報給 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 這個細(xì)分狀態(tài)非常值得我們關(guān)注:它意味著 Pod 不僅已經(jī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 對象是整個 Kubernetes 體系中最核心的一個概念,也是后面講解各種控制器時都要用到的输涕。