名詞說明:本文提到的
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 ~
關注公眾號加群,更多原創干貨與你分享 ~