溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

揭秘:如何為 Kubernetes 實(shí)現(xiàn)原地升級

發(fā)布時(shí)間:2020-08-11 21:02:01 來源:ITPUB博客 閱讀:229 作者:阿里巴巴云原生 欄目:云計(jì)算

揭秘:如何為 Kubernetes 實(shí)現(xiàn)原地升級

作者 | 王思宇(酒祝) 阿里云技術(shù)專家

參與阿里巴巴云原生文末留言互動(dòng),即有機(jī)會(huì)獲得贈(zèng)書福利及作者答疑!

概念介紹

原地升級一詞中,“升級”不難理解,是將應(yīng)用實(shí)例的版本由舊版替換為新版。那么如何結(jié)合 Kubernetes 環(huán)境來理解“原地”呢?

我們先來看看 K8s 原生 workload 的發(fā)布方式。這里假設(shè)我們需要部署一個(gè)應(yīng)用,包括 foo、bar 兩個(gè)容器在 Pod 中。其中,foo 容器第一次部署時(shí)用的鏡像版本是 v1,我們需要將其升級為 v2 版本鏡像,該怎么做呢?

  • 如果這個(gè)應(yīng)用使用 Deployment 部署,那么升級過程中 Deployment 會(huì)觸發(fā)新版本 ReplicaSet 創(chuàng)建 Pod,并刪除舊版本 Pod。如下圖所示:

揭秘:如何為 Kubernetes 實(shí)現(xiàn)原地升級

在本次升級過程中,原 Pod 對象被刪除,一個(gè)新 Pod 對象被創(chuàng)建。新 Pod 被調(diào)度到另一個(gè) Node 上,分配到一個(gè)新的 IP,并把 foo、bar 兩個(gè)容器在這個(gè) Node 上重新拉取鏡像、啟動(dòng)容器。

  • 如果這個(gè)應(yīng)該使用 StatefulSet 部署,那么升級過程中 StatefulSet 會(huì)先刪除舊 Pod 對象,等刪除完成后用同樣的名字在創(chuàng)建一個(gè)新的 Pod 對象。如下圖所示:

揭秘:如何為 Kubernetes 實(shí)現(xiàn)原地升級

值得注意的是,盡管新舊兩個(gè) Pod 名字都叫 pod-0,但其實(shí)是兩個(gè)完全不同的 Pod 對象(uid也變了)。StatefulSet 等到原先的 pod-0 對象完全從 Kubernetes 集群中被刪除后,才會(huì)提交創(chuàng)建一個(gè)新的 pod-0 對象。而這個(gè)新的 Pod 也會(huì)被重新調(diào)度、分配IP、拉鏡像、啟動(dòng)容器。

  • 而所謂原地升級模式,就是在應(yīng)用升級過程中避免將整個(gè) Pod 對象刪除、新建,而是基于原有的 Pod 對象升級其中某一個(gè)或多個(gè)容器的鏡像版本:

揭秘:如何為 Kubernetes 實(shí)現(xiàn)原地升級

在原地升級的過程中,我們僅僅更新了原 Pod 對象中 foo 容器的 image 字段來觸發(fā) foo 容器升級到新版本。而不管是 Pod 對象,還是 Node、IP 都沒有發(fā)生變化,甚至 foo 容器升級的過程中 bar 容器還一直處于運(yùn)行狀態(tài)。

總結(jié):這種只更新 Pod 中某一個(gè)或多個(gè)容器版本、而不影響整個(gè) Pod 對象、其余容器的升級方式,被我們稱為 Kubernetes 中的原地升級。

收益分析

那么,我們?yōu)槭裁匆?Kubernetes 中引入這種原地升級的理念和設(shè)計(jì)呢?

首先,這種原地升級的模式極大地提升了應(yīng)用發(fā)布的效率,根據(jù)非完全統(tǒng)計(jì)數(shù)據(jù),在阿里環(huán)境下原地升級至少比完全重建升級提升了 80% 以上的發(fā)布速度。這其實(shí)很容易理解,原地升級為發(fā)布效率帶來了以下優(yōu)化點(diǎn):

  1. 節(jié)省了調(diào)度的耗時(shí),Pod 的位置、資源都不發(fā)生變化;
  2. 節(jié)省了分配網(wǎng)絡(luò)的耗時(shí),Pod 還使用原有的 IP;
  3. 節(jié)省了分配、掛載遠(yuǎn)程盤的耗時(shí),Pod 還使用原有的 PV(且都是已經(jīng)在 Node 上掛載好的);
  4. 節(jié)省了大部分拉取鏡像的耗時(shí),因?yàn)?Node 上已經(jīng)存在了應(yīng)用的舊鏡像,當(dāng)拉取新版本鏡像時(shí)只需要下載很少的幾層 layer。

其次,當(dāng)我們升級 Pod 中一些 sidecar 容器(如采集日志、監(jiān)控等)時(shí),其實(shí)并不希望干擾到業(yè)務(wù)容器的運(yùn)行。但面對這種場景,Deployment 或 StatefulSet 的升級都會(huì)將整個(gè) Pod 重建,勢必會(huì)對業(yè)務(wù)造成一定的影響。而容器級別的原地升級變動(dòng)的范圍非常可控,只會(huì)將需要升級的容器做重建,其余容器包括網(wǎng)絡(luò)、掛載盤都不會(huì)受到影響。

最后,原地升級也為我們帶來了集群的穩(wěn)定性和確定性。當(dāng)一個(gè) Kubernetes 集群中大量應(yīng)用觸發(fā)重建 Pod 升級時(shí),可能造成大規(guī)模的 Pod 飄移,以及對 Node 上一些低優(yōu)先級的任務(wù) Pod 造成反復(fù)的搶占遷移。這些大規(guī)模的 Pod 重建,本身會(huì)對 apiserver、scheduler、網(wǎng)絡(luò)/磁盤分配等中心組件造成較大的壓力,而這些組件的延遲也會(huì)給 Pod 重建帶來惡性循環(huán)。而采用原地升級后,整個(gè)升級過程只會(huì)涉及到 controller 對 Pod 對象的更新操作和 kubelet 重建對應(yīng)的容器。

技術(shù)背景

在阿里巴巴內(nèi)部,絕大部分電商應(yīng)用在云原生環(huán)境都統(tǒng)一用原地升級的方式做發(fā)布,而這套支持原地升級的控制器就位于 OpenKruise 開源項(xiàng)目中。

也就是說,阿里內(nèi)部的云原生應(yīng)用都是統(tǒng)一使用 OpenKruise 中的擴(kuò)展 workload 做部署管理的,而并沒有采用原生 Deployment/StatefulSet 等。


那么 OpenKruise 是如何實(shí)現(xiàn)原地升級能力的呢?在介紹原地升級實(shí)現(xiàn)原理之前,我們先來看一些原地升級功能所依賴的原生 Kubernetes 功能:

背景 1:Kubelet 針對 Pod 容器的版本管理

每個(gè) Node 上的 Kubelet,會(huì)針對本機(jī)上所有 Pod.spec.containers 中的每個(gè) container 計(jì)算一個(gè) hash 值,并記錄到實(shí)際創(chuàng)建的容器中。

如果我們修改了 Pod 中某個(gè) container 的 image 字段,kubelet 會(huì)發(fā)現(xiàn) container 的 hash 發(fā)生了變化、與機(jī)器上過去創(chuàng)建的容器 hash 不一致,而后 kubelet 就會(huì)把舊容器停掉,然后根據(jù)最新 Pod spec 中的 container 來創(chuàng)建新的容器。

這個(gè)功能,其實(shí)就是針對單個(gè) Pod 的原地升級的核心原理。

背景 2:Pod 更新限制

在原生 kube-apiserver 中,對 Pod 對象的更新請求有嚴(yán)格的 validation 校驗(yàn)邏輯:

// validate updateable fields:
// 1.  spec.containers[*].image
// 2.  spec.initContainers[*].image
// 3.  spec.activeDeadlineSeconds

簡單來說,對于一個(gè)已經(jīng)創(chuàng)建出來的 Pod,在 Pod Spec 中只允許修改 containers/initContainers 中的 image 字段,以及 activeDeadlineSeconds 字段。對 Pod Spec 中所有其他字段的更新,都會(huì)被 kube-apiserver 拒絕。

背景 3:containerStatuses 上報(bào)

kubelet 會(huì)在 pod.status 中上報(bào) containerStatuses,對應(yīng) Pod 中所有容器的實(shí)際運(yùn)行狀態(tài):

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: nginx
    image: nginx:latest
status:
  containerStatuses:
  - name: nginx
    image: nginx:mainline
    imageID: docker-pullable://nginx@sha256:2f68b99bc0d6d25d0c56876b924ec20418544ff28e1fb89a4c27679a40da811b

絕大多數(shù)情況下,spec.containers[x].image 與 status.containerStatuses[x].image 兩個(gè)鏡像是一致的。

但是也有上述這種情況,kubelet 上報(bào)的與 spec 中的 image 不一致(spec 中是 nginx:latest,但 status 中上報(bào)的是 nginx:mainline)。

這是因?yàn)椋琸ubelet 所上報(bào)的 image 其實(shí)是從 CRI 接口中拿到的容器對應(yīng)的鏡像名。而如果 Node 機(jī)器上存在多個(gè)鏡像對應(yīng)了一個(gè) imageID,那么上報(bào)的可能是其中任意一個(gè):

$ docker images | grep nginx
nginx            latest              2622e6cca7eb        2 days ago          132MB
nginx            mainline            2622e6cca7eb        2 days ago

因此,一個(gè) Pod 中 spec 和 status 的 image 字段不一致,并不意味著宿主機(jī)上這個(gè)容器運(yùn)行的鏡像版本和期望的不一致。

背景 4:ReadinessGate 控制 Pod 是否 Ready

在 Kubernetes 1.12 版本之前,一個(gè) Pod 是否處于 Ready 狀態(tài)只是由 kubelet 根據(jù)容器狀態(tài)來判定:如果 Pod 中容器全部 ready,那么 Pod 就處于 Ready 狀態(tài)。

但事實(shí)上,很多時(shí)候上層 operator 或用戶都需要能控制 Pod 是否 Ready 的能力。因此,Kubernetes 1.12 版本之后提供了一個(gè) readinessGates 功能來滿足這個(gè)場景。如下:

apiVersion: v1
kind: Pod
spec:
  readinessGates:
  - conditionType: MyDemo
status:
  conditions:
  - type: MyDemo
    status: "True"
  - type: ContainersReady
    status: "True"
  - type: Ready
    status: "True"

目前 kubelet 判定一個(gè) Pod 是否 Ready 的兩個(gè)前提條件:

  1. Pod 中容器全部 Ready(其實(shí)對應(yīng)了 ContainersReady condition 為 True);
  2. 如果 pod.spec.readinessGates 中定義了一個(gè)或多個(gè) conditionType,那么需要這些 conditionType 在 pod.status.conditions 中都有對應(yīng)的 status: “true” 的狀態(tài)。

只有滿足上述兩個(gè)前提,kubelet 才會(huì)上報(bào) Ready condition 為 True。

實(shí)現(xiàn)原理

了解了上面的四個(gè)背景之后,接下來分析一下 OpenKruise 是如何在 Kubernetes 中實(shí)現(xiàn)原地升級的原理。

1. 單個(gè) Pod 如何原地升級?

由“背景 1”可知,其實(shí)我們對一個(gè)存量 Pod 的 spec.containers[x] 中字段做修改,kubelet 會(huì)感知到這個(gè) container 的 hash 發(fā)生了變化,隨即就會(huì)停掉對應(yīng)的舊容器,并用新的 container 來拉鏡像、創(chuàng)建和啟動(dòng)新容器。

由“背景 2”可知,當(dāng)前我們對一個(gè)存量 Pod 的 spec.containers[x] 中的修改,僅限于 image 字段。

因此,得出第一個(gè)實(shí)現(xiàn)原理:**對于一個(gè)現(xiàn)有的 Pod 對象,我們能且只能修改其中的 spec.containers[x].image 字段,來觸發(fā) Pod 中對應(yīng)容器升級到一個(gè)新的 image。

2. 如何判斷 Pod 原地升級成功?

接下來的問題是,當(dāng)我們修改了 Pod 中的 spec.containers[x].image 字段后,如何判斷 kubelet 已經(jīng)將容器重建成功了呢?

由“背景 3”可知,比較 spec 和 status 中的 image 字段是不靠譜的,因?yàn)楹苡锌赡?status 中上報(bào)的是 Node 上存在的另一個(gè)鏡像名(相同 imageID)。

因此,得出第二個(gè)實(shí)現(xiàn)原理: 判斷 Pod 原地升級是否成功,相對來說比較靠譜的辦法,是在原地升級前先將 status.containerStatuses[x].imageID 記錄下來。在更新了 spec 鏡像之后,如果觀察到 Pod 的 status.containerStatuses[x].imageID 變化了,我們就認(rèn)為原地升級已經(jīng)重建了容器。

但這樣一來,我們對原地升級的 image 也有了一個(gè)要求: 不能用 image 名字(tag)不同、但實(shí)際對應(yīng)同一個(gè) imageID 的鏡像來做原地升級,否則可能一直都被判斷為沒有升級成功(因?yàn)?status 中 imageID 不會(huì)變化)。

當(dāng)然,后續(xù)我們還可以繼續(xù)優(yōu)化。OpenKruise 即將開源鏡像預(yù)熱的能力,會(huì)通過 DaemonSet 在每個(gè) Node 上部署一個(gè) NodeImage Pod。通過 NodeImage 上報(bào)我們可以得知 pod spec 中的 image 所對應(yīng)的 imageID,然后和 pod status 中的 imageID 比較即可準(zhǔn)確判斷原地升級是否成功。

3. 如何確保原地升級過程中流量無損?

在 Kubernetes 中,一個(gè) Pod 是否 Ready 就代表了它是否可以提供服務(wù)。因此,像 Service 這類的流量入口都會(huì)通過判斷 Pod Ready 來選擇是否能將這個(gè) Pod 加入 endpoints 端點(diǎn)中。

由“背景 4”可知,從 Kubernetes 1.12+ 之后,operator/controller 這些組件也可以通過設(shè)置 readinessGates 和更新 pod.status.conditions 中的自定義 type 狀態(tài),來控制 Pod 是否可用。

因此,得出第三個(gè)實(shí)現(xiàn)原理: 可以在 pod.spec.readinessGates 中定義一個(gè)叫 InPlaceUpdateReady 的 conditionType。

在原地升級時(shí):

  1. 先將 pod.status.conditions 中的 InPlaceUpdateReady condition 設(shè)為 “False”,這樣就會(huì)觸發(fā) kubelet 將 Pod 上報(bào)為 NotReady,從而使流量組件(如 endpoint controller)將這個(gè) Pod 從服務(wù)端點(diǎn)摘除;
  2. 再更新 pod spec 中的 image 觸發(fā)原地升級。

原地升級結(jié)束后,再將 InPlaceUpdateReady condition 設(shè)為 “True”,使 Pod 重新回到 Ready 狀態(tài)。

另外在原地升級的兩個(gè)步驟中,第一步將 Pod 改為 NotReady 后,流量組件異步 watch 到變化并摘除端點(diǎn)可能是需要一定時(shí)間的。因此我們也提供優(yōu)雅原地升級的能力,即通過 gracePeriodSeconds 配置在修改 NotReady 狀態(tài)和真正更新 image 觸發(fā)原地升級兩個(gè)步驟之間的靜默期時(shí)間。

4. 組合發(fā)布策略

原地升級和 Pod 重建升級一樣,可以配合各種發(fā)布策略來執(zhí)行:

  • partition:如果配置 partition 做灰度,那么只會(huì)將 replicas-partition 數(shù)量的 Pod 做原地升級;
  • maxUnavailable:如果配置 maxUnavailable,那么只會(huì)將滿足 unavailable 數(shù)量的 Pod 做原地升級;
  • maxSurge:如果配置 maxSurge 做彈性,那么當(dāng)先擴(kuò)出來 maxSurge 數(shù)量的 Pod 之后,存量的 Pod 仍然使用原地升級;
  • priority/scatter:如果配置了發(fā)布優(yōu)先級/打散策略,會(huì)按照策略順序?qū)?Pod 做原地升級。

總結(jié)

如上文所述, OpenKruise 結(jié)合 Kubernetes 原生提供的 kubelet 容器版本管理、readinessGates 等功能,實(shí)現(xiàn)了針對 Pod 的原地升級能力。

而原地升級也為應(yīng)用發(fā)布帶來大幅的效率、穩(wěn)定性提升。值得關(guān)注的是,隨著集群、應(yīng)用規(guī)模的增大,這種提升的收益越加明顯。正是這種原地升級能力,在近兩年幫助了阿里巴巴超大規(guī)模的應(yīng)用容器平穩(wěn)遷移到了基于 Kubernetes 的云原生環(huán)境,而原生 Deployment/StatefulSet 是完全無法在這種體量的環(huán)境下鋪開使用的。(歡迎加入釘釘交流群:23330762)

- 贈(zèng)書福利 -

揭秘:如何為 Kubernetes 實(shí)現(xiàn)原地升級

6 月 19 日 12:00 前在【阿里巴巴云原生公眾號】留言區(qū) 提出你的疑問,精選留言點(diǎn)贊第 1 名將免費(fèi)獲得此書,屆時(shí)我們還會(huì)請本文作者針對留言點(diǎn)贊前 5 名的問題進(jìn)行答疑!

課程推薦

為了更多開發(fā)者能夠享受到 Serverless 帶來的紅利,這一次,我們集結(jié)了 10+ 位阿里巴巴 Serverless 領(lǐng)域技術(shù)專家,打造出最適合開發(fā)者入門的 Serverless 公開課,讓你即學(xué)即用,輕松擁抱云計(jì)算的新范式——Serverless。

點(diǎn)擊即可免費(fèi)觀看課程: https://developer.aliyun.com/learning/roadmap/serverless

“ 阿里巴巴云原生關(guān)注微服務(wù)、Serverless、容器、Service Mesh 等技術(shù)領(lǐng)域、聚焦云原生流行技術(shù)趨勢、云原生大規(guī)模的落地實(shí)踐,做最懂云原生開發(fā)者的公眾號?!?/p>

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI