K8s 有好用的 autoscaling 功能,但你知道除了 pod 之外,node 也可以 auto scaling 嗎?帥,你知道就不用分享了啊 🚬

本文以重點整理的方式,先介紹目前常見的 Autoscaler,再介紹一些防止 pod 被亂殺的 config。

(撰於 2020-03-23,基於 Kubernetes 1.17,但 Api Versions 太多請自行查閱手冊)

讓我們歡迎第一位 Autoscaler 出場!

Cluster Autoscaler(CA)

負責調整 node-pool 的 node size scaling,屬於 cluster level autoscaler。

白話文:開新機器,關沒路用的機器 😈

  • Scale-up: 有 pod 的狀態是 unschedulable
  • Scale-down: 觀察 pod 總共的 memory/CPU request 是否 < 50%(非真實的 resource utilization)+ 沒有其他 pod/node 的條件限制
  • 可設定 min/maxi poolsize(GKE),自己管理的叢集可以設定更多參數
  • 會參照 PriorityClass 來調控 pod,但就是僅僅設立一條貧窮截止線,當前是 -10 ,autoscaler 不會因為低於此線的 pod 而去 scale-up,需要 scale-down 也不會理會 node 裡面是否有這種 pod
  • 部分設定設不好會讓 CA 沒辦法 scaling
    • CA 要關 node 然後 evict pod 時違反 pod affinity/anti-affinity 和 PodDisruptionBudget
    • 在 node 加上 annotation 可防止被 scale down:"cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"
  • 理論上 CA 很快,預設發現 unschedulable 10 秒就 scale-up,瓶頸會在 Node provision(開機器)需要數分鐘
  • ⚠️ GKE 若要 enable CA,會讓 K8s master 暫停重開,不可不謹慎
  • ⚠️ 手動更改 autoscalable node-pool 的 node label 等欄位會掉,因為修改不會傳播到 node template

Horizontal Pod Autoscaler(HPA)

管理 pod 數量的 autoscaler,屬於 pod level,同時也是一種 K8s API resource。

白話文:自動增減 replica 數量

  • Scale-up: 檢查 metrics,發現過了 threshold 就增加 deployment 的 replicas
  • Scale-down: 檢查 metrics,發現過了 threshold 就減少 deployment 的 replicas
  • 在 scale up/down 之後都會等個三五分鐘穩定後,再開始檢查 metric(如果突然爆漲應該來不及?)
  • 可以設定 custom/external metrics 來觸發 autoscaling
  • ⚠️ v2beta2 以上的 HPA 才有 metrics 可以檢查,v1 只能檢查 CPU utilization

目前 hpa.spec 有以下的 fields:

  • maxReplicas:恩,字面上的意思
  • minReplicas:autoscaler 可以設定的最小 replicas 值,預設為 1
  • scaleTargetRef:要 scale 的 resource,通常設定為 deployment 等 scheduler(statefulset 不行)
  • metrics:設定 scaling 要檢查的 metrics,跟 metrics.k8s.io 這些 API object 有關,可設定
    1. Resource: 目標資源的 Memory/CPU 等,通常透過 metrics-server 建立
    2. Pod: pod 上的 metrics,不同的是 Resource 預先定義好了,但 Pod 則是可以而外傭 custom.metrics.k8s.io API 來定義
    3. Object: 和 Pod 類似,是其他在同個 namespace 下面的 Resource Object 的 metrics,例如 Ingress hit-per-seconds
    4. External: 外部的 metrics,例如 load balancer,我們可以透過 prometheus-adapter 將 Prometheus 的 metrics 格式轉換為 metrics API 的格式,所以大膽向外求援吧

如果想玩玩 HPA,請參考官方文件走過一遍,但千萬不要在正式環境亂玩 😈。

去看了 Kuberentes 現在有什麼 stable metrics,得到以下答案,帥

Vertical Pod Autoscaler(VPA)

自動推薦並設定最適合 deployment pod 的 resource requests/limits 設定(最低資源需求和可用資源上限),不需要再 runtime 監控透過人工調整到正確的 CPU/Memory。

白話文:是一個能解放你的生產力的推薦系統啦

  • Scale-up: 檢查 metrics,發現過了 threshold 就減少 deployment 的 pod template 的 resources.requests,再透過重啟 pod 實際更新
  • Scale-down: 檢查 metrics,發現過了 threshold 就減少 deployment 的 pod template 的 resources.requests,再透過重啟 pod 實際更新
  • 是一個 CRD(Custom Resource Definition object
  • VPA 不會更動 resources.limit,事實上,在當前 MVP 是將 limit 設定為 infinity
  • VPA 的改動會參考該 pod 的 historic data(嗯,很有自學能力)
  • GKE 可以一鍵啟用,但是 API version 改變也要透過 google cloud
  • ⚠️ VPA 目前不該跟 HPA 一起用(VPA 0.7.1),除非 HPA 用了 custom metric 來觸發
  • ⚠️ 對,你發現了一定要重啟 pod 才會更新,這是當前 VPA 的限制,如果受不鳥就暫時先別用吧
  • ⚠️ 目前和 JVM 有點不搭,因為 JVM 隱匿病情不公開透明

目前 vpa.spec 有以下幾個 fields:

  • targetRef: 要調整的 resource,通常設定為 deployment 等 scheduler(statefulset 不行)
  • updatePolicy:會如何調節 Pod,總共有三種模式,但其實你實際只會用到 Recreate
    • Off:關,可用來 dry-run
    • Initial: 只會在 pod creation 是指派資源,之後整個生命週期都沒 autoscaler 的事
    • Recreate: 會透過 delete/recreate 來調節 pod 整個生命週期的 resources
    • Auto:自動用任何 updatePolicy 來做事,目前等於 Recreate
  • resourcePolicy.containerPolicies: 決定 autoscaler 如何對特定 container 計算資源用量,是一個 array of struct,有幾個 field
    • containerName:想對哪個 container 開刀
    • mode:有 AutoOff 來決定是否對該 container autoscale
    • minAllowed:推薦資源用量的最低限度
    • maxAllowed:推薦資源用量的最高限度

因為文件更新太慢,建議直接看 VPA 的 source code 來看到底怎麼用,或是看 Google 的文件,此外 GKE 可以一鍵開啟 VPA,beta 版的功能公開出來還不加 beta tag,膽大包青天。

當然,如果你對實作細節和如何推薦很有興趣的話,也可以看最初的 design proposal

如何讓該死的 Pod 死掉,不該死的 Pod 晚點死

以上是 Autoscaler 的介紹,但無論 scale pod 或 node,都可能殺掉部分的 pod,為了保持網路穩定,或是某些服務需要定量的 replica set 存活(例如用到 raft 共識演算法的服務),我們需要一些設定,讓 pod 不會長在不該長的 node,也不會死得不明不白。

Pod Resources Rquests and Limits

K8s 決定 Pod 放在哪,有一個要點:哪個 Node 的資源夠就往哪放。嘟嘟好,pod.spec 可以設定 container 需要多少運算資源(CPU、Memory 等):

  • containers[].resources.requests:就是 minimum,該容器「至少需要多少資源」
  • containers[].resources.limits:就是 maximum,該 容器「至多可使用多少資源」

Scheduler 會按照 resources.requests 決定 pod 要放在哪,一個 node 上面所有 pod containers 的 requests 總和不能超過該 node 提供的運算資源總量,否則新 Pod 會放不上去該 node。

那想知道 limits 怎麼計算的嗎?你不想知道但我還是要說:不同的 container runtime 統計運算資源使用量的方式各異,以最常見的 Docker 來說,CPU 用量是「每 100ms 該 container 佔用多少 CPU time」來決定,而 Memory 用量則是用 docker run--memory 來限制,詳情請參閱官方手冊

這裡偷一下 Sysdig 的圖,視覺化來理解 resources requests:

image from Sysdig: Understanding Kubernetes limits and requests by example

總而言之,在 Pod 要被 schedule 到 node,都會參考這些設定來調控,Cluster Autoscaler 也不例外,如果發現 Autoscaler 運作不能,檢查一下這些設定,然後記得 requests = 下限limit = 上限準沒錯。

要限制運算資源使用,還有 ResourceQuota(限制 namespace 資源使用)和 LimitRange()這兩個 API Resources 可用,甚至可以管理 storage,但設定有些複雜,按下不表。

然後 GKE 會幫你亂加 ResourceQuota,Google 髒髒。

Pod Disruption Budget(PDB)

PDB 字面上意思是「Pod 的崩潰預算」,說穿了,就是設定Pod 遇到事故至少要活幾個至多能死幾個。 還有機房封城 SRE 進不去重開 我們先來定義「事故」。舉凡系統當機、機房斷電、海底電纜被鯊魚咬,或是封城 SRE 無法進去重開機,這些可能會造成機器損毀的事故都是一種 disruption,但這些是「非自願性事故」。當然,K8s 管理員想要 scale down 一個 node 來維護或是省錢也是一種事故,這種就是「自願性事故」,PDB 就是為了防止管理員那天晚上喝了太多酒不小心一次 drain 太多 node 的最後一道安全鎖

PDB 有下列幾個 fields:

  • maxUnavailable:恩,剛剛講過,最多能死幾個 pod
  • minAvailable:至少要有幾個存活
  • selector:這個你不知道自己 kubectl explain pdb.spec.selector 吧 💩

講起來 PDB 很簡單,但其實它非常實用,必須細細品嚐,例如你有個用了 distributed service,由於這種用了共識演算法的服務 qurorum 都需要有一定數量(例如 3),所以你可能選擇對 StatefulSet 的 pod 設定 minAvailable: 3;或是你發現你根本不能死任何一個服務,也可以直接設定 maxUnavailable: 0,這樣 K8s 管理員發現沒辦法 drain node 的時候就會來找你,你就可以好好規劃手動調整,一起 DevOps 惹。

Pod Affinity/Anti-Affinity

Affinity,親和性,如 water affinity 是親水性,Pod affinity 代表「Pod 會趨於被安置在哪一個 node」,anti-affinity 則反之。

Pod 可以設定兩種 affinity:Node Affinity 和 Pod Affinity。

  • Node Affinity: 透過各種 expression(or、and、in 等)選擇不同的 node 來安置 pod,實際上就是強化版的 node selector,但可以設定各種「required」或「preferred」屬性決定條件是否必須。
  • Pod Affinity: 這是跨 pod 的 affinity,設定上和 Node Affinity 類似,換句話說就是:這個 pod A 已經在這個 Node 上面跑了,所以 pod B 根據 podAffinity 的設定也要(或不要)在這個 node 上面跑。

由於 Pod Affinity 還有一個 topologyKey 可以指定 Node label,讓 affinity 限制在有同一個 label 的 node 上面運作,這個 topologyKey 因為實作上安全性和效能考量,有諸多限制,請詳閱公開說明書

  • ⚠️ 目前實作上,配合 CA 使用可能造成 CA 慢三個數量級,且設定不好可能無法 scale down,請注意
  • 其實還有和 affinity 相反的 taint/toleration,會確認 Pod 不在特定的 Node 上安置,有機會再談

Pod Priority and Preemption

人有貧富貴賤,而 SRE 最賤,Pod 當然也不例外,我們可以透過 PriorityClass 這個 API resource,讓一些 Pod 就是比其他 Pod 還高貴(或低賤)。

當設定 pod.spec.priorityClassName 後,這個 pod 就獲得階級身分,如果遇上了有些 pod 沒辦法 schedule 到 node 上,priorityClass 較低的 pod 就會被迫踢出 node,讓高級的 pod 先 schedule,就是這麼現實。

  • PriorityClass 和 StorageClass 一樣,是 non-namspaced 的資源
  • priorityclass.value:是一個 integer,越高越優先
  • priorityclass.preemptionPolicy:是否能夠擠掉現有的 pod
    • Never:只能搶排隊在 Pending 但還沒 schedule 的 pod 前面先被安置
    • PreemptLowerPriority:比較兇,會嘗試幹掉已經 scheduled 的低順位 pod
  • NonPreemptingPriority feature gate 在 1.15 截止 1.17 都還在 Alpha,預設 false
  • QoS(由 pod resources.requests/limits 組合出來)是正交關係,互相影響程度低
  • ⚠️ PriorityClass 對 PDB 的支援是「盡其所能」,也就是說在需要 evict pod 時,會盡力去找可能符合 PDB 的受害者,但如果沒找到,低順位的 pod 還是會被幹掉

事實上 k8s 有預設兩個 PriorityClass,專門給 K8s 核心元件,原則上不要任意動到這些 PriorityClass,也不要搶他們的資源。

小結

感謝您耐心看完這篇冗文,來總複習一下:

  1. Cluster Autoscaler 控制增減 node 機器,舒服
  2. Horizontal Pod Autoscaler 控制 deployment 的 replicas 數量,舒服
  3. Verticla Pod Autoscaler 控制 deployment 的 resource requests/limit 數,舒服
  4. 儘量設定 Pod resources requests 與 limit,但這代表要熟悉 app 使用資源多寡,除非用 VPA
  5. Pod Disruption Budget 是你最後一道鎖,務必設定
  6. 若服務需要強 HA(跨 region/zone、或跨不同實體機房),請設定 (anti-)affinity
  7. 如果你有些 app 高人一等,非常非常重要,重要到可以把別人的 pod 幹掉,請設定 PriorityClass

切記,若要測試 autoscaling,千萬注意當前在哪個 cluster context,別說我沒提醒,小心弄壞了正式環境的 Kubernetes cluster 😈。

References