k8s健康檢查(七)--技術流ken

 

默認的健康檢查

 

強大的自愈能力是 Kubernetes 這類容器編排引擎的一個重要特性。自愈的默認實現方式是自動重啓發生故障的容器。除此以外,用戶還能夠利用 Liveness 和 Readiness 探測機制設置更精細的健康檢查,進而實現以下需求:web

  1. 零停機部署。數據庫

  2. 避免部署無效的鏡像。後端

  3. 更加安全的滾動升級。緩存

 

每一個容器啓動時都會執行一個進程,此進程由 Dockerfile 的 CMD 或 ENTRYPOINT 指定。若是進程退出時返回碼非零,則認爲容器發生故障,Kubernetes 就會根據 restartPolicy 重啓容器。安全

 

第一步: 下面咱們模擬一個容器發生故障的場景,Pod 配置文件以下:服務器

Pod 的 restartPolicy 設置爲 OnFailure,默認爲 Always。app

sleep 10; exit 1 模擬容器啓動 10 秒後發生故障。、負載均衡

 

第二步:執行 kubectl apply 建立 Pod,命名爲 healthcheck。ide

[root@ken ~]# kubectl apply -f healthcheck.yml
pod/healthcheck created

 

第三步:過幾分鐘查看 Pod 的狀態:oop

[root@ken ~]# kubectl get pod -o wide
NAME                     READY   STATUS             RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
healthcheck              0/1     CrashLoopBackOff   2          85s   10.244.1.47   host1   <none>           <none>

可看到容器當前已經重啓了 2次。

 

在上面的例子中,容器進程返回值非零,Kubernetes 則認爲容器發生故障,須要重啓。但有很多狀況是發生了故障,但進程並不會退出。好比訪問 Web 服務器時顯示 500 內部錯誤,多是系統超載,也多是資源死鎖,此時 httpd 進程並無異常退出,在這種狀況下重啓容器多是最直接最有效的解決方案,那咱們如何利用 Health Check 機制來處理這類場景呢?

 

Liveness探測

 

Liveness 探測讓用戶能夠自定義判斷容器是否健康的條件。若是探測失敗,Kubernetes 就會重啓容器。

 

第一步:建立以下 Pod:

啓動進程首先建立文件 /tmp/healthy,30 秒後刪除,在咱們的設定中,若是 /tmp/healthy 文件存在,則認爲容器處於正常狀態,反正則發生故障。

livenessProbe 部分定義如何執行 Liveness 探測:

 

探測的方法是:經過 cat 命令檢查 /tmp/healthy 文件是否存在。若是命令執行成功,返回值爲零,Kubernetes 則認爲本次 Liveness 探測成功;若是命令返回值非零,本次 Liveness 探測失敗。

initialDelaySeconds: 10 指定容器啓動 10 以後開始執行 Liveness 探測,咱們通常會根據應用啓動的準備時間來設置。好比某個應用正常啓動要花 30 秒,那麼 initialDelaySeconds 的值就應該大於 30

periodSeconds: 5 指定每 5 秒執行一次 Liveness 探測。Kubernetes 若是連續執行 3 Liveness 探測均失敗,則會殺掉並重啓容器。

 

第二步:下面建立 Pod liveness:

[root@ken ~]# kubectl apply -f healthcheck.yml
pod/liveness created

 

從配置文件可知,最開始的 30 秒,/tmp/healthy 存在,cat 命令返回 0Liveness 探測成功

 

第三步:這段時間 kubectl describe pod liveness Events部分會顯示正常的日誌。

[root@ken ~]# kubectl describe pod liveness
...
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  30s   default-scheduler  Successfully assigned default/liveness to host1
  Normal  Pulling    29s   kubelet, host1     pulling image "busybox"
  Normal  Pulled     27s   kubelet, host1     Successfully pulled image "busybox"
  Normal  Created    27s   kubelet, host1     Created container
  Normal  Started    27s   kubelet, host1     Started container

 

第四步:35秒後再次查看日誌

35 秒以後,日誌會顯示 /tmp/healthy 已經不存在,Liveness 探測失敗。再過幾十秒,幾回探測都失敗後,容器會被重啓。

[root@ken ~]# kubectl describe pod liveness
...
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  60s                default-scheduler  Successfully assigned default/liveness to host1
  Normal   Pulling    59s                kubelet, host1     pulling image "busybox"
  Normal   Pulled     57s                kubelet, host1     Successfully pulled image "busybox"
  Normal   Created    57s                kubelet, host1     Created container
  Normal   Started    57s                kubelet, host1     Started container
  Warning  Unhealthy  13s (x3 over 23s)  kubelet, host1     Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory

 

第五步:查看pod

能夠發現容器開始被重啓

 

Readiness 探測 

 

除了 Liveness 探測,Kubernetes Health Check 機制還包括 Readiness 探測。

 

用戶經過 Liveness 探測能夠告訴 Kubernetes 何時經過重啓容器實現自愈;Readiness 探測則是告訴 Kubernetes 何時能夠將容器加入到 Service 負載均衡池中,對外提供服務。

 

第一步:Readiness 探測的配置語法與 Liveness 探測徹底同樣

這個配置文件只是將前面例子中的 liveness 替換爲了 readiness,咱們看看有什麼不一樣的效果。

 

第二步:部署

[root@ken ~]# kubectl apply -f readness.yml
pod/readiness created

[root@ken ~]# kubectl get pod readiness
NAME        READY   STATUS    RESTARTS   AGE
readiness   0/1     Running   0          17s

[root@ken ~]# kubectl get pod readiness
NAME        READY   STATUS    RESTARTS   AGE
readiness   1/1     Running   0          18s


[root@ken ~]# kubectl get pod readiness
NAME        READY   STATUS    RESTARTS   AGE
readiness   0/1     Running   0          84s

Pod readiness 的 READY 狀態經歷了以下變化:

 

剛被建立時,READY 狀態爲不可用。

15 秒後(initialDelaySeconds + periodSeconds),第一次進行 Readiness 探測併成功返回,設置 READY 爲可用。

30 秒後,/tmp/healthy 被刪除,連續 3 次 Readiness 探測均失敗後,READY 被設置爲不可用。

 

第三步:經過 kubectl describe pod readiness 也能夠看到 Readiness 探測失敗的日誌。

[root@ken ~]# kubectl describe pod readiness
...
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  104s               default-scheduler  Successfully assigned default/readiness to host1
  Normal   Pulling    103s               kubelet, host1     pulling image "busybox"
  Normal   Pulled     101s               kubelet, host1     Successfully pulled image "busybox"
  Normal   Created    101s               kubelet, host1     Created container
  Normal   Started    100s               kubelet, host1     Started container
  Warning  Unhealthy  1s (x14 over 66s)  kubelet, host1     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 探測判斷容器是否已經準備好對外提供服務。

 

健康檢測在scale up中的應用

 

對於多副本應用,當執行 Scale Up 操做時,新副本會做爲 backend 被添加到 Service 的負責均衡中,與已有副本一塊兒處理客戶的請求。考慮到應用啓動一般都須要一個準備階段,好比加載緩存數據,鏈接數據庫等,從容器啓動到正真可以提供服務是須要一段時間的。咱們能夠經過 Readiness 探測判斷容器是否就緒,避免將請求發送到尚未 ready backend

 

第一步:下面是示例應用的配置文件。

重點關注 readinessProbe 部分。這裏咱們使用了不一樣於 exec 的另外一種探測方法 -- httpGetKubernetes 對於該方法探測成功的判斷條件是 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 次失敗,容器又會從負載均衡中移除,直到下次探測成功從新加入。

 

健康檢測在滾動更新中的應用

 

現有一個正常運行的多副本應用,接下來對應用進行更新(好比使用更高版本的 image),Kubernetes 會啓動新副本,而後發生了以下事件:

 

正常狀況下新副本須要 10 秒鐘完成準備工做,在此以前沒法響應業務請求。

但因爲人爲配置錯誤,副本始終沒法完成準備工做(好比沒法鏈接後端數據庫)。

先別繼續往下看,如今請花一分鐘思考這個問題:若是沒有配置 Health Check,會出現怎樣的狀況?

 

由於新副本自己沒有異常退出,默認的 Health Check 機制會認爲容器已經就緒,進而會逐步用新副本替換現有副本,其結果就是:當全部舊副本都被替換後,整個應用將沒法處理請求,沒法對外提供服務。若是這是發生在重要的生產系統上,後果會很是嚴重。

若是正確配置了 Health Check,新副本只有經過了 Readiness 探測,纔會被添加到 Service;若是沒有經過探測,現有副本不會被所有替換,業務仍然正常進行。

 

第一步:用以下配置文件 app.v1.yml 模擬一個 10 副本的應用:

10 秒後副本可以經過 Readiness 探測。

 

第二步:執行部署操做

[root@ken ~]# kubectl apply -f app.v1.yml
deployment.apps/app created
[root@ken ~]# kubectl get deployment app
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
app    10/10   10           10          71s
[root@ken ~]# kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
app-56878b4676-45bmq   1/1     Running   0          80s
app-56878b4676-5w5ck   1/1     Running   0          80s
app-56878b4676-6mnvz   1/1     Running   0          80s
app-56878b4676-fb8fk   1/1     Running   0          80s
app-56878b4676-gvdbr   1/1     Running   0          80s
app-56878b4676-lppmt   1/1     Running   0          80s
app-56878b4676-lwr6p   1/1     Running   0          80s
app-56878b4676-n8l7w   1/1     Running   0          80s
app-56878b4676-rn68g   1/1     Running   0          80s
app-56878b4676-z8ltd   1/1     Running   0          80s

 

第三步:接下來滾動更新應用,配置文件 app.v2.yml 以下:

很顯然,因爲新副本中不存在 /tmp/healthy,是沒法經過 Readiness 探測的。驗證以下:

 

第二步:查看探測結果、

[root@ken ~]# kubectl apply -f app.v2.yml --record
deployment.apps/app configured
[root@ken ~]# kubectl get deployment app
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
app    8/10    5            8           4m24s
[root@ken ~]# kubectl get deployment app
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
app    8/10    5            8           4m34s
[root@ken ~]# kubectl get deployment app
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
app    8/10    5            8           4m37s
[root@ken ~]# kubectl get pod
NAME                   READY   STATUS    RESTARTS   AGE
app-56878b4676-45bmq   1/1     Running   0          4m45s
app-56878b4676-5w5ck   1/1     Running   0          4m45s
app-56878b4676-fb8fk   1/1     Running   0          4m45s
app-56878b4676-gvdbr   1/1     Running   0          4m45s
app-56878b4676-lppmt   1/1     Running   0          4m45s
app-56878b4676-lwr6p   1/1     Running   0          4m45s
app-56878b4676-n8l7w   1/1     Running   0          4m45s
app-56878b4676-rn68g   1/1     Running   0          4m45s
app-84fc656775-6s88l   0/1     Running   0          42s
app-84fc656775-drg26   0/1     Running   0          42s
app-84fc656775-hjpsd   0/1     Running   0          42s
app-84fc656775-npn2t   0/1     Running   0          42s
app-84fc656775-slknn   0/1     Running   0          42s

先關注 kubectl get pod 輸出:

 

從 Pod 的 AGE 欄可判斷,最後 5 Pod 是新副本,目前處於 NOT READY 狀態。

舊副本從最初 10 個減小到 8 個。

再來看 kubectl get deployment app 的輸出:

DESIRED 10 表示指望的狀態是 10 READY 的副本。

CURRENT 13 表示當前副本的總數:即 8 箇舊副本 + 5 個新副本。

UP-TO-DATE 5 表示當前已經完成更新的副本數:即 5 個新副本。

AVAILABLE 8 表示當前處於 READY 狀態的副本數:即 8箇舊副本。

在咱們的設定中,新副本始終都沒法經過 Readiness 探測,因此這個狀態會一直保持下去。

 

上面咱們模擬了一個滾動更新失敗的場景。不過幸運的是:Health Check 幫咱們屏蔽了有缺陷的副本,同時保留了大部分舊副本,業務沒有因更新失敗受到影響。

接下來咱們要回答:爲何新建立的副本數是 5 個,同時只銷毀了 2 箇舊副本?

緣由是:滾動更新經過參數 maxSurge 和 maxUnavailable 來控制副本替換的數量。

 

maxSurge

此參數控制滾動更新過程當中副本總數的超過 DESIRED 的上限。maxSurge 能夠是具體的整數(好比 3),也能夠是百分百,向上取整。maxSurge 默認值爲 25%

在上面的例子中,DESIRED 爲 10,那麼副本總數的最大值爲:

roundUp(10 + 10 * 25%) = 13

因此咱們看到 CURRENT 就是 13

 

maxUnavailable

此參數控制滾動更新過程當中,不可用的副本相佔 DESIRED 的最大比例。 maxUnavailable 能夠是具體的整數(好比 3),也能夠是百分百,向下取整。maxUnavailable 默認值爲 25%

在上面的例子中,DESIRED 爲 10,那麼可用的副本數至少要爲:

10 - roundDown(10 * 25%) = 8

因此咱們看到 AVAILABLE 就是 8

maxSurge 值越大,初始建立的新副本數量就越多;maxUnavailable 值越大,初始銷燬的舊副本數量就越多。

理想狀況下,咱們這個案例滾動更新的過程應該是這樣的:

首先建立 3 個新副本使副本總數達到 13 個。

而後銷燬 2 箇舊副本使可用的副本數降到 8 個。

當這 2 箇舊副本成功銷燬後,可再建立 2 個新副本,使副本總數保持爲 13 個。

當新副本經過 Readiness 探測後,會使可用副本數增長,超過 8

進而能夠繼續銷燬更多的舊副本,使可用副本數回到 8。

舊副本的銷燬使副本總數低於 13,這樣就容許建立更多的新副本。

這個過程會持續進行,最終全部的舊副本都會被新副本替換,滾動更新完成。

 

更新失敗回退

 

[root@ken ~]# kubectl rollout history deployment app
deployment.extensions/app 
REVISION  CHANGE-CAUSE
1         <none>
2         kubectl apply --filename=app.v2.yml --record=tr

[root@ken ~]# kubectl rollout undo deployment app --to-revision=1
deployment.extensions/app rolled back
[root@ken ~]# kubectl get deployment app
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
app    10/10   10           10          10m

 

若是要定製 maxSurge 和 maxUnavailable,能夠以下配置:

相關文章
相關標籤/搜索