1. 程式人生 > 其它 >Kubernetes Pod狀態和生命週期管理

Kubernetes Pod狀態和生命週期管理

​Pod​​​是​​kubernetes​​​中你可以建立和部署的最小也是最簡的單位。​​Pod​​代表著叢集中執行的程序。

​​Pod​​​中封裝著應用的容器(有的情況下是好幾個容器),儲存、獨立的網路​​IP​​​,管理容器如何執行的策略選項。​​Pod​​​代表著部署的一個單位:​​kubernetes​​中應用的一個例項,可能由一個或者多個容器組合在一起共享資源。

​​Docker​​​是​​kubernetes​​​中最常用的容器執行時,但是​​Pod​​也支援其他容器執行時。

在​​Kubernetes​​​叢集中​​Pod​​有如下兩種方式:

一個Pod中執行一個容器。“每個​​Pod​​中一個容器”的模式是最常見的用法;在這種使用方式中,你可以把​​Pod​​想象成單個容器的封裝,​​Kubernetes​​管理的是​​Pod​​而不是直接管理容器。
在一個Pod中同時執行多個容器。一個​​Pod​​也可以同時封裝幾個需要緊密耦合互相協作的容器,它們之間共享資源。這些在同一個​​Pod​​中的容器可以互相協作成為一個​​service​​單位——一個容器共享檔案,另一個​​“sidecar”​​容器來更新這些檔案。​​Pod​​將這些容器的儲存資源作為一個實體來管理。
​​Pod​​​中共享的環境包括​​Linux​​​的​​namespace​​​、​​cgroup​​​和其他可能的隔絕環境,這一點跟​​Docker​​​容器一致。在​​Pod​​的環境中,每個容器可能還有更小的子隔離環境。

​​Pod​​​中的容器共享​​IP​​​地址和埠號,它們之間可以通過​​localhost​​​互相發現。它們之間可以通過程序間通訊,例如​​SystemV​​​訊號或者​​POSIX​​​共享記憶體。不同​​Pod​​​之間的容器具有不同的​​IP​​​地址,不能直接通過​​IPC​​通訊。

​​Pod​​​中的容器也有訪問共享​​volume​​​的許可權,這些​​volume​​​會被定義成​​pod​​的一部分並掛載到應用容器的檔案系統中。

就像每個應用容器,​​pod​​​被認為是臨時(非持久的)實體。在​​Pod​​​的生命週期中討論過,​​pod​​​被建立後,被分配一個唯一的​​ID(UID)​​​,排程到節點上,並一致維持期望的狀態直到被終結(根據重啟策略)或者被刪除。如果​​node​​​死掉了,分配到了這個​​node​​​上的​​pod​​​,在經過一個超時時間後會被重新排程到其他​​node​​​節點上。一個給定的​​pod​​​(如​​UID​​​定義的)不會被“重新排程”到新的節點上,而是被一個同樣的​​pod​​​取代,如果期望的話甚至可以是相同的名字,但是會有一個新的​​UID​​。

Pod中如何管理多個容器

​​Pod​​​中可以同時執行多個程序(作為容器執行)協同工作。同一個​​Pod​​​中的容器會自動的分配到同一個​​node​​​上。同一個​​Pod​​中的容器共享資源、網路環境和依賴,它們總是被同時排程。

注意在一個​​Pod​​​中同時執行多個容器是一種比較高階的用法。只有當你的容器需要緊密配合協作的時候才考慮用這種模式。例如,你有一個容器作為​​web​​​伺服器執行,需要用到共享的​​volume​​​,有另一個​​“sidecar”​​容器來從遠端獲取資源更新這些檔案,如下圖所示:

​​Pod​​中可以共享兩種資源:網路和儲存

網路:每個​​pod​​​都會被分配一個唯一的​​IP​​​地址。​​Pod​​​中的所有容器共享網路空間,包括​​IP​​​地址和埠。​​Pod​​​內部的容器可以使用​​localhost​​​互相通訊。​​Pod​​中的容器與外界通訊時,必須分配共享網路資源(例如使用宿主機的埠對映)。

儲存:可以為一個​​Pod​​​指定多個共享的​​Volume​​​。​​Pod​​​中的所有容器都可以訪問共享的​​volume​​​。​​Volume​​​也可以用來持久化​​Pod​​中的儲存資源,以防容器重啟後文件丟失。

使用Pod


你很少會直接在​​kubernetes​​​中建立單個​​Pod​​​。因為​​Pod​​​的生命週期是短暫的,用後即焚的實體。當​​Pod​​​被建立後(不論是由你直接建立還是被其它​​Controller​​​),都會被​​Kubernetes​​​排程到叢集的​​Node​​​上。直到​​Pod​​​的程序終止、被刪掉、因為缺少資源而被驅逐、或者​​Node​​​故障之前這個​​Pod​​​都會一直保持在那個​​Node​​上。

注意:重啟​​Pod​​​中的容器跟重啟​​Pod​​​不是一回事。​​Pod​​​只提供容器的執行環境並保持容器的執行狀態,重啟容器不會造成​​Pod​​重啟。

​​Pod​​​不會自愈。如果​​Pod​​​執行的​​Node​​​故障,或者是排程器本身故障,這個​​Pod​​​就會被刪除。同樣的,如果​​Pod​​​所在​​Node​​​缺少資源或者​​Pod​​​處於維護狀態,​​Pod​​​也會被驅逐。​​Kubernetes​​​使用更高階的稱為​​Controller​​​的抽象層,來管理​​Pod​​​例項。雖然可以直接使用​​Pod​​​,但是在​​Kubernetes​​​中通常是使用​​Controller​​​來管理​​Pod​​的。

​​Controller​​​可以建立和管理多個​​Pod​​​,提供副本管理、滾動升級和叢集級別的自愈能力。例如,如果一個​​Node​​​故障,​​Controller​​​就能自動將該節點上的​​Pod​​​排程到其他健康的​​Node​​上。

Pod物件的生命週期

​​Pod​​​物件自從其建立開始至其終止退出的時間範圍稱為其生命週期。在這段時間中,​​Pod​​​會處於多種不同的狀態,並執行一些操作;其中,建立主容器(​​main container​​​)為必需的操作,其他可選的操作還包括執行初始化容器(​​init container​​​)、容器啟動後鉤子(​​post start hook​​​)、容器的存活性探測(​​liveness probe​​​)、就緒性探測(​​readiness probe​​​)以及容器終止前鉤子(​​pre stop hook​​​)等,這些操作是否執行則取決於​​Pod​​的定義。如下圖所示:

Pod phase

​​Pod​​​的​​status​​​欄位是一個​​PodStatus​​​的物件,​​PodStatus​​​中有一個​​phase​​欄位。

無論是手動建立還是通過​​Deployment​​​等控制器建立,​​Pod​​​物件總是應該處於其生命程序中以下幾個相位(​​phase​​)之一。

掛起(​​Pending​​):​​API Server​​建立了​​pod​​資源物件已存入​​etcd​​中,但它尚未被排程完成,或者仍處於從倉庫下載映象的過程中。
執行中(​​Running​​):​​Pod​​已經被排程至某節點,並且所有容器都已經被​​kubelet​​建立完成。
成功(​​Succeeded​​):​​Pod​​中的所有容器都已經成功終止並且不會被重啟
失敗(​​Failed​​):​​Pod​​中的所有容器都已終止了,並且至少有一個容器是因為失敗終止。即容器以​​非0​​狀態退出或者被系統禁止。
未知(​​Unknown​​):​​Api Server​​無法正常獲取到​​Pod​​物件的狀態資訊,通常是由於無法與所在工作節點的​​kubelet​​通訊所致。

Pod的建立過程

​​Pod​​​是​​kubernetes​​​的基礎單元,理解它的建立過程對於瞭解系統運作大有裨益。如下圖描述了一個​​Pod​​資源物件的典型建立過程。

使用者通過​​kubectl​​或其他​​API​​客戶端提交了​​Pod Spec​​給​​API Server​​。
​​API Server​​嘗試著將​​Pod​​物件的相關資訊存入​​etcd​​中,待寫入操作執行完成,​​API Server​​即會返回確認資訊至客戶端。
​​API Server​​開始反映​​etcd​​中的狀態變化。
所有的​​kubernetes​​元件均使用​​“watch”​​機制來跟蹤檢查​​API Server​​上的相關的變動。
​​kube-scheduler​​(排程器)通過其​​“watcher”​​覺察到​​API Server​​建立了新的​​Pod​​物件但尚未繫結至任何工作節點。
​​kube-scheduler​​為​​Pod​​物件挑選一個工作節點並將結果資訊更新至​​API Server​​。
排程結果資訊由​​API Server​​更新至​​etcd​​儲存系統,而且​​API Server​​也開始反映此​​Pod​​物件的排程結果。
​​Pod​​被排程到的目標工作節點上的​​kubelet​​嘗試在當前節點上呼叫​​Docker​​啟動容器,並將容器的結果狀態返回送至​​API Server​​。
​​API Server​​將​​Pod​​狀態資訊存入​​etcd​​系統中。
在​​etcd​​確認寫入操作成功完成後,​​API Server​​將確認資訊傳送至相關的​​kubelet​​,事件將通過它被接受。
Pod生命週期中的重要行為
1)初始化容器 

初始化容器(​​init container​​)即應用程式的主容器啟動之前要執行的容器,常用於為主容器執行一些預置操作,它們具有兩種典型特徵。

1)初始化容器必須執行完成直至結束,若某初始化容器執行失敗,那麼​​kubernetes​​​需要重啟它直到成功完成。(注意:如果​​pod​​​的​​spec.restartPolicy​​​欄位值為“​​Never​​”,那麼執行失敗的初始化容器不會被重啟。)

2)每個初始化容器都必須按定義的順序序列執行。

2)容器探測

容器探測(​​container probe​​​)是​​Pod​​​物件生命週期中的一項重要的日常任務,它是​​kubelet​​​對容器週期性執行的健康狀態診斷,診斷操作由容器的處理器(​​handler​​​)進行定義。​​Kubernetes​​​支援三種處理器用於​​Pod​​探測:

​​ExecAction​​:在容器內執行指定命令,並根據其返回的狀態碼進行診斷的操作稱為​​Exec​​探測,狀態碼為​​0​​表示成功,否則即為不健康狀態。
​​TCPSocketAction​​:通過與容器的某​​TCP​​埠嘗試建立連線進行診斷,埠能夠成功開啟即為正常,否則為不健康狀態。
​​HTTPGetAction​​:通過向容器​​IP​​地址的某指定埠的指定​​path​​發起​​HTTP GET​​請求進行診斷,響應碼為​​2xx​​或​​3xx​​時即為成功,否則為失敗。
任何一種探測方式都可能存在三種結果:​​“Success”(成功)​​​、​​“Failure”(失敗)​​​、​​“Unknown”(未知)​​​,只有​​success​​表示成功通過檢測。

容器探測分為兩種型別:

存活性探測(livenessProbe):用於判定容器是否處於“執行”(​​Running​​)狀態;一旦此類檢測未通過,​​kubelet​​將殺死容器並根據重啟策略(​​restartPolicy​​)決定是否將其重啟;未定義存活檢測的容器的預設狀態為“​​Success​​”。
就緒性探測(readinessProbe):用於判斷容器是否準備就緒並可對外提供服務;未通過檢測的容器意味著其尚未準備就緒,端點控制器(如​​Service​​物件)會將其​​IP​​從所有匹配到此​​Pod​​物件的​​Service​​物件的端點列表中移除;檢測通過之後,會再將其​​IP​​新增至端點列表中。

什麼時候使用存活(liveness)和就緒(readiness)探針? 

如果容器中的程序能夠在遇到問題或不健康的情況下自行崩潰,則不一定需要存活探針,​​kubelet​​​將根據​​Pod​​​的​​restartPolicy​​自動執行正確的操作。

如果希望容器在探測失敗時被殺死並重新啟動,那麼請指定一個存活探針,並指定​​restartPolicy​​​為​​Always​​​或​​OnFailure​​。

如果要僅在探測成功時才開始向​​Pod​​​傳送流量,請指定就緒探針。在這種情況下,就緒探針可能與存活探針相同,但是​​spec​​​中的就緒探針的存在意味著​​Pod​​將在沒有接收到任何流量的情況下啟動,並且只有在探針探測成功才開始接收流量。

如果希望容器能夠自行維護,可以指定一個就緒探針,該探針檢查與存活探針不同的端點。

注意:如果只想在​​Pod​​​被刪除時能夠排除請求,則不一定需要使用就緒探針;在刪除​​Pod​​​時,​​Pod​​​會自動將自身置於未完成狀態,無論就緒探針是否存在。當等待​​Pod​​​中的容器停止時,​​Pod​​仍處於未完成狀態。

容器的重啟策略

​​PodSpec​​​中有一個​​restartPolicy​​​欄位,可能的值為​​Always​​​、​​OnFailure​​​和​​Never​​​。預設為​​Always​​​。​​restartPolicy​​​適用於​​Pod​​​中的所有容器。而且它僅用於控制在同一節點上重新啟動​​Pod​​​物件的相關容器。首次需要重啟的容器,將在其需要時立即進行重啟,隨後再次需要重啟的操作將由​​kubelet​​​延遲一段時間後進行,且反覆的重啟操作的延遲時長依次為​​10秒、20秒、40秒... 300秒​​​是最大延遲時長。事實上,一旦繫結到一個節點,​​Pod​​物件將永遠不會被重新繫結到另一個節點,它要麼被重啟,要麼終止,直到節點發生故障或被刪除。

Always:但凡​​Pod​​物件終止就將其重啟,預設值
OnFailure:僅在​​Pod​​物件出現錯誤時方才將其重啟
Never:從不重啟


Pod存活性探測示例
設定exec探針示例

[root@k8s-master ~]# vim manfests/liveness-exec.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
labels:
test: liveness-exec
spec:
containers:
- name: liveness-exec-container
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","touch /tmp/healthy; sleep 30; rm -f /tmp/healthy; sleep 3600"]
livenessProbe:
exec:
command: ["test","-e","/tmp/healthy"]
initialDelaySeconds: 1
periodSeconds: 3

[root@k8s-master ~]# kubectl create -f manfests/liveness-exec.yaml #建立pod
pod/liveness-exec-pod created
[root@k8s-master ~]# kubectl get pods #檢視pod
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 0 6s

#等待一段時間後再次檢視其狀態
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 2 2m46s

上面的資源清單中定義了一個​​pod​​​物件,基於​​busybox​​​映象啟動一個執行​​“touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 3600"​​​命令的容器,此命令在容器啟動時建立了​​/tmp/healthy​​​檔案,並於​​60​​​秒之後將其刪除。存活性探針執行​​”test -e /tmp/healthy"​​​命令檢查​​/tmp/healthy​​​檔案的存在性,若檔案存在則返回狀態碼​​0​​​,表示成功通過測試。在60秒內使用​​“kubectl describe pods/liveness-exec-pod”​​​檢視其詳細資訊,其存活性探測不會出現錯誤。而超過​​60​​​秒之後,再執行該命令檢視詳細資訊,可以發現存活性探測出現了故障,並且還可通過​​“kubectl get pods"​​​檢視該​​pod​​的重啟的相關資訊。

設定HTTP探針示例

基於​​HTTP​​​的探測(​​HTTPGetAction​​​)向目標容器發起一個​​HTTP​​​請求,根據其響應碼進行結果判定,響應碼如​​2xx​​​或​​3xx​​​時表示測試通過。通過該命令”​​# kubectl explain pod.spec.containers.livenessProbe.httpGet​​​“檢視​​httpGet​​定義的欄位

host <string>:請求的主機地址,預設為Pod IP,也可以在httpHeaders中使用“Host:”來定義。
httpHeaders <[]Object>:自定義的請求報文首部。
port <string>:請求的埠,必選欄位。
path <string>:請求的HTTP資源路徑,即URL path。
scheme <string>:建立連線使用的協議,僅可為HTTP或HTTPS,預設為HTTP。

[root@k8s-master ~]# vim manfests/liveness-httpget.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-http
namespace: default
labels:
test: liveness
spec:
containers:
- name: liveness-http-demo
image: nginx:1.12
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Healthz > /usr/share/nginx/html/healthz"]
livenessProbe:
httpGet:
path: /healthz
port: http
scheme: HTTP
[root@k8s-master ~]# kubectl create -f manfests/liveness-httpget.yaml #建立pod
pod/liveness-http created
[root@k8s-master ~]# kubectl get pods #檢視pod
NAME READY STATUS RESTARTS AGE
liveness-http 1/1 Running 0 7s

[root@k8s-master ~]# kubectl describe pods/liveness-http #檢視liveness-http詳細資訊
......
Containers:
liveness-http-demo:
......
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Mon, 09 Sep 2019 15:43:29 +0800
Ready: True
Restart Count: 0
......

上面清單中定義的​​httpGet​​​測試中,通過​​lifecycle​​​中的​​postStart hook​​​建立了一個專用於​​httpGet​​​測試的頁面檔案​​healthz​​​,請求的資源路徑為​​"/healthz"​​​,地址預設為​​Pod IP​​​,埠使用了容器中頂一個埠名稱​​http​​​,這也是明確了為容器指明要暴露的埠的用途之一。並檢視健康狀態檢測相關的資訊,健康狀態檢測正常時,容器也將正常執行。下面通過​​“kubectl exec”​​​命令進入容器刪除由​​postStart hook​​​建立的測試頁面​​healthz​​。再次檢視容器狀態

[root@k8s-master ~]# kubectl exec pods/liveness-http -it -- /bin/sh #進入到上面建立的pod中
# rm -rf /usr/share/nginx/html/healthz #刪除healthz測試頁面
#

[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
liveness-http 1/1 Running 1 10m

[root@k8s-master ~]# kubectl describe pods/liveness-http
......
Containers:
liveness-http-demo:
......
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Mon, 09 Sep 2019 15:53:04 +0800
Last State: Terminated
Reason: Completed
Exit Code: 0
Started: Mon, 09 Sep 2019 15:43:29 +0800
Finished: Mon, 09 Sep 2019 15:53:03 +0800
Ready: True
Restart Count: 1
......

通過上面測試可以看出,當發起​​http​​請求失敗後,容器將被殺掉後進行了重新構建。

設定TCP探針

基於​​TCP​​​的存活性探測(​​TCPSocketAction​​​)用於向容器的特定埠發起​​TCP​​​請求並建立連線進行結果判定,連線建立成功即為通過檢測。相比較來說,它比基於​​HTTP​​​的探測要更高效、更節約資源,但精確度略低,畢竟連線建立成功未必意味著頁面資源可用。通過該命令​​”# kubectl explain pod.spec.containers.livenessProbe.tcpSocket“​​​檢視​​tcpSocket​​定義的欄位

host <string>:請求連線的目標IP地址,預設為Pod IP
port <string>:請求連線的目標埠,必選欄位


[root@k8s-master ~]# vim manfests/liveness-tcp.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-tcp-pod
namespace: default
labels:
test: liveness-tcp
spec:
containers:
- name: liveness-tcp-demo
image: nginx:1.12
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
tcpSocket:
port: http

上面清單中定義的​​tcpSocket​​​測試中,通過向容器的​​80​​埠發起請求,如果埠正常,則表明正常執行。

livenessProbe行為屬性
[root@k8s-master ~]# kubectl explain pods.spec.containers.livenessProbe
KIND: Pod
VERSION: v1

RESOURCE: livenessProbe <Object>

exec command 的方式探測,例如 ps 一個程序是否存在

failureThreshold 探測幾次失敗 才算失敗, 預設是連續三次

initialDelaySeconds 初始化延遲探測,即容器啟動多久之後再開始探測,預設為0秒

periodSeconds 每隔多久探測一次,預設是10秒

successThreshold 處於失敗狀態時,探測操作至少連續多少次的成功才算通過檢測,預設為1秒

timeoutSeconds 存活性探測的超時時長,預設為1秒

httpGet http請求探測

tcpSocket 埠探測

Pod就緒性探測示例


​​Pod​​​物件啟動後,容器應用通常需要一段時間才能完成其初始化過程,例如載入配置或資料,甚至有些程式需要執行某類的預熱過程,若在這個階段完成之前即接入客戶端的請求,勢必會等待太久。因此,這時候就用到了就緒性探測(​​readinessProbe​​)。

與存活性探測機制類似,就緒性探測是用來判斷容器就緒與否的週期性(預設週期為10秒鐘)操作,它用於探測容器是否已經初始化完成並可服務於客戶端請求,探測操作返回​​”success“​​狀態時,即為傳遞容器已經”就緒“的訊號。

就緒性探測也支援​​Exec​​​、​​HTTPGet​​​和​​TCPSocket​​​三種探測方式,且各自的定義機制也都相同。但與存活性探測觸發的操作不同的是,探測失敗時,就緒探測不會殺死或重啟容器以保證其健康性,而是通知其尚未就緒,並觸發依賴於其就緒狀態的操作(例如,從​​Service​​​物件中移除此​​Pod​​​物件)以確保不會有客戶端請求接入此​​Pod​​物件。

這裡只是示例​​http​​​探針示例,不論是​​httpGet​​​還是​​exec​​​還是​​tcpSocket​​和存活性探針類似。

設定HTTP探針示例

#終端1:
[root@k8s-master ~]# vim manfests/readiness-httpget.yaml #編輯readiness-httpget測試pod的yaml檔案
apiVersion: v1
kind: Pod
metadata:
name: readiness-http
namespace: default
labels:
test: readiness-http
spec:
containers:
- name: readiness-http-demo
image: nginx:1.12
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
path: /index.html
port: http
scheme: HTTP
[root@k8s-master ~]# kubectl create -f manfests/readiness-httpget.yaml #建立pod
pod/readiness-http created
[root@k8s-master ~]# kubectl get pods 檢視pod狀態
NAME READY STATUS RESTARTS AGE
liveness-tcp-pod 1/1 Running 1 7d18h
readiness-http 1/1 Running 0 7s


#新開啟一個終端2進入到容器裡面
[root@k8s-master ~]# kubectl exec pods/readiness-http -it -- /bin/sh #進入上面建立的pod
# rm -f /usr/share/nginx/html/index.html #刪除nginx的主頁面檔案
# ls /usr/share/nginx/html
50x.html
#


#回到終端1上面檢視pod狀態
[root@k8s-master ~]# kubectl get pods #檢視pod狀態
NAME READY STATUS RESTARTS AGE
liveness-tcp-pod 1/1 Running 1 7d18h
readiness-http 0/1 Running 0 2m44s

通過上面測試可以看出,當我們刪除了​​nginx​​​主頁檔案後,​​readinessProbe​​​發起的測試就會失敗,此時我們再檢視​​pod​​​的狀態會發現並不會將​​pod​​​刪除重新啟動,只是在​​READY欄位​​​可以看出,當前的​​Pod​​處於未就緒狀態。