k8s優雅停服

2023-09-11 21:00:24

在應用程式的整個生命週期中,正在執行的 pod 會由於多種原因而終止。在某些情況下,Kubernetes 會因使用者輸入(例如更新或刪除 Deployment 時)而終止 pod。在其他情況下,Kubernetes 需要釋放給定節點上的資源時會終止 pod。無論哪種情況,Kubernetes 都允許在 pod 中執行的容器在可設定的時間內正常關閉。

請檢視下面的圖表,以便更好地瞭解刪除 pod 時發生的情況。

以下是 Pod 關閉的 2 個場景。

優雅關機

在這種情況下,pod 中的容器會在寬限期內正常關閉。容器的「正常關閉」狀態表示執行可選的 pre-stop hook 和 Pod 響應 SIGTERM 訊號。一旦容器成功退出,Kubelet 就會從 API Server 中刪除 pod。

強制關機

在這種情況下,容器無法在寬限期內關閉。關閉失敗可能是由於多種原因,包括

  • 應用程式忽略 SIGTERM 訊號,
  • pre-stop hook 花費的時間超過寬限期,
  • 應用程式清理資源花費的時間超過寬限期
  • 以上的組合

當應用程式在寬限期內無法關閉時,Kubelet 會傳送一個 SIGKILL 訊號來強制關閉 pod 中執行的程序。根據應用程式,這可能會導致資料丟失和麵向用戶的錯誤。

在本文中,我們將重點分析優雅關閉部分。

識別問題

在 Kubernetes 中,每次部署都意味著在刪除舊 pod 的同時建立新版本的 pod。
如果在此過程中沒有正常關閉,可能會出現兩個問題:
1.當前正在處理請求的 pod 被移除,如果請求不是冪等的,則會導致狀態不一致。
2.Kubernetes 將流量路由到已經被刪除的 Pod,導致處理請求失敗,使用者體驗差。

分析問題

在刪除 Kubernetes pod 的過程中,有兩條平行的時間線,如下圖所示。一是改變網路規則的時間線。另一個是 pod 的刪除。

當運維人員或部署管道執行kubectl delete pod 命令時,兩個過程開始。

網路規則生效

1.kube-apiserver 接收到 pod 刪除請求,將 pod 在 Etcd 中的狀態更新為 Terminating;
2.Endpoint Controller 從 Endpoint 物件中刪除 pod 的 IP;
3.kuber-proxy 根據 Endpoint 物件的變化更新 iptables 的規則,不再將流量路由到被刪除的 Pod。

刪除 pod

1.kube-apiserver 接收到 Pod 刪除請求,將 Pod 的再 Etcd 中的狀態更新為 Terminating
2.Kubelet 在節點清理容器相關資源,如儲存、網路
3.Kubelet 向容器傳送 SIGTERM;如果容器內的程序沒有設定,容器將立即退出。
4.如果容器在預設的 30 秒內沒有退出,Kubelet 將傳送 SIGKILL 並強制它退出。

通過刪除 pod 的過程,我們可以看到如果容器內的程序沒有設定,容器會立即退出,導致問題 1。
由於更新網路規則和刪除 Pod 是同時進行的,因此不能保證在刪除 Pod 之前更新網路規則。這就是可能導致問題 2 的原因。

解決方案

以下設定可以解決這些問題:

1.為容器內的程序設定正常關閉。
2.新增 preStopHook。
3.修改終止 GracePeriodSeconds。

下圖顯示了設定後的時間線

對於問題 1:為容器內的程序設定正常關閉

以 SpringBoot 為例,啟用優雅關閉可以 Spring Boot 組態檔中新增下面設定:

server:
    shutdown: graceful

spring:
    lifecycle:
         timeout-per-shutdown-phase: 30s

通過使用上述設定,Spring Boot 保證在收到 SIGTERM 後不再接受新請求,並在超時內完成所有正在進行的請求的處理。即使無法及時完成,也會記錄相關資訊,然後強制退出。

對於 timeout 的值,應參考處理請求的最大允許持續時間。根據我們的經驗,除特殊情況外,所有請求通常在 30 秒內完成處理。對於未在定義的超時時間內完成的,我們將在紀錄檔監控中捕獲超時並行送警報,然後解決超時的根本原因並採取相應的措施。

這就是可以解決問題 1 的方法。其他語言和框架應該有類似的設定。

對於問題 2:新增 preStopHook

要處理問題 2,我們必須在不再將新流量路由到該 pod 後開始刪除該 pod。因此,應該將 preStopHook 新增到 Kubernetes yaml 檔案中,讓 Kubelet 在收到刪除 pod 事件時「sleep 一下」,並在開始刪除 pod 之前留出足夠的時間來更新網路規則。

lifecycle:
  preStop:
     exec:
        command: ["sh", "-c", "sleep 10"]  # set prestop hook

上述設定將導致 Kubelet 等待設定的時間。

修改終止 GracePeriodSeconds

參考之前刪除 Pod 的分析,Kubernetes 為容器刪除留下了 30 秒的最大時間尺度。如果 Spring 的優雅關閉超時時間和 Kubernetes 的 preStopHooks 之和超過 30 秒,可能會導致 Kubernetes 在 Spring Boot 處理完請求之前強行刪除容器。因此,如果過程超過 30 秒,則應將 timerminationGracePeriodSeconds 調整為超出 Spring 加 preStopHook 的優雅關閉超時。

terminationGracePeriodSeconds: 45

最後,完整的 Kubernetes yaml 檔案如下所示:

apiVersion: apps/v1
kind: Deployment
metadata:
   name: gracefulshutdown-app
spec:
  replicas: 3
  selector:
     matchLabels:
           app: gracefulshutdown-app
  template:
    metadata:
       labels:
         app: gracefulshutdown-app
    spec:
      containers:
        - name: graceful-shutdown-test
          image: gracefulshutdown-app:latest
          ports:
            - containerPort: 8080
          lifecycle:
            preStop:
              exec:
                command: ["sh", "-c", "sleep 10"]  #set prestop hook
       terminationGracePeriodSeconds: 45 # terminationGracePeriodSeconds

1.在Spring Boot中設定正常關閉可確保在容器終止之前完成處理正在進行的請求。
2.設定 preStopHook 確認刪除 pod 和更新網路規則之間的順序關係。3. 最後,為了給程序留出充裕的時間來處理所有請求,設定 terminationGracePeriodSeconds。

通過這三個步驟,我們可以充分解決這兩個問題。本文描述了一種解決方案,用於確保假設服務將正確處理零停機部署所需的所有請求。因此,構建此功能將豐富使用者體驗並減少將缺陷引入服務的影響。

最後,推薦一個部署應用的平臺: https://github.com/512team/dhorse

演示地址:http://dhorse-demo2.512.team