將雲原生進行到底:騰訊百萬級別容器雲平臺實踐揭祕

圖片

導讀|基於 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 提供一個暴力解決方案就是強刪機制。該機制只是刪除這個數據對象的數據,並不是銷燬這個容器。這樣導致一個問題,如果進行強制銷燬,可能老容器會殘留,新容器又起來了,這時老的容器會影響節點。

所以容器銷燬階段卡住會影響容器銷燬重建這個基本需求,而且它的原因是複雜多樣的,在大規模系統環境中更容易出現,而已有的自愈機制是沒有涵蓋這種場景的,所以我們就需要提供一種全新的自愈機制。傳統的解決方案是通過腳本掃描到它,對於定位到的問題,沒有解決方案的需要臨時隔離,已有解決方案的就要明確修復。但這並不是一個閉環方案,因爲還有很多未知問題,對未知問題來說業務關心的是儘可能恢復,而對平臺來說爲了保證穩定性,需要儘可能知道這些根因,去收斂這類問題。

所以我們兼顧這兩個需求,要縮小定位範圍、縮短定位週期,提高定位效率。對定位到根因的我們要去評估它的影響面,防止增量發生。而已經有解決方案的,我們需要有全網修復能力,出現異常的時候要告警,從而實現閉環解決方案。

我們想到了是智能運維的方法,它依靠大規模訓練樣本,關注相關性。而故障處理一般是小樣本量,強調專業和因果性,所以它並不很適合這種場景。但在智能運維的決策樹模型裏有些概念可以拿來參考,譬如基尼係數、信息熵、剪枝等。

圖片

最後簡單介紹一下決策樹模型的實現。

第一步需要建立模型,是關於信息熵的權衡,要平衡自愈機制和定位效率。我們在選擇信息熵的時候是自頂向下的推導路徑,從節點異常再到容器調用鏈異常,再到具體系統日誌。

第二步是特徵選取,基尼係數越小特徵越明確,所以我們選擇共同特徵作爲一個特徵值。同時選擇一些已知問題或者根因比較明確的作爲葉子節點。

最後一步是模型優化,例如剪枝優化,通過後剪枝的方式解決過擬合現象。同時簡化模型。通過這種方式,

當容器發生銷燬失敗時,能夠觸發自愈路徑。同時,對於新增問題,我們可以縮短問題範圍,提高定位效率。

通過以上三步,最終我們探索出了這樣一種全新的容器銷燬失敗自愈機制。期望本文思路對你有幫助~

⛳️公衆號粉絲專享:點擊文末“閱讀原文”,參與調研抽BANQ防水U盤

騰訊工程師技術乾貨直達:

1、手把手教你搭建Hexo博客

2、如何不改一行代碼,讓Hippy啓動速度提升50%?

3、內存泄露?騰訊工程師2個壓箱底的方法和工具

4、一文讀懂Go函數調用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章