如何为Kubernetes实现原地升级
作者 | 王思宇(酒祝) ?阿里云技術專家
參與阿里巴巴云原生文末留言互動,即有機會獲得贈書福利及作者答疑!
概念介紹
原地升級一詞中,“升級”不難理解,是將應用實例的版本由舊版替換為新版。那么如何結合 Kubernetes 環境來理解“原地”呢?
我們先來看看 K8s 原生 workload 的發布方式。這里假設我們需要部署一個應用,包括 foo、bar 兩個容器在 Pod 中。其中,foo 容器第一次部署時用的鏡像版本是 v1,我們需要將其升級為 v2 版本鏡像,該怎么做呢?
- 如果這個應用使用 Deployment 部署,那么升級過程中 Deployment 會觸發新版本 ReplicaSet 創建 Pod,并刪除舊版本 Pod。如下圖所示:
在本次升級過程中,原 Pod 對象被刪除,一個新 Pod 對象被創建。新 Pod 被調度到另一個 Node 上,分配到一個新的 IP,并把 foo、bar 兩個容器在這個 Node 上重新拉取鏡像、啟動容器。
- 如果這個應該使用 StatefulSet 部署,那么升級過程中 StatefulSet 會先刪除舊 Pod 對象,等刪除完成后用同樣的名字在創建一個新的 Pod 對象。如下圖所示:
值得注意的是,盡管新舊兩個 Pod 名字都叫 pod-0,但其實是兩個完全不同的 Pod 對象(uid也變了)。StatefulSet 等到原先的 pod-0 對象完全從 Kubernetes 集群中被刪除后,才會提交創建一個新的 pod-0 對象。而這個新的 Pod 也會被重新調度、分配IP、拉鏡像、啟動容器。
- 而所謂原地升級模式,就是在應用升級過程中避免將整個 Pod 對象刪除、新建,而是基于原有的 Pod 對象升級其中某一個或多個容器的鏡像版本:
在原地升級的過程中,我們僅僅更新了原 Pod 對象中 foo 容器的 image 字段來觸發 foo 容器升級到新版本。而不管是 Pod 對象,還是 Node、IP 都沒有發生變化,甚至 foo 容器升級的過程中 bar 容器還一直處于運行狀態。
總結:這種只更新 Pod 中某一個或多個容器版本、而不影響整個 Pod 對象、其余容器的升級方式,被我們稱為 Kubernetes 中的原地升級。
收益分析
那么,我們為什么要在 Kubernetes 中引入這種原地升級的理念和設計呢?
首先,這種原地升級的模式極大地提升了應用發布的效率,根據非完全統計數據,在阿里環境下原地升級至少比完全重建升級提升了 80% 以上的發布速度。這其實很容易理解,原地升級為發布效率帶來了以下優化點:
其次,當我們升級 Pod 中一些 sidecar 容器(如采集日志、監控等)時,其實并不希望干擾到業務容器的運行。但面對這種場景,Deployment 或 StatefulSet 的升級都會將整個 Pod 重建,勢必會對業務造成一定的影響。而容器級別的原地升級變動的范圍非常可控,只會將需要升級的容器做重建,其余容器包括網絡、掛載盤都不會受到影響。
最后,原地升級也為我們帶來了集群的穩定性和確定性。當一個 Kubernetes 集群中大量應用觸發重建 Pod 升級時,可能造成大規模的 Pod 飄移,以及對 Node 上一些低優先級的任務 Pod 造成反復的搶占遷移。這些大規模的 Pod 重建,本身會對 apiserver、scheduler、網絡/磁盤分配等中心組件造成較大的壓力,而這些組件的延遲也會給 Pod 重建帶來惡性循環。而采用原地升級后,整個升級過程只會涉及到 controller 對 Pod 對象的更新操作和 kubelet 重建對應的容器。
技術背景
在阿里巴巴內部,絕大部分電商應用在云原生環境都統一用原地升級的方式做發布,而這套支持原地升級的控制器就位于?OpenKruise?開源項目中。
也就是說,阿里內部的云原生應用都是統一使用 OpenKruise 中的擴展 workload 做部署管理的,而并沒有采用原生 Deployment/StatefulSet 等。
那么 OpenKruise 是如何實現原地升級能力的呢?在介紹原地升級實現原理之前,我們先來看一些原地升級功能所依賴的原生 Kubernetes 功能:
背景 1:Kubelet 針對 Pod 容器的版本管理
每個 Node 上的 Kubelet,會針對本機上所有 Pod.spec.containers 中的每個 container 計算一個 hash 值,并記錄到實際創建的容器中。
如果我們修改了 Pod 中某個 container 的 image 字段,kubelet 會發現 container 的 hash 發生了變化、與機器上過去創建的容器 hash 不一致,而后 kubelet 就會把舊容器停掉,然后根據最新 Pod spec 中的 container 來創建新的容器。
這個功能,其實就是針對單個 Pod 的原地升級的核心原理。
背景 2:Pod 更新限制
在原生 kube-apiserver 中,對 Pod 對象的更新請求有嚴格的?validation 校驗邏輯:
// validate updateable fields: // 1. spec.containers[*].image // 2. spec.initContainers[*].image // 3. spec.activeDeadlineSeconds簡單來說,對于一個已經創建出來的 Pod,在 Pod Spec 中只允許修改 containers/initContainers 中的 image 字段,以及 activeDeadlineSeconds 字段。對 Pod Spec 中所有其他字段的更新,都會被 kube-apiserver 拒絕。
背景 3:containerStatuses 上報
kubelet 會在 pod.status 中上報 containerStatuses,對應 Pod 中所有容器的實際運行狀態:
apiVersion: v1 kind: Pod spec:containers:- name: nginximage: nginx:latest status:containerStatuses:- name: nginximage: nginx:mainlineimageID: docker-pullable://nginx@sha256:2f68b99bc0d6d25d0c56876b924ec20418544ff28e1fb89a4c27679a40da811b絕大多數情況下,spec.containers[x].image 與 status.containerStatuses[x].image 兩個鏡像是一致的。
但是也有上述這種情況,kubelet 上報的與 spec 中的 image 不一致(spec 中是 nginx:latest,但 status 中上報的是 nginx:mainline)。
這是因為,kubelet 所上報的 image 其實是從 CRI 接口中拿到的容器對應的鏡像名。而如果 Node 機器上存在多個鏡像對應了一個 imageID,那么上報的可能是其中任意一個:
$ docker images | grep nginx nginx latest 2622e6cca7eb 2 days ago 132MB nginx mainline 2622e6cca7eb 2 days ago因此,一個 Pod 中 spec 和 status 的 image 字段不一致,并不意味著宿主機上這個容器運行的鏡像版本和期望的不一致。
背景 4:ReadinessGate 控制 Pod 是否 Ready
在 Kubernetes 1.12 版本之前,一個 Pod 是否處于 Ready 狀態只是由 kubelet 根據容器狀態來判定:如果 Pod 中容器全部 ready,那么 Pod 就處于 Ready 狀態。
但事實上,很多時候上層 operator 或用戶都需要能控制 Pod 是否 Ready 的能力。因此,Kubernetes 1.12 版本之后提供了一個 readinessGates 功能來滿足這個場景。如下:
apiVersion: v1 kind: Pod spec:readinessGates:- conditionType: MyDemo status:conditions:- type: MyDemostatus: "True"- type: ContainersReadystatus: "True"- type: Readystatus: "True"目前 kubelet 判定一個 Pod 是否 Ready 的兩個前提條件:
只有滿足上述兩個前提,kubelet 才會上報 Ready condition 為 True。
實現原理
了解了上面的四個背景之后,接下來分析一下 OpenKruise 是如何在 Kubernetes 中實現原地升級的原理。
1. 單個 Pod 如何原地升級?
由“背景 1”可知,其實我們對一個存量 Pod 的 spec.containers[x] 中字段做修改,kubelet 會感知到這個 container 的 hash 發生了變化,隨即就會停掉對應的舊容器,并用新的 container 來拉鏡像、創建和啟動新容器。
由“背景 2”可知,當前我們對一個存量 Pod 的 spec.containers[x] 中的修改,僅限于 image 字段。
因此,得出第一個實現原理:**對于一個現有的 Pod 對象,我們能且只能修改其中的 spec.containers[x].image 字段,來觸發 Pod 中對應容器升級到一個新的 image。
2. 如何判斷 Pod 原地升級成功?
接下來的問題是,當我們修改了 Pod 中的 spec.containers[x].image 字段后,如何判斷 kubelet 已經將容器重建成功了呢?
由“背景 3”可知,比較 spec 和 status 中的 image 字段是不靠譜的,因為很有可能 status 中上報的是 Node 上存在的另一個鏡像名(相同 imageID)。
因此,得出第二個實現原理:判斷 Pod 原地升級是否成功,相對來說比較靠譜的辦法,是在原地升級前先將 status.containerStatuses[x].imageID 記錄下來。在更新了 spec 鏡像之后,如果觀察到 Pod 的 status.containerStatuses[x].imageID 變化了,我們就認為原地升級已經重建了容器。
但這樣一來,我們對原地升級的 image 也有了一個要求:不能用 image 名字(tag)不同、但實際對應同一個 imageID 的鏡像來做原地升級,否則可能一直都被判斷為沒有升級成功(因為 status 中 imageID 不會變化)。
當然,后續我們還可以繼續優化。OpenKruise 即將開源鏡像預熱的能力,會通過 DaemonSet 在每個 Node 上部署一個 NodeImage Pod。通過 NodeImage 上報我們可以得知 pod spec 中的 image 所對應的 imageID,然后和 pod status 中的 imageID 比較即可準確判斷原地升級是否成功。
3. 如何確保原地升級過程中流量無損?
在 Kubernetes 中,一個 Pod 是否 Ready 就代表了它是否可以提供服務。因此,像 Service 這類的流量入口都會通過判斷 Pod Ready 來選擇是否能將這個 Pod 加入 endpoints 端點中。
由“背景 4”可知,從 Kubernetes 1.12+ 之后,operator/controller 這些組件也可以通過設置 readinessGates 和更新 pod.status.conditions 中的自定義 type 狀態,來控制 Pod 是否可用。
因此,得出第三個實現原理:可以在 pod.spec.readinessGates 中定義一個叫 InPlaceUpdateReady 的 conditionType。
在原地升級時:
原地升級結束后,再將 InPlaceUpdateReady condition 設為 "True",使 Pod 重新回到 Ready 狀態。
另外在原地升級的兩個步驟中,第一步將 Pod 改為 NotReady 后,流量組件異步 watch 到變化并摘除端點可能是需要一定時間的。因此我們也提供優雅原地升級的能力,即通過 gracePeriodSeconds 配置在修改 NotReady 狀態和真正更新 image 觸發原地升級兩個步驟之間的靜默期時間。
4. 組合發布策略
原地升級和 Pod 重建升級一樣,可以配合各種發布策略來執行:
- partition:如果配置 partition 做灰度,那么只會將 replicas-partition 數量的 Pod 做原地升級;
- maxUnavailable:如果配置 maxUnavailable,那么只會將滿足 unavailable 數量的 Pod 做原地升級;
- maxSurge:如果配置 maxSurge 做彈性,那么當先擴出來 maxSurge 數量的 Pod 之后,存量的 Pod 仍然使用原地升級;
- priority/scatter:如果配置了發布優先級/打散策略,會按照策略順序對 Pod 做原地升級。
總結
如上文所述,OpenKruise?結合 Kubernetes 原生提供的 kubelet 容器版本管理、readinessGates 等功能,實現了針對 Pod 的原地升級能力。
而原地升級也為應用發布帶來大幅的效率、穩定性提升。值得關注的是,隨著集群、應用規模的增大,這種提升的收益越加明顯。正是這種原地升級能力,在近兩年幫助了阿里巴巴超大規模的應用容器平穩遷移到了基于 Kubernetes 的云原生環境,而原生 Deployment/StatefulSet 是完全無法在這種體量的環境下鋪開使用的。(歡迎加入釘釘交流群:23330762)
-?贈書福利?-
6 月 19 日 12:00 前在【阿里巴巴云原生公眾號】留言區提出你的疑問,精選留言點贊第 1 名將免費獲得此書,屆時我們還會請本文作者針對留言點贊前 5 名的問題進行答疑!
課程推薦
為了更多開發者能夠享受到 Serverless 帶來的紅利,這一次,我們集結了 10+ 位阿里巴巴 Serverless 領域技術專家,打造出最適合開發者入門的 Serverless 公開課,讓你即學即用,輕松擁抱云計算的新范式——Serverless。
點擊即可免費觀看課程:https://developer.aliyun.com/learning/roadmap/serverless
“阿里巴巴云原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦云原生流行技術趨勢、云原生大規模的落地實踐,做最懂云原生開發者的公眾號。”
原文鏈接
本文為云棲社區原創內容,未經允許不得轉載。
總結
以上是生活随笔為你收集整理的如何为Kubernetes实现原地升级的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MaxCompute客户端在window
- 下一篇: 认识Java异步编程