<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          略干,Kubernetes 集群二進(jìn)制無損升級(jí)實(shí)踐

          共 11365字,需瀏覽 23分鐘

           ·

          2021-12-19 12:12

          作者:vivo互聯(lián)網(wǎng)服務(wù)器團(tuán)隊(duì)-Shu Yingya


          一、背景


          活躍的社區(qū)和廣大的用戶群,使 Kubernetes 仍然保持3個(gè)月一個(gè)版本的高頻發(fā)布節(jié)奏。高頻的版本發(fā)布帶來了更多的新功能落地和 bug 及時(shí)修復(fù),但是線上環(huán)境業(yè)務(wù)長期運(yùn)行,任何變更出錯(cuò)都可能帶來巨大的經(jīng)濟(jì)損失,升級(jí)對(duì)企業(yè)來說相對(duì)吃力,緊跟社區(qū)更是幾乎不可能,因此高頻發(fā)布和穩(wěn)定生產(chǎn)之間的矛盾需要容器團(tuán)隊(duì)去衡量和取舍。



          vivo 互聯(lián)網(wǎng)團(tuán)隊(duì)建設(shè)大規(guī)模 Kubernetes 集群以來,部分集群較長時(shí)間一直使用 v1.10 版本,但是由于業(yè)務(wù)容器化比例越來越高,對(duì)大規(guī)模集群穩(wěn)定性、應(yīng)用發(fā)布的多樣性等訴求日益攀升,集群升級(jí)迫在眉睫。集群升級(jí)后將解決如下問題:


          • 高版本集群在大規(guī)模場(chǎng)景做了優(yōu)化,升級(jí)可以解決一系列性能瓶頸問題。


          • 高版本集群才能支持 OpenKruise 等 CNCF 項(xiàng)目,升級(jí)可以解決版本依賴問題。


          • 高版本集群增加的新特性能夠提高集群資源利用率,降低服務(wù)器成本同時(shí)提高集群效率。


          • 公司內(nèi)部維護(hù)多個(gè)不同版本集群,升級(jí)后減少集群版本碎片化,進(jìn)一步降低運(yùn)維成本。


          這篇文章將會(huì)從0到1的介紹 vivo 互聯(lián)網(wǎng)團(tuán)隊(duì)支撐在線業(yè)務(wù)的集群如何在不影響原有業(yè)務(wù)正常運(yùn)行的情況下從 v1.10 版本升級(jí)到 v1.17 版本。之所以升級(jí)到 v1.17 而不是更高的 v1.18 以上版本, 是因?yàn)樵?v1.18 版本引入的代碼變動(dòng) [1] 會(huì)導(dǎo)致 extensions/v1beta1 等高級(jí)資源類型無法繼續(xù)運(yùn)行(這部分代碼在 v1.18 版本刪除)。


          二、無損升級(jí)難點(diǎn)


          容器集群搭建通常有二進(jìn)制 systemd 部署和核心組件靜態(tài) Pod 容器化部署兩種方式,集群 API 服務(wù)多副本對(duì)外負(fù)載均衡。兩種部署方式在升級(jí)時(shí)沒有太大區(qū)別,二進(jìn)制部署更貼合早期集群,因此本文將對(duì)二進(jìn)制方式部署的集群升級(jí)做分享。


          對(duì)二進(jìn)制方式部署的集群,集群組件升級(jí)主要是二進(jìn)制的替換、配置文件的更新和服務(wù)的重啟;從生產(chǎn)環(huán)境 SLO 要求來看,升級(jí)過程務(wù)必不能因?yàn)榧航M件自身邏輯變化導(dǎo)致業(yè)務(wù)重啟。因此升級(jí)的難點(diǎn)集中在下面幾點(diǎn):


          首先,當(dāng)前內(nèi)部集群運(yùn)行版本較低,但是運(yùn)行容器數(shù)量卻很多,其中部分仍然是單副本運(yùn)行,為了不影響業(yè)務(wù)運(yùn)行,需要盡可能避免容器重啟,這無疑是升級(jí)中最大的難點(diǎn),而在 v1.10 版本和 v1.17 版本之間,kubelet 關(guān)于容器 Hash 值計(jì)算方式發(fā)生了變化,也就是說一旦升級(jí)必然會(huì)觸發(fā) kubelet 重新啟動(dòng)容器。


          其次,社區(qū)推薦的方式是基于偏差策略 [2] 的升級(jí)以保證高可用集群升級(jí)同時(shí)不會(huì)因?yàn)?API resources 版本差異導(dǎo)致 kube-apiserve 和 kubelet 等組件出現(xiàn)兼容性錯(cuò)誤,這就要求每次升級(jí)組件版本不能有2個(gè) Final Release 以上的偏差,比如直接從 v1.11 升級(jí)至 v1.13是不推薦的。


          再次,升級(jí)過程中由于新特性的引入,API 兼容性可能引發(fā)舊版本集群的配置不生效,為整個(gè)集群埋下穩(wěn)定性隱患。這便要求在升級(jí)前盡可能的熟悉升級(jí)版本間的 ChangeLog,排查出可能帶來潛在隱患的新特性。


          三、無損升級(jí)方案


          針對(duì)前述的難點(diǎn),本節(jié)將逐個(gè)提出針對(duì)性解決方案,同時(shí)也會(huì)介紹升級(jí)后遇到的高版本 bug 和解決方法。希望關(guān)于升級(jí)前期兼容性篩查和升級(jí)過程中排查的問題能夠給讀者帶來啟發(fā)。


          3.1 升級(jí)方式


          在軟件領(lǐng)域,主流的應(yīng)用升級(jí)方式有兩種,分別是原地升級(jí)和替換升級(jí)。目前這兩種升級(jí)方式在業(yè)內(nèi)互聯(lián)網(wǎng)大廠均有采用,具體方案選擇與集群上業(yè)務(wù)有很大關(guān)系。


          替換升級(jí)

          1)Kubernetes 替換升級(jí)是先準(zhǔn)備一個(gè)高版本集群,對(duì)低版本集群通過逐個(gè)節(jié)點(diǎn)排干、刪除最后加入新集群的方式將低版本集群內(nèi)節(jié)點(diǎn)逐步輪換升級(jí)到新版本。


          2)替換升級(jí)的優(yōu)點(diǎn)是原子性更強(qiáng),逐步升級(jí)各個(gè)節(jié)點(diǎn),升級(jí)過程不存在中間態(tài),對(duì)業(yè)務(wù)安全更有保障;缺點(diǎn)是集群升級(jí)工作量較大,排干操作對(duì)pod重啟敏感度高的應(yīng)用、有狀態(tài)應(yīng)用、單副本應(yīng)用等都不友好。


          原地升級(jí)

          1)Kubernetes 原地升級(jí)是對(duì)節(jié)點(diǎn)上服務(wù)如 kube-controller-manager、 kubelet 等組件按照一定順序批量更新,從節(jié)點(diǎn)角色維度批量管理組件版本。


          2)原地升級(jí)的優(yōu)點(diǎn)是自動(dòng)化操作便捷,并且通過適當(dāng)?shù)男薷哪軌蚝芎玫谋WC容器的生命周期連續(xù)性;缺點(diǎn)是集群升級(jí)中組件升級(jí)順序很重要,升級(jí)中存在中間態(tài),并且一個(gè)組件重啟失敗可能影響后續(xù)其他組件升級(jí),原子性差。



          vivo 容器集群上運(yùn)行的部分業(yè)務(wù)對(duì)重啟容忍度較低,盡可能避免容器重啟是升級(jí)工作的第一要?jiǎng)?wù)。當(dāng)解決好升級(jí)版本帶來的容器重啟后,結(jié)合業(yè)務(wù)容器化程度和業(yè)務(wù)類型不同,因地制宜的選擇升級(jí)方式即可。二進(jìn)制部署集群建議選擇原地升級(jí)的方式,具有時(shí)間短,操作簡捷,單副本業(yè)務(wù)不會(huì)被升級(jí)影響的好處。


          3.2 跨版本升級(jí)


          由于Kubernetes 本身是基于 API 的微服務(wù)架構(gòu),Kuberntes 內(nèi)部架構(gòu)也是通過 API 的調(diào)用和對(duì)資源對(duì)象的 List-Watch 來協(xié)同資源狀態(tài),因此社區(qū)開發(fā)者在設(shè)計(jì) API 時(shí)遵循向上或向下兼容的原則。這個(gè)兼容性規(guī)則也是遵循社區(qū)的偏差策略 [2],即 API groups 棄用、啟用時(shí),對(duì)于 Alpha 版本會(huì)立即生效,對(duì)于 Beta 版本將會(huì)繼續(xù)支持3個(gè)版本,超過對(duì)應(yīng)版本將導(dǎo)致 API resource version 不兼容。例如 kubernetes 在 v1.16 對(duì) Deployment 等資源的 extensions/v1beta1 版本執(zhí)行了棄用,在v1.18 版本從代碼級(jí)別執(zhí)行了刪除,當(dāng)跨3個(gè)版本以上升級(jí)時(shí)會(huì)導(dǎo)致相關(guān)資源無法被識(shí)別,相應(yīng)的增刪改查操作都無法執(zhí)行。


          如果按照官方建議的升級(jí)策略,從 v1.10 升級(jí)到 v1.17 需要經(jīng)過至少 7 次升級(jí),這對(duì)于業(yè)務(wù)場(chǎng)景復(fù)雜的生產(chǎn)環(huán)境來說運(yùn)維復(fù)雜度高,業(yè)務(wù)風(fēng)險(xiǎn)大。


          對(duì)于類似的 API breaking change 并不是每個(gè)版本都會(huì)存在,社區(qū)建議的偏差策略是最安全的升級(jí)策略,經(jīng)過細(xì)致的 Change Log 梳理和充分的跨版本測(cè)試,我們確認(rèn)這幾個(gè)版本之間不能存在影響業(yè)務(wù)運(yùn)行和集群管理操作的 API 兼容性問題,對(duì)于 API 類型的廢棄,可以通過配置 apiserver 中相應(yīng)參數(shù)來啟動(dòng)繼續(xù)使用,保證環(huán)境業(yè)務(wù)繼續(xù)正常運(yùn)行。


          3.3 避免容器重啟


          在初步驗(yàn)證升級(jí)方案時(shí)發(fā)現(xiàn)大量容器都被重建,重啟原因從升級(jí)后 kubelet 組件日志看到是 "Container definition changed"。結(jié)合源碼報(bào)錯(cuò)位于 pkg/kubelet/kuberuntime_manager.go 文件 computePodActions 方法,該方法用來計(jì)算 pod 的 spec 哈希值是否發(fā)生變化,如果變化則返回 true,告知 kubelet syncPod 方法觸發(fā) pod 內(nèi)容器重建或者 pod 重建。


          kubelet 容器 Hash 計(jì)算;


          func (m *kubeGenericRuntimeManager) computePodActions(pod *v1.Pod, podStatus *kubecontainer.PodStatus) podActions { restart := shouldRestartOnFailure(pod) if _, _, changed := containerChanged(&container, containerStatus); changed { message = fmt.Sprintf("Container %s definition changed", container.Name) // 如果 container spec 發(fā)生變化,將會(huì)強(qiáng)制重啟 container(將 restart 標(biāo)志位設(shè)置為 true) restart = true } ... if restart { message = fmt.Sprintf("%s, will be restarted", message) // 需要重啟的 container 加入到重啟列表 changes.ContainersToStart = append(changes.ContainersToStart, idx) }} func containerChanged(container *v1.Container, containerStatus *kubecontainer.ContainerStatus) (uint64, uint64, bool) { // 計(jì)算 container spec 的 Hash 值 expectedHash := kubecontainer.HashContainer(container) return expectedHash, containerStatus.Hash, containerStatus.Hash != expectedHash}


          相對(duì)于 v1.10 版本,v1.17 版本在計(jì)算容器 Hash 時(shí)使用的是 container 結(jié)構(gòu) json 序列化后的數(shù)據(jù),而不是 v1.10 版本使用 container struct 的結(jié)構(gòu)數(shù)據(jù)。而且高版本 kubelet 中對(duì)容器的結(jié)構(gòu)也增加了新的屬性,通過 go-spew 庫計(jì)算出結(jié)果自然不一致,進(jìn)一步向上傳遞返回值使得 syncPod 方法觸發(fā)容器重建。


          那是否可以通過修改 go-spew 對(duì) container struct 的數(shù)據(jù)結(jié)構(gòu)剔除新增的字段呢? 答案是肯定的,但是卻不是優(yōu)雅的方式,因?yàn)檫@樣對(duì)核心代碼邏輯侵入較為嚴(yán)重,以后每個(gè)版本的升級(jí)都需要定制代碼,并且新增的字段越來越多,維護(hù)復(fù)雜度也會(huì)越來越高。換個(gè)角度,如果在升級(jí)過渡期間將屬于舊版本集群 kubelet 創(chuàng)建的 Pod 跳過該檢查,則可以避免容器重啟。


          和圈內(nèi)同事交流后發(fā)現(xiàn)類似思路在社區(qū)已有實(shí)現(xiàn),本地創(chuàng)建一個(gè)記錄舊集群版本信息和啟動(dòng)時(shí)間的配置文件,kubelet 代碼中維護(hù)一個(gè) cache 讀取配置文件,在每個(gè) syncPod 周期中,當(dāng) kubelet 發(fā)現(xiàn)自身 version 高于 cache 中記錄的 oldVersion, 并且容器啟動(dòng)時(shí)間早于當(dāng)前 kubelet 啟動(dòng)時(shí)間,則會(huì)跳過容器 Hash 值計(jì)算。升級(jí)后的集群內(nèi)運(yùn)行定時(shí)任務(wù)探測(cè) Pod 的 containerSpec 是否與高版本計(jì)算方式計(jì)算得到 Hash 結(jié)果全部一致,如果是則可以刪除掉本地配置文件,syncPod 邏輯恢復(fù)到與社區(qū)完全一致。


          具體方案參考?這種實(shí)現(xiàn)的好處是對(duì)原生 kubelet 代碼侵入小,沒有改變核心代碼邏輯,而且未來如果還需要升級(jí)高版本也可以復(fù)用該代碼。如果集群內(nèi)所有 Pod 都是當(dāng)前版本 kubelet 創(chuàng)建,則會(huì)恢復(fù)到社區(qū)自身的邏輯。



          3.4 Pod 非預(yù)期驅(qū)逐問題


          Kubernetes 雖然迭代了十幾個(gè)版本,但是每個(gè)迭代社區(qū)活躍度仍然很高,保持著每個(gè)版本大約30個(gè)關(guān)于拓展性增強(qiáng)和穩(wěn)定性提升的新特性。選擇升級(jí)很大一方面原因是引入很多社區(qū)開發(fā)的新特性來豐富集群的功能與提升集群穩(wěn)定性。新特性開發(fā)也是遵循偏差策略,跨大版本升級(jí)很可能導(dǎo)致在部分配置未加載的情況下啟用新特性,這就給集群帶來穩(wěn)定性風(fēng)險(xiǎn),因此需要梳理影響 Pod 生命周期的一些特性,尤其關(guān)注控制器相關(guān)的功能。


          這里注意到在 v1.13 版本引入的 TaintBasedEvictions 特性用于更細(xì)粒度的管理 Pod 的驅(qū)逐條件。在 v1.13基于條件版本之前,驅(qū)逐是基于 NodeController 的統(tǒng)一時(shí)間驅(qū)逐,節(jié)點(diǎn) NotReady 超過默認(rèn)5分鐘后,節(jié)點(diǎn)上的 Pod 才會(huì)被驅(qū)逐;在 v1.16 默認(rèn)開啟 TaintBasedEvictions 后,節(jié)點(diǎn) NotReady 的驅(qū)逐將會(huì)根據(jù)每個(gè) Pod 自身配置的 TolerationSeconds ?來差異化的處理。


          舊版本集群創(chuàng)建的 Pod 默認(rèn)沒有設(shè)置 TolerationSeconds,一旦升級(jí)完畢 TaintBasedEvictions 被開啟,節(jié)點(diǎn)變成 NotReady 后 5 秒就會(huì)驅(qū)逐節(jié)點(diǎn)上的 Pod。對(duì)于短暫的網(wǎng)絡(luò)波動(dòng)、kubelet 重啟等情況都會(huì)影響集群中業(yè)務(wù)的穩(wěn)定性。



          TaintBasedEvictions 對(duì)應(yīng)的控制器是按照 pod 定義中的 tolerationSeconds 決定 Pod 的驅(qū)逐時(shí)間,也就是說只要正確設(shè)置 Pod 中的 tolerationSeconds 就可以避免出現(xiàn) Pod 的非預(yù)期驅(qū)逐。


          在v1.16 版本社區(qū)默認(rèn)開啟的 DefaultTolerationSeconds 準(zhǔn)入控制器基于 k8s-apiserver 輸入?yún)?shù) default-not-ready-toleration-seconds 和 default-unreachable-toleration-seconds 為 Pod 設(shè)置默認(rèn)的容忍度,以容忍 notready:NoExecute 和 unreachable:NoExecute 污點(diǎn)。?


          新建 Pod 在請(qǐng)求發(fā)送后會(huì)經(jīng)過 DefaultTolerationSeconds 準(zhǔn)入控制器給 pod 加上默認(rèn)的 tolerations。但是這個(gè)邏輯如何對(duì)集群中已經(jīng)創(chuàng)建的 Pod 生效呢?查看該準(zhǔn)入控制器發(fā)現(xiàn)除了支持 create 操作,update 操作也會(huì)更新 pod 定義觸發(fā) DefaultTolerationSeconds 插件去設(shè)置 tolerations。因此我們通過給集群中已經(jīng)運(yùn)行的 Pod 打 label 就可以達(dá)成目的。

          tolerations:- effect: NoExecute  key: node.kubernetes.io/not-ready  operator: Exists  tolerationSeconds: 300- effect: NoExecute  key: node.kubernetes.io/unreachable  operator: Exists  tolerationSeconds: 300


          3.5 Pod MatchNodeSelector


          為了判斷升級(jí)時(shí) Pod 是否發(fā)生非預(yù)期的驅(qū)逐以及是否存在 Pod 內(nèi)容器批量重啟,有腳本去實(shí)時(shí)同步節(jié)點(diǎn)上非Running狀態(tài)的Pod和發(fā)生重啟的容器。


          在升級(jí)過程中,突然多出來數(shù)十個(gè) pod 被標(biāo)記為 MatchNodeSelector 狀態(tài),查看該節(jié)點(diǎn)上業(yè)務(wù)容器確實(shí)停止。kubelet 日志中看到如下錯(cuò)誤日志;

          predicate.go:132] Predicate failed on Pod: nginx-7dd9db975d-j578s_default(e3b79017-0b15-11ec-9cd4-000c29c4fa15), for reason: Predicate MatchNodeSelector failedkubelet_pods.go:1125] Killing unwanted pod "nginx-7dd9db975d-j578s"


          經(jīng)分析,Pod 變成 MatchNodeSelector 狀態(tài)是因?yàn)?kubelet 重啟時(shí)對(duì)節(jié)點(diǎn)上 Pod 做準(zhǔn)入檢查時(shí)無法找到節(jié)點(diǎn)滿足要求的節(jié)點(diǎn)標(biāo)簽,pod 狀態(tài)就會(huì)被設(shè)置為 Failed 狀態(tài),而 Reason 被設(shè)置為 MatchNodeSelector。在 kubectl 命令獲取時(shí),printer 做了相應(yīng)轉(zhuǎn)換直接顯示了Reason,因此我們看到 Pod 狀態(tài)是 MatchNodeSelector。通過給節(jié)點(diǎn)加上標(biāo)簽,可以讓 Pod 重新調(diào)度回來,然后刪除掉 MatchNodeSelector 狀態(tài)的 Pod 即可。


          建議在升級(jí)前寫腳本檢查節(jié)點(diǎn)上 pod 定義中使用的 NodeSelector 屬性節(jié)點(diǎn)是否都有對(duì)應(yīng)的 Label。?


          3.6 無法訪問 kube-apiserver?


          預(yù)發(fā)環(huán)境升級(jí)后的集群運(yùn)行在 v1.17 版本后,突然有節(jié)點(diǎn)變成 NotReady 狀態(tài)告警,分析后通過重啟 kubelet 節(jié)點(diǎn)恢復(fù)正常。繼續(xù)分析出錯(cuò)原因發(fā)現(xiàn) kubelet 日志中出現(xiàn)了大量 use of closed network connection 報(bào)錯(cuò)。在社區(qū)搜索相關(guān) issue 發(fā)現(xiàn)有類似的問題,其中有開發(fā)者描述了問題的起因和解決辦法,并且在 v1.18 已經(jīng)合入了代碼。


          問題的起因是 kubelet 默認(rèn)連接是 HTTP/2.0 長連接,在構(gòu)建 client 到 server的連接時(shí)使用的 golang net/http2 包存在 bug,在 http 連接池中仍然能獲取到 broken 的連接,也就導(dǎo)致 kubelet 無法正常與 kube-apiserver 通信。


          golang社區(qū)通過增加 http2 連接健康檢查規(guī)避這個(gè)問題,但是這個(gè) fix 仍然存在 bug ,社區(qū)在 golang v1.15.11 版本徹底修復(fù)。我們內(nèi)部通過 backport 到 v1.17 分支,并使用 golang 1.15.15 版本編譯二進(jìn)制解決了此問題。


          3.7 TCP 連接數(shù)問題


          在預(yù)發(fā)布環(huán)境測(cè)試運(yùn)行期間,偶然發(fā)現(xiàn)集群每個(gè)節(jié)點(diǎn) kubelet 都有近10個(gè)長連接與 kube-apiserver 通信,這與我們認(rèn)知的 kubelet 會(huì)復(fù)用連接與 kube-apiserver 通信明顯不符,查看 v1.10 版本環(huán)境也確實(shí)只有1個(gè)長連接。這種 TCP 連接數(shù)增加情況無疑會(huì)對(duì) LB 造成了壓力,隨著節(jié)點(diǎn)增多,一旦 LB 被拖垮,kubelet 無法上報(bào)心跳,節(jié)點(diǎn)會(huì)變成 NotReady,緊接著將會(huì)有大量 Pod 被驅(qū)逐,后果是災(zāi)難性的。因此除去對(duì) LB 本身參數(shù)調(diào)優(yōu)外,還需要定位清楚kubelet 到 kube-apiserver 連接數(shù)增加的原因。


          在本地搭建的 v1.17.1 版本 kubeadm 集群 kubelet 到 kube-apiserver 也僅有1個(gè)長連接,說明這個(gè)問題是在 v1.17.1 到升級(jí)目標(biāo)版本之間引入的,排查后(問題)發(fā)現(xiàn)增加了判斷邏輯導(dǎo)致 kubelet 獲取 client 時(shí)不再從 cache 中獲取緩存的長連接。transport 的主要功能其實(shí)就是緩存了長連接,用于大量 http 請(qǐng)求場(chǎng)景下的連接復(fù)用,減少發(fā)送請(qǐng)求時(shí) TCP(TLS) 連接建立的時(shí)間損耗。在該 PR 中對(duì) transport 自定義 RoundTripper 的接口,一旦 tlsConfig 對(duì)象中有 Dial 或者 Proxy 屬性,則不使用 cache 中的連接而新建連接。

          // client-go 從 cache 獲取復(fù)用連接邏輯func tlsConfigKey(c *Config) (tlsCacheKey, bool, error) {    ...     if c.TLS.GetCert != nil || c.Dial != nil || c.Proxy != nil {        // cannot determine equality for functions        return tlsCacheKey{}, false, nil    }...}  func (c *tlsTransportCache) get(config *Config) (http.RoundTripper, error) {    key, canCache, err := tlsConfigKey(config)    ...     if canCache {        // Ensure we only create a single transport for the given TLS options        c.mu.Lock()        defer c.mu.Unlock()         // See if we already have a custom transport for this config        if t, ok := c.transports[key]; ok {            return t, nil        }    }...} // kubelet 組件構(gòu)建 client 邏輯func buildKubeletClientConfig(ctx context.Context, s *options.KubeletServer, nodeName types.NodeName) (*restclient.Config, func(), error) {    ...    kubeClientConfigOverrides(s, clientConfig)    closeAllConns, err := updateDialer(clientConfig)    ...    return clientConfig, closeAllConns, nil} // 為 clientConfig 設(shè)置 Dial屬性,因此 kubelet 構(gòu)建 clinet 時(shí)會(huì)新建 transportfunc updateDialer(clientConfig *restclient.Config) (func(), error) {    if clientConfig.Transport != nil || clientConfig.Dial != nil {        return nil, fmt.Errorf("there is already a transport or dialer configured")    }    d := connrotation.NewDialer((&net.Dialer{Timeout: 30 * time.Second, KeepAlive: 30 * time.Second}).DialContext)    clientConfig.Dial = d.DialContext    return d.CloseAll, nil


          在這里構(gòu)建 closeAllConns 對(duì)象來關(guān)閉已經(jīng)處于 Dead 但是尚未 Close 的連接,但是上一個(gè)問題通過升級(jí) golang 版本解決了這個(gè)問題,因此我們?cè)诒镜卮a分支回退了該修改中的部分代碼解決了 TCP 連接數(shù)增加的問題。


          最近追蹤社區(qū)發(fā)現(xiàn)已經(jīng)合并了解決方案 ,通過重構(gòu) client-go 的接口實(shí)現(xiàn)對(duì)自定義 RESTClient 的 TCP 連接復(fù)用。


          四、無損升級(jí)操作


          跨版本升級(jí)最大的風(fēng)險(xiǎn)是升級(jí)前后對(duì)象定義不一致,可能導(dǎo)致升級(jí)后的組件無法解析保存在 ETCD 數(shù)據(jù)庫中的對(duì)象;也可能是升級(jí)存在中間態(tài),kubelet 還未升級(jí)而控制平面組件升級(jí),存在上報(bào)狀態(tài)異常,最壞的情況是節(jié)點(diǎn)上 Pod 被驅(qū)逐。這些都是升級(jí)前需要考慮并通過測(cè)試驗(yàn)證的。


          經(jīng)過反復(fù)測(cè)試,上述問題在 v1.10 到 v1.17 之間除了部分廢棄的 API Resources 通過增加 kube-apiserver 配置方式其他情況暫時(shí)不存在。為了保證升級(jí)時(shí)及時(shí)能處理未覆蓋到的特殊情況,強(qiáng)烈建議升級(jí)前備份 ETCD 數(shù)據(jù)庫,并在升級(jí)期間停止控制器和調(diào)度器,避免非預(yù)期的控制邏輯發(fā)生(實(shí)際上這里應(yīng)該是停止 controller manager 中的部分控制器,不過需要修改代碼編譯臨時(shí) controller manager ,增加了升級(jí)流程步驟和管理復(fù)雜度,因此直接停掉了全局控制器)。


          除卻以上代碼變動(dòng)和升級(jí)流程注意事項(xiàng),在替換二進(jìn)制升級(jí)前,就剩下比對(duì)新老版本服務(wù)的配置項(xiàng)的區(qū)別以保證服務(wù)成功啟動(dòng)運(yùn)行。對(duì)比后發(fā)現(xiàn),kubelet 組件啟動(dòng)時(shí)不再支持 --allow-privileged 參數(shù),需要?jiǎng)h除。值得說明的是,刪除不代表高版本不再支持節(jié)點(diǎn)上運(yùn)行特權(quán)容器,在 v1.15 以后通過 Pod Security Policy 資源對(duì)象來定義一組 pod 訪問的安全特征,更細(xì)粒度的做安全管控。


          基于上面討論的無損升級(jí)代碼側(cè)的修改編譯二進(jìn)制,再對(duì)集群組件配置文件中各個(gè)配置項(xiàng)修改后,就可以著手線上升級(jí)。整個(gè)升級(jí)步驟為:


          • 備份集群(二進(jìn)制,配置文件,ETCD數(shù)據(jù)庫等);

          • 灰度升級(jí)部分節(jié)點(diǎn),驗(yàn)證二進(jìn)制和配置文件正確性

          • 提前分發(fā)升級(jí)的二進(jìn)制文件;

          • 停止控制器、調(diào)度器和告警;

          • 更新控制平面服務(wù)配置文件,升級(jí)組件;

          • 更新計(jì)算節(jié)點(diǎn)服務(wù)配置文件,升級(jí)組件;

          • 為節(jié)點(diǎn)打 Label 觸發(fā) pod 增加 tolerations 屬性;

          • 打開控制器和調(diào)度器,啟用告警;

          • 集群業(yè)務(wù)點(diǎn)檢,確認(rèn)集群正常。


          升級(jí)過程中建議節(jié)點(diǎn)并發(fā)數(shù)不要太高,因?yàn)榇罅抗?jié)點(diǎn) kubelet 同時(shí)重啟上報(bào)信息,對(duì) kube-apiserver 前面使用的 LB 帶來沖擊,特別情況下可能節(jié)點(diǎn)心跳上報(bào)失敗,節(jié)點(diǎn)狀態(tài)會(huì)在 NotReady 與 Ready 狀態(tài)間跳動(dòng)。


          五、總結(jié)


          集群升級(jí)是困擾容器團(tuán)隊(duì)比較長時(shí)間的事,在經(jīng)過一系列調(diào)研和反復(fù)測(cè)試,解決了上面提到的數(shù)個(gè)關(guān)鍵問題后,成功將集群從 v1.10 升級(jí)到 v1.17 版本,1000 個(gè)節(jié)點(diǎn)的集群分批執(zhí)行升級(jí)操作,大概花費(fèi) 10 分鐘,后續(xù)在完成平臺(tái)接口改造后將會(huì)再次升級(jí)到更高版本。


          集群版本升級(jí)提高了集群的穩(wěn)定性、增加了集群的擴(kuò)展性,同時(shí)還豐富了集群的能力,升級(jí)后的集群也能夠更好的兼容 CNCF 項(xiàng)目。


          如開篇所述,按照偏差策略頻繁對(duì)大規(guī)模集群升級(jí)可能不太現(xiàn)實(shí),因此跨版本升級(jí)雖然風(fēng)險(xiǎn)較大,但是也是業(yè)界廣泛采用的方式。在 2021 年中國 KubeCon 大會(huì)上,阿里巴巴也有關(guān)于零停機(jī)跨版本升級(jí) Kubernetes 集群的分享,主要是關(guān)于應(yīng)用遷移、流量切換等升級(jí)關(guān)鍵點(diǎn)的介紹,升級(jí)的準(zhǔn)備工作和升級(jí)過程相對(duì)復(fù)雜。相對(duì)于阿里巴巴的集群跨版本替換升級(jí)方案,原地升級(jí)的方式需要在源碼上做少量修改,但是升級(jí)過程會(huì)更簡單,運(yùn)維自動(dòng)化程度更高。


          由于集群版本具有很大的可選擇性,本文所述的升級(jí)并不一定廣泛適用,筆者更希望給讀者提供生產(chǎn)集群在跨版本升級(jí)時(shí)的思路和風(fēng)險(xiǎn)點(diǎn)。升級(jí)過程短暫,但是升級(jí)前的準(zhǔn)備和調(diào)研工作是費(fèi)時(shí)費(fèi)力的,需要對(duì)不同版本 Kubernetes 特性和源碼深入探索,同時(shí)對(duì) Kubernetes 的 API 兼容性策略和發(fā)布策略擁有完整認(rèn)知,這樣便能在升級(jí)前做出充分的測(cè)試,也能更從容面對(duì)升級(jí)過程中突發(fā)情況。


          六、參考鏈接


          [1]https://github.com

          [2] https://kubernetes.io/version-skew-policy

          [3] 具體方案參考:https://github.comstart

          [4] 類似的問題: https://github.com/kubernetes

          [5] https://github.com/golang/34978

          [6] https://github.com/kubernetes/100376

          [7] https://github.com/kubernetes/95427

          [8] https://github.com/kubernetes/105490


          瀏覽 88
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  操皮视频| 国产视频一区二区三区四 | 亚洲18禁在线 | 999一区二区三区 | 97爽无码人妻AⅤ精品牛牛 |