2019年天貓618大促,螞蟻金服首次在大促中對調度系統和技術棧全面應用Kubernetes,突破了Kubernetes單集羣萬節點的規模,總節點數達到數十萬個,這是世界最大規模的 Kubernetes 集羣之一,而這距離開發團隊下載Kubernetes代碼僅一年之久。node
去年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 之上,而且將組件和節點的交付過程封裝成Operator。有了 Kubernetes 和 Operator 以後,想要交付一個 Kubernetes 集羣給應用方就像提交Pod同樣容易,而且集羣的各類組件都能自動故障自愈。這能夠理解爲,用Operator這種雲原生的方式交付整個基礎設施。若是不是當初的這個決定,很難想象組件和節點在離開 Kubernetes 以後如何運維和自動化恢復。框架
爲此,開發團隊設計出了 Kube-on-Kube-Operator,它的用途是將各個機房交付給用戶的 Kubernetes 集羣(稱爲 「業務集羣」) 的組件和服務也都跑在一個 Kubernetes (稱爲 「元集羣」) 上。下圖是Kube-on-Kube-Operator的核心架構圖:less
在擁有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去交付螞蟻金服的全部基礎設施和應用。
自動化和 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 知足性能需求。
一、優先知足和保證 API Server 計算資源需求
在常規部署模式下,API Server 會和Controller Manager、Scheduler 等核心組件一塊兒部署在一臺節點上,在Kube-on-Kube 架構下也是採用這種部署模式,以達到合理使用資源的目的。在這種部署架構下,將 API Server 的資源優先級設置到最高級別,也就是在 Kubernetes 資源級別表達裏的 Guaranteed 級別,而且儘量將物理節點全部資源都佔用;同時將其餘組件的優先級相對下降,即將其設置成 Burstable 級別,以保證API Server的資源需求。
二、均衡 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 升級策略以下:
三、開啓 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 大約下降了一半。
四、修復請求鏈路中丟失 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 積壓。
五、優化客戶端行爲
目前,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,
能夠說,全面實現雲原生是每一個開發者都須要參與的「革命」,而螞蟻金服也將做爲發起者和分享者參與其中。