k8s從入門到放棄(2): 縮擴容&更新

Lumin發表於2019-03-08

前言

自動縮擴容是現代化的容器排程平臺帶給我們的最激動人心的一項能力。在上規模的業務系統中我們無時無刻不面臨著這樣的難題:使用者的流量往往隨著時間波動,甚至偶爾出現不可預測的峰值(毛刺流量),每當流量增加時都需要手工的對應用進行擴容,峰值流量消失後又需要將擴容的機器回收,運維起來費時費力。

幸運的是,k8s這樣的容器排程平臺正在逐漸幫助我們解決這樣的問題,它帶來的AutoScaler功能目前已經支援在多個不同維度上的彈性縮擴容,可以根據應用的負載情況進行自適應。儘管在一些較為苛刻的場景下,由於容器啟動速度等原因的限制,AutoScaler的效果還不盡人意,但相信在不久的將來,自動縮擴容方案將會完全成熟,屆時我們將輕鬆獲取具有強大彈效能力的服務叢集。

現在來試著使用一下k8s的Scale相關功能吧

手動調整服務規模

我們可以使用kubectl提供的命令來手動調整某個Deployment的規模,也就是其包含的Pod數量,這裡拿上一節裡建立的HelloWorld服務來作為例子,當前的deployment狀態如下:

k8s從入門到放棄(2): 縮擴容&更新

  • DISIRED 表示配置時宣告的期望副本數
  • CURRENT 表示當前正在執行的副本數
  • UP-TO_DATE 表示符合預期狀態的副本數(比如某個副本當機了,這裡就不會計算那個副本)
  • AVAILABLE 表示目前可用的副本數

我們可以使用kubectl scale命令來手動調整deployment的規模,現在嘗試把helloworld服務的副本數量擴充到4個:

k8s從入門到放棄(2): 縮擴容&更新

執行命令後,k8s很快就為我們建立了另外3個helloworld的副本,這時候整個服務就有多個例項在執行了,那麼對應Service的負載均衡功能便會生效,可以看到Service的狀態裡已經偵測到多個EndPoint:

k8s從入門到放棄(2): 縮擴容&更新

當我們連續呼叫service時,可以看到每次實際呼叫的pod都不同(這裡對服務的原始碼做了一些修改,列印出了hostname):

k8s從入門到放棄(2): 縮擴容&更新

同理,我們也可以用同樣的方式把服務縮容,比如把副本集的數量下調到兩個:

k8s從入門到放棄(2): 縮擴容&更新

自動縮擴容:極致彈性?

參考好文

剛才的例子中,我們是通過命令列的方式來手動調整服務規模的,顯然在面對線上真實流量時是不能這麼做的,我們希望排程平臺能夠根據服務的負載來智慧地調控規模,進行彈性縮擴容。這時候就輪到k8s中的AutoScaler出場了

到目前為止,k8s一共提供了3個不同維度的AutoScaler,如下圖:

k8s從入門到放棄(2): 縮擴容&更新

k8s把彈性伸縮分為兩類:

  • 資源維度:保障叢集資源池大小滿足整體規劃,當叢集內的資源不足以支撐產出新的pod時,就會觸發邊界進行擴容
  • 應用維度:保障應用的負載處在預期的容量規劃內

對應兩種伸縮策略:

  • 水平伸縮

    • 叢集維度:自動調整資源池規模(新增/刪除Worker節點)
    • Pod維度:自動調整Pod的副本集數量
  • 垂直伸縮

    • 叢集維度:不支援
    • Pod維度:自動調整應用的資源分配(增大/減少pod的cpu、記憶體佔用)

其中最為成熟也是最為常用的伸縮策略就是HPA(水平Pod伸縮),所以下面以它為例來重點分析,官方文件在此:https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/

基本流程

任何彈性系統的縮擴容都無外乎於以下幾個步驟:

  1. 採集監控指標
  2. 聚合監控指標,判斷是否需要執行縮擴容
  3. 執行縮擴容動作

下面就按照這個順序來分析HPA的工作方式,這裡先給出一個HPA大體的架構圖:

k8s從入門到放棄(2): 縮擴容&更新

監控指標

根據官方文件的描述,HPA是使用巡檢(Control Loop)的機制來採集Pod資源使用情況的,預設採集間隔為15s,可以通過Controller Manager(Master節點上的一個程式)的--horizontal-pod-autoscaler-sync-period引數來手動控制。

目前HPA預設採集指標的實現是Heapster,它主要採集CPU的使用率;beta版本也支援自定義的監控指標採集,但尚不穩定,不推薦使用

因此可以簡單認為,HPA就是通過CPU的使用率作為監控指標的

聚合演算法

採集到CPU指標後,k8s通過下面的公式來判斷需要擴容多少個pod

desiredReplicas = ceil[currentReplicas * ( currentMetricValue / desiredMetricValue )]
複製程式碼

ceil表示向上取整,舉個實際例子,假設某個服務執行了4個Pod,當前的CPU使用率為50%,預期的CPU使用率為25%,那麼滿足預期的實際Pod數量就是4 * (50% / 25%) = 8個,即需要將Pod容量擴大一倍,增加4個Pod來滿足需求

當然上述的指標並不是絕對精確的,首先,k8s會盡可能的讓指標往期望值靠近,而不是完全相等,其次HPA設定了一個容忍度(tolerance)的概念,允許指標在一定範圍內偏離期望值,預設是0.1,這就意味著如果你設定排程策略為CPU預期使用率 = 50%,實際的排程策略會是小於45%或者大於55%進行縮擴容,HPA會盡力把指標控制在這個範圍內(容忍度可以通過--horizontal-pod-autoscaler-tolerance來調整)

另外有兩點需要說明的細節:

一是k8s做出決策的間隔,它不會連續地執行擴縮容動作,而是存在一定的cd,目前擴容動作的cd為3分鐘,縮容則為5分鐘

二是k8s針對擴容做了一個最大限制,每次擴容的pod數量不會大於當前副本數量的2倍

HPA實踐

最後我們來嘗試實際使用一下HPA,依然是使用kubectl命令列的方式來建立:

kubectl autoscale deployment helloworld --cpu-percent=10 --min=1 --max=5
複製程式碼

執行上面的命令後,可以在dashboard看到HPA已經被建立,策略是以CPU使用率10%為臨界值,最少副本集數量為1,最大為5:

k8s從入門到放棄(2): 縮擴容&更新

使用kubectl get hpa也可以看到:

k8s從入門到放棄(2): 縮擴容&更新

但是這裡出現了異常,targets的當前CPU使用率始終顯示為unknown,網上說是因為副本集裡沒有配置resources導致的。我們需要在dashboard裡找到對應的副本集,然後在spec.containers.resources裡宣告requests和limits值:

k8s從入門到放棄(2): 縮擴容&更新

其中requests表示pod所需要分配的資源配額,limits表示單個pod最大能夠獲取到的資源配額。

配置完之後還是沒有出現指標,繼續百度,發現沒有安裝heapster支援,那就安裝,具體方法可見此文,注意需要把國外gcr的映象通過阿里雲代理一把才能安裝(如果你不太會搞,可以用我轉好的,見github),influxDBgrafanaheapster三個元件都裝好後,記得再裝一把heapster rbac(在kubeconfig/rbac目錄下),最後重啟一波minikube,開啟控制檯,可以看到節點頁面已經有了指標影像:

k8s從入門到放棄(2): 縮擴容&更新

kubectl top命令也能正常返回數值了:

k8s從入門到放棄(2): 縮擴容&更新

然而,kubectl get hpa顯示cpu的當前指標還是unknown,鬱悶,進到deployment裡看一把日誌:

k8s從入門到放棄(2): 縮擴容&更新

可以看到HPA一直在報無法從metrics API獲取請求資源的錯誤,github走一波,找到一個類似的issue,原因是1.9版本以後的k8s預設的監控指標來源從heapster變成了metrics-server(我用的1.0,坑),不過要再安裝一套metrics-server實在太麻煩了,可以通過修改kube-controller-manager的啟動引數來禁用這個新特性。

minikube ssh進入到vm內部,然後找到/etc/kubernetes/manifests/kube-controller-manager.yaml這個檔案,在啟動引數里加上--horizontal-pod-autoscaler-use-rest-clients=false,這時候對應的容器組會自動重啟更新,再執行kubectl get hpa,可以看到cpu的指標終於Ok了:

k8s從入門到放棄(2): 縮擴容&更新

留下了感動的淚水,終於可以開始壓測試水了,趕緊搞個buzybox壓起來:

kubectl run -i --tty load-generator --image=busybox /bin/sh
複製程式碼

寫個迴圈去訪問我們的helloworld服務(為了更快的出效果,我把服務的scale縮減到只有一個副本集,直接壓這個pod的ip)

/ # while true; do wget -q -O- http://172.17.0.5:8080; done
複製程式碼

大概2,3分鐘後就能看到結果,此時檢視hpa的狀態,可以看到已經觸發水平擴容,副本集的數量由1個追加到4個:

k8s從入門到放棄(2): 縮擴容&更新

dashboard上也能看到效果:

k8s從入門到放棄(2): 縮擴容&更新

關掉壓測指令碼,靜置一會兒,再看HPA的狀態:

k8s從入門到放棄(2): 縮擴容&更新

自動縮容到了1個副本集,那麼HPA的實踐使用到這裡就算是結束了

更新、釋出、回滾

服務程式碼必然是會經常修改的,當程式碼發生變動時,就需要重新打包生成映象,然後進行釋出和部署,來看看在k8s中是如何對這些步驟進行處理的。

現在我們對程式碼做了一些改動,加上了hostname的輸出,然後打包形成了一個2.0版本的新映象,下面需要把這個新版本的映象部署到k8s的容器叢集上,使用kubectl set image在deployment上指定新的映象:

kubectl set image deployments/helloworld helloworld=registry.cn-qingdao.aliyuncs.com/gold-faas/gold-demo-service:2.0
複製程式碼

執行該命令後,可以從kubectl和dashboard中看到,新的容器組正在被建立,同時舊的容器組也在被回收:

k8s從入門到放棄(2): 縮擴容&更新

k8s從入門到放棄(2): 縮擴容&更新

最終所有的舊版本pod都會被回收,只留下新發布版本的容器組副本集:

k8s從入門到放棄(2): 縮擴容&更新

來測試一下服務是否更新:

k8s從入門到放棄(2): 縮擴容&更新

假設我們的釋出到線上的版本出現了致命的問題,需要緊急將服務回退到上一個版本,該怎麼做呢?使用kubectl rollout undo即可,來看看效果:

k8s從入門到放棄(2): 縮擴容&更新

可以看到執行回滾命令後,新版本的pod開始被回收,同時上一個版本映象所對應的pod被喚醒(這裡速度非常快,我感覺k8s在釋出完新服務後可能並不會立刻銷燬歷史版本的pod例項,而是會快取一段時間,這樣非常快速的回滾,只要把service的負載均衡指回歷史版本的pod就可以了)

k8s從入門到放棄(2): 縮擴容&更新

回滾完成後再度請求服務,可以發現服務已經變回了上一個版本的內容:

k8s從入門到放棄(2): 縮擴容&更新

上面這些釋出和回滾的特性在k8s中被稱為滾動更新,它允許我們按照一定的比例逐步(可以手工設定百分比)更新pod,從而實現平滑的更新和回滾,也可以藉此實現類似藍綠髮布和金絲雀釋出的功能。

小結

通過實際操作體驗了k8s的縮擴容以及釋出機制,雖然遇到了幾個坑,但整體上用起來還是非常絲滑的,到這裡k8s的基本功能就探索的差不多了,從下一篇開始將會繼續深入分析k8s的原理,敬請期待!

原文地址 http://marklux.cn/blog/107, 轉載請註明出處

相關文章