1. 程式人生 > >Kubernetes 健康狀態檢查(九)

Kubernetes 健康狀態檢查(九)

強大的自愈能力是 Kubernetes 這類容器編排引擎的一個重要特性。自愈的預設實現方式是自動重啟發生故障的容器。除此之外,使用者還可以利用 LivenessReadiness 探測機制設定更精細的健康檢查,進而實現如下需求:

  • 零停機部署。
  • 避免部署無效的映象。
  • 更加安全的滾動升級。

一、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 探測:

  1. 探測的方法是:通過 cat 命令檢查 /tmp/healthy 檔案是否存在。如果命令執行成功,返回值為零,Kubernetes 則認為本次 Liveness 探測成功;如果命令返回值非零,本次 Liveness 探測失敗。
  2. initialDelaySeconds: 10 指定容器啟動 10 之後開始執行 Liveness 探測,我們一般會根據應用啟動的準備時間來設定。比如某個應用正常啟動要花 30 秒,那麼initialDelaySeconds
    的值就應該大於 30。
  3. 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 探測做個比較:

  1. Liveness 探測和 Readiness 探測是兩種 Health Check 機制,如果不特意配置,Kubernetes 將對兩種探測採取相同的預設行為,即通過判斷容器啟動程序的返回值是否為零來判斷探測是否成功。
  2. 兩種探測的配置方法完全一樣,支援的配置引數也一樣。不同之處在於探測失敗後的行為:Liveness 探測是重啟容器;Readiness 探測則是將容器設定為不可用,不接收 Service 轉發的請求。
  3. 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 指定埠。

上面配置的作用是:

  1. 容器啟動 10 秒之後開始探測。
  2. 如果 http://[container_ip]:8080/healthy 返回程式碼不是 200-400,表示容器沒有就緒,不接收 Service web-svc 的請求。
  3. 每隔 5 秒再探測一次。
  4. 直到返回程式碼為 200-400,表明容器已經就緒,然後將其加入到 web-svc 的負責均衡中,開始處理客戶請求。
  5. 探測會繼續以 5 秒的間隔執行,如果連續發生 3 次失敗,容器又會從負載均衡中移除,直到下次探測成功重新加入。

對於生產環境中重要的應用都建議配置 Health Check,保證處理客戶請求的容器都是準備就緒的 Service backend。

四、Health Check 在 Rolling Update 中的應用

Health Check 另一個重要的應用場景是 Rolling Update。試想一下下面的情況,現有一個正常執行的多副本應用,接下來對應用進行更新(比如使用更高版本的 image),Kubernetes 會啟動新副本,然後發生瞭如下事件:

  1. 正常情況下新副本需要 10 秒鐘完成準備工作,在此之前無法響應業務請求。
  2. 但由於人為配置錯誤,副本始終無法完成準備工作(比如無法連線後端資料庫)。

因為新副本本身沒有異常退出,預設的 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 輸出:

  1. 從 Pod 的 AGE 欄可判斷,最後 2 個 Pod 是新副本,目前處於 NOT READY 狀態。
  2. 舊副本從最初 10 個減少到 8 個。

再來看 kubectl get deployment app 的輸出:

  1. DESIRED 10 表示期望的狀態是 10 個 READY 的副本。
  2. UP-TO-DATE 2 表示當前已經完成更新的副本數:即 2 個新副本。
  3. AVAILABLE 9 表示當前處於 READY 狀態的副本數:即 9箇舊副本。

在我們的設定中,新副本始終都無法通過 Readiness 探測,所以這個狀態會一直保持下去。

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

滾動更新可以通過引數 maxSurgemaxUnavailable 來控制副本替換的數量。