Kubernetes Resource Management— 伸縮自在的 Cluster

smalltown
Starbugs Weekly 星巴哥技術專欄
12 min readSep 19, 2022

Background

當把 Kubernetes 架設好提供給相關單位使用時,Kubernetes 資源管理的挑戰就開始了,一般來說需要從三個層面去做考量:

📚 Pod

對於 Container 來說,會先遇到的當然就是與 Pod 相關的設定,有用過 K8s 的人應該都會知道在 Pod 的 Spec 中可以設定 CPU 與 Memory 的 Request 與 Limit 值,問題情境對話如下:

👷 Ops: 整個 K8s Cluster 又被你們團隊的應用服務搞得烏煙瘴氣了啦,不是有說過要對 Pod 設定 CPU 和 Memory 的 Request 與 Limit 值?!

👨‍💻 Dev: 我哪知道要設定多少,而且那麼多的 K8s Deployment Manifests 我改不完啦!

📚 Node

通常在一開始使用 K8s 時都只會建立一種 Node,例如 4 Core CPU 與 16G 的 Memory,不過隨著運行越來越多種應用服務,為了滿足其各自的需求,一定會開始建立越來越多種,問題情境對話如下:

👨‍💻 Dev: 我這個服務至少需要 32 GB 的 Ram !

👨‍💻 Dev: 我這個服務需要顯示卡的運算資源!

👨‍💻 Dev: 對方說他們的防火牆只能設定一個固定 IP !

👷 Ops: OK!好! (我能說不嗎?!)

📚 Cluster

當 K8s Cluster 內有滿滿的 Pod 運行在眾多類型的 Node 上時,要怎麼控制 整個 Cluster 的使用效率?避免資源的浪費,將錢用在刀口上面!問題情境對話如下:

👨‍💼 Manager: 我剛好看到 Dashboard 上面寫說我們正式環境的 K8s Cluster 資源使用率只有 20%,那我們多開了 80% 的機器資源拿來做什麼啊?

👷 Ops: 你是沒有聽過 82 法則嗎?(誤) 就之前生意很好的時候,多開了很多的機器,後來還沒有關掉

所以這篇文章想要將我們公司 (MaiCoin Group) 開發與維運團隊解決上面提到諸多問題的工具,方式與經驗整理成這篇文章,分享給也有使用 K8s 的人

Prerequisites

📚 Node Resource Reservation

https://kubernetes.io/docs/tasks/administer-cluster/reserve-compute-resources/

就像蓋大樓一樣,一開始的地基一定要先打好 (當 K8s 的節點為託管服務時, 理論上不用費心在此),一台 K8s 的節點除了上面運行的 Pod (Container) 之外,其實還需要提供資源給作業系統K8s (kubelet, kube-proxy… 等),要是沒有這樣做的話…

當 Pod 使用太多的資源,造成節點剩餘的資源無法供作業系統與 K8s 正常運行 ➡️ 結果變成該節點無法使用 ➡️ 運行於節點上的 Pod 也都全部受到影響,正所謂覆巢之下無完卵,所以一定要按照官方文件去進行設定,不過官方文件並沒有給出要預留多少資源的建議,這時可以參考 GCP 的 GKE 所給出的建議值,設定完成後就可以不用擔心運行在節點上面的 Pod 危及到節點的安危XD (對這個主題想要更深入瞭解的話,可以參考 learnk8s 的這篇文章)

📚 Git Repository Structure

app-repo
├── Dockerfile
├── circle.yml
├── helm
├── ...
└── tests

目前公司內的 Git Repository 會把應用服務 Lifecycle 需要的相關設定組態都存放在其中,例如 CI/CD Framework 使用的 Pipeline 設定檔案,單元測試腳本…等,當然還有應用服務需要的 K8s Manifest 也在其中,理論上希望開發團隊可以自主掌握開發週期所以需要的一切事務,所以 SRE 的角色就要確保運行環境的穩定性,跟開發者一起合作

Scaling Options

Kubernetes 的 Scaling 機制共有三種,這幾年來有滿多的文章都有提過,這裏稍微提一下概念,後面會針對 Vertical Pod AutoScaler 和 Cluster Autoscaler 多提及一些使用上的經驗,接著就讓我們以 Pod 和 Node 兩個層面來述說:

📚 Pod

針對 Pod 來做 Auto-Scaling 的機制有兩種,分別是 HPA (Horizontal Pod Autoscaler) 與 VPA (Vertical Pod Autoscaler)

  • HPA: 首先必須於 HPA 這個資源定義當 CPU 或是 Memory 達成多少的使用率時,需要讓 Deployment 的 Replica 去做增加或是減少,讓運行於其上的應用服務可以應付目前的使用者請求量級,這也是 K8s 內預設就有的功能;但假如覺得在功能上還是無法符合需求的話,可以參考 KEDA (Kubernetes Event-Driven Autoscaling)
  • VPA: 跟 HAP 相反,VPA 並不會讓 Pod 的個數變多,而是根據定義去對目標 Deployment 去調整其可以使用的 CPU 與 Memory,不過他不是預設的功能,必須要自行安裝才行 (文件參考)

📚 Node

當 HPA 和 VPA 發揮效用之後,Pod 所使用到的資源就會自動去做增加與減少,這時候就有可能遇到 K8s 節點不夠或是太多的情況,因此就會需要額外的工具來協助

  • Cluster Autoscaler: 就是負責這個任務,當他察覺到 1) Pod 發生 Pending 2) 整台節點的使用率過低時,就會去跟 Cloud Provider 溝通,讓他們針對 K8s 叢集的節點個數做調整,不過這個也不是預設功能,可以根據文件去做安裝與設定
  • Karpenter: 假如是使用 AWS 的人還可以參考由其自己推出的 Karpenter,畢竟是自己推出的工具,因此可以跟貼近 AWS 的使用情境

Pod Resource Management

其實要開發人員在一開始就將 Pod 的 Resource Request 與 Limit 就設定好是不切實際的,因為根據現實狀況來說 1) 有時候根本不可能有時間去做完整的壓力測試 2) 只能使用單個 Replica 類型的應用服務,資源使用量會一直改變,所以我自己是採用循序漸進的做法:

📚 Default Limit Range

一開始先把 Grafana Dashboard 打開,觀察每一個 Namespace 裡面的 Pod 對於 CPU 和 Memory 的使用量,然後去設定每個 Namespace 的 Default Limit Range,這樣一來,就算所有的 Pod 都沒有設定 Resource Request,也會套用到預設的值,讓 Pod 在 Cluster 內相對平均分散,減少一堆 Pod 都聚集在某幾台節點內的情況發生,避免運行在這些節點內的應用服務資源匱乏進而影響到使用者體驗

📚 Performance Testing

對於附載較重的應用服務,當然還是建議在 CI 流程中去做壓力測試,根據壓力測試的結果就可以設定 Resource Request 與 Limit,進而再透過 HPA 讓 Pod 可以根據使用者的量級來自動做調整

📚 Can Horizontal Autoscaling

但要是真的抽不出時間與資源來實行壓力測試的話,那就還是退一步透過 Grafana Dashboard 來觀察資源使用情形,先取得負載相對重的應用服務資源使用狀況 (負載較輕的應用服務理論上已經透過上面提到的 Default Limit Range 緩解了),接著就一樣去設定 Resource Request 與 Limit,最後再進一步加上 HPA 的設定

📚 Cannot Horizontal Autoscaling

假如應用服務無法做橫向擴展,而且也沒有辦法完成壓力測試的狀況下,為了貪圖方便一開始就要求很多資源的話,就會很容易造成浪費,不過要求開發人員一直去調整 K8s Manifest 裡面的 Resource Request 與 Limit 也有點麻煩,這時候就推薦可以試試看使用 VPA,讓他去幫忙動態調整 Resource Request 與 Limit (跟上面一樣,先以附載重的應用服務優先設定,附載輕的先透過 Default Limit Range 緩解)

VPA 的使用上有三個小地方需要注意:

  • VPA 在調整 Resource Request 與 Limit 時會將 Pod 重新開啟
  • VPA 會尊重 PDB (Pod Disruption Budget) 來決定是否可以重新開啟 Pod
  • Deployment 的 Label 要設定好,避免 VPA 使用 Label Selector 時選到多個 Deployment,導致不預期的調整結果

Different Node Groups (Pools)

就像一開始所提到的,在現實使用情境不太可能只有一種節點類型,通常都有超多種個,因為自己主要使用的公有雲環境為 AWS,因此底下會就 AWS Auto-Scaling Group 與 Kubernetes Cluster Autoscaler 一些使用上需要注意的地方,避免 Cluster Autoscaler 沒有幫忙增加正確的節點

📚 ASG Resource Tag

理論上在 AWS 的使用情境之下,一個 AWS ASG 通常就是對應到一種 K8s Node Type,因此假如想要讓 Cluster Autoscaler 對不同的 AWS ASG 做不同的動作,做法就是透過位於 ASG 上的 Tag 去做控制,例如某種 Node Type 不想要支援 Cluster Autoscaler,某幾個 Node Type 需要靈敏一點的延展策略…等,這時候就是要針對不同的 AWS ASG 去下不同的 Tag (設定文件)

📚 Scaling-In

有 Scaling Out 當然就會有 Scaling In,當機器的資源使用量太低時,Cluster Autoscaler 就會將節點個數降低,避免資源浪費;但有些 Pod 是不太適合突然被關掉的,例如 Job 類型或是 Replica 為 1 的應用 (Replica 數目大於 1 的應用服務可以設定 PDB),這時候就可以在 annotation 加上 "cluster-autoscaler.kubernetes.io/safe-to-evict":"false" ,當 Cluster Autoscaler 想要將節點移除時,假如運行於其中的 Pod 含有此 annotation 的話,就會被跳過避免憾事發生

📚 1 to N vs. 0 to N

而為了讓 Pod 可以運行在適當的節點類型之中,通常就會探討到 Taints and TolerationsAssigning Pods to Nodes 這兩個議題,當你的 AWS ASG 最小機器數量為 1 時,其實不需要做什麼對應的處理才對,不過當你的 AWS ASG 最小數量為 0 時就沒這麼簡單了

因為讓 Cluster Autoscaler 根據使用量將 AWS ASG X 的機器數目縮減為 0 時,假設突然又有一定要開在 AWS ASG X Node Type 的 Pod 被產生出來,這時候 Cluster Autoscaler 並不會知曉要去哪裡生出這種機器來,因為他從詢問 K8s 和 AWS 都沒人知道,所以這時候 Pod 就會持續卡在 Pending 的狀態

這時候一樣是需要透過 ASG Resource Tag 的方式來解決,將 k8s.io/cluster-autoscaler/node-template/taint/<taint-name> 或是 k8s.io/cluster-autoscaler/node-template/taint/<taint-name> 設定到 tag 中,就可以解決此問題 (細節可以參考設定文件)

Cloud Provider Capacity

把上面的所有設定都完成之後,Pod 的數量與大小會自動根據資源使用量改變,Node 也會自我調節,但千萬不要忘記了就算自己身處在雲端之中,其實底層還是實體的資料中心,只是你看不到而已

📚 Soft Limit

通常 Public Cloud 都不會讓你可以任意的開超多的機器,都會有個預設的上限值,所以記得要去監控這個數值,當快要不夠時記得去申請調高可以使用的機器,CPU 或是 Memory…等上限

假如是因為某些需求需要大量的機器的話,例如大型活動,壓力測試…等,一定要先跟 Cloud Provider 做溝通,避免遇到機器開的太慢或甚至是開不出來的尷尬情況,畢竟底層也是實體機器,提早讓對方知道需求,也可以提早應對與調度資源

📚 Hard Limit

假如你是大型客戶,搞不好會撞到 Public Cloud Provider 的實體機器上限,可能今年度機器的採買就是沒有買那麼多,或是資料中心的空間已滿無法再增加新機器,這時候可能就要考慮搬家了

Conclusion

雖然上面講了一大堆,但其實使用 K8s 要做到 Auto-Scaling 真的比以前單純使用 VM 部署應用服務時來的簡單多了,例如不需要考慮 Scaling Out 的應用程式部署與版本問題,然後再加上使用的人多,所以已經有上面提到的現成且成熟的工具可供使用;希望這篇文章可以幫助到維運 K8s 的人員,讓大家都可以享受到 K8s 自動擴展的好處,把時間節省下來投資在其他更重要的事情上面

--

--

smalltown
Starbugs Weekly 星巴哥技術專欄

原來只是一介草 QA,但開始研究自動化維運雲端服務後,便一頭栽進 DevOps 的世界裏,熱愛鑽研各種可以提升雲端服務品質及增進團隊開發效率的開源技術