1. 程式人生 > >從零到破萬節點!支撐618大促背後的螞蟻金服Kubernetes叢集

從零到破萬節點!支撐618大促背後的螞蟻金服Kubernetes叢集

2019年天貓618大促,螞蟻金服首次在大促中對排程系統和技術棧全面應用Kubernetes,突破了Kubernetes單叢集萬節點的規模,總節點數達到數十萬個,這是世界最大規模的 Kubernetes 叢集之一,而這距離開發團隊下載Kubernetes程式碼僅一年之久。

背景

去年6月份,螞蟻金服的 Kubernetes開發團隊剛剛下載Kubernetes程式碼,從零開始嘗試在內部落地Kubernetes叢集,並推動雲原生實踐。2019年天貓618大促,螞蟻金服首次在排程系統和技術棧全量應用Kubernetes,平穩度過大促並突破 Kubernetes 單叢集萬節點規模,機房和叢集數量達到數十個,總節點達到數十萬個,這是世界最大規模的 Kubernetes 叢集之一

螞蟻金服的 Kubernetes開發團隊是一個僅有十幾人組成的小隊團,他們用短短一年時間,通過擴充套件 Kubernetes 的方式,將螞蟻金服老的排程系統的功能對齊,還將一系列令人興奮的Kubernetes 功能帶回並落地到螞蟻金服。開發團隊能在如此短時間內取得如此成績,所依靠的正是雲原生技術:開發團隊使用雲原生技術讓開發和迭代敏捷化,同時讓一切過程自動化。開發團隊在落地 Kubernetes 過程中,就已經嘗試將雲原生的理念實踐並執行,取得的優秀成果將為整個螞蟻金服後續的全面雲原生化提供優秀範本。

本文將分享螞蟻金服的 Kubernetes開發團隊如何使用雲原生的技術去推進 Kubernetes 在螞蟻金服的大規模落地,同時也會分享一些對於大規模 Kubernetes叢集效能優化的經驗。

將Kubernetes執行在Kubernetes上

雲原生的核心理念是讓應用無差別執行在任何一朵雲上,即將應用變成雲的 “原住民”。而螞蟻金服的 Kubernetes 開發團隊在專案開始時需要思考的是如何將 Kubernetes 雲原生化的執行在各個機房,並在沒有任何基礎設施的雲機房也能無差別執行Kubernetes

首先,新建一個Kubernetes是一項繁瑣的事情:初始化一堆證書,包括安全的儲存證書;有序拉起二十幾個元件,同時組織好彼此之間的引用關係,保證後續能夠方便地升級;最後還要做自動化故障恢復。

一旦擁有Kubernetes,如果某個應用提出應用釋出、自動化運維等需求,可以很簡單的完成。但是,如何才能讓Kubernetes本身也享受到 Kubernetes 帶來的強大功能呢?

那麼,這句話應該如何理解呢?在螞蟻金服啟動落地 Kubernetes 時,就已經預見內部對“自動化運維和交付”的巨大依賴。螞蟻金服需要管理幾十個叢集、幾十萬計算節點。同時,隨著迭代的進行,擴充套件元件越來越多(到目前已經有三十多個)。

因此,在經過一系列討論之後,螞蟻金服決定將 Kubernetes執行在Kubernetes 之上,並且將元件和節點的交付過程封裝成Operator。有了 Kubernetes 和 Operator 之後,想要交付一個 Kubernetes 叢集給應用方就像提交Pod一樣容易,並且叢集的各種元件都能自動故障自愈。這可以理解為,用Operator這種雲原生的方式交付整個基礎設施。如果不是當初的這個決定,很難想象元件和節點在離開 Kubernetes 之後如何運維和自動化恢復。

為此,開發團隊設計出了 Kube-on-Kube-Operator,它的用途是將各個機房交付給使用者的 Kubernetes 叢集(稱為 “業務叢集”) 的元件和服務也都跑在一個 Kubernetes (稱為 “元叢集”) 上。下圖是Kube-on-Kube-Operator的核心架構圖:

在擁有Kube-on-Kube-Operator 之後,螞蟻金服能在分鐘級甚至秒級建立一個 Kubernetes 叢集。同時,所有業務叢集 Kubernetes Master 元件都執行在元叢集 Kubernetes 上,憑藉 Kubernetes 的能力可以做到秒級故障恢復。

Kube-on-Kube-Operator 用雲原生方式搞定了 Kubernetes Master 元件,那麼 kubelet 和 Worker 節點呢?

眾所周知,將一個 Worker 節點加入叢集也需要一堆複雜的事情:生成隨機證書,安裝 Runtime 以及各種底層軟體,設定 kubelet 啟動引數等。對此,螞蟻金服團隊在思考能否像 Kubernetes 自動化維護 Pod 一樣自動化維護 Worker 節點呢?

Node-Operator 正是在這樣的背景下誕生的,其使用宣告式程式設計、面向終態的特性去管理 Worker節點。Node-Operator 的職責涵蓋從雲廠商購買計算節點開始到接管 Kubenertes 節點整個生命週期:從初始化證書、kubelet 以及節點必要元件,到自動化升級元件版本,再到最後的節點下線。同時,Node-Opeator 也會訂閱 NPD(Node Problem Detector) 上報的節點故障資訊,進行一系列自動化修復。下圖是 Node-Operator 的核心架構圖:

Kube-on-Kube-Operator 和Node-Operator 在生產環境中表現穩定,經歷了數十個大迭代以及無數小迭代。同時,Kube-on-Kube-Operator自動化運維了數十個生產叢集,為每日自動化功能測試和迴歸測試快速建立和銷燬臨時測試叢集。Node-Operator接管了幾乎整個螞蟻金服的物理機生命週期,這種思想也繼承到了下一代運維管控系統。

除了 Kube-on-Kube-Operator 和Node-Operator,在 “automate everything”信念的驅動下,螞蟻金服還開發出了各種 Operator去交付螞蟻金服的所有基礎設施和應用。

自動化流水線與GitOps

自動化和 CI/CD 是雲原生非常重視的理念,螞蟻金服開發團隊將其應用到研發過程中,讓研發、測試和釋出的一系列過程都自動化。

上線 Kubernetes 初期,在保持飛速迭代的情況下,也需要確保程式碼質量,團隊的規定是:任何元件都要有單獨的 e2e 測試集或者測試用例,同時每天晚上都會將最新程式碼放入全新的沙箱叢集測試,另外還會定期或者觸發式在生產叢集執行功能驗證測試。

為了提高效率並更好完成測試,開發團隊建立了自動化流水線。最開始的自動化流水線是為了服務測試,在自動化流水線建立工作集,幾乎所有工作集都通過呼叫 Kube-on-Kube-Operator 和 Node-Operator 建立了全新的沙箱叢集,然後開始執行各自測試集的測試,最後銷燬叢集傳送測試結果。

為了在自動化流水線上更方便的建立工作集,團隊將所有元件和測試集都進行映象化,使用Kube-on-Kube-Operator 和 Kubernetes 元叢集可以方便、快速地建立測試用的沙箱叢集,然後使用容器和配置注入的方式讓測試集(Job on Kubernetes)執行在任何環境、指定任何叢集跑測試。

後來,團隊在流水線加上自動化釋出:流水線將期望的元件釋出完成,然後自動觸發功能驗證測試。如果釋出或者測試失敗,通過釘釘機器人通知對應負責人,如果成功,幾乎可以做到無人值守釋出。

Kubernetes 一個令人興奮的特性就是將各種部署資源的宣告統一和標準化:如果需要一個線上無狀態服務,只需向 Kubernetes提交一個 Deployment;如果需要負載均衡,只需提交一個 Service;如果需要持久化儲存卷,就提交一個PVC(PersistentVolumeClaim);如果需要儲存配置檔案或者密碼儲存,只需提交ConfigMap 或者 Secret,然後在 Pod 裡面引用就可以。Kube-on-Kube-Operator 就是用這些標準的資源定義Kubernetes元叢集釋出各個 Kubernetes 業務叢集的元件。

但是,在Kube-on-Kube-Operator 的使用過程中,團隊發現Kubernetes 釋出還是不夠透明:Kube-on-Kube-Operator 承包了釋出過程,即使它是一個非常輕量的觸發器,用來將業務叢集部署資原始檔提交到 Kubernetes 元叢集,但執行一次釋出也需要比較繁瑣的操作。這在快速的迭代團隊中,教會新同事使用 Kube-on-Kube-Operator 宣告版本、修改 Cluster(代表一個叢集的 CRD) 版本引用,看起來不夠 “敏捷”。Kube-on-Kube-Operator 儼然變成了一個 PaaS,但業務團隊為什麼要花精力學習一個 “非標準 PaaS” 的使用呢?

事實上,Kubernetes 已經將一切標準化,使用Git 自帶的版本管理、 PR Review 功能就可以實現部署資原始檔管理系統。

因此,開發團隊引入 GitOps 理念, GitOps 基於經過驗證的 DevOps 技術——大體類似於 CI/CD和宣告式基礎設施即程式碼,而構建為Kubernetes應用程式提供了一聯合的、可自動生成的生命週期框架。

GitOps將原先包裹在 PaaS 或者 Kube-on-Kube-Operator 的黑盒全部公開化和民主化。每個人都能從名為 kubernetes-resources 的 Git 倉庫看到 Kubernets 元件目前在線上的部署狀態:版本、規格、副本數、引用的資源。每個人都能參與釋出,只要提交 PR,經過事先設定的管理員 Review 和 Approve 之後,就可以自動釋出到線上並開始跑測試。團隊使用 kustomize 的 base/overlays 功能做到各叢集的差異化部署,同時又能避免同一個元件在各個Kubernetes 叢集重複編寫。

開發團隊還將 GitOps 能力開放給業務方,為此建立了名為 partner-resources 的 Git 倉庫,任何業務應用的開發同學都能訪問該倉庫並提交 PR,經過 Review 合併到Master 後,自動化流水線會將部署資源生效到指定 Kubernetes 叢集,從而進行業務應用的雲原生實踐和落地。

可能很多同學會不由自主的將透明、民主和 “不安全”掛上鉤。恰恰相反,kubernetes-resources 和 partner-resources 裡面沒有任何一個祕鑰檔案,只有許可權宣告(RBAC)檔案。許可權宣告需要 Review 才能合入,而身份識別使用了 Kubernetes 自動注入祕鑰(ServiceAccount)功能。在ServiceMesh 普及之後,祕鑰檔案更加被弱化,所有身份識別都依賴 Mesh。同時,螞蟻金服執行的 Kubernetes 叢集採用了最高的安全標準,所有鏈路都使用 TLS 雙向加密和身份認證。

在雲原生時代,Kubernetes叢集其實已經是最好的元資料系統。同時,Kubernetes 各種 Workload 配合工作讓使用者提交的部署資源一直維持在期望狀態;而 GitOps 擁有的版本記錄、PR Review 功能等是最好的部署資原始檔管理系統。即使目前還有一些路要走,比如目前Kubernetes 缺少灰度釋出等更高階功能的 Workload,但是在不久的將來肯定能看到這些特性被放入Workload。

全面雲原生化

Kubernetes 是雲原生的基礎,螞蟻金服在過去一年從零到全面落地 Kubernetes ,並在期望的規模下做到優秀的吞吐量。可以說,過去一年完成了雲原生的基礎建設。

同時,非常多其它雲原生技術在Kubernetes 叢集內並行探索和落地,達到生產級別,甚至支援大促,比如ServiceMesh等。簡單來看,螞蟻金服落地雲原生的目的可以總結為三點:標準化交付、提高研發效率和提高資源利用率。

其中,標準化交付比較好理解,螞蟻金服圍繞 Kubernetes 建設 PaaS 和應用交付體系,使用 Kubernetes 統一的資源宣告方式交付所有應用,同時讓所有應用自動化注入基礎服務,如ServiceMesh,統一日誌,Tracing 等;提高研發效率注重提高每個應用開發者的工作效率,讓其使用 Serverless、CI/CD展開日常工作,讓開發者背靠雲原生技術棧做更敏捷的開發和迭代;最後,使用 Kubernetes 統一資源和排程,讓所有的應用、Job、GPU 排程都使用 Kubernetes 叢集統一的資源池。開發團隊在 Kubernetes 統一資源池內做了一系列排程優化和混布技術,讓資源利用率有質的提升。

大規模叢集效能優化

如果按照Kubernetes最新版本提供的能力,做到單叢集萬節點並不是特別困難。但是,在這種背景下,讓整個叢集保持較高吞吐量和效能,並在618大促時依舊對各種響應做到及時反饋是不容易的。

螞蟻金服通過系列壓測和迭代將單叢集做到了上萬規模。然而,在這個過程中,整個團隊發現不能太迷信壓測資料,壓測場景其實非常片面,而生產環境和壓測環境有非常大差異,主要體現在 Kubernetes 擴充套件元件的客戶端行為和一些極端情況。

舉例來說,一個是 Daemonset,螞蟻金服內部一個叢集已經擁有十個左右的 Daemonset 系統 Agent,在如此大規模叢集內上線一個使用 Kubernetes 客戶端的不規範Daemonset 都可能使API Server 陷入崩潰狀態;另一個是 Webhook,螞蟻金服擁有一系列Webhook Server 擴充套件功能,它們的響應速度都會影響到 API Server 的效能,甚至引起記憶體和 goruntine 洩露。從上述示例不難發現,其實要將大規模叢集維持在健康狀態需要全鏈路優化和調優,而不僅僅侷限於 API Server和etcd,更不是僅僅停留在壓測資料。

開發團隊將叢集節點規模上升到萬級別的時候,發現更多的瓶頸在 Kubernetes API Server。相對來說,etcd 的表現比較穩定。團隊做了系列優化和調整,來讓API Server 滿足效能需求。

1、優先滿足和保證 API Server 計算資源需求在常規部署模式下,API Server 會和Controller Manager、Scheduler 等核心元件一起部署在一臺節點上,在Kube-on-Kube 架構下也是採用這種部署模式,以達到合理使用資源的目的。在這種部署架構下,將 API Server 的資源優先順序設定到最高級別,也就是在 Kubernetes 資源級別表達裡的 Guaranteed 級別,並且儘可能將物理節點所有資源都佔用;同時將其他元件的優先順序相對降低,即將其設定成 Burstable 級別,以保證API Server的資源需求。

2、均衡 API Server 負載
在 Kubernetes 架構下,所有元件均面向APIServer展開工作,因此元件對API Server的請求鏈路健康非常敏感,只要API Server發生升級、重啟,所有元件幾乎都會在秒級內發起新的一系列List/Watch請求。如果使用滾動升級模式逐個升級 API Server 的模式去升級 API Server,那麼很有可能在升級之後,絕大多數客戶端請求都會打在一個API Server例項上。

如果負載不均衡,使得API Server進入 “一人工作,多人圍觀” 的場面,那麼很可能導致 API Server 發生雪崩效應。更糟糕的情況是,因為 Kubernetes 的 client-go 程式碼庫使用了 TLS 鏈路複用的特性,客戶端不會隨著執行時間增長,因為連結重建將負載均衡掉。

開發團隊研究後發現,這個問題可以通過優化客戶端將請求平衡掉來解決,當前正在著手研發,成功後也會回饋給開源社群。在這個特性還未釋出之前,可以通過設定升級 API Server 的策略使用 “先擴後縮” 來緩解該問題,即先將新版本的API Server全部創建出來,然後再下線老版本的 API Server。使用 Kubernetes 的 Deployment 表達三副本的 API Server 升級策略如下:

3、開啟 NodeLease Feature
對於提升 Kubernetes 叢集規模來說,NodeLease 是一個非常重要的 Feature 。在沒有開啟 NodeLease 之前,Kubelet 會使用 Update Node Status 的方式更新節點心跳,而一次這樣的心跳會向 API Server 傳送大約10 KB資料量。

在大規模場景下,API Server 處理心跳請求是非常大的開銷。而開啟 NodeLease 之後,Kubelet 會使用非常輕量的 NodeLease 物件(0.1 KB)更新請求替換老的 Update Node Status 方式,這大大減輕了 API Server 的負擔。在上線NodeLease 功能之後,叢集API Server 開銷的 CPU 大約降低了一半。

4、修復請求鏈路中丟失 Context 的場景
眾所周知,Go語言標準庫的HTTP請求使用 request.Context() 方法獲取的 Context 來判斷客戶端請求是否結束。如果客戶端已經退出請求,而 API Server 還在處理請求,那麼就可能導致請求處理 goruntine 殘留和積壓。

在API Server陷入效能瓶頸時,APIServer 已經來不及處理請求,而客戶端發起的重試請求,會將 API Server帶入雪崩效應:處理已取消請求的 goruntine 會積壓的越多,直到 API Server 陷入OOM。開發團隊找出了一系列在處理請求沒有使用 Context 的場景,並向上遊社群提交了修復方案,包括使用 client-go 可能導致的 goruntine;Admission 和 Webhook 可能引起的 goruntine 積壓。

5、優化客戶端行為
目前,API Server 限流功能只限制最大讀和寫併發數,而沒有限制特定客戶端請求併發量的功能。因此,API Server 其實是比較脆弱的,一個客戶端頻繁的 List 數目較大資源(如 Pod, Node 等)都有可能會讓 API Server 陷入效能瓶頸。開發團隊強制要求所有客戶端使用 Informer 去 List/Watch 資源,並且禁止在處理邏輯裡面直接呼叫 Client 去向 API Server List 資源。而社群開始重視這方面,通過引入 Priority and Fairness 特性去更加細粒度的控制客戶端的請求限制。

在後續越來越多的系統 Daemonset 上線之後,團隊發現,即使做到了所有客戶端使用 Informer,叢集內的 List Pod 請求依舊很多。這主要是 Kubelet 和 Daemonset 帶來的,可以用 Bookmark 特性來解決這個問題,在未上線 Bookmark 的叢集內,可以調整 API Server 對資源的更新事件快取量來緩解該問題 (如 --watch-cache-sizes=node#1000,pod#5000),同時調整 Daemonset Re-Watch 的時間間隔:

結束語

在螞蟻金服雲原生實踐和落地的過程,開發團隊認識到,專案順利實踐與開源社群的幫助密切相關。除了Kubernetes,螞蟻金服團隊還使用了其它開源專案,比如 Prometheus。Prometheus 的意義在於標準化Metrics 和其查詢語句,任何應用都可以使用 Prometheus 埋點並記錄 Metrics,而螞蟻金服通過自研採集任務排程系統,以及資料持久化方案,使得Prometheus 資料不會有任何 “斷點” ,同時還支援永久歷史資料查詢。

目前,螞蟻金服已向 Kubernetes 社群提交了許多大規模場景下的效能提升和優化方案,上面提到在Kubernetes API Server效能優化過程中發現的問題,以及修復最新Kubernetes版本中的許多 Daemonset 的 bug ,將Daemonset的生產可用性提高一個層級。同時,螞蟻金服也將眾多技術開源給社群,包括金融級分散式框架SOFAStack。

可以說,全面實現雲原生是每個開發者都需要參與的“革命”,而螞蟻金服也將作為發起者和分享者參與其中。


原文連結
本文為雲棲社群原創內容,未經