一、容器的存活性探測livenessProbe
存活探針清單格式
Pod配置格式中,spec.containers.livenessProbe字段用於定義此類檢測,配置格式如下所示。但一個容器之上僅能定義一種類型的探針,即exec、httpGet和tcpSocket三者互斥,它們不可在一個容器同時使用,只能三選一。
apiVersion: v1 kind: Pod metadata: name: ... namespace: ... spec: containers: - name: ... image: ... imagePullPolicy: ... livenessProbe: exec<Object> #命令式探針 httpGet<Object> #http Get 類型的探針 tcpSocket<Object> #tcp Socket類型的探針 #上面的探針類型只能三選一 initialDelaySeconds<integer> #發起初次探測請求的延時時長,容器啟動多長時間后發起第一次探測請求。 periodSeconds<integer> #請求周期 timeoutSeconds<integer> #超時時長 successThreshold #成功閥值,在容器運行不正常的情況下,連續探測多少次成功后,檢測通過。 failureThreshold #失敗閥值,在容器運行正常的情況下,連續探測多少次失敗后,檢測失敗。
探針之外的其他字段用於定義探測操作的行為方式,沒有明確定義這些屬性字段時,它們會使用各自的默認值,各字段的詳細說明如下。
▪initialDelaySeconds <integer>:首次發出存活探測請求的延遲時長,即容器啟動多久之后開始第一次探測操作,顯示為delay屬性;默認為0秒,即容器啟動后便立刻進行探測;該參數值應該大於容器的最大初始化時長,以避免程序永遠無法啟動。 ▪timeoutSeconds <integer>:存活探測的超時時長,顯示為timeout屬性,默認為1秒,最小值也為1秒;應該為此參數設置一個合理值,以避免因應用負載較大時的響應延遲導致Pod被重啟。 ▪periodSeconds <integer>:存活探測的頻度,顯示為period屬性,默認為10秒,最小值為1秒;需要注意的是,過高的頻率會給Pod對象帶來較大的額外開銷,而過低的頻率又會使得對錯誤反應不及時。 ▪successThreshold <integer>:處於失敗狀態時,探測操作至少連續多少次的成功才被認為通過檢測,顯示為#success屬性,僅可取值為1。 ▪failureThreshold:處於成功狀態時,探測操作至少連續多少次的失敗才被視為檢測不通過,顯示為#failure屬性,默認值為3,最小值為1;盡量設置寬容一些的失敗計數,能有效避免一些場景中的服務級聯失敗。
查看一個calico的pod
root@k8s-master01:~# kubectl describe pod calico-node-xjw6q -n kube-system ...... Liveness: exec [/bin/calico-node -felix-live -bird-live] delay=10s timeout=1s period=10s #success=1 #failure=6 Readiness: exec [/bin/calico-node -felix-ready -bird-ready] delay=0s timeout=1s period=10s #success=1 #failure=3 ......
1、exec存活探針
exec類型的探針通過在目標容器中執行由用戶自定義的命令來判定容器的健康狀態,命令狀態返回值為0表示“成功”通過檢測,其余值均為“失敗”狀態。spec.containers.livenessProbe.exec字段只有一個可用屬性command,用於指定要執行的命令。
apiVersion: v1 kind: Pod metadata: name: liveness-exec-demo namespace: default spec: containers: - name: demo image: ikubernetes/demoapp:v1.0 imagePullPolicy: IfNotPresent livenessProbe: exec: command: ['/bin/sh', '-c', '[ "$(curl -s 127.0.0.1/livez)" == "OK" ]'] initialDelaySeconds: 5 periodSeconds: 5
該配置清單中定義的Pod對象為demo容器定義了exec探針,它通過在容器本地執行測試命令來比較curl -s 127.0.0.1/livez的返回值是否為OK以判定容器的存活狀態。命令成功執行則表示容器正常運行,否則3次檢測失敗之后則將其判定為檢測失敗。首次檢測在容器啟動5秒之后進行,請求間隔也是5秒。
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl apply -f liveness-exec-demo.yaml pod/liveness-exec-demo created root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl get pod NAME READY STATUS RESTARTS AGE liveness-exec-demo 1/1 Running 0 18s #重啟次數為0
創建完成后,Pod中的容器demo會正常運行,存活檢測探針也不會遇到檢測錯誤而導致容器重啟。若要測試存活狀態檢測的效果,可以手動將/livez的響應內容修改為OK之外的其他值,例如FAIL。
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl exec liveness-exec-demo -- curl -s -X POST -d 'livez=FAIL' 127.0.0.1/livez
而后經過1個檢測周期,可通過Pod對象的描述信息來獲取相關的事件狀態,例如,由下面命令結果中的事件可知,容器因健康狀態檢測失敗而被重啟。
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl describe pod liveness-exec-demo ...... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 100s default-scheduler Successfully assigned default/liveness-exec-demo to 172.168.33.211 Normal Pulling 100s kubelet Pulling image "ikubernetes/demoapp:v1.0" Normal Pulled 96s kubelet Successfully pulled image "ikubernetes/demoapp:v1.0" in 3.148561284s Normal Created 96s kubelet Created container demo Normal Started 96s kubelet Started container demo Warning Unhealthy 15s (x4 over 89s) kubelet Liveness probe failed: Normal Killing 15s kubelet Container demo failed liveness probe, will be restarted #livenessprobe檢測失敗。容器被重啟
下面輸出信息中的Containers一段中還清晰顯示了容器健康狀態檢測及狀態變化的相關信息:容器當前處於Running狀態,但前一次是為Terminated,原因是退出碼為137的錯誤信息,它表示進程是被外部信號所終止。137事實上由兩部分數字之和生成:128+signum,其中signum是導致進程終止的信號的數字標識,9表示SIGKILL,這意味着進程是被強行終止的。
Containers: demo: Container ID: docker://99edd216d313a69fd5beb1b51e18d1c61c1f520f678d10423bb17285c61583f8 Image: ikubernetes/demoapp:v1.0 Image ID: docker-pullable://ikubernetes/demoapp@sha256:6698b205eb18fb0171398927f3a35fe27676c6bf5757ef57a35a4b055badf2c3 Port: <none> Host Port: <none> State: Running Started: Wed, 13 Oct 2021 07:04:11 +0800 Last State: Terminated Reason: Error Exit Code: 137 Started: Wed, 13 Oct 2021 07:02:20 +0800 Finished: Wed, 13 Oct 2021 07:04:11 +0800 Ready: True Restart Count: 1 #容器重啟次數為1 Liveness: exec [/bin/sh -c [ "$(curl -s 127.0.0.1/livez)" == "OK" ]] delay=5s timeout=1s period=5s #success=1 #failure=3 Environment: <none> Mounts: /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-2fp2n (ro)
待容器重啟完成后,/livez的響應內容會重置鏡像中默認定義的OK,因而其存活狀態檢測不會再遇到錯誤,這模擬了一種典型的通過“重啟”應用而解決問題的場景。需要特別說明的是,exec指定的命令運行在容器中,會消耗容器的可用計算資源配額,另外考慮到探測操作的效率等因素,探測操作的命令應該盡可能簡單和輕量。
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl get pod NAME READY STATUS RESTARTS AGE liveness-exec-demo 1/1 Running 1 5m12s #RESTART狀態為1及已經重啟了一次,重啟后容器可以正常提供服務 root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl exec liveness-exec-demo -- curl -s 127.0.0.1 iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.1, ServerName: liveness-exec-demo, ServerIP: 172.20.58.223!
2、HTTP探針
▪host <string>:請求的主機地址,默認為Pod IP;也可以在httpHeaders使用“Host:”來定義。 ▪port <string>:請求的端口,必選字段。 ▪httpHeaders <[]Object>:自定義的請求報文頭部。 ▪path <string>:請求的HTTP資源路徑,即URL path。 ▪scheme:建立連接使用的協議,僅可為HTTP或HTTPS,默認為HTTP。
下面是一個定義在資源清單文件liveness-httpget-demo.yaml中的示例,它使用HTTP探針直接對/livez發起訪問請求,並根據其響應碼來判定檢測結果。
apiVersion: v1 kind: Pod metadata: name: liveness-httpget-demo namespace: default spec: containers: - name: demo image: ikubernetes/demoapp:v1.0 imagePullPolicy: IfNotPresent livenessProbe: httpGet: path: '/livez' port: 80 scheme: HTTP initialDelaySeconds: 5
上面清單文件中定義的httpGet測試中,請求的資源路徑為/livez,地址默認為Pod IP,端口使用了容器中定義的端口名稱http,這也是明確為容器指明要暴露的端口的用途之一。下面測試其效果,首先創建此Pod對象:
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl apply -f liveness-httpget-demo.yaml pod/liveness-httpget-demo created root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl get pod NAME READY STATUS RESTARTS AGE liveness-httpget-demo 1/1 Running 0 25s
首次檢測為延遲5秒,這剛好超過了demoapp的/livez接口默認會延遲響應的時長。鏡像中定義的默認響應是以200狀態碼響應、以OK為響應結果,存活狀態檢測會成功完成。為了測試存活狀態檢測的效果,同樣可以手動將/livez的響應內容修改為OK之外的其他值,例如FAIL。
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl exec liveness-httpget-demo -- curl -s -X POST -d "livez=FAIL" 127.0.0.1/livez #而后經過至少1個檢測周期后,可通過Pod對象的描述信息來獲取相關的事件狀態,例如,由下面命令的結果中的事件可知,容器因健康狀態檢測失敗而被重啟。 root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl describe pod liveness-httpget-demo ...... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 41s default-scheduler Successfully assigned default/liveness-httpget-demo to 172.168.33.211 Normal Pulled 41s kubelet Container image "ikubernetes/demoapp:v1.0" already present on machine Normal Created 41s kubelet Created container demo Normal Started 41s kubelet Started container demo Warning Unhealthy 31s kubelet Liveness probe failed: Get "http://172.20.58.224:80/livez": context deadline exceeded (Client.Timeout exceeded while awaiting headers) Warning Unhealthy 12s (x2 over 22s) kubelet Liveness probe failed: HTTP probe failed with statuscode: 506 Normal Killing 12s kubelet Container demo failed liveness probe, will be restarted
一般來說,HTTP探針應該針對專用的URL路徑進行,例如前面示例中特別為其准備的/livez,此URL路徑對應的Web資源也應該以輕量化的方式在內部對應用程序的各關鍵組件進行全面檢測,以確保它們可正常向客戶端提供完整的服務。
3、TCP探針
TCP探針是基於TCP協議進行存活性探測(TCPSocketAction),通過向容器的特定端口發起TCP請求並嘗試建立連接進行結果判定,連接建立成功即為通過檢測。相比較來說,它比基於HTTP協議的探測要更高效、更節約資源,但精准度略低,畢竟連接建立成功未必意味着頁面資源可用。 spec.containers.livenessProbe.tcpSocket字段用於定義此類檢測,它主要有以下兩個可用字段:
1)host <string>:請求連接的目標IP地址,默認為Pod自身的IP; 2)port <string>:請求連接的目標端口,必選字段,可以名稱調用容器上顯式定義的端口。
下面是一個定義在資源清單文件liveness-tcpsocket-demo.yaml中的示例,它向Pod對象的TCP協議的80端口發起連接請求,並根據連接建立的狀態判定測試結果。為了能在容器中通過iptables阻止接收對80端口的請求以驗證TCP檢測失敗,下面的配置還在容器上啟用了特殊的內核權限NET_ADMIN。
apiVersion: v1 kind: Pod metadata: name: liveness-tcpsocket-demo namespace: default spec: containers: - name: demo image: ikubernetes/demoapp:v1.0 imagePullPolicy: IfNotPresent ports: - name: http containerPort: 80 securityContext: capabilities: add: - NET_ADMIN livenessProbe: tcpSocket: port: http periodSeconds: 5 initialDelaySeconds: 20
按照配置,將該清單中的Pod對象創建在集群之上,20秒之后即會進行首次的tcpSocket檢測。
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl apply -f liveness-tcpsocket-demo.yaml pod/liveness-tcpsocket-demo created root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl get pod NAME READY STATUS RESTARTS AGE liveness-tcpsocket-demo 1/1 Running 0 17s
容器應用demoapp啟動后即監聽於TCP協議的80端口,tcpSocket檢測也就可以成功執行。為了測試效果,可使用下面的命令在Pod的Network名稱空間中設置iptables規則以阻止對80端口的請求:
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl exec liveness-tcpsocket-demo -- iptables -A INPUT -p tcp --dport 80 -j REJECT
而后經過至少1個檢測周期后,可通過Pod對象的描述信息來獲取相關的事件狀態,例如,由下面命令的結果中的事件可知,容器因健康狀態檢測失敗而被重啟。
root@k8s-master01:/apps/k8s-yaml/livessnessprob# kubectl describe pod liveness-tcpsocket-demo ...... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 99s default-scheduler Successfully assigned default/liveness-tcpsocket-demo to 172.168.33.212 Normal Pulled 99s kubelet Container image "ikubernetes/demoapp:v1.0" already present on machine Normal Created 99s kubelet Created container demo Normal Started 99s kubelet Started container demo Warning Unhealthy 14s (x3 over 24s) kubelet Liveness probe failed: dial tcp 172.20.135.178:80: i/o timeout Normal Killing 14s kubelet Container demo failed liveness probe, will be restarted
二、Pod的重啟策略
Pod對象的應用容器因程序崩潰、啟動狀態檢測失敗、存活狀態檢測失敗或容器申請超出限制的資源等原因都可能導致其被終止,此時是否應該重啟則取決於Pod上的restartPolicy(重啟策略)字段的定義,該字段支持以下取值。
1)Always:無論因何原因、以何種方式終止,kubelet都將重啟該Pod,此為默認設定。 2)OnFailure:僅在Pod對象以非0方式退出時才將其重啟。 3)Never:不再重啟該Pod。
需要注意的是,restartPolicy適用於Pod對象中的所有容器,而且它僅用於控制在同一個節點上重新啟動Pod對象的相關容器。首次需要重啟的容器,其重啟操作會立即進行,而再次重啟操作將由kubelet延遲一段時間后進行,反復的重啟操作的延遲時長依次為10秒、20秒、40秒、80秒、160秒和300秒,300秒是最大延遲時長。 事實上,一旦綁定到一個節點,Pod對象將永遠不會被重新綁定到另一個節點,它要么被重啟,要么被終止,直到節點故障、被刪除或被驅逐。
Pod對象啟動后,應用程序通常需要一段時間完成其初始化過程,例如加載配置或數據、緩存初始化等,甚至有些程序需要運行某類預熱過程等,因此通常應該避免在Pod對象啟動后立即讓其處理客戶端請求,而是需要等待容器初始化工作執行完成並轉為“就緒”狀態,尤其是存在其他提供相同服務的Pod對象的場景更是如此。 與存活探針不同的是,就緒狀態檢測是用來判斷容器應用就緒與否的周期性(默認周期為10秒鍾)操作,它用於檢測容器是否已經初始化完成並可服務客戶端請求。與存活探針觸發的操作不同,檢測失敗時,就緒探針不會殺死或重啟容器來確保其健康狀態,而僅僅是通知其尚未就緒,並觸發依賴其就緒狀態的其他操作(例如從Service對象中移除此Pod對象),以確保不會有客戶端請求接入此Pod對象。 就緒探針也支持Exec、HTTP GET和TCP Socket這3種探測方式,且它們各自的定義機制與存活探針相同。因而,將容器定義中的livenessProbe字段名替換為readinessProbe,並略做適應性修改即可定義出就緒性檢測的配置來,甚至有些場景中的就緒探針與存活探針的配置可以完全相同。 demoapp應用程序通過/readyz暴露了專用於就緒狀態檢測的接口,它於程序啟動約15秒后能夠以200狀態碼響應、以OK為響應結果,也支持用戶使用POST請求方法通過readyz參數傳遞自定義的響應內容,不過所有非OK的響應內容都被響應以5xx的狀態碼。一個簡單的示例如下面的配置清單(readiness-httpget-demo.yaml)所示。
就緒探針檢測成功,該pod才會被service加入到endpoint,使其向外提供服務;失敗則會將該pod從endpoint中刪除,知道下次檢測成功才重新加入service的endpoint
kind: Service apiVersion: v1 metadata: name: services-readiness-demo namespace: default spec: selector: app: web ports: - name: http protocol: TCP port: 80 targetPort: 80 --- apiVersion: v1 kind: Pod metadata: name: readiness-httpget-demo namespace: default labels: app: web spec: containers: - name: demo image: ikubernetes/demoapp:v1.0 imagePullPolicy: IfNotPresent readinessProbe: httpGet: path: '/readyz' port: 80 scheme: HTTP initialDelaySeconds: 15 timeoutSeconds: 2 periodSeconds: 5 failureThreshold: 3 restartPolicy: Always
測試該Pod就緒探針的作用。按照配置,將Pod對象創建在集群上約15秒后啟動首次探測,在該探測結果成功返回之前,Pod將一直處於未就緒狀態:
root@k8s-master01:/apps/k8s-yaml/readinessprobe# kubectl apply -f readiness-httpget-demo.yaml service/services-readiness-demo configured pod/readiness-httpget-demo created [root@k8s-master01 apps]# kubectl get pod -n default -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES readiness-httpget-demo 1/1 Running 0 45s 10.244.135.155 k8s-node03 <none> <none>
接着運行kubectl get -w命令監視其資源變動信息,由如下命令結果可知,盡管Pod對象處於Running狀態,但直到就緒檢測命令執行成功后Pod資源才轉為“就緒”。
root@k8s-master01:~# kubectl get pod readiness-httpget-demo -w NAME READY STATUS RESTARTS AGE readiness-httpget-demo 0/1 Running 0 4s readiness-httpget-demo 1/1 Running 0 31s
就緒檢測成功后,該pod才會被添加進services的endpoint
root@k8s-master01:/apps/k8s-yaml/readinessprobe# kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES readiness-httpget-demo 1/1 Running 0 2m20s 172.20.58.226 172.168.33.211 <none> <none> root@k8s-master01:/apps/k8s-yaml/readinessprobe# kubectl describe services services-readiness-demo Name: services-readiness-demo Namespace: default Labels: <none> Annotations: <none> Selector: app=web Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.68.169.49 IPs: 10.68.169.49 Port: http 80/TCP TargetPort: 80/TCP Endpoints: 172.20.58.226:80 #pod的ip Session Affinity: None Events: <none>
Pod運行過程中的某一時刻,無論因何原因導致的就緒狀態檢測的連續失敗都會使得該Pod從就緒狀態轉變為“未就緒”,並且會從各個通過標簽選擇器關聯至該Pod對象的Service后端端點列表中刪除。為了測試就緒狀態檢測效果,下面修改/readyz響應以非OK內容。
root@k8s-master01:/apps/k8s-yaml/readinessprobe# kubectl exec readiness-httpget-demo -- curl -s -XPOST -d 'readyz=FAIL' 127.0.0.1/readyz
而后在至少1個檢測周期之后,通過該Pod的描述信息可以看到就緒檢測失敗相關的事件描述,命令及結果如下所示:
root@k8s-master01:/apps/k8s-yaml/readinessprobe# kubectl describe pod readiness-httpget-demo ...... Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 3m26s default-scheduler Successfully assigned default/readiness-httpget-demo to 172.168.33.211 Normal Pulled 3m26s kubelet Container image "ikubernetes/demoapp:v1.0" already present on machine Normal Created 3m26s kubelet Created container demo Normal Started 3m26s kubelet Started container demo Warning Unhealthy 2m59s (x3 over 3m9s) kubelet Readiness probe failed: Get "http://172.20.58.226:80/readyz": context deadline exceeded (Client.Timeout exceeded while awaiting headers) Warning Unhealthy 1s (x5 over 21s) kubelet Readiness probe failed: HTTP probe failed with statuscode: 507 #容器不會被重啟,但是service中endpoint會刪除改pod的ip root@k8s-master01:/apps/k8s-yaml/readinessprobe# kubectl describe services services-readiness-demo Name: services-readiness-demo Namespace: default Labels: <none> Annotations: <none> Selector: app=web Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 10.68.169.49 IPs: 10.68.169.49 Port: http 80/TCP TargetPort: 80/TCP Endpoints: #pod ip已經被刪除,service不會將客戶請求轉發至該pod Session Affinity: None Events: <none>
四、總結
readnessprobe探針的exec和tcp的使用方式與livenessprobe的使用方式一樣。
區別:
livenessprobe檢測失敗,容器會根據定義的重啟策略來重啟容器。
readnessprobe檢測失敗,容器不會被重啟,pod會從相應的service的endpoint中刪除。
使用方式:
livenessprobe和readnessprobe的方式也一樣,都使用相同的探針(如:都使用exec或http),其他參數的配置也要求一致。
同時配置的優點:
一個pod在livenessprobe檢測失敗后,同時從service的endpoint中刪除改pod,使其客戶的請求不會被轉發到有問題的pod上。
