1. 程式人生 > >kubernetes叢集升級的正確姿勢

kubernetes叢集升級的正確姿勢

kubernetes社群非常活躍,每季度都會發佈一個release。但是線上叢集業務可用性要求較高,場景複雜,任何微小的變更都需要非常小心,此時跟隨社群版本進行升級略顯吃力。但是為了能夠使用到最新的一些feature我們必須不定期進行一些升級操作,在經歷了一次線上叢集的升級操作,踩完一些坑之後,分享一些收穫與感悟。原來的叢集版本是1.10,為了提高GPU叢集的資源利用率,需要在排程器層面支援一些搶佔排程等新特性,所以升級到1.14,此次升級的叢集規模不是特別大,但是有一些線上任務,還是需要慎重操作。目前kubernetes社群中所有的工具(包括使用較多的kubeadm,kops等工具)對於生產環境高可用的叢集升級都顯的比較乏力,所以還是建議手動升級,更加安全可控,出現問題便於定位修復。

升級策略

升級方式可以分為原地升級和異地升級:

  • 原地升級是指在原有叢集直接替換二進位制進行升級,此種方式升級方便,運維代價較小,理論上來說多副本的業務不會有downtime,在充分測試的情況下優先選擇原地升級.
  • 異地升級是指新搭建一個一模一樣的高版本叢集,然後手動或自動遷移業務到新叢集。此種方式需要上層提供滾動升級的能力,支援兩個backend,需要額外的開發工作。且預設double一份原叢集pod到新叢集不會對業務造成影響,對於使用ceph等持久化儲存的叢集來說,可能會有問題。但此種方式升級更加安全可控,風險更低。

官方建議升級過程

  1. 首先閱讀相關release node,重點關注其中幾部分: Known Issues,Action Requireed,Deprecations and removals。社群會將一些變化highlight到這裡,閱讀這些變化可以明確自己需要採取哪些行動。
  2. kubernetes 建議不斷地進行小版本升級,而不是一次進行大的版本跳躍。具體的相容策略是: slave元件可以與master元件最多延遲兩個小版本(minor version),但是不能比master元件新。client不能與master元件落後一個小版本,但是可以高一個版本,也就是說: v1.3的master可以與v1.1,v1.2,v1.3的slave元件一起使用,與v1.2,v1.3,v1.4 client一起使用。官方建議每次升級不要跨越兩個版本,升級順序為: master,addons,salve。
  3. slave節點的升級是滾動升級,官方建議首先使用kubectl drain驅逐pod之後,然後升級kubelet,因為kubelet會有一些狀態資訊檔案儲存在node節點上,社群並不保證這些狀態檔案在版本間的相容性。
  4. apiserver升級之前需要確保resource version被正常支援,目前kubernetes會逐步廢棄掉,例如: DaemonSet,Deployment,ReplicaSet 所使用的 extensions/v1beta1,apps/v1beta1,apps/v1beta2 將會在v1.16中完全廢棄掉,屆時,如果你再去讀取這些版本的資源,apiserver將不會認識這些資源,任何的增刪改查都無法進行,只能通過etcdctl進行刪除。目前社群正在開發遷移工具,並且在支援該工具之前,所有的版本移除操作都會被凍結,所以目前(2019.5.20)來說是相對安全的。

生產實踐升級過程

  1. 如果採用官方建議的升級策略需要小版本不斷升級,但是線上運維壓力較大,業務場景複雜,大多數情況下不可能跟隨社群版本不斷升級,所以可以在充分測試的前提下進行大版本跳躍升級。
  2. 官方建議升級kubelet之前先將所有pod驅逐,但是此種方式可能會造成業務容器頻繁重啟。例如升級nodeA時pod漂移到未升級節點nodeB,後續升級nodeB可能需要繼續驅逐該pod。為避免頻繁重啟業務,在充分測試的情況下不用驅逐,直接原地升級即可。目前(2019.6.5)在master元件升級之後重啟或升級kubelet會導致大部分容器重啟,因為kubelet通過hash(container spec)來生成容器的唯一標識,不同版本間container spec會發生變化,引起hash值變化,進而導致容器重啟,參見kubernetes/kubernetes: Issue #63814。在不驅逐pod的情況下原地升級最壞情況下只有一次重啟,而且volume等資訊不會發生變化,對於叢集的擾動也較小。 有能力的同學可以研究下如何做到升級kubelet不重啟容器。
  3. 雖然kubernetes建議先升級master元件,然後再升級node元件,但是實際應用過程中建議先停掉controller-manager,然後升級master元件,node元件,最後再升級controller-manager,因為controller-manager中會進行一些狀態的調諧(reconcile),對於actual status不符合desire status的物件會觸發一些操作。升級過程中儘管我們會進行充分的測試,但是也難免出現一些非預期的情況下,例如apiserver中某些資源物件的相容性不好,或者其中的某些欄位進行調整,觸發controller-manager執行非預期操作,例如重建一個deployment下所有的pod,更糟糕的是,如果此時kubelet還未升級,就可能不認識新版本一些資源物件中的新增的某些欄位,此時老的pod被刪掉了,但是新的pod沒有起來,就造成一定的服務中斷。(後面會有相關的案例)
  4. 升級過程中建議調高日誌級別,方便定位問題,在升級完成之後在降低日誌級別。
  5. 備份etcd資料,並進行故障恢復的演練,作為升級失敗的最後一道防線。
  6. 升級之前review叢集狀態,確保所有元件正常工作,review重要業務的例項數,避免副本數為1,並且適當設定PDB; 需要檢查是否有單個node帶有特殊的label,可能有pod依賴於該label,如果該node異常導致pod也會發生故障(這個坑真的有人踩過:參見:ingress-cordoned)。

升級過程中發現的“坑” (已填)

  1. 上面的實踐3中就是筆者測試的時候發現的一個坑: 目前升級會導致部分controller(daemonset)重建容器,升級之後因為pod spec發生變化,部分controller會建立新的controllerrevision ,升級更新所有其控制的容器。如果先升級master/controller-manager,因為此時kubelet還未升級,版本較低,會出現不相容的欄位,嚴重情況下kubelet會reject掉該pod,導致最終該node上此daemonset container退出並無法重啟。如果daemonset 設定為滾動升級,並且maxUnavailable設定為1的話,可以避免一定損失,最多隻允許同時掛掉一個daemonset container。參見kubernetes/kubernetes: Issue #78633。所以最佳實踐還是升級之前停掉Controller-manager,叢集實際完畢之後最後升級controller-manager。設定所有controller的升級策略為滾動升級並設定最大不可用例項數。對於上述Issue官方在新版本中已經進行修復並backport到舊版本中。
  2. 對於一些剛從alpha轉到beta的特性,beta版本是預設開啟的版本,對於這些特性一定要非常小心,可能仍然會存在一些bug,建議還是手動關閉,對於一些可用性要求較高的叢集建議只使用GA的feature。這次踩的一個坑就是node lease特性,該特性在1.14中剛好是Beta版本預設開啟,這是一個非常有用的feature,對於大規模叢集可以有效降低apiserver,etcd的負載,但是kubelet中node lease的預設renew週期是hardcode的10s中且不可調整,也就是說此時node與master心跳的頻率只能是10s,此時指定--node-status-update-frequencyflag沒有任何作用,如果controller-manager中配置的--node-monitor-grace-period剛好是10s,這時候node會不斷地在ready和non-ready直接搖擺,參見kubernetes/kubernetes issue#80172,該issue已經被筆者給修復掉了 :)。

寫在後面

kubernetes還是在高速的迭代過程中,升級過程中出現不相容是在所難免的, 唯有搞懂內部的實現機制才能保障叢集的長治久安。發現問題也需要我們有能力去解決並且反饋到社群中,取之於社群,理應回報於社群。

reference

Kubernetes Release Version