
“Pod” 這一詞是我們經(jīng)常聽到的一個詞語,我們知道Pod 是k8s 最基本的單元對象,Pod里邊包含著容器,容器里邊運(yùn)行著我們的服務(wù),也就是進(jìn)程,那么我們知道為何k8s 要有pod的概念它的作用都有什么,接下來我們要分析pod的存在意義。
什么是Pod

Pod 的中文含義叫 ‘豆莢’,豆莢里邊放的都是豌豆,豌豆可以理解為容器,Pod 可以理解為“虛擬機(jī)”的概念,所以Pod里邊運(yùn)行著一組容器
Pod,其實(shí)是一組共享了某些資源的容器,是k8s最基礎(chǔ)的一個對象,關(guān)于 Pod 最重要的一個事實(shí)是:它只是一個邏輯概念。
為什么有Pod
Pod是一組容器的邏輯概念,根據(jù)容器的設(shè)計理念一個容器里邊只運(yùn)行一個主進(jìn)程,也就是說容器的本質(zhì)就是進(jìn)程,Pod 里的所有容器,共享的是同一個 Network Namespace,并且可以聲明共享同一個 Volume。
接下來我們思考一下Pod 里的容器是如何做到共享Network Namespace、和Volume 等資源的 ?
其實(shí)在創(chuàng)建容器的時候,k8s會默認(rèn)創(chuàng)建一個中間容器,這個容器叫作 Infra 容器,這個容器永遠(yuǎn)都是第一個被創(chuàng)建的,而其他用戶定義的容器,則通過 Join Network Namespace 的方式,與 Infra 容器關(guān)聯(lián)在一起。這樣的組織關(guān)系,可以用下面這樣一個示意圖來表達(dá):

如上圖所示,這個 Pod 里有兩個用戶容器 A 和 B,還有一個 Infra 容器。很容易理解,在 Kubernetes 項目里,Infra 容器一定要占用極少的資源,所以它使用的是一個非常特殊的鏡像,叫作:k8s.gcr.io/pause。這個鏡像是一個用匯編語言編寫的、永遠(yuǎn)處于“暫?!睜顟B(tài)的容器,解壓后的大小也只有 100~200 KB 左右。
而在 Infra 容器“Hold 住”Network Namespace 后,用戶容器就可以加入到 Infra 容器的 Network Namespace 當(dāng)中了。所以,如果你查看這些容器在宿主機(jī)上的 Namespace 文件,它們指向的值一定是完全一樣的。
這也就意味著,對于 Pod 里的容器 A 和容器 B 來說:
- 它們可以直接使用 localhost 進(jìn)行通信;
- 它們看到的網(wǎng)絡(luò)設(shè)備跟 Infra 容器看到的完全一樣;
- 一個 Pod 只有一個 IP 地址,也就是這個 Pod 的 Network Namespace 對應(yīng)的 IP 地址;
- 當(dāng)然,其他的所有網(wǎng)絡(luò)資源,都是一個 Pod 一份,并且被該 Pod 中的所有容器共享;
- Pod 的生命周期只跟 Infra 容器一致,而與容器 A 和 B 無關(guān)。
有了這個設(shè)計之后,共享 Volume 就簡單多了:Kubernetes 項目只要把所有 Volume 的定義都設(shè)計在 Pod 層級即可。
Pod 這種“超親密關(guān)系”容器的設(shè)計思想,實(shí)際上就是希望,當(dāng)用戶想在一個容器里跑多個功能并不相關(guān)的應(yīng)用時,應(yīng)該優(yōu)先考慮它們是不是更應(yīng)該被描述成一個 Pod 里的多個容器。
最典型的例子是:WAR 包與 Web 服務(wù)器。
假如現(xiàn)在我們用docker的方式來處理,
第一種方式就是WAR 包 放到 Tomat webapps 的目錄下,制作一個鏡像然后發(fā)布部署,每次更新WAR 包內(nèi)容 或者升級 Tomat 的時候都需要重新制作一個新的鏡像
第二種方式 只發(fā)布一個Tomat 鏡像 ,然后把WAR 包的地址掛載到Tomat 里,但是問題是如何讓每一臺宿主機(jī),都預(yù)先準(zhǔn)備好這個存儲有 WAR 包的目錄呢?這樣來看,你只能獨(dú)立維護(hù)一套分布式存儲系統(tǒng)了。
實(shí)際上,有了 Pod 之后,這樣的問題就很容易解決了。我們可以把 WAR 包和 Tomcat 分別做成鏡像,然后把它們作為一個 Pod 里的兩個容器“組合”在一起。
apiVersion: v1
kind: Pod
metadata:
name: javaweb-2
spec:
initContainers:
- image: geektime/sample:v2
name: war
command: ["cp", "/sample.war", "/app"]
volumeMounts:
- mountPath: /app
name: app-volume
containers:
- image: geektime/tomcat:7.0
name: tomcat
command: ["sh","-c","/root/apache-tomcat-7.0.42-v2/bin/start.sh"]
volumeMounts:
- mountPath: /root/apache-tomcat-7.0.42-v2/webapps
name: app-volume
ports:
- containerPort: 8080
hostPort: 8001
volumes:
- name: app-volume
emptyDir: {}
在這個 Pod 中,我們定義了兩個容器,第一個容器使用的鏡像是 geektime/sample:v2,這個鏡像里只有一個 WAR 包(sample.war)放在根目錄下。而第二個容器則使用的是一個標(biāo)準(zhǔn)的 Tomcat 鏡像。
不過,你可能已經(jīng)注意到,WAR 包容器的類型不再是一個普通容器,而是一個 Init Container 類型的容器。
在 Pod 中,所有 Init Container 定義的容器,都會比 spec.containers 定義的用戶容器先啟動。并且,Init Container 容器會按順序逐一啟動,而直到它們都啟動并且退出了,用戶容器才會啟動。
所以,這個 Init Container 類型的 WAR 包容器啟動后,我執(zhí)行了一句”cp /sample.war /app”,把應(yīng)用的 WAR 包拷貝到 /app 目錄下,然后退出。
而后這個 /app 目錄,就掛載了一個名叫 app-volume 的 Volume。
接下來就很關(guān)鍵了。Tomcat 容器,同樣聲明了掛載 app-volume 到自己的 webapps 目錄下。
所以,等 Tomcat 容器啟動時,它的 webapps 目錄下就一定會存在 sample.war 文件:這個文件正是 WAR 包容器啟動時拷貝到這個 Volume 里面的,而這個 Volume 是被這兩個容器共享的。
像這樣,我們就用一種“組合”方式,解決了 WAR 包與 Tomcat 容器之間耦合關(guān)系的問題。
實(shí)際上,這個所謂的“組合”操作,正是容器設(shè)計模式里最常用的一種模式,它的名字叫:sidecar。
顧名思義,sidecar 指的就是我們可以在一個 Pod 中,啟動一個輔助容器,來完成一些獨(dú)立于主進(jìn)程(主容器)之外的工作。
比如,在我們的這個應(yīng)用 Pod 中,Tomcat 容器是我們要使用的主容器,而 WAR 包容器的存在,只是為了給它提供一個 WAR 包而已。所以,我們用 Init Container 的方式優(yōu)先運(yùn)行 WAR 包容器,扮演了一個 sidecar 的角色。
Pod 生命周期

- 執(zhí)行初始化容器init container,其實(shí)在這之上還有一個infra container,pod 的生命周期是跟infra container一致的,然后才開始 init container, init container 可以定義多個, 依次執(zhí)行,只有當(dāng)前 init container 創(chuàng)建成功完成退出之后再執(zhí)行下一個 init container
- 開始初始化main container,再初始化main container 如果用戶設(shè)置了 hook 則會執(zhí)行hook的操作 post start hook, 然后開始執(zhí)行初始化main container
- main container 創(chuàng)建成功之后 如果用戶設(shè)置了容器的探活設(shè)置(livenessProbe 和 readinessProbe), kubelet 會根據(jù) 設(shè)置的類型來訪問Pod容器,根據(jù)返回的結(jié)果決定是否重啟或者是否能被訪問到。
容器探測的類型
ExecAction:在容器中執(zhí)行命令,狀態(tài)碼為0表示成功,否則即為不健康狀態(tài)。
TCPSocketAction:與容器TCP端口建立連接進(jìn)行診斷,端口能夠成功即為正常,否則為不健康狀態(tài)
HTTPGetAction: 向容器內(nèi)指定IP 和 端口發(fā)送 http 請求,響應(yīng)碼為2xx 或者 3xx即為成功
- livenessProbe:指示容器是否正在運(yùn)行。如果存活探測失敗,則 kubelet 會殺死容器,并且容器進(jìn)行重啟。如果容器不提供存活探針,則默認(rèn)狀態(tài)為Success。
- readinessProbe:指示容器是否準(zhǔn)備好服務(wù)請求。如果就緒探測失敗,端點(diǎn)控制器將從與 Pod 匹配的所有 Service 的端點(diǎn)中刪除該 Pod 的 IP 地址。初始延遲之前的就緒狀態(tài)默認(rèn)為 Failure。如果容器不提供就緒探針,則默認(rèn)狀態(tài)為 Success。
- 當(dāng)Pod 被刪除的時候 如果用戶設(shè)置了 hook 則會執(zhí)行hook的操作 post stop hook, 執(zhí)行完畢之后刪除Pod
總結(jié)
這遍文章知道了Pod 對象是k8s 最基礎(chǔ)的原子單位,它里邊可以運(yùn)行多個容器,多個容器之間的網(wǎng)絡(luò)是存儲都是共享的,做到共享的實(shí)現(xiàn)方式是引用了一個中間容器 infra container, 在創(chuàng)建Pod 之前首先創(chuàng)建infra container, 然后然后再創(chuàng)建Pod里的容器,如果有init container 那么先創(chuàng)建init container 再創(chuàng)建main container,創(chuàng)建好的container 則通過 Join Network Namespace 的方式,與 Infra 容器關(guān)聯(lián)在一起, Pod 的生命周期與Infra 是一致的。