Serverless 與容器決戰在即?有了彈性伸縮就不一樣了
作者 | 阿里雲容器技術專家 莫源
本文整理自莫源於 8 月 31 日 K8s & cloudnative meetup 深圳場的演講內容。****關注“阿里巴巴雲原生”公眾號,回覆關鍵詞****“資料”,即可獲得 2019 全年 meetup 活動 PPT 合集及 K8s 最全知識圖譜。
導讀:Serverless 和 Autoscaling 是近些年來廣大開發者非常關心的內容。有人說 Serverless 是容器 2.0,終有一天容器會和 Serverless 進行一場決戰,分出勝負。實際上,容器和 Serverless 是可以共存並且互補的,特別是在 Autoscaling 相關的場景下,Serverless 可以與容器完美相容,彌補容器場景在使用簡單、速度、成本的缺欠,在本文中將會為大家介紹容器在彈性場景下的原理、方案與挑戰,以及 Serverless 是如何幫助容器解決這些問題的。
當我們在談論"彈性伸縮"的時候
當我們在談論"彈性伸縮"的時候,我們在談論什麼?"彈性伸縮"對於團隊中不同的角色有不同的意義,而這正是彈性伸縮的魅力所在。
從一張資源曲線圖講起
這張圖是闡述彈性伸縮問題時經常引用的一張圖,表示的是叢集的實際資源容量和應用所需容量之間的關係。
- 其中紅色的曲線表示的是應用實際所需的容量,因為應用的資源申請量相比節點而言會小很多,因此曲線相對比較平滑;
- 而綠色的折線表示的是叢集的實際資源容量,折線的拐點表明此時進行了手動的容量調整,例如增加節點或者移除節點,因為單個節點的資源容量固定且相對較大,因此以折線為主。
首先,我們先看左側第一塊黃色柵格的區域,這個區域表示叢集的容量無法滿足業務的容量所需,在實際的場景中,通常會伴隨出現由於資源不足而無法排程的 Pod 等現象。
中間的柵格區域,叢集的容量遠高於實際資源所需的容量,此時會出現資源的浪費,實際的表現通常是節點的負載分配不均,部分節點上面無排程負載,而另外一些節點的負載相對較高。
右側柵格區域表示的是激增的峰值容量,我們可以看到,到達峰值前的曲率是非常陡峭的,這種場景通常是由於流量激增、大批量任務等非常規容量規劃內的場景,激增的峰值流量給運維同學的反應時間非常短,一旦處理不當就有可能引發事故。
彈性伸縮對於不同角色的人員,有著不同的意義:
- 開發人員希望通過彈性伸縮使應用獲得高可用的保障;
- 運維人員希望通過彈性伸縮降低基礎設施的管理成本;
- 架構師希望通過彈性伸縮得到靈活彈性的架構應對突發的激增峰值。
彈性伸縮有多種不同的元件和方案,選擇適合自己業務需求的方案是落地執行前的第一步。
Kubernetes 彈性伸縮能力解讀
Kubernetes 彈性伸縮的相關元件
Kubernetes 彈性伸縮的元件可以從兩個維度進行解讀:一個是伸縮方向,一個是伸縮物件。
從伸縮方向上,分為橫向與縱向。從伸縮物件上,分為節點與 Pod。那麼將這個象限進行展開,就變成如下 3 類元件:
- cluster-autoscaler,節點水平伸縮;
- HPA & cluster-proportional-autoscaler,Pod 水平伸縮;
- vertical pod autoscaler&addon resizer,Pod 縱向伸縮。
其中 HPA 與 Cluster-Autoscaler 是開發者最常組合使用的彈性伸縮元件。HPA 負責容器的水平伸縮,Cluster-Autoscaler 負責節點的水平伸縮。很多的開發者會產生這樣的疑問:為什麼彈性伸縮一個功能需要細化成這麼多元件分開處理,難道不可以直接設定一個閾值,就實現叢集的自動水位管理嗎?
Kubernetes 的彈性伸縮挑戰
瞭解 Kubernetes 的排程方式可以幫助開發者更好的理解 Kubernetes 彈性伸縮的設計哲學。在 Kubernetes 中,排程的最小單元是一個 Pod,Pod 會根據排程策略被排程到滿足條件的節點上,這些策略包括資源的匹配關係、親和性與反親和性等等,其中資源的匹配關係的計算是排程中的核心要素。
通常和資源相關的有如下四個概念:
- Capacity 表示一個節點所能分配的容量總量;
- Limit 表示一個 Pod 能夠使用的資源總量;
- Request 表示一個 Pod 在排程上佔用的資源空間;
- Used 表示一個 Pod 的真實資源使用。
在瞭解這四個基本概念和使用場景之後,我們再來看下 Kubernetes 彈性伸縮的三大難題:
- 容量規劃炸彈
還記得在沒有使用容器前,是如何做容量規劃的嗎?一般會按照應用來進行機器的分配,例如,應用 A 需要 2 臺 4C8G 的機器,應用 B 需要 4 臺 8C16G 的機器,應用 A 的機器與應用 B 的機器是獨立的,相互不干擾。到了容器的場景中,大部分的開發者無需關心底層的資源了,那麼這個時候容量規劃哪裡去了呢?
在 Kubernetes 中是通過 Request
和 Limit
的方式進行設定,Request
表示資源的申請值,Limit
表示資源的限制值。既然 Request
和 Limit
才是容量規劃的對等概念,那麼這就代表著資源的實際計算規則要根據 Request
和 Limit
才更加準確。而對於每個節點預留資源閾值而言,很有可能會造成小節點的預留無法滿足排程,大節點的預留又排程不完的場景。
- 百分比碎片陷阱
在一個 Kubernetes 叢集中,通常不只包含一種規格的機器。針對不同的場景、不同的需求,機器的配置、容量可能會有非常大的差異,那麼叢集伸縮時的百分比就具備非常大的迷惑性。
假設我們的叢集中存在 4C8G 的機器與 16C32G 兩種不同規格的機器,對於 10% 的資源預留而言,這兩種規格是所代表的意義是完全不同的。特別是在縮容的場景下,通常為了保證縮容後的叢集不處在震盪狀態,我們會一個節點一個節點來縮容節點,那麼如何根據百分比來判斷當前節點是處在縮容狀態就尤為重要。此時如果大規格機器有較低的利用率被判斷縮容,那麼很有可能會造成節點縮容後,容器重新排程後的爭搶飢餓。如果新增判斷條件,優先縮容小配置的節點,則有可能造成縮容後資源的大量冗餘,最終叢集中可能會只剩下所有的巨石
節點。
- 資源利用率困境
叢集的資源利用率是否可以真的代表當前的叢集狀態呢?當一個 Pod 的資源利用率很低的時候,不代表就可以侵佔他所申請的資源。在大部分的生產叢集中,資源利用率都不會保持在一個非常高的水位,但從排程來講,資源的排程水位應該保持在一個比較高的水位。這樣才能既保證叢集的穩定可用,又不過於浪費資源。
如果沒有設定 Request
與 Limit
,而叢集的整體資源利用率很高,這意味著什麼?這表示所有的 Pod 都在被以真實負載為單元進行排程,相互之間存在非常嚴重的爭搶,而且簡單的加入節點也絲毫無法解決問題,因為對於一個已排程的 Pod 而言,除了手動排程與驅逐,沒有任何方式可以將這個 Pod 從高負載的節點中移走。那如果我們設定了 Request
與 Limit
而節點的資源利用率又非常高的時候說明了什麼呢?很可惜這在大部分的場景下都是不可能的,因為不同的應用不同的負載在不同的時刻資源的利用率也會有所差異,大概率的情況是叢集還沒有觸發設定的閾值就已經無法排程 Pod 了。
在瞭解了 Kubernetes 彈性伸縮的三大問題後,我們再來看下 Kubernetes 的解決辦法是什麼?
Kubernetes 的彈性伸縮設計哲學
Kubernetes 的設計理念是將彈性伸縮分成排程層伸縮和資源層伸縮。排程層負責根據指標、閾值伸縮出調度單元,而資源層伸縮負責滿足排程單元的資源需求。
在排程層通常是通過 HPA 的方式進行 Pod 的水平伸縮,HPA 的使用方式和我們傳統意義上理解的彈性伸縮是非常接近和類似的,通過設定判斷的指標、判斷的閾值來進行水平伸縮。
在資源層目前主流的方案是通過 cluster-autoscaler 進行節點的水平伸縮。當出現 Pod 由於資源不足造成無法排程時,cluster-autoscaler 會嘗試從配置伸縮組中,選擇一個可以滿足排程需求的組,並自動向組內加入例項,當例項啟動後註冊到 Kubernetes 後,kube-scheduler 會重新觸發 Pod 的排程,將之前無法排程的 Pod 排程到新生成的節點上,從而完成全鏈路的擴容。
同樣在縮容時,排程層會現根據資源的利用率與設定的閾值比較,實現 Pod 水平的縮容。當節點上 Pod 的排程資源降低到資源層縮容閾值的時候,此時 Cluster-Autoscaler 會進行低排程百分比的節點的排水,排水完成後會進行節點的縮容,完成整個鏈路的收縮。
Kubernetes 彈性伸縮方案的阿克琉斯之踵
經典的 Kubernetes 彈性伸縮的案例
這張圖是一個非常經典的彈性伸縮的案例,可以代表大多數的線上業務的場景。應用的初始架構是一個 Deployment,下面有兩個 Pod,這個應用的接入層是通 過Ingress Controller 的方式進行對外暴露的,我們設定應用的伸縮策略為:單個 Pod 的 QPS 到達 100,則進行擴容,最小為 2 個 Pod,最大為 10 個 Pod。
HPA controller 會不斷輪訓 alibaba-cloud-metrics-adapter,來獲取 Ingress Gateway 當前路由的 QPS 指標。當 Ingress Gateway 的流量到達 QPS 閾值時,HPA controller 會觸發 Deployment 的 Pod 數目變化;當 Pod 的申請容量超過叢集的總量後,cluster-autoscaler 會選擇合適的伸縮組,彈出相應的 Node,承載之前未排程的 Pod。
這樣一個經典的彈性伸縮案例就解析完畢了,那麼在實際的開發過程中,會遇到哪些問題呢?
經典的 Kubernetes 彈性伸縮的缺點與解法
首先是擴容時延的問題,社群標準模式是通過建立、釋放 ECS 的方式,擴容的時延在 2min-2.5min 左右,而阿里雲獨立的極速模式是通過建立、停機、啟動的方式進行實現,停機時只收取儲存的費用,不收取計算的費用。可以通過非常低廉的價格獲得 50% 以上的彈性效率。
此外複雜度也是 cluster-autoscaler 繞不過的問題,想要用好 cluster-autoscaler,需要深入的瞭解 cluster-autoscaler 的一些內部機制,否則極有可能造成無法彈出或者無法縮容的場景。
對於大多數的開發者而言,cluster-autoscaler 的工作原理是黑盒的,而且 cluster-autoscaler 目前最好的問題排查方式依然是檢視日誌。一旦 cluster-autoscaler 出現執行異常後者由於開發者配置錯誤導致無法如預期的伸縮,那麼 80% 以上的開發者是很難自己進行糾錯的。
阿里雲容器服務團隊開發了一款 kubectl plugin,可以提供 cluster-autoscaler 更深層次的可觀測性,可以檢視當前 cluster-autoscaler 所在的伸縮階段以及自動彈性伸縮糾錯等能力。
雖然目前遇到的幾個核心的問題,都不是壓死駱駝的最後一棵稻草。但是我們一直在思考,是否有其他的方式可以讓彈性伸縮使用起來更簡單、更高效?
阿克琉斯的馬丁靴 - Serverless Autoscaling
資源層伸縮的核心問題在於學習成本較高、排錯困難、時效性差。當回過頭來看 Serverless 的時候,我們可以發現這些問題恰好是 Serverless 的特點與優勢,那麼是否有辦法讓 Serverless 成為 Kubernetes 資源層的彈性方案呢?
Serverless Autoscaling 元件 - virtual-kubelet-autoscaler
阿里雲容器服務團隊開發了 virtual-kubelet-autoscaler,一個在 Kubernetes 中實現 serverless autoscaling 的元件。
當出現了無法排程的 Pod 的時候,virtual-kubelet 負責承載真實的負載,可以理解為一個虛擬節點,擁有無限大的 capacity。當 Pod 排程到 virtual-kubelet 上時,會將 Pod 通過輕量級例項 ECI 進行啟動。目前 ECI 的啟動時間在 30s 之內,程式從排程開始到執行一般會在 1 分鐘內拉起。
與 cluster-autoscaler 類似,virtual-kubelet-autoscaler 也需要使用模擬排程的機制來判斷 Pod 是否可以被真實處理和承載,但是相比 cluster-autoscaler 而言,存在如下差異:
- virtual-kubelet-autoscaler 模擬排程的物件是增加了排程策略的 Pod Template 並非 Node Template。
- virtual-kubelet-autoscaler 的核心是選擇 virtual-kubelet 來承載負載,一旦 Pod 模擬排程成功繫結到 virtual-kubelet 上後,Pod 的生命週期管理、問題的排查等就與傳統的 Pod 沒有差異,不再是黑盒排查問題。
virtual-kubelet-autoscaler 不是"銀彈"
virtual-kubelet-autoscaler 並不是用來替代 cluster-autoscaler 的,virtual-kubelet-autoscaler 的優勢在於使用簡單、高彈性高併發,按量按需計費。但是與此同時也犧牲了部分的相容性,目前對 cluster-pi、coredns 等機制支援的還並不完善,只需少許的配置 virtual-kubelet-autoscaler 是可以和 cluster-autoscaler 相容的。virtual-kubelet-autoscaler 特別適合的場景是大資料離線任務、CI/CD 作業、突發型線上負載等。
最後
serverless autoscaling 已經逐漸成為 Kubernetes 彈性伸縮的重要組成部分,當 serverless autoscaling 相容性基本補齊的時候,serverless 使用簡單、無需運維、成本節約的特性會與 Kubernetes 形成完美互補,實現 Kubernetes 彈性伸縮的新飛躍