Kubernetes 健康狀態檢查(九)
強大的自愈能力是 Kubernetes 這類容器編排引擎的一個重要特性。自愈的預設實現方式是自動重啟發生故障的容器。除此之外,使用者還可以利用 Liveness
和 Readiness
探測機制設定更精細的健康檢查,進而實現如下需求:
- 零停機部署。
- 避免部署無效的映象。
- 更加安全的滾動升級。
一、Liveness 探測
Liveness 探測讓使用者可以自定義判斷容器是否健康的條件。如果探測失敗,Kubernetes 就會重啟容器。
我們建立一個 Pod 的配置檔案liveness.yaml
,可以使用命令kubectl explain pod.spec.containers.livenessProbe
apiVersion: v1 kind: Pod metadata: name: liveness labels: test: liveness spec: restartPolicy: OnFailure containers: - name: liveness image: busybox args: - /bin/sh - -c - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600 livenessProbe: exec: command: - cat - /tmp/healthy initialDelaySeconds: 10 periodSeconds: 5
啟動程序首先建立檔案 /tmp/healthy,30 秒後刪除,在我們的設定中,如果 /tmp/healthy 檔案存在,則認為容器處於正常狀態,反正則發生故障。
livenessProbe
部分定義如何執行 Liveness 探測:
- 探測的方法是:通過
cat
命令檢查 /tmp/healthy 檔案是否存在。如果命令執行成功,返回值為零,Kubernetes 則認為本次 Liveness 探測成功;如果命令返回值非零,本次 Liveness 探測失敗。 initialDelaySeconds: 10
指定容器啟動 10 之後開始執行 Liveness 探測,我們一般會根據應用啟動的準備時間來設定。比如某個應用正常啟動要花 30 秒,那麼initialDelaySeconds
periodSeconds: 5
指定每 5 秒執行一次 Liveness 探測。Kubernetes 如果連續執行 3 次 Liveness 探測均失敗,則會殺掉並重啟容器。
下面建立 Pod liveness:
[[email protected] ~]# kubectl apply -f liveness.yaml
pod/liveness created
從配置檔案可知,最開始的 30 秒,/tmp/healthy
存在,cat 命令返回 0,Liveness 探測成功,這段時間 kubectl describe pod liveness
的 Events部分會顯示正常的日誌。
[[email protected] ~]# kubectl describe pod liveness
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 25s kubelet, node02 pulling image "busybox"
Normal Pulled 24s kubelet, node02 Successfully pulled image "busybox"
Normal Created 24s kubelet, node02 Created container
Normal Started 23s kubelet, node02 Started container
Normal Scheduled 23s default-scheduler Successfully assigned default/liveness to node02
35 秒之後,日誌會顯示 /tmp/healthy
已經不存在,Liveness 探測失敗。再過幾十秒,幾次探測都失敗後,容器會被重啟。
[[email protected] ~]# kubectl describe pod liveness
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 6m9s default-scheduler Successfully assigned default/liveness to node02
Normal Pulled 3m41s (x3 over 6m10s) kubelet, node02 Successfully pulled image "busybox"
Normal Created 3m41s (x3 over 6m10s) kubelet, node02 Created container
Normal Started 3m40s (x3 over 6m9s) kubelet, node02 Started container
Warning Unhealthy 2m57s (x9 over 5m37s) kubelet, node02 Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
Normal Pulling 2m27s (x4 over 6m11s) kubelet, node02 pulling image "busybox"
Normal Killing 60s (x4 over 4m57s) kubelet, node02 Killing container with id docker://liveness:Container failed liveness probe.. Container will be killed and recreated.
然後我們檢視容器,已經重啟了一次。
[[email protected] ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness 1/1 Running 3 5m13s
二、Readiness 探測
使用者通過 Liveness 探測可以告訴 Kubernetes 什麼時候通過重啟容器實現自愈;Readiness 探測則是告訴 Kubernetes 什麼時候可以將容器加入到 Service 負載均衡池中,對外提供服務。
Readiness 探測的配置語法與 Liveness 探測完全一樣,我們建立配置檔案readiness.yaml
。
apiVersion: v1
kind: Pod
metadata:
name: readiness
labels:
test: readiness
spec:
restartPolicy: OnFailure
containers:
- name: readiness
image: busybox
args:
- /bin/sh
- -c
- touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
建立 Pod,然後檢視其狀態。
[[email protected] ~]# kubectl apply -f readiness.yaml
pod/readiness created
剛剛建立時,READY
狀態為不可用。
[[email protected] ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
readiness 0/1 Running 0 21s
15 秒後(initialDelaySeconds + periodSeconds),第一次進行 Readiness 探測併成功返回,設定 READY
為可用。
[[email protected] ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
readiness 1/1 Running 0 38s
30 秒後,/tmp/healthy 被刪除,連續 3 次 Readiness 探測均失敗後,READY
被設定為不可用。
[[email protected] ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
readiness 0/1 Running 0 63s
通過 kubectl describe pod readiness
也可以看到 Readiness 探測失敗的日誌。
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Pulling 5m29s kubelet, node01 pulling image "busybox"
Normal Scheduled 5m25s default-scheduler Successfully assigned default/readiness to node01
Normal Pulled 5m13s kubelet, node01 Successfully pulled image "busybox"
Normal Created 5m12s kubelet, node01 Created container
Normal Started 5m12s kubelet, node01 Started container
Warning Unhealthy 28s (x51 over 4m38s) kubelet, node01 Readiness probe failed: cat: can't open '/tmp/healthy': No such file or directory
下面對 Liveness 探測和 Readiness 探測做個比較:
- Liveness 探測和 Readiness 探測是兩種 Health Check 機制,如果不特意配置,Kubernetes 將對兩種探測採取相同的預設行為,即通過判斷容器啟動程序的返回值是否為零來判斷探測是否成功。
- 兩種探測的配置方法完全一樣,支援的配置引數也一樣。不同之處在於探測失敗後的行為:Liveness 探測是重啟容器;Readiness 探測則是將容器設定為不可用,不接收 Service 轉發的請求。
- Liveness 探測和 Readiness 探測是獨立執行的,二者之間沒有依賴,所以可以單獨使用,也可以同時使用。用 Liveness 探測判斷容器是否需要重啟以實現自愈;用 Readiness 探測判斷容器是否已經準備好對外提供服務。
三、Health Check 在 Scale Up 中的應用
對於多副本應用,當執行 Scale Up 操作時,新副本會作為 backend 被新增到 Service 的負責均衡中,與已有副本一起處理客戶的請求。考慮到應用啟動通常都需要一個準備階段,比如載入快取資料,連線資料庫等,從容器啟動到正真能夠提供服務是需要一段時間的。我們可以通過 Readiness 探測判斷容器是否就緒,避免將請求傳送到還沒有 ready 的 backend。
下面我們建立一個配置檔案來說明這種情況。
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
template:
metadata:
labels:
run: web
spec:
containers:
- name: web
images: myhttpd
ports:
- containerPort: 8080
readinessProbe:
httpGet:
scheme: HTTP
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: web-svc
spec:
selector:
run: web
ports:
- protocol: TCP
port: 8080
targetPort: 80
重點關注 readinessProbe
部分。這裡我們使用了不同於 exec
的另一種探測方法 -- httpGet
。Kubernetes 對於該方法探測成功的判斷條件是 http 請求的返回程式碼在 200-400 之間。
schema
指定協議,支援 HTTP(預設值)和 HTTPS。path
指定訪問路徑。port
指定埠。
上面配置的作用是:
- 容器啟動 10 秒之後開始探測。
- 如果 http://[container_ip]:8080/healthy 返回程式碼不是 200-400,表示容器沒有就緒,不接收 Service web-svc 的請求。
- 每隔 5 秒再探測一次。
- 直到返回程式碼為 200-400,表明容器已經就緒,然後將其加入到 web-svc 的負責均衡中,開始處理客戶請求。
- 探測會繼續以 5 秒的間隔執行,如果連續發生 3 次失敗,容器又會從負載均衡中移除,直到下次探測成功重新加入。
對於生產環境中重要的應用都建議配置 Health Check,保證處理客戶請求的容器都是準備就緒的 Service backend。
四、Health Check 在 Rolling Update 中的應用
Health Check 另一個重要的應用場景是 Rolling Update。試想一下下面的情況,現有一個正常執行的多副本應用,接下來對應用進行更新(比如使用更高版本的 image),Kubernetes 會啟動新副本,然後發生瞭如下事件:
- 正常情況下新副本需要 10 秒鐘完成準備工作,在此之前無法響應業務請求。
- 但由於人為配置錯誤,副本始終無法完成準備工作(比如無法連線後端資料庫)。
因為新副本本身沒有異常退出,預設的 Health Check 機制會認為容器已經就緒,進而會逐步用新副本替換現有副本,其結果就是:當所有舊副本都被替換後,整個應用將無法處理請求,無法對外提供服務。如果這是發生在重要的生產系統上,後果會非常嚴重。
如果正確配置了 Health Check,新副本只有通過了 Readiness 探測,才會被新增到 Service;如果沒有通過探測,現有副本不會被全部替換,業務仍然正常進行。
下面通過例子來實踐 Health Check 在 Rolling Update 中的應用。
用如下配置檔案 app.v1.yml 模擬一個 10 副本的應用:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: app
spec:
replicas: 10
template:
metadata:
labels:
run: app
spec:
containers:
- name: app
images: busybox
args:
- /bin/sh
- -c
- sleep 10; touch /tmp/healthy; sleep 30000
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
10 秒後副本能夠通過 Readiness 探測。
[[email protected] ~]# kubectl apply -f app.v1.yaml
deployment.extensions/app created
[[email protected] ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
app-56878b4676-4rftg 1/1 Running 0 34s
app-56878b4676-6jtn4 1/1 Running 0 34s
app-56878b4676-6smfj 1/1 Running 0 34s
app-56878b4676-8pnc2 1/1 Running 0 34s
app-56878b4676-hxzjk 1/1 Running 0 34s
app-56878b4676-mglht 1/1 Running 0 34s
app-56878b4676-t2qs6 1/1 Running 0 34s
app-56878b4676-vgw44 1/1 Running 0 34s
app-56878b4676-vnxfx 1/1 Running 0 34s
app-56878b4676-wb9rh 1/1 Running 0 34s
接下來滾動更新應用,配置檔案 app.v2.yml
如下:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: app
spec:
replicas: 10
template:
metadata:
labels:
run: app
spec:
containers:
- name: app
image: busybox
args:
- /bin/sh
- -c
- sleep 30000
readinessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 10
periodSeconds: 5
很顯然,由於新副本中不存在 /tmp/healthy,是無法通過 Readiness 探測的。驗證如下:
[[email protected] ~]# kubectl apply -f app.v2.yaml
deployment.extensions/app configured
[[email protected] ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
app-56878b4676-4rftg 1/1 Running 0 4m42s
app-56878b4676-6jtn4 1/1 Running 0 4m42s
app-56878b4676-6smfj 1/1 Running 0 4m42s
app-56878b4676-hxzjk 1/1 Running 0 4m42s
app-56878b4676-mglht 1/1 Running 0 4m42s
app-56878b4676-t2qs6 1/1 Running 0 4m42s
app-56878b4676-vgw44 1/1 Running 0 4m42s
app-56878b4676-vnxfx 1/1 Running 0 4m42s
app-56878b4676-wb9rh 1/1 Running 0 4m42s
app-84fc656775-hf954 0/1 Running 0 66s
app-84fc656775-p287w 0/1 Running 0 66s
[[email protected] ~]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
app 9/10 2 9 7m1s
先關注 kubectl get pod
輸出:
- 從 Pod 的 AGE 欄可判斷,最後 2 個 Pod 是新副本,目前處於 NOT READY 狀態。
- 舊副本從最初 10 個減少到 8 個。
再來看 kubectl get deployment app
的輸出:
- DESIRED 10 表示期望的狀態是 10 個 READY 的副本。
- UP-TO-DATE 2 表示當前已經完成更新的副本數:即 2 個新副本。
- AVAILABLE 9 表示當前處於 READY 狀態的副本數:即 9箇舊副本。
在我們的設定中,新副本始終都無法通過 Readiness 探測,所以這個狀態會一直保持下去。
上面我們模擬了一個滾動更新失敗的場景。不過幸運的是:Health Check 幫我們遮蔽了有缺陷的副本,同時保留了大部分舊副本,業務沒有因更新失敗受到影響。
滾動更新可以通過引數 maxSurge
和 maxUnavailable
來控制副本替換的數量。