在應用程式的整個生命週期中,正在執行的 pod 會由於多種原因而終止。在某些情況下,Kubernetes 會因使用者輸入(例如更新或刪除 Deployment 時)而終止 pod。在其他情況下,Kubernetes 需要釋放給定節點上的資源時會終止 pod。無論哪種情況,Kubernetes 都允許在 pod 中執行的容器在可設定的時間內正常關閉。
請檢視下面的圖表,以便更好地瞭解刪除 pod 時發生的情況。
以下是 Pod 關閉的 2 個場景。
在這種情況下,pod 中的容器會在寬限期內正常關閉。容器的「正常關閉」狀態表示執行可選的 pre-stop hook 和 Pod 響應 SIGTERM 訊號。一旦容器成功退出,Kubelet 就會從 API Server 中刪除 pod。
在這種情況下,容器無法在寬限期內關閉。關閉失敗可能是由於多種原因,包括
當應用程式在寬限期內無法關閉時,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。
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。
下圖顯示了設定後的時間線
以 SpringBoot 為例,啟用優雅關閉可以 Spring Boot 組態檔中新增下面設定:
server:
shutdown: graceful
spring:
lifecycle:
timeout-per-shutdown-phase: 30s
通過使用上述設定,Spring Boot 保證在收到 SIGTERM 後不再接受新請求,並在超時內完成所有正在進行的請求的處理。即使無法及時完成,也會記錄相關資訊,然後強制退出。
對於 timeout 的值,應參考處理請求的最大允許持續時間。根據我們的經驗,除特殊情況外,所有請求通常在 30 秒內完成處理。對於未在定義的超時時間內完成的,我們將在紀錄檔監控中捕獲超時並行送警報,然後解決超時的根本原因並採取相應的措施。
這就是可以解決問題 1 的方法。其他語言和框架應該有類似的設定。
要處理問題 2,我們必須在不再將新流量路由到該 pod 後開始刪除該 pod。因此,應該將 preStopHook 新增到 Kubernetes yaml 檔案中,讓 Kubelet 在收到刪除 pod 事件時「sleep 一下」,並在開始刪除 pod 之前留出足夠的時間來更新網路規則。
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 10"] # set prestop hook
上述設定將導致 Kubelet 等待設定的時間。
參考之前刪除 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