更新應用時如何實現 K8s 滾動更新?

雲棲號資訊:【點擊查看更多行業資訊】 在這裏您可以找到不同行業的第一手的上雲資訊,還在等什麼,快來!


Kubernetes 集羣中,業務通常採用 Deployment + LoadBalancer 類型 Service 的方式對外提供服務,其典型部署架構如圖 1 所示。這種架構部署和運維都十分簡單方便,但是在應用更新或者升級時可能會存在服務中斷,引發線上問題。今天我們來詳細分析下這種架構爲何在更新應用時會發生服務中斷以及如何避免服務中斷。

 

 

 

 

圖1 業務部署圖

爲何會發生服務中斷

Deployment 滾動更新時會先創建新 pod,等待新 pod running 後再刪除舊 pod。

新建 Pod

 

 

 

 

圖 2 服務中斷示意圖

中斷原因:Pod running 後被加入到 Endpoint 後端,容器服務監控到 Endpoint 變更後將 Node 加入到 SLB 後端。此時請求從 SLB 轉發到 Pod 中,但是 Pod 業務代碼還未初始化完畢,無法處理請求,導致服務中斷,如圖 2 所示。

解決方法:爲 pod 配置就緒檢測,等待業務代碼初始化完畢後後再將 node 加入到 SLB 後端。

刪除 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 兩類資源入手,接下來將針對上述中斷原因介紹相應的配置方法。

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 秒以上。

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 對比

結論

Terway 網絡模式 (推薦方式)

選用 ENI 模式的 svc + 設定 Pod 優雅終止 + 就緒檢測。

Flannel 網絡模式

  • 如果集羣中 slb 數量不多且不需要保留源 ip:選用 cluster 模式 + 設定 Pod 優雅終止 + 就緒檢測;

  • 如果集羣中 slb 數量較多或需要保留源 ip:選用 local 模式 + 設定 Pod 優雅終止 + 就緒檢測 + 原地升級(保證更新過程中每個節點上至少有一個 Running Pod)。

【雲棲號在線課堂】每天都有產品技術專家分享! 課程地址:https://yqh.aliyun.com/live 立即加入社羣,與專家面對面,及時瞭解課程最新動態! 【雲棲號在線課堂 社羣】https://c.tb.cn/F3.Z8gvnK

原文發佈時間:2020-06-03 本文作者:阿里巴巴雲原生 本文來自:“掘金”,瞭解相關信息可以關注“掘金”

上雲就看雲棲號:更多雲資訊,上雲案例,最佳實踐,產品入門,訪問:https://yqh.aliyun.com/

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章