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處於未就緒狀態。