
名詞說明:本文提到的
k8s集群特指阿里雲ack(Alibaba Cloud Container Service for Kubernetes)集群
1、概述
Kubernetes解決了應用的編排、生命周期、自我健康檢查和恢復等問題,隨着應用容器化(雲原生化)的不斷完善和落地,方方面面需要考慮的問題也就隨之而來
其中應用的調度不乏重要,其關乎着應用的穩定性、資源利用率的完整性與合理性
2、原生調度原則
Kubernetes API Server接受客戶端提交Pod對象創建請求后的操作過程中,一個重要的步驟是由調度器程序kube-scheduler從當前集群中選擇一個可用的最佳節點來接收井運行它,通常是默認的調度器default-scheduler負責執行此類任務
KIND: Deployment
VERSION: apps/v1
FIELD: schedulerName <string>
DESCRIPTION:
If specified, the pod will be dispatched by specified scheduler. If not
specified, the pod will be dispatched by default scheduler.
設計調度需要考慮的因素:單獨和整體的資源請求、硬件/軟件/策略限制、親和以及反親和要求、數據局域性、負載間的干擾等等
k8s調度機制是k8s原生提供的一種高效優雅的資源分配機制,它的核心功能是為每個Pod找到最適合它的節點,通過合理利用k8s原生提供的調度能力,根據業務特性配置合理的調度策略,能有效提高集群中的資源利用率
2.1 調度流程
原生的調度流程整體上分為以下三步
-
預選(過濾)——選出可以調度的節點
-
優選(打分)——對選出的節點進行排序
-
選定——按照
pod優先級選定調度的節點
2.2 調度策略
常見的原生調度策略整體上也分為以下幾種類型
-
Topology——拓撲域調度:例如域(
Region)、可用區(Zone)進行拓撲划分 -
nodeName——選定節點調度:直接指定
Node主機名進行調度(點對點) -
NodeSelector——節點選擇器調度:節點標簽選擇器調度
-
NodeAffinity——節點親和性調度:針對
pod和node之間的調度關系,分為硬親和和軟親和 -
podAffinity——Pod親和性調度:針對
pod和pod之間的調度關系,也分為硬親和、軟親和和反親和 -
Taint、Toleration——污點和容忍調度:節點拒絕
Pod調度(污點)和Pod能接納節點污點(容忍)兩個維度
3、應用和服務概況
應用按照服務用途維度划分主要分為兩類:普通service類型和worker類型,其中分別包含
普通service類型
- 有狀態服務:少數服務,如
mysql、redis - 無狀態服務:多數服務
worker類型
- 普通
worker服務 gpu型worker服務
按照應用使用的資源類別划分,可對應用大致分為以下幾類
- 通用計算
CPU計算密集型:大量計算,消耗CPU資源IO密集型:網絡、磁盤IO要求高- 通用型:對
CPU和IO要求相對適中
- 異構計算
GPU計算型:深度學習訓練GPU虛擬化型:圖形和圖像處理
- 某些特殊領域和用途的服務:例如高主頻、高內存等等
4、阿里雲集群概況
4.1 集群概況
本文的kubernetes集群都是由阿里雲ack托管的,其中包含了ACK Pro版和邊緣 Pro 版兩種類型的集群
邊緣 Pro 版主要是涉及雲上雲下的GPU節點混合部署的集群
本文僅討論ACK Pro集群(其中Master節點由阿里雲容器服務創建並托管)
4.1 node節點的規划
4.1.1 阿里雲ecs介紹
選擇服務器的硬件資源配置就和我們購買辦公或個人PC、筆記本一樣,主要需要考慮主板、CPU、內存、硬盤等硬件配置
CPU與內存通信,主要通過地址、數據、控制三大總線
先簡單了解一下CPU核數與內存的配比主要要遵守的基本原則
- 頻率要同步:內存的核心頻率要等於或稍大於
CPU的外頻 - 帶寬要匹配:內存的數據帶寬跟
CPU前端總線的帶寬盡量相等 - 主板要調控:當以上兩個條件有時是不可能同時能滿足時就要靠主板通過異步設置來調控
通常CPU和內存的配比是1:2、1:4、1:8,至於為什么,這也是一個值得討論的話題
阿里雲ack將集群的master節點托管了,因此只需要考慮如何規划node節點。由於是“花錢”買服務,當然要本着較高“性價比”的原則去合理搭配node節點的選型和配比
節點即虛擬機,在阿里雲也叫做ECS,先來看下目前阿里雲通用的x86節點有哪些種類,在阿里雲官網將ECS實例分為很多種:通用型、計算型、內存型、大數據型、本地SSD型、高主頻型、安全增強型、GPU型、異構服務型、突發型、共享型等等(簡直太多了)
為了合理選型,一個重要的途徑是理解ECS的實例規格族的命名方式和其信息布局,常見的如下
-
通用型,適用於大多數場景,代稱是
g系列,其vCPU和內存的配比是1:4 -
計算型,某些場景下對
CPU算力要求會更高一點,代稱是c系列,其vCPU和內存的配比是1:2 -
內存型,提供更多的內存能力,代稱是
r系列,其vCPU和內存的配比是1:8 -
大數據型和本地
SSD型,這兩種的CPU和內存的配比都是1:4,區別在於本地盤的類型不一樣,導致適合的場景也是不一樣的,大數據型的簡稱是d,本地SSD型簡稱是i -
高主頻型,通常的
CPU的主頻應該是2.5G赫茲,有一些可以是達到3.2G赫茲甚至更高,這種就是高主頻型,代號會在前面加上hf標識
阿里雲實例規格的命名方式和規律如下

實例選型原則通常遵循下面三點
- 相同大小的企業級實例比入門級實例性能更穩定,但入門級實例性價比更高,因為企業級實例獨占
vCPU,不存在資源爭搶 - 相同實例規格,新一代比老一代性價比更高,新一代實例規格釋放更多技術紅利
- 合適的實例規格搭配合適的塊存儲類型才能達到預期性能(高效雲盤/
SSD/ESSD/本地盤)
4.1.2 k8s集群節點選型原則
k8s集群節點如何選型?
從CPU為出發點,CPU選定的同時,按照一定配比的內存大小也相應確定
節點區分標准線的划定。vCPU的個數是決定實例價格的關鍵,vCPU個數實例的一個xlarge單位代表4個vCPU,以8xlarge即32個vCPU為分界線,小於32個vCPU的實例划分為較小CPU核數即small節點,把大於或等於32個vCPU的實例划分為較大CPU核數即large節點
但是,需要購買分別高、低至多少個vCPU的節點作為k8s集群的node呢?換句話說,是使用更少的大節點還是使用更多的小節點來組建k8s集群呢?
可以把整個k8s集群中所有node組成的節點理解成為一個大型的單個節點,換句話說,這就和一台價格昂貴全部滿配的物理機一樣,其node節點就是這台物理機虛擬出來的VM
舉個栗子,需要一個節點池總量為64C/256GB的集群,考慮k8s需要多節點,因此兩個比較極端的配比是2台32C/128GB的較大節點和8台8C/32GB的較小節點
這里對大節點和小節點的優缺點分析和列舉了以下幾點
- 大節點個數總量較少,帶來的管理成本會更少
- 大節點支持資源使用較高的“飢餓”型應用,即資源消耗較高的應用
- 發生
node節點級別的擴縮容時,大節點成本更高,因為一次就需要擴容配置較高的節點 - 大節點每個節點運行的
pod數較多,相應的k8s組件壓力更大,當出現大批量pod頻繁創建銷毀時,組件性能、時效性和可靠性都會下降,因此k8s官方推薦的節點pod最大數默認為110 - 大節點
pod副本分布更集中,由於節點數量減少,在同一個節點出現相關pod副本的可能性增大,當出現故障時,對pod影響較大。如果pod副本數更少,那么對整個應用來說,故障率和中斷率也更高 - 按照阿里雲資源預留公式推算的預留資源更多,大節點相對可分配給
pod的資源總量就會變少

說到這里,到底是該使用少量大節點還是大量小節點呢?按照上面的分析,各有利弊,因此並沒有一定之規
退而求其次,可以均衡搭配,使用不同大小的節點來混合構建集群,對於某些特殊的服務,還可以單獨做節點池
另外,對於worker任務型服務,充分利用雲平台彈性伸縮能力,選用搶占式實例,更節省成本
k8s集群初始化創建時,會創建一個默認的節點池default-nodepool,一般會選擇3台中等配置的ECS實例
往往為了應用在不同環境的隔離,在資源足夠的情況下,會將dev、staging、prod等不同環境放在各自不同的集群中,當然也可以將prod生產環境單獨做集群,dev和staging環境放在一套集群,這樣成本更低
剩下就是根據應用的特點、成本設計與規划節點池
4.1.3 k8s集群節點池設計
根據服務特性規划出不同的節點池,混合組建成集群,有助於集群資源利用率的提高
使用搶占式策略,並結合利用了公有雲的彈性伸縮能力,用於自動擴縮集群節點數量,以真正實現資源利用率的提升,可以在較大程度上優化用戶賬單
根據業務現有相關服務的類型特點,加上合理考慮成本與收費類型的前提,加上默認的節點池,將節點池分為以下幾類
| 節點池類型 | 適用環境 | 付費策略 | 節點池/節點命名 | 資源配額 | 服務特點 |
|---|---|---|---|---|---|
| 系統節點池 | prod nonprod | 包年包月 | default-nodepool |
適用於部署k8s集群系統組件 |
|
| 穩定型節點池 | prod nonprod | 包年包月 | ${env}-packets |
32C/128GB/500GB |
適用於副本數相對穩定,資源要求相對適中,穩定性要求相對較高,版本迭代速率相對較小的核心服務 |
| 大磁盤型節點 | prod | 包年包月 | ${env}-disk-large |
32C/256GB/5TB |
適用於對磁盤讀寫空間較大的服務 |
| nonprod | 包年包月 | ${env}-disk-large |
16C/128GB/2TB |
適用於對磁盤讀寫空間較大的服務 | |
| 大配置節點 | prod nonprod | 搶占式 | ${env}-large |
32C/128GB/500GB 32C/256GB/500GB |
適用於CPU/Mem資源要求較大的worker類型服務 |
| 小配置節點 | prod nonprod | 搶占式 | ${env}-small |
16C/64GB/300GB |
適用於CPU/Mem資源要求較小的worker類型服務 |
| 大配置較大磁盤節點 | prod | 搶占式 | ${env}-medium-disk-large |
32C/256GB/1TB |
適用於CPU/Mem/Disk資源要求較大的worker類型服務 |
| nonprod | 搶占式 | ${env}-medium-disk-large |
32C/128GB/500GB |
適用於CPU/Mem/Disk資源要求較大的worker類型服務 |
5、調度策略的設計
5.1 原生調度類型的取舍
有了規划出的不同節點池,就需要根據每個應用的特性,基於原生的調度原則進行調度,但是原生的調度也相對多樣,如何取舍,還是都用?(不是小孩子,當然不能全都要)
例如應用的pod之間還沒有很明顯的親和性及反親和性需求,也還沒有嚴格的網絡入站出站限制的需求,不同節點也都處於雲網絡的各個交換機組成的大型子網內
因此,不采用拓撲域、Pod親和性調度這兩種調度方式
對於nodeName調度,比較單一,也不采用
對於優先和搶占調度,目前也還沒有嚴格意義上區分服務的優先等級,因為可以認為每個服務都是整個業務不可缺少的一環。換句話說,目前對於服務的優先級還沒有明確的評判標准。因此服務質量QoS采用最高的優先級即Guaranteed,其要求Pod里的每個容器都必須有CPU 和內存的request和limit,而且值必須相等
對於NodeSelector和NodeAffinity,后者更為靈活,具有優先調度的功能,其組合方式有兩種,硬親和和軟親和,其判斷親和性的計算方法也多樣,不僅是等值匹配,相當於升級版本的NodeSelector。NodeAffinity適用於讓Pod調度到某些節點上,以及不想讓pod調度到某些節點上,而且如果節點設置了Label,但是Pod沒有任何的NodeAffinity設置,那么Pod還是可以調度到這些節點上的
另外一方面,如果集群中的節點有多種類別,使用NodeAffinity對某一類節點做label,目的是想讓某些pod調度到這些節點。而如果pod沒有標識親和性調度,那么pod有可能調度到集群中其他的節點,這種情況下結果是不可控的,因為總不可能所有節點都標識同樣的親和性
NodeAffinity親和性的設計本身就是為了拉近在調度時pod和node之間的距離,但又沒有辦法避免上面的問題,於是就有了污點和容忍
污點的特點是,常用在某個或某些Node不讓大多數Pod調度而只讓少部分Pod調度
對於Taint和Toleration,出發點一個在於Node,一個在於Pod,其組合方式有以下幾種
| Node污點 | Pod容忍 | 是否調度成功 | 原因 |
|---|---|---|---|
PreferNoSchedule |
PreferNoSchedule |
是 | node的污點與pod的容忍相匹配 |
PreferNoSchedule |
NoSchedule |
是 | node的污點低於pod的容忍 |
PreferNoSchedule |
NoExecute |
是 | node的污點低於pod的容忍 |
NoSchedule |
PreferNoSchedule |
否 | node的污點高於pod的容忍 |
NoSchedule |
NoSchedule |
是 | node的污點與pod的容忍相匹配 |
NoSchedule |
NoExecute |
否 | node的污點與pod的容忍互逆 |
NoExecute |
PreferNoSchedule |
否 | node的污點高於pod的容忍 |
NoExecute |
NoSchedule |
否 | node的污點高於pod的容忍 |
NoExecute |
NoExecute |
否 | pod會不斷重建和殺掉 |
由上述分析可得
Node如果打上PreferNoSchedule的污點,那么Pod只要配置了容忍都會被調度上,甚至於沒有設置任何污點容忍的Pod也能調度到此節點上。原因在於PreferNoSchedule的意思是優先不調度,但是當沒有節點可用時Pod仍然能調度到此節點
Node如果打上NoExecute的污點,那么Pod只要配置了容忍都會被調度上
因此Node的污點類型為NoSchedule,對於不同的節點池,打上不同的污點,例如large:true :NoSchedule
為了規范,對於pod來說,容忍度的操作符統一使用Equal屬性。然后根據不同節點池的污點設置對應的容忍,例如
tolerations:
- effect: NoSchedule
key: large
operator: Equal
value: 'true'
5.2 局部最優解理論
局部最優,是在工程設計中經常采用的理論,指對於一個問題的解在一定范圍或區域內最優,或者說解決問題或達成目標的手段在一定范圍或限制內最優
例如往往對於應用的優化是沒有天花板的,拿到調度方案的設計來說也是一樣,因為多種方案最終還是要為業務服務,隨着業務的復雜性、變化性會不斷變化,這種情況下會更容易實現和接收在某些局部條件下最優(更優)的方案
局部最優解的質量不一定都是差的。尤其是當有了確定的評判標准標明得出的解是可以接受的話,通常會接收局部最優的結果。這樣,從成本、效率等多方面考慮,才是實際工程中會采取的策略

5.3 結合服務特性的調度原則
基於對上面原生調度類型的分析及取舍,可以認為每個不同節點池即每一類節點都算作稀缺資源,通常應該不允許或者不建議對不符合的服務進行調度,也就是每一類節點都應該是被特定調度的。因為有了對應的服務我們才去合理取材,選擇對應特性的節點,這也直接和用戶的賬單掛鈎並且占比非常大
另外一方面,集群沒有多租戶,沒有嚴格的應用隔離性要求
結合應用特性,目前采用的調度主要結合nodeSelector、Taint及Toleration,規范如下
按照節點池的分類,分別給node打上了以下污點
| 節點池 | 標簽(label) | 污點(Taints) |
|---|---|---|
${env}-packets |
packets:true |
packets:true :NoSchedule |
${env}-disk-large |
disk-large:true |
disk-large:true :NoSchedule |
${env}-large |
workload_type:spot prod:true |
prod:true :NoSchedule large:true :NoSchedule |
${env}-small |
workload_type:spot prod:true |
prod:true :NoSchedule small:true :NoSchedule |
${env}-medium-disk-large |
workload_type:spot medium-disk-large:true |
medium-disk-large:true :NoSchedule |
服務調度規范,根據上述node標簽和污點,在對應服務的pod中分別對應需要如下的nodeSelector和tolerations
# 調度到packets節點池的節點
nodeSelector:
packets: 'true'
tolerations:
- effect: NoSchedule
key: packets
operator: Equal
value: 'true'
---
# 調度到disk-large節點池的節點
nodeSelector:
disk-large: 'true'
tolerations:
- effect: NoSchedule
key: disk-large
operator: Equal
value: 'true'
---
# 調度到large節點池的節點
nodeSelector:
prod: 'true'
tolerations:
- effect: NoSchedule
key: prod
operator: Equal
value: 'true'
- effect: NoSchedule
key: large
operator: Equal
value: 'true'
---
# 調度到small節點池的節點
nodeSelector:
prod: 'true'
tolerations:
- effect: NoSchedule
key: prod
operator: Equal
value: 'true'
- effect: NoSchedule
key: small
operator: Equal
value: 'true'
---
# 調度到medium-disk-large節點池的節點
nodeSelector:
medium-disk-large: 'true'
tolerations:
- effect: NoSchedule
key: medium-disk-large
operator: Equal
value: 'true'
5.4 應用分類標准
有了對節點的分類規范及節點的調度方法使用規范,不同的應用服務應該以什么標准來區分它應該調度到哪一類節點呢,主要參考以下標准
-
對於新接入的應用服務,在優化
code的前提下,接入前做好應用占用穩定時資源測試、性能時資源測試以及持續觀測,例如借助於監控、netdata等工具,對應用的資源限制給定一個較為合理的閾值 -
對於普適性應用,沒有一個嚴格的標准來區分是應該調度到大節點還是小節點。因此和大節點和小節點區分標准一樣,加上考慮到節點的資源預留情況,約定以
5C/5G為判定應用資源大小的分界線,小於5個vCPU的應用划分為調度到較小CPU核數即small節點,把大於或等於5個vCPU的實例划分為較大CPU核數即large節點 -
對於占用
CPU較低、但內存等IO要求較高的應用,這類也是應用最為普遍的類型,即IO密集型,應用在運行期間,99%的時間都花在IO上,花在CPU上的時間很少,為了不浪費CPU資源以及碎片化資源的集中管理,盡量保障節點pod數量的均衡,約定將這類應用調度到較大CPU核數即large節點 -
對於特殊應用,例如穩定且核心的、大磁盤的、大內存的、計算密集型的這類應用很好區分,對應節點池調度即可
6、展望
本文分析介紹基於ack,結合k8s原生調度方式,綜合考慮現有應用自身特性、節點池資源特性、成本、效率等方向而設計的調度規范參考
后續隨着業務的復雜性增強,會將更多的調度設計原理與業務相結合,充分利用公有雲及k8s的新生特性,例如在離線業務混合部署、拓撲感知調度、二次調度、彈性容器實例ECI、Serveless函數計算等,甚至於出現可用的調度方式都不滿足的調度需求,實現自定義調度,進而實現更多更好更強貼近業務、使用效率提升的服務調度方式,根據更多的數據指標和服務中鏈路的追蹤來進一步優化業務,最終實現業務的快速迭代、自動部署、獨立高效
See you ~
關注公眾號加群,更多原創干貨與你分享 ~
