更新應用時,如何實現 K8s 零中斷滾動更新?
作者 | 子白(阿里雲開發工程師)、溪恆(阿里雲技術專家)
《深入淺出 Kubernetes》一書共彙集 12 篇技術文章,幫助你一次搞懂 6 個核心原理,吃透基礎理論,一次學會 6 個典型問題的華麗操作!
Kubernetes 叢集中,業務通常採用 Deployment + LoadBalancer 型別 Service 的方式對外提供服務,其典型部署架構如圖 1 所示。這種架構部署和運維都十分簡單方便,但是在應用更新或者升級時可能會存在服務中斷,引發線上問題。今天我們來詳細分析下這種架構為何在更新應用時會發生服務中斷以及如何避免服務中斷。
圖 1 - 業務部署圖
為何會發生服務中斷?
Deployment 滾動更新時會先建立新 pod,等待新 pod running 後再刪除舊 pod。
1. 新建 Pod
圖 2 - 服務中斷示意圖
中斷原因:Pod running 後被加入到 Endpoint 後端,容器服務監控到 Endpoint 變更後將 Node 加入到 SLB 後端。此時請求從 SLB 轉發到 Pod 中,但是 Pod 業務程式碼還未初始化完畢,無法處理請求,導致服務中斷,如圖 2 所示。
解決方法:為 pod 配置就緒檢測,等待業務程式碼初始化完畢後後再將 node 加入到 SLB 後端。
2. 刪除 Pod
在刪除舊 pod 過程中需要對多個物件(如 Endpoint、ipvs/iptables、SLB)進行狀態同步,並且這些同步操作是非同步執行的,整體同步流程如圖 3 所示。
圖 3 - Deployment 更新時序圖
Pod
pod 狀態變更:將 Pod 設定為 Terminating 狀態,並從所有 Service 的 Endpoints 列表中刪除。此時,Pod 停止獲得新的流量,但在 Pod 中執行的容器不會受到影響;
執行 preStop Hook:Pod 刪除時會觸發 preStop Hook,preStop Hook 支援 bash 指令碼、TCP 或 HTTP 請求;
傳送 SIGTERM 訊號:向 Pod 中的容器傳送 SIGTERM 訊號;
等待指定的時間:terminationGracePeriodSeconds 欄位用於控制等待時間,預設值為 30 秒。該步驟與 preStop Hook 同時執行,因此 terminationGracePeriodSeconds 需要大於 preStop 的時間,否則會出現 preStop 未執行完畢,pod 就被 kill 的情況;
傳送 SIGKILL 訊號:等待指定時間後,向 pod 中的容器傳送 SIGKILL 訊號,刪除 pod。
中斷原因:上述 1、2、3、4 步驟同時進行,因此有可能存在 Pod 收到 SIGTERM 訊號並且停止工作後,還未從 Endpoints 中移除的情況。此時,請求從 slb 轉發到 pod 中,而 Pod 已經停止工作,因此會出現服務中斷,如圖 4 所示。
圖 4 - 服務中斷示意圖
解決方法:為 pod 配置 preStop Hook,使 Pod 收到 SIGTERM 時 sleep 一段時間而不是立刻停止工作,從而確保從 SLB 轉發的流量還可以繼續被 Pod 處理。
iptables/ipvs
中斷原因:當 pod 變為 termintaing 狀態時,會從所有 service 的 endpoint 中移除該 pod。kube-proxy 會清理對應的 iptables/ipvs 條目。而容器服務 watch 到 endpoint 變化後,會呼叫 slb openapi 移除後端,此操作會耗費幾秒。由於這兩個操作是同時進行,因此有可能存在節點上的 iptables/ipvs 條目已經被清理,但是節點還未從 slb 移除的情況。此時,流量從 slb 流入,而節點上已經沒有對應的 iptables/ipvs 規則導致服務中斷,如圖 5 所示。
圖 5 - 服務中斷示意圖
解決方法如下:
Cluster 模式:Cluster 模式下 kube-proxy 會把所有業務 Pod 寫入 Node 的 iptables/ipvs 中,如果當前 Node 沒有業務 pod,則該請求會被轉發給其他 Node,因此不會存在服務中斷,如 6 所示;
圖 6 - Cluster 模式請求轉發示意圖
Local 模式:Local 模式下,kube-proxy 僅會把 Node 上的 pod 寫入 iptables/ipvs。當 Node 上只有一個 pod 且狀態變為 terminating 時,iptables/ipvs 會將該 pod 記錄移除。此時請求轉發到這個 node 時,無對應的 iptables/ipvs 記錄,導致請求失敗。這個問題可以通過原地升級來避免,即保證更新過程中 Node 上至少有一個 Running Pod。原地升級可以保障 Node 的 iptables/ipvs 中總會有一條業務 pod 記錄,因此不會產生服務中斷,如圖 7 所示;
圖 7 - Local 模式原地升級時請求轉發示意圖
ENI 模式 Service:ENI 模式繞過 kube-proxy,將 Pod 直接掛載到 SLB 後端,因此不存在因為 iptables/ipvs 導致的服務中斷。
圖 8 - ENI 模式請求轉發示意圖
SLB
圖 9 - 服務中斷示意圖
中斷原因:容器服務監控到 Endpoints 變化後,會將 Node 從 slb 後端移除。當節點從 slb 後端移除後,SLB 對於繼續發往該節點的長連線會直接斷開,導致服務中斷。
解決方法:為 SLB 設定長連結優雅中斷(依賴具體雲廠商)。
如何避免服務中斷?
避免服務中斷可以從 Pod 和 Service 兩類資源入手,接下來將針對上述中斷原因介紹相應的配置方法。
1. Pod 配置
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
spec:
containers:
- name: nginx
image: nginx
# 存活檢測
livenessProbe:
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 30
successThreshold: 1
tcpSocket:
port: 5084
timeoutSeconds: 1
# 就緒檢測
readinessProbe:
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 30
successThreshold: 1
tcpSocket:
port: 5084
timeoutSeconds: 1
# 優雅退出
lifecycle:
preStop:
exec:
command:
- sleep
- 30
terminationGracePeriodSeconds: 60
注意:需要合理設定就緒檢測(readinessProbe)的探測頻率、延時時間、不健康閾值等資料,部分應用啟動時間本身較長,如果設定的時間過短,會導致 POD 反覆重啟。
livenessProbe 為存活檢測,如果失敗次數到達閾值(failureThreshold)後,pod 會重啟,具體配置見官方文件;
readinessProbe 為就緒檢查,只有就緒檢查通過後,pod 才會被加入到 Endpoint 中。容器服務監控到 Endpoint 變化後才會將 node 掛載到 slb 後端;
preStop 時間建議設定為業務處理完所有剩餘請求所需的時間,terminationGracePeriodSeconds 時間建議設定為 preStop 的時間再加30秒以上。
2. Service 配置
Cluster 模式(externalTrafficPolicy: Cluster)
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
externalTrafficPolicy: Cluster
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: nginx
type: LoadBalancer
容器服務會將叢集中所有節點掛載到 SLB 的後端(使用 BackendLabel 標籤配置後端的除外),因此會快速消耗 SLB quota。SLB 限制了每個 ECS 上能夠掛載的 SLB 的個數,預設值為 50,當 quota 消耗完後會導致無法建立新的監聽及 SLB。
Cluster 模式下,如果當前節點沒有業務 pod 會將請求轉發給其他 Node。在跨節點轉發時需要做 NAT,因此會丟失源 IP。
Local 模式(externalTrafficPolicy: Local)
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: default
spec:
externalTrafficPolicy: Local
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
run: nginx
type: LoadBalancer
# 需要儘可能的讓每個節點在更新的過程中有至少一個的Running的Pod
# 通過修改UpdateStrategy和利用nodeAffinity儘可能的保證在原地rolling update
# * UpdateStrategy可以設定Max Unavailable為0,保證有新的Pod啟動後才停止之前的pod
# * 先對固定的幾個節點打上label用來排程
# * 使用nodeAffinity+和超過相關node數量的replicas數量保證儘可能在原地建新的Pod
# 例如:
apiVersion: apps/v1
kind: Deployment
......
strategy:
rollingUpdate:
maxSurge: 50%
maxUnavailable: 0%
type: RollingUpdate
......
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: deploy
operator: In
values:
- nginx
容器服務預設會將 Service 對應的 Pod 所在的節點加入到 SLB 後端,因此 SLB quota 消耗較慢。Local 模式下請求直接轉發到 pod 所在 node,不存在跨節點轉發,因此可以保留源 IP 地址。Local 模式下可以通過原地升級的方式避免服務中斷,yaml 檔案如上。
ENI 模式(阿里雲特有模式)
apiVersion: v1
kind: Service
metadata:
annotations:
service.beta.kubernetes.io/backend-type: "eni"
name: nginx
spec:
ports:
- name: http
port: 30080
protocol: TCP
targetPort: 80
selector:
app: nginx
type: LoadBalancer
Terway 網路模式下,通過設定 service.beta.kubernetes.io/backend-type:
"eni" annotation 可以建立 ENI 模式的 SLB。ENI 模式下,pod 會直接掛載到 SLB 後端,不經過 kube-proxy,因此不存在服務中斷的問題。請求直接轉發到 pod,因此可以保留源 IP 地址。
三種 svc 模式對比如下圖所示:
圖 10 - Service 對比
3. 結論
Terway 網路模式 (推薦方式)
選用 ENI 模式的 svc + 設定 Pod 優雅終止 + 就緒檢測。
Flannel 網路模式
如果叢集中 slb 數量不多且不需要保留源 ip:選用 cluster 模式 + 設定 Pod 優雅終止 + 就緒檢測;
如果叢集中 slb 數量較多或需要保留源 ip:選用 local 模式 + 設定 Pod 優雅終止 + 就緒檢測 + 原地升級(保證更新過程中每個節點上至少有一個 Running Pod)。
4. Reference
容器生命週期鉤子
Configure Liveness, Readiness and Startup Probes
通過負載均衡訪問服務
Kubernetes 最佳實踐:優雅的中止
Kubernetes 社群相關討論 :Create ability to do zero downtime deployments when using externalTrafficPolicy: Local,Graceful Termination for External Traffic Policy Local
容器服務 kubernetes(ACK)中應用優雅上下線
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69975341/viewspace-2695822/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- docker stack滾動更新web應用DockerWeb
- SpringBoot應用零停機滾動更新Spring Boot
- k8s教程:12.Rolling Update--實現我們的滾動更新K8S
- GeneralUpdate實現應用程式更新
- k8s中pod滾動更新如何減少流量丟失K8S
- 詳細聊聊k8s deployment的滾動更新(一)K8S
- 詳細聊聊k8s deployment的滾動更新(二)K8S
- sqlalchemy實現時間列自動更新SQL
- 輕鬆實現.NET應用自動更新:AutoUpdater.NET教程
- 輕鬆部署 Laravel 應用 | 《13. 更新與回滾》Laravel
- 更新欄位時更新時間不自動更新(不更新 updated_at 欄位)
- ansible高階操作 serial滾動更新
- Flutter 滾動元件內容更新時自動定位到底端的方法Flutter元件
- flutter_xupdate 一鍵實現Flutter應用版本更新Flutter
- windows10應用商店怎麼更新_win10應用商店更新如何操作WindowsWin10
- MySQL 如何實現資料更新MySql
- git - 實時更新Git
- Kubernetes:更新與回滾
- 使用 electron-updater 自動更新應用
- Vue實現左右選單聯動實現(更新)Vue
- Flutter 應用熱更新Flutter
- 用AutoLayout實現分頁滾動
- win10 怎麼禁用更新商店_如何禁止win10系統應用商店自動更新Win10
- win10應用商店關閉更新怎麼操作_如何禁止win10應用商店更新Win10
- WPF的實時更新
- 當資料改變時,VUE是如何實現DOM更新的?Vue
- 3 月應用實時監控服務 ARMS 產品功能更新
- Java中的動態配置更新:從配置中心到應用熱載入的實現Java
- JavaScript 自動更新時間JavaScript
- 基於Canal+Kafka實現快取實時更新Kafka快取
- 創新顛覆! BothWin助力手遊、移動應用實現“點開即玩”與“自動更新”
- uniapp實現熱更新APP
- 微軟向HoloLens使用者推送1804更新:同時釋出兩款混合現實應用微軟
- Android 解析包時出現問題 的解決方案(應用檢查更新)Android
- 什麼是視差滾動?如何實現視差滾動的效果?
- PostgreSQL自動更新時間戳SQL時間戳
- 使用 updateAppConfig 更新 Nuxt 應用配置APPUX
- 陣列有哪些方法支援響應式更新的?底層原理如何實現?陣列