螞蟻大規模 Kubernetes 叢集無損升級實踐指南【探索篇】

SOFAStack發表於2022-02-11

文|王連平(花名:燁川 )

螞蟻集團高階開發工程師

負責螞蟻 Kubernetes 叢集容器交付,專注於叢集交付能力、交付效能及交付 Trace 等相關領域

本文 12623 字 閱讀 20 分鐘

—— 庖丁解牛,讓升級不再煩惱

PART. 1 背 景

螞蟻 Sigma 作為螞蟻集團核心的基礎設施,經過多年的發展其規模已經處於業界領先位置,大規模叢集對 Kubernetes 的穩定性及功能性提出更高的要求。螞蟻 Sigma 力爭在萬級規模的雲原生環境下,挑戰高效穩定、無損無感的雲原生作業系統升級,給使用者帶來極致穩定的、功能新穎的雲原生服務。

為什麼要持續迭代升級 ?

Kubernetes 社群的活躍度非常高,眾多的雲原生愛好者為社群貢獻智慧,推動社群版本不斷更新。升級是為了緊跟社群的步伐,及時享用社群沉澱下來的優秀特性,進而給公司帶來更大利益。

為什麼升級那麼難 ?

按照螞蟻 Sigma 的規模,升級對我們來講是一件非常不容易的事情,主要體現在:

  • 在升級準備階段,要全量推動客戶端進行升級,業務方要安排專門的人投入進來,耗時耗力;
  • 在升級過程中,為了規避版本滾動時對 Kubernetes 資源操作可能帶的來不可預期後果,升級過程中一般會關停流量,業務體感不好;
  • 對於升級時間視窗選擇,為了給使用者更好的服務體驗,升級要放到業務量少的時間進行,這對平臺運維人員不太友好。

因此,升級過程中如何提升使用者、研發、SRE 的幸福感是我們想要達成的目標。我們期望實現無損升級來降低升級風險,解耦使用者來提升幸福感,高效迭代來提供更強大的平臺能力,最終實現無人值守。

本文將結合螞蟻 Sigma 系統升級實踐,從 Kubernetes 系統升級的目標、挑戰開始,逐步剖析相關的 Kubernetes 知識,針對這些挑戰給出螞蟻 Sigma 的一些原則和思考。

【兩種不同的升級思路】

在介紹挑戰和收益前,我們先了解下當前叢集升級的方式。Kubernetes 升級與普通軟體升級類似,主要有以下兩種常見的升級方式:替換升級和原地升級。

  • 替換升級:將應用執行的環境切換到新版本,將舊版本服務下線,即完成替換。在 Kubernetes 升級中,即升級前建立新版本的 Kubernetes 叢集,將應用遷移到新的 Kubernetes 叢集中,然後將舊版本叢集下線。當然,這種替換升級可以從不同粒度替換,從叢集為度則是切換叢集;從節點維度,則管控節點元件單獨升級後,kubelet 節點升級時遷移節點上的 Pod 到新版本節點,下線舊版本節點。
  • 原地升級:將升級的軟體包原地替換,舊服務程式停止,用新的軟體包重新執行服務。在 Kubernetes 升級中,apiserver 和 kubelet 採用原地軟體包更新,然後重啟服務,這種方式與替換升級最大的區別在於節點上的 workload 不用遷移,應用不用中斷,保持業務的連續性。

上述兩種方式各有優缺點,螞蟻 Sigma 採用的是原地升級。

【方法論-庖丁解牛】

採用原地升級時也必然會遇到原地升級的問題,其中最主要問題就是相容性問題,主要包含兩個方面:Kubernetes API 和元件內部的控制邏輯相容性。

Kubernetes API 層面包含 API 介面、resource 結構和 feature 三方面變化,而元件內部控制邏輯變化主要是 resource 在 Kubernetes 內部流轉行為的變化。

前者是影響使用者及叢集穩定性最重要的因素,也是我們重點解決的問題。

圖片

API 介面的變化固然要涉及到客戶端的升級,特別是對於 deprecated 和 removed 的 API,客戶端無法再使用舊版本的 API 介面。resource 介面的變化主要指 resource 欄位變化,欄位的調整意味著 API 能力的變化,同一 resource 在新舊版本中存在欄位上的差異會導致 API 能力上差異,主要體現在新增某個欄位、廢棄某個欄位和欄位預設值變化。feature 方面,主要是一些 feature 的 GA 導致 featrue 開關能力被移除,以及一些新的 feature 的加入。

面對上述的核心問題,我們將升級中遇到的相容性問題按照升級階段分為“升級前”、“升級中”和“升級後”三個階段。

  • 升級前,將面臨大量客戶端升級推動問題,通過探索版本之間的差異和多版本客戶端並存的問題,我們來制定一些規則,這將大大減少客戶端升級的數量,提升升級的效率。
  • 升級中,將面臨多版本 apiserver 並存的問題,以及資料的儲存版本轉換問題,當然還會有可回滾性的問題,這些問題我們將採用精細化流量控制能力避免篡改,壓制 resource 儲存版本和 GVK 版本保證可回滾,同時對於 etcd 中的資料進行版本遷移,如此實現無損升級和回滾。
  • 升級後,對於少量的可能引發不可接受故障的客戶端,我們通過識別資源修改請求意圖,降低篡改的風險。

圖片

還有一個重要的環節,整個過程我們要做到自動化、視覺化,在升級過程中流量的充分灰度是很有必要的,升級節奏的自動化推進和應急場景下的人工可控性也是非常重要的,這些將在另一篇文章中詳細介紹。

整體來看,我們通過客戶端最小化升級和滾動自動化升級能力、提升升級的效率,通過精細化流量控制、灰度可回滾能力以及長效的欄位管控能力,提升整個升級過程的可靠性、穩定性。

PART. 2 升級前

叢集升級必然會有 API 的更新和迭代,主要體現在 API 新增、演進和移除,在 Kubernetes 中 API 的演進一般是 Alpha、beta、GA,一個 resouce 的 API version 會按照上述版本進行迭代,當一個 API 新增時,最開始是 Alpha 階段,例如"cert-manager.io/v1alpha3",經過若干次迭代,新特性進入 beta 版本,最後進入穩定的 GA 版本,這個過程可能跨若干個大的社群版本,一些版本會在 GA 版本穩定執行一定時間後被 deprached 掉,並且被 deprached 的 API 版本在一段時間後會被直接移除,這就對我們的客戶端有了升級的剛性需求。

在介紹客戶端升級前,先介紹下一般 resource API 變化有哪些方面。

Schema 變化

不同版本的 Kubernetes 資源的 Schema 欄位可能存在差異,主要表現在以下兩個方面:

  • 欄位的增加/刪除/修改
  • 欄位的預設值調整

欄位增刪改

Kubernetes 的 resource 如果對某個欄位的調整,包括:增加、刪除、修改三種。對於“增加”操作,可以在新 GV(GroupVersion)出現也可以在舊 GV 中出現。對於“刪除”和“修改”,一般只會在新的 GV 中出現。

基於以上條件,對於新版本 APISever 引入的 resource 欄位調整,可以得出以下結論:

圖片

欄位預設值變化

欄位預設值變化是指,在新舊 apiserver 中 resource 某個欄位預設值填充不一致。字斷預設值變化可能帶來的兩個問題:

  • container hash 變化,會導致容器重啟
  • 影響控制元件的控制動作

欄位變化帶來的影響主要在客戶端新舊版本交叉訪問和 apiserver 多版本並存交叉訪問上,具體影響在下文中介紹。

客戶端升級

客戶端升級是為了相容新版本 API,保證在升級後不出現問題,同時實現 operator 精確化、差異化升級,提升升級效率。低版本客戶端不升級會遇到的如下問題:

核心問題

按照新舊版本 GVK(GroupVersionKind)的變化梳理一下升級過程中低版本客戶端可能出現的各種情況:

圖片

如上圖所示,核心問題主要有以下三種:

1.低版本客戶端訪問已經被 depreached/removed 的 GroupVersionKind

2.低版本客戶端操作的 resource 存在欄位增加問題

3.低版本操作的 resource 存在欄位預設值變化問題

針對第一個問題,訪問已經被 depreached 特別是被 removed 的 GVK 時,伺服器端直接返回類 404 錯誤,表示此 rest url 或者次 GVK 已經不存在。

針對第二個和第三個問題,都出現在低版本客戶端的 Update 操作,為什麼對於 patch 操作不會出現問題呢?因為 Update 操作是全量更新,patch 操作是區域性更新,全量更新的情況下,如果客戶端版本低沒有新增欄位或者沒預設值變化的欄位,此時去 Update 此 resource,提交的請求資料中不會出現此欄位,但是此欄位在 apiserver 內部會被補全和填充,如此此欄位的值就完全依賴 apiserver 內部的邏輯了。

針對欄位增加的情況,我們做了一個實驗,如下:

1.18 版本中 Ingress 中多了一個 patchType 的欄位,我們首先通過 1.18 的客戶端進行建立,並設定 pathType=Prefix,然後通過 1.16 版本的客戶端進行 Update,發現此職被修改為 pathType 的預設值。如下:

圖片

思考與解決

針對第一個問題,處理方式比較明確,客戶端必須升級,因為 GVK 已經從新版本的 apiserver 中移除了。按照 Kubernetes 社群的 API 廢棄規則(給定類別的 API 版本在新的、穩定性未降低的 API 版本釋出之前不可被廢棄;除了每類 API 版本中的最新版本,舊的 API 版本在其被宣佈被廢棄之後至少一定時長內仍需被支援),我們可以顯式地控制一些 API 向下相容,來滿足一些低版本客戶端的需求,但是此方式不是無限的,在某個高版本中總是會被移除掉的,所以不推薦這麼做來延遲或容忍客戶端的升級。

針對第二個和第三個問題,都涉及到 Update 操作,因為 Update 操作會出現誤操作的情況。如果使用者客戶端版本較低,但是使用者並不關心 resource 的新增欄位,也不想使用這些新功能,他可以完全不理會此欄位,對於 create/delete/patch 都沒有問題,同時 patch 操作也不會針對此欄位進行。所以控制好 Update 操作的客戶端就可以避免新增欄位的篡改行為。

PART. 3 升級中

Kubernetes 叢集的升級主要包含客戶端升級、核心元件升級,核心元件包括 apiserver、controller-manger、scheduler 和 kubelet。

這裡的客戶端是廣義上的客戶端,即業務的 operator 及管控的 operator 都稱為客戶端。客戶端的升級由客戶端自行做流量灰度,大版本升級過程中最核心的是 apiserver 升級過程中可能會出現髒資料問題。

這裡提到的髒資料問題主要體現在以下兩個方面:

  • 多版本 apiserver 交叉操作同一資源
    是高低版本 apiserver 中對有 Schema 變化資源的操作會出現篡改問題,其問題本質與多版本客戶端操作同一有 Schema 變化的資源時發生的篡改一致。只不過這裡是 apiserver 版本不一樣,客戶端版本是否一致都會引起篡改問題。
  • 儲存在 etcd 中的資料如何保證正確更新
    是大家一般不太注意的問題,因為 apiserver 升級過程中會幫我們很好的處理,但也不是百分百完美處理,這裡單獨拿出來講一下也有助於大家對 Kubernetes 資料儲存有更深入的瞭解。

髒資料的問題在升級過程中很容易聯想到可回滾性,我們無法保證升級百分百成功,但是我們一定要有可回滾能力,按照社群的建議 Kubernetes 在升級過程中不建議回滾,它會帶來更多的相容性問題。

上述這些問題將在下文詳細講述。

多版本 apiserver 並存】

從升級的過程中可以看到,在流量管控時主要管控的流量有兩項:

  • Updateu/patch 動作的流量
  • 剩餘其他所有流量

這裡重點提到的是 Update 流量,作為管控的主要原因也同低版本客戶端篡改欄位原因一樣。

客戶端的問題是,當 apiserver 都是高版本時,客戶端存在高低版本同時操作同一 resource 時會出現篡改,所以我們會推動具有 Update 動作的客戶端進行升級。

升級 apiserver 時另外一個問題出現了,從流程上看,升級過程中流量會同時打到 1.16 和 1.18 版本的客戶端上,這樣即使客戶端高版本,通過不同版本的 apiserver 寫操作同一 resource 同樣會出現篡改現象。

多版本 apiserver 交叉訪問

此問題,我們同樣按照前文提到的 resource 變化的型別來講述。

  • 欄位變化

欄位變化包含增加、刪除和修改三種,對於刪除和修改會在新的 GVK 中出現,所以我們只考慮增加的情形。如下圖所示,Kubernetes 的 Pod 在 1.18 版本比 1.16 版本中多了一個欄位"NewFiled",升級過程中如果交叉訪問同一 PodA 則會出現 PodA 儲存的資料不斷不斷變化,首先通過 1.18 版本 apiserver 建立 PodA,然後通過 1.16 的 apiserver Update 後 PodA 的新增欄位會被刪除,再通過 1.18 版本的 apiserver Update 欄位又被填充回來。

圖片

針對此此問題,有以下結論:

(1)對於欄位增加情況,當通過舊版本 apiserver 更新帶有新欄位的資源時存在欄位預設值被刪除的風險;對於欄位刪除和修改兩種情況,無此風險;

(2)如果新增欄位被用於計算 container hash,但由於 apiserver 升級時 kubelet 還處於 1.16 版本,所以依舊按照 1.16 版本計算 hash,apiserver 交叉變化不會導致容器重建。

  • 欄位預設值變化

欄位預設值變化是指,在新舊 apiserver 中,對某個資源欄位預設值填充不一致。如下圖所示,1.16 版本的 Kubernetes 中 Pod 欄位"FiledKey"預設值為"default_value_A",到 1.18 版本時該欄位預設值變為"default_value_B",通過 1.18 apiserver 建立 PodA 後,再通過 1.16 版本 apiserver 更新會出現預設值被篡改的問題。這種情況的發生條件相對苛刻,一般 Update 之前會拉下叢集中當前的 Pod 配置,更改關心的欄位後重新 Update 回去,這種方式會保持預設值變化的欄位值,但是如果使用者不拉取叢集 Pod 配置,直接 Update 就會出現問題。

圖片

針對此此問題,有以下結論:

  • 某個欄位在使用預設填充功能時,其值將依賴 apiserver 中 defaulting 值進行變化。
  • 如果新增欄位被用於計算 container hash,將引發容器重建風險。

思考與解決

前面介紹了多版本 apiserver 交叉訪問的問題,接下來我們如何解此問題。

解決這個問題的本質是管控 Update/patch 兩種操作的流量,說到這裡可能有人會有疑問,通過多版本 apiserver 獲取 resource 豈不是也有欄位不同的問題?如果有,那麼 get/watch 流量也需要管控。

這裡有個前置事實需要講一下,升級 apiserver 之前會存在多個版本的客戶端,這些客戶端有些能看到欄位變化有些看不到,但是升級前是他們是一個穩定狀態。高版本客戶端看不到新增欄位時也可以穩定執行,對新增欄位帶來的新特性並沒有很強的依賴,對於低版本客戶端壓根兒看不到新增欄位更不關心新特性。我們核心目標是保證升級過程中沒有欄位篡改的問題來規避升級過程中同一 resource 檢視頻繁切換,帶來的不可控的管控行為。

螞蟻 Sigma 已經落地管控層面的 Service Mesh 能力,那麼在升級過程中利用強大的 mesh 能力來做精細化流量管控,規避了交叉訪問的問題我們升級過程中的黑暗地帶也會變得越來越窄,心裡會踏實很多。

etcd 資料儲存更新

Kubernetes 中的資料儲存有一套自己完整的理論,這裡我們先簡單介紹下 Kubernetes 中一個資源從請求進入到存入 etcd 的幾次變換,之後再詳細介紹升級過程中可能遇到的問題及我們的思考。

Kubernetes資源版本轉換

  • apiserver 中的資源版本

Kubernetes 中的 resource 都會有一個 internal version,因為在整個迭代過程中一個 resource 可能會對應多個 version,比如 deployment 會有extensions/v1beta1,apps/v1。

為了避免出現問題,kube-apiserver 必須要知道如何在每一對版本之間進行轉換(例如,v1⇔v1alpha1,v1⇔v1beta1,v1beta1⇔v1alpha1),因此其使用了一個特殊的 internal version,internal version 作為一個通用的 version 會包含所有 version 的欄位,它具有所有 version 的功能。Decoder 會首先把 creater object 轉換到 internal version,然後將其轉換為 storage version,storage version 是在 etcd 中儲存時的另一個 version。

  • apiserver request 處理

一次 request 請求在 apiserver 中的流轉:

、、、Go
http filter chain | => | http handler

auth ⇒ sentinel ⇒ apf => conversion ⇒ admit ⇒ storage
、、、

一個資源讀取/儲存的過程如下:

圖片

資料儲存相容性

本文將側重講解 Kubernetes 中 API resource 儲存的資料在升級過程中如何保證相容性的。主要回答以下兩個問題:

問題一:Kubernetes 中儲存在 etcd 中的 API resource 長什麼樣?

Kubernetes 中的 resource 都會有一個 internal version,這個 internal version 只用作在 apiserver 記憶體中資料處理和流轉,並且這個 Internal version 對應的資料是當前 apiserver 支援的多個 GV 的全集,例如 1.16 中支援 apps/v1beta1 和 apps/v1 版本的 deployments。

但是儲存到 etcd 時,apiserver 要先將這個 internal 版本轉換為 storage 版本 storage 版本怎麼確定的呢?

如下,分為兩種情況:

  • core resource
    儲存版本在 apiserver 初始化時確定,針對某個 GroupVersion 分兩步確定其儲存版本:

(1)確定與本 GV 使用同一儲存版本 group resource ---> StorageFactory 中靜態定義的 overrides

(2)在 group 中選擇優先順序最高的 version 作為儲存版本---> Schema 註冊時按照靜態定義順序獲取

問題二:不同版本的 Kubernetes 中 如何做到儲存資料相容?

storage version 在 Kubernetes 版本迭代更新中不是一成不變的,也會不斷的更新。首先我們看一個 Kubernetes 中的儲存版本的升級規則:給定 API 組的 “storage version(儲存版本)”在既支援老版本也支援新版本的 Kubernetes 釋出 版本出來以前不可以提升其版本號。

這條規則換句話說,當某個 Kubernetes 版本中某個 resource 的 storage version 有變化時,此版本的 Kubernetes 一定是同時支援新舊兩個 storage version 的。如此加上 Schema 中多個 version 之間的轉換能力,就能輕鬆做到版本的升級和降級了。

對於升級或者降級,apiserver 可以動態的識別當前 etcd 中儲存的是什麼版本的資料,並將其轉換為 Internal 版本,然後寫入到 etcd 時再轉換為當前升級後的最新的 Storage Version 進行存入。

圖片

思考與解決

從上文可以看到,apiserver 具備動態轉換儲存版本的的能力,但是要對舊版本的資料進行一次讀寫操作。動態轉換的能力也不是無限的,在某個 Kubernetes 版本中轉換的版本是當前版本相容支援的幾個版本。

假設某個 resource 資料在 etcd 中一直沒有讀取,此時 Kubernetes的版本已經升了好幾個版本,並且 apiserver 已經不再相容 etcd 中的版本,當讀取到此資料時就會報錯,首先這條資料是無法再訪問到了,嚴重的可能導致 apiserver crash。

因此我們需要在版本升級過程中保證把 etcd 中的資料都轉換為最新版本。很自然的,大家會想到通過自己實現一個轉換器來解決這個問題,思路沒有問題,但這會給人一種重複造輪子的感覺,因為 apiserver 在各個版本中已經有了轉換器的能力,我們只需要把原生的轉換能力利用好就行,每次升級後用 apiserver 原有儲存資料轉換能力把資料做一下更新,這樣可以輕鬆規避多次版本升級後資料殘留或不相容問題。不要小看這個動作,很容易被忽略,可以想象一下在原本就緊張的升級過程中突然出現 apiserver crash 的詭異現象,這個時候內心一定是崩潰的。

升級可回滾

提到升級大家自然地會想到回滾,Kubernetes 叢集的升級類似業務應用的迭代釋出,如果過程出現問題怎麼辦,最快速的止血方式就回退版本。

一個單體的應用服務很容易做到版本回退,但對於資料中心作業系統的回滾沒有那麼容易,其問題涉及到很多方面,這裡我們認為以下幾個問題是 Kubernetes 升級回滾中遇到的常見的棘手問題:

  • API 不相容,導致回退後元件呼叫失敗
  • etcd 中資料儲存不相容問題

API 不相容

API 相容性問題前面已經詳細講述了 API 變化的幾種型別,這裡再提下主要為 API 介面的變化和 Schema 的欄位變化。

對於 API 介面的變化問題並不大,原因是升級前所有控制器與客戶端與低版本的 apiserver 已經達到一個穩定的狀態,也就是說 API 介面是可用的,所以回退後問題不大。但是通常在升級到高的 apiserver 版本後會有一些新的 GVK 出現,如一些 Alpha 的能力出現或者 Beta 版本的 GV 變成了 GA 版本。

一個真實的例子:1.16 到 1.18 升級過程中新增了 v1beta1.discovery.k8s.io 這個 GV,這種情況下低版本的 apiserver 是不識別新版的 GV 的,apiserver 回滾後雖然能正常啟動,但是在執行涉及到這個 GV 的操作時就會出問題,比如刪除一個 namespace 時會刪除這個 ns 下所有的資源,將遍歷所有的 GV,此時會出現卡殼 ns 刪不掉的現象。

另外一個是 Schema 的變化,對於回滾其實可以看成另外一種升級,從高版本“升級”到低版本,這個過程遇到的問題與低版本“升級”到高版本是一致的,即高低版本客戶端訪問篡改問題和多版本 apiserver 並存交叉訪問問題,但是客戶端問題在回滾過程中並不存在,因為高版本的客戶端向下是相容的。對於交叉訪問的問題,交叉訪問的問題同樣會利用精細化流量控制做規避。

etcd 資料儲存不相容

資料儲存問題在升級過程中遇到,在回滾過程中同樣遇到,其核心在當一個 resource 的儲存版本在高版本的 apiserver 中發生了變化,並且新的儲存 GV 在低版本的 apiserver 中不識別,導致回退後通過舊版本的 apiserver 獲取對應資源時發生錯誤,這個錯誤就是發生在 Storagte Version 到 Internel Version 轉換過程中。

一個例子:在 1.16 中 csinodes 的儲存版本為 v1beta1,到 1.18 中升級成了 v1 版本,如果從 1.18 直接回退到 1.16,csinode 這個資源獲取會出錯,原因是 1.16 的 apiserver 中壓根兒沒有這個 v1 版本的 csinode。

圖片

講到這裡可能會有人問,為什麼要跨版本升級呢?

上述這個問題如果是從 1.16 到 1.17 再到 1.18 逐個版本升級就不會出現了,這個想法非常好,但對於螞蟻 Sigma Kubernetes 這種體量來講頻繁的升級難度較大,這也是我們做此事的原生動力,將升級變得更自動化、效率更高,當這個目標實現後此問題也就不復存在了,在當前階段回退儲存版本不相容問題仍然棘手。

思考與解決

升級本身就是一次引入眾多變數的操作,我們儘量做到在變化中找到一條能把控的路子,最基本的方法論就是控制變數,所以對於 API 相容性問題,我們核心的原則為:新特性沒有必要開啟的先進性壓制,保證可回滾。

壓制的主要目標有兩個:

  • 高版本 apiserver 中新增的 GVK
    保證它們在升級的這個版本中不會出現
  • etcd 中的資料的儲存版本
    儲存版本對使用者是透明的,我們也要保證壓制調整對使用者也是無感的,調整和壓制的手段可以通過對 apiserver 程式碼做相容性調整來實現。

對於其他相容性問題,目前沒有很好的方案解決,當前我們的主要通過升級回滾 e2e 測試暴露問題,針對不相容的問題做相應相容性修改。

相容性壓制的手段只存在於升級過程中,也是升級過程中臨時現象。壓制調整的時候我們需要充分的考量是否會引入其他不可控問題,這個要具體看 GVK 自身的變化來定。當然,一切還要從理論回到實踐,充分的 e2e 測試也是需要的。有了理論和測試兩把利刃的加持,我相信相容性問題會迎刃而解。

以上是升級過程中遇到的三個棘手的問題,以及相關的解決思路,接下來介紹下升級後的保障工作。

PART. 4 升級後

大版本升級時無法保證 100% 的客戶端都升級到對應的最新版本。雖然升級前我們會推動 Update 流量的客戶端進行升級,但是可能做不到 100% 升級,更重要的,升級後可能也會出現某個使用者用低版本的客戶端進行訪問。我們期望通過 webhook 能夠避免升級後低版本客戶端意外篡改 resource 欄位,達到真正的升級無損的目的。

欄位管控主要原則一句話總結:防止預設值變化的欄位,被使用者使用低版本客戶端以 Update 的方式修改為新的 default 值。

欄位管控

殘留問題

欄位管控的最大挑戰是,如何準確的識別出使用者是否是無意篡改欄位。判斷使用者是否無意篡改需要拿到兩個關鍵資訊:

  • 使用者原始請求內容
    使用者原始請求內容是判斷使用者是否無意篡改的關鍵,如果原始請求中有某個欄位的內容,說明使用者是明確要修改,此時不需要管控。
  • 使用者客戶端版本資訊
    否則,要看使用者客戶端版本是否低於當前叢集版本,如果不低於叢集版本說明使用者有此欄位明確修改不需要管控,如果低於叢集版本這個欄位使用者可能看不到就需要管控了。

那麼問題來了,如何拿到這兩個資訊呢?先說使用者原始請求內容,這個資訊按照 Kubernetes 的能力,我們無法通過 webhook 或者其他外掛機制很輕鬆的拿到請求內容,apiserver 呼叫 webhook 時的內容已經是經過版本轉換後的內容。

再說使用者客戶端版本資訊,這個資訊雖然可以從 apiserver 的監控中拿到,當然我們為了與管控鏈路對接,並不是直接拉取的監控資訊,而是在 apiserver 中做了資訊補充。

思考與解決

解決此問題本質上是理解“使用者原始意圖”,能夠識別出哪些動作是無意的篡改哪些是真正的需求,此動作需要依賴以下兩個資訊:

  • 使用者原始請求資訊
  • 使用者客戶端版本資訊

上述兩個資訊存在的獲取和準確性問題是我們後續的工作方向之一,當前我們還沒很好的辦法。理解使用者的意圖並且是實時理解是非常困難的,折中辦法是定義一套規則,按照規則來識別使用者是否在使用低版本的客戶端篡改一些核心欄位,我們寧可誤殺一千也不放過一個壞操作,因為造成業務容器的不符合預期,意外行為很輕鬆就會帶來一個 P 級故障。

圖片

PART. 5 提升效果

以上的這些探索在螞蟻 Sigma 已經實戰,升級效率得到了很大的提升,主要體現在以下幾個方面:

  • 升級過程不用再停機了,釋出時間縮短為 0,整個過程避免了大量 Pod 延遲交付,平臺使用者甚至沒有任何體感,即使升級過程中排查問題也可以從容地應對,讓升級過程更安靜、更放鬆、更順滑;
  • 升級前期推動的客戶端升級數量得到了大幅降低,數量減少了 80%,整體升級推動時間減少了約 90%,減少了 80% 的業務方人力投入,整個升級工作輕鬆了許多;
  • 升級的過程實現了自動化推進,為了防止意外發生升級過程還可以隨時實現人工介入,升級過程解放了 Sigma 研發和 SRE 的雙手,可以端起咖啡看進度了;
  • 升級過程實現流量精準化控制,針對叢集上千個名稱空間的流量按照規則實現了灰度測試,針對新版本例項進行幾十次 BVT 測試,從兩眼一抹黑到心中有底氣的轉變還是挺棒的。

圖片

PART. 6 未來之路

整體來講,做好升級核心就是要做好相容性這件事,同時也要把整個過程做的更自動化一些,觀測性做的更好一些,接下來有幾個方向的工作要繼續進行:

1.更精準

當前在管控的資訊獲取上還有缺失,在流量的管控上當前採用 namespace 的維度來處理,這些都存在精度不夠的問題。前面也提到過,我們正在進行管控元件 Mesh 化能力建設,未來會有更靈活的細粒度流量管控和資料處理能力。同時,藉助 Mesh 能力,實現管控元件升級過程中多版本流量灰度測試,對升級做到精確、可控。

2.平臺化

本文介紹的這些挑戰和技術方案,其實都是升級過程中的一部分,整個過程包含了前期的客戶端最小化升級、核心元件滾動升級和後續的管控,這個過程繁瑣且易出錯,我們期望把這些過程規範化、平臺化,把類似差異化比對、流量管控、流量監控等的工具整合到平臺中,讓升級更方便。

3.更高效

社群迭代速度非常快,當前的迭代速度是無法跟上社群,我們通過上述更智慧、平臺化的能力,提升基礎設施的升級速度。當然,升級的速度也與叢集的架構有非常大的關係,後續螞蟻會走向聯邦叢集的架構,在聯邦架構下可以對特定的使用者 API 做向前相容和轉換,由此可以極大地解耦客戶端與 apiserver 的升級關係。

對於螞蟻 Sigma 規模級別的 Kubernetes 叢集來講升級不是一件容易的事,Sigma 作為螞蟻最核心的執行底座,我們想做到通過技術手段讓基礎設施的迭代升級達到真正的無感、無損,讓使用者不再等待,讓自己不再焦慮。面對 Kubernetes 這個龐然大物要實現上述目標頗有挑戰性,但這並不能阻止我們探索的步伐。道長且阻,行則將至,作為全球 Kubernetes 規模化建設頭部的螞蟻集團將繼續向社群輸出更穩定、更易用的技術,助力雲原生成為技術驅動發展的核心動力。

圖片

螞蟻 Sigma 團隊致力於規模化雲原生排程平臺的建設,為業務提供更快更好更穩的容器資源交付,近期我們在叢集穩定性、高效能方面也取得了顯著的成果,歡迎大家相互交流。

「參考資料」

《Kubernetes API 策略》

《Kubernetes 1.16 版本介紹》

《Kubernetes 叢集正確升級姿勢》

求賢若渴:

螞蟻集團 Kubernetes 叢集排程系統支撐了螞蟻集團線上、實時業務的百萬級容器資源排程, 向上層各類金融業務提供標準的容器服務及動態資源排程能力, 肩負螞蟻集團資源成本優化的責任。我們有業界規模最大 Kubernetes 叢集,最深入的雲原生實踐,最優秀的排程技術。

歡迎有意在 Kubernetes/雲原生/容器/核心隔離混部/排程/叢集管理深耕的同學加入,北京、上海、杭州期待大家的加入。

聯絡郵箱: xiaoyun.maoxy@antgroup.com

本週推薦閱讀

攀登規模化的高峰 - 螞蟻集團大規模 Sigma 叢集 ApiServer 優化實踐

螞蟻集團萬級規模 k8s 叢集 etcd 高可用建設之路

螞蟻大規模 Sigma 叢集 Etcd 拆分實踐

Service Mesh 在中國工商銀行的探索與實踐

img

相關文章