導讀|基於 K8s 的雲原生容器化已經在騰訊內部海量業務中大範圍落地實踐。業務從傳統的虛擬機器部署形態無縫切換到容器部署形態,執行在 K8s 上的應用從無狀態服務擴充套件到有狀態服務,這個過程經歷了哪些改造?同時,K8s 如何經受住業務形態複雜多樣、模組數量龐大的考驗?遇到哪些新的挑戰?如何最佳化?效果怎麼樣?騰訊雲高階工程師林沐將為你解答。
線上業務資源容器化部署的問題與最佳化方案騰訊平臺的業務基本都屬於線上業務。這些業務以前在虛擬機器部署時,是透過物理機操辦的方式生產出很多虛擬機器,對於業務來說是不感知的。當業務發現虛擬機器負載較低時,可將多個線上業務混部來提高資源利用率。這種資源管理方式到容器化部署時發生了一些變化,主要有四方面的內容。容器交付。每個 Pod 在交付的同時需要宣告規格大小,規格大小要改變時 Pod 必須銷燬重建,無法透過混部來新增業務。節點均衡。K8s 每個節點上部署多個 Pod,每個節點上的 Pod 型別、數量也都不相同,要保證節點均衡是一個挑戰。K8s 的雲原生特性,也就是彈性,是否能夠符合線上業務在生產環境中的需求?叢集池化。K8s 是按叢集維度管理,而平臺有上萬個業務,這麼多業務如何對映到不同的叢集實現條帶化管理?第一,資源利用率提升——動態壓縮和超賣。我們面臨一個痛點是使用者配置的容器規模不合理,普遍偏大,這樣節點裝箱率和負載比較低。所以第一個最佳化方式就是 Pod 資源動態壓縮,Pod 請求雙核處理器 4G 記憶體,在排程時壓縮成單核 4G 記憶體。因為 CPU 屬於可壓縮資源,記憶體屬於不可壓縮資源。這裡修改的只是 Request 大小,並不修改 Limit,所以不影響容器實際能使用的上限值。這樣能提高節點的裝箱率。接下來是 Node 資源動態超賣,根據負載情況超賣更多 CPU 核心。第二,節點負載均衡——動態排程和重排程。資源壓縮超賣能提高節點的裝箱率和負載使用率,但 Pod 是共享Node 的,壓縮和超賣會加劇它們之間的干擾。於是我們開發了動態排程器,當每一個 Pod 排程時,能夠感知存量 Node 當前的實時負載情況,從而對增量 Pod 在 Node 當中均衡處理,掉到一個低負載的節點上。存量 Pod 在節點上也有可能發生高負載,這時我們在節點上部署 Pod-Problem-Detecor、NodeProblem-Detecor,檢測出哪個 Pod 會導致節點高負載,哪些 Pod 屬於敏感 Pod,透過事件上報告訴API Server,讓排程器將異常 Pod、敏感 Pod 重新排程到空閒節點。第三,K8s 業務彈性伸縮——協同彈性。線上業務最關心業務穩定性。有時業務負載超出預期時,因為最大負載數配置過低,導致業務雪崩。所以我們對 HPA 進行最佳化,加入 HPAPlus-Controller,除了支援彈性最大副本策略之外,還能夠支援業務自定義配置進行伸縮。第二個是 VPAPlus-Controller,可以 Pod 突發高負載進行快速擴容,對有狀態的服務也可以進行無感知擴縮容。第四,叢集資源管理——動態配額和資源騰挪。從平臺的角度,K8s 叢集也是一個重要的維護物件。平臺透過動態 Operator 的方式控制業務對叢集的可見性以及配額大小,使得各個叢集的業務是分佈均勻的。叢集本身也有規模大小,有節點伸縮,叫做 HNA。HNA 能夠根據叢集負載情況自動補充資源或釋放資源。生產環境中一種情況是,有時候突發活動,在公共資源池裡沒有特定資源,需要從其他系統裡騰挪資源。所以我們開發了彈性資源計劃 Operator,它會給每個節點、每個叢集下發任務,要求每個叢集釋放一些Node 出來。這批節點的數量要儘可能符合業務的數量要求,同時要對存量業務的負載質量不產生影響。我們的方式是透過動態規劃的方式解決問題,從而在業務做活動,或者緊急情況下,能夠使叢集之間的資源也能夠流轉。容器化對動態路由同步的挑戰與解決方案每一個 Pod 在銷燬重建的時候會動態新增或提取路由。一般來說,生產環節中的路由是第三方系統負責,當 Pod 正常的時候系統給它轉發流量,或者做名詞解析,當它摘除時就從名詞服務裡剔除。但我們的平臺在生產環節中會遇到一些特殊情況。第一個情況就是容器化之後容器的變更更加頻繁。第二個變化在於業務規模非常龐大,單個負載的 Pod 可能成千上萬。第三是業務層面的變化,雲原生的方式是一個叢集對一個路由入口,但在生產環節又是第三方路由系統,允許雲上雲下混合部署,跨叢集多路由服務共享路由。動態路由是容器化的關鍵路徑,是要解決的核心問題。在微觀層面,業務對容器執行階段有特殊需求,包括容器分級、路由和程式的執行狀態一致、大批次探針失敗時要實現路由熔斷。生產環節中路由系統是非常多的,每個路由系統會對應一種控制元件。所以我們需要路由同步 Controller的統一框架。這個框架理論上是一個旁路 Controller,因此存在不可靠的問題。例如在 Pod 下線前,銷燬的時候不保證已經剔除路由;又比如在滾動更新時,可能上一批還沒有新增路由,下一批就開始銷燬重建。由於有些業務又比較敏感,必須要求絕對保證線下和滾動的時候路由的正確性,於是我們利用了 K8s 雲原生的刪除保護、滾動更新機制來實現這一需求。當業務在銷燬之前先剔除路由,業務在滾動更新的時候先保證上一批新增。透過這種方式將路由融入到 Pod 生命週期裡,來實現業務的可靠性。對於執行階段,例如容器異常自動重啟,或者 Pod 其中一個容器透過原地生成的方式啟動,這些場景就會繞過前面提到的滾動更新和刪除保護。所以還要在執行階段保證業務之間的快速同步。業務大批次變更又會產生大量事件,導致 Controller 積壓問題。對此我們第一個最佳化方式是使用 Service 粒度事件合併,將事件數量成數量級減少來提高速度。第二個是雙佇列模型。K8s 的 Controller 裡有定時歷史對賬機制,會將所有的 Pod 物件全部入佇列。我們需要將實時和定時的事件分開,這樣既能夠解決定時對賬,又能解決實時處理需求。這裡面有一個細節問題,兩個不同佇列可能在同一個時刻會有同一個事件要處理,這就需要相互感知的能力避免這種情況發生。下一個 Controller 框架的核心點在於支援共享路由。但云原生的 K8s 機制裡是一個叢集對應一個路由入口,所以我們在 Controller 框架裡增加一個路由同步記錄,也是按照 Service 的粒度去記錄的。如果業務系統產生髒資料,例如觸發一個剔除操作,但是路由系統返回成功了,實際上沒有剔除,那麼下一次它去同步處理這個事件的時候發現它沒有被剔除,那麼還是會再重新剔除一遍。也就是說路由操作等於期望值去 Diff當前值,而它的期望值就等於 Endpoint 和 Pod 生命週期的交集,當前值就是路由系統裡面的情況加上路由記錄,二者再取差積就是要做的路由操作。旁路 Controller 作為一個元件會有異常的時候,雖然 K8s 提供 Leader 機制,但這個機制被 Controller拉起時需要預載入存量 service 資料,如果資料量非常大需要很久時間。我們的解決方式是,每一個Controller 執行的時候屬於主備模式,這樣當主容器掛掉的時候,備容器獲得鎖,之間的間隔就是整個Controller 同步中斷的最長時間,之後備容器就可以快速接管路由通路服務。中斷期間可能發生事件丟失問題,我們透過定時歷史對賬機制解決這個問題。我們還有特殊需求,是業務為了相容虛擬機器部署的一種管理方式,主要針對容器的執行階段以及特殊處理。這裡的需求包括容器分級、流量平衡、路由熔斷。這些需求對傳統的 Endpoint Controller 而言是不感知的,原來只維護 Ready 和 Not Ready 的狀態,沒有感知更細分的狀態去維護容器的角色和狀態。如果這是由路由 Controller 來實現,那麼對這些特殊場景來說是牽一髮而動全身的,每一個元件都得同時開發,修改一遍,維護成本是很高的。所以我們提供了一種解決方案——Endpoint-Plus Controller。它將維護容器角色和狀態的能力下沉到 Endpoint 來實現。它與 Endpoint 和路由同步 Controller 之間建立一種互動協議,就是 Endpoint Ready 時新增路由,Not Ready 時禁用路由,不在 Endpoint 裡刪除路由。這樣所有元件都是統一的,而且每次業務的新需求只要修改 Endpoint 就對全部生效,這樣實現了動態路由同步的橋樑作用。一種全新的容器銷燬失敗自愈機制探索最後一個話題是關於容器銷燬失敗自愈的。前面提到了動態排程、彈性伸縮、容災遷移、流水線釋出,這些操作都有一個前提,就是容器銷燬重建時老的容器要銷燬,新的容器能建立出來。但實際上在生產環境中這並不是 100% 能保證的。因為容器是共享的,多個容器在同一個節點上,卡住的時候會涉及到很多原因:呼叫鏈很長,只要其中任何軟體出現 BUG 都會卡住;管理容器對業務容器有侵入,造成卡頓;業務容器之間互相干擾;共享核心、Cgroup、Namespace,並不保證所有資源絕對完全隔離;共享節點資源,當 CPU、磁碟 IO 高負載時會影響整個節點上的所有 Pod。K8s 發展到現在已經有了一套很完善的自愈機制。對容器異常來說,雲原生 K8s 提供一個暴力解決方案就是強刪機制。該機制只是刪除這個資料物件的資料,並不是銷燬這個容器。這樣導致一個問題,如果進行強制銷燬,可能老容器會殘留,新容器又起來了,這時老的容器會影響節點。所以容器銷燬階段卡住會影響容器銷燬重建這個基本需求,而且它的原因是複雜多樣的,在大規模系統環境中更容易出現,而已有的自愈機制是沒有涵蓋這種場景的,所以我們就需要提供一種全新的自愈機制。傳統的解決方案是透過指令碼掃描到它,對於定位到的問題,沒有解決方案的需要臨時隔離,已有解決方案的就要明確修復。但這並不是一個閉環方案,因為還有很多未知問題,對未知問題來說業務關心的是儘可能恢復,而對平臺來說為了保證穩定性,需要儘可能知道這些根因,去收斂這類問題。所以我們兼顧這兩個需求,要縮小定位範圍、縮短定位週期,提高定位效率。對定位到根因的我們要去評估它的影響面,防止增量發生。而已經有解決方案的,我們需要有全網修復能力,出現異常的時候要告警,從而實現閉環解決方案。我們想到了是智慧運維的方法,它依靠大規模訓練樣本,關注相關性。而故障處理一般是小樣本量,強調專業和因果性,所以它並不很適合這種場景。但在智慧運維的決策樹模型裡有些概念可以拿來參考,譬如基尼係數、資訊熵、剪枝等。第一步需要建立模型,是關於資訊熵的權衡,要平衡自愈機制和定位效率。我們在選擇資訊熵的時候是自頂向下的推導路徑,從節點異常再到容器呼叫鏈異常,再到具體系統日誌。第二步是特徵選取,基尼係數越小特徵越明確,所以我們選擇共同特徵作為一個特徵值。同時選擇一些已知問題或者根因比較明確的作為葉子節點。最後一步是模型最佳化,例如剪枝最佳化,透過後剪枝的方式解決過擬合現象。同時簡化模型。透過這種方式,當容器發生銷燬失敗時,能夠觸發自愈路徑。同時,對於新增問題,我們可以縮短問題範圍,提高定位效率。透過以上三步,最終我們探索出了這樣一種全新的容器銷燬失敗自愈機制。期望本文思路對你有幫助~
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024924/viewspace-2929985/,如需轉載,請註明出處,否則將追究法律責任。