K8s 容器的定向排程與親和性
K8s 叢集節點 CPU 使用率高!記憶體溢位(OOM)!當機!導致大量微服務癱瘓怎麼辦?可能是排程策略沒做好,看完這篇文章掌握提高叢集穩定性的管理訣竅。
Kubernetes(K8s)是一個開源的容器編排工具,而容器排程是其非常重要的特性,所謂的排程是指將容器(Pod)分配到叢集中的節點上執行的過程。為了更好地控制容器的排程,K8s 提供了多種排程策略,其中包括定向排程和親和性策略。在實際的 K8s 叢集維護場景中,合理使用這些排程策略,對叢集的穩定性至關重要。本文將透過分享實踐案例,幫助你更好地理解和使用這些功能。
定向排程
定向排程透過 nodeName 和 nodeSelector 來宣告 Pod 期望排程的目標節點,這種方式的排程是強制性的,不管節點是否存在,是否當機,都會往宣告的節點上去排程,當目標不存在或不可排程時,將會導致 Pod 無法執行。
- nodeName
強制將 Pod 排程到指定主機名的節點上,這種方式簡單粗暴,沒有經過 Scheduler 的排程邏輯。
示例:我有一個機器學習的應用,需要排程到叢集中唯一的 GPU 節點上,可以這樣做。
apiVersion: apps/v1
kind: Deployment
metadata:
name: athena
spec:
replicas: 1
selector:
matchLabels:
app: athena
template:
metadata:
labels:
app: athena
spec:
containers:
- name: athena
image: athena:2.0.0
nodeName: k8s-node-gpu-1
- NodeSelector
強制將 Pod 排程到指定標籤的節點上,這種方式透過 Label-selector 機制實現,在 Pod 建立之前,會由 Schedule 的 MatchNodeSelector 排程策略根據 Label 匹配節點,再將 Pod 排程到目標節點上。
示例:我有一個機器學習的應用,需要排程到叢集中帶有 hardware-type:gpu 標籤的節點上,帶有該標籤的節點有多臺,可以這樣做。
apiVersion: apps/v1
kind: Deployment
metadata:
name: athena
spec:
replicas: 1
selector:
matchLabels:
app: athena
template:
metadata:
labels:
app: athena
spec:
containers:
- name: athena
image: athena:2.0.0
nodeSelector:
hardware-type: gpu
# gpu-type: T4 (允許有多label匹配)
定向排程比較簡單粗暴,那有沒有相對溫和、靈活點的排程策略呢?當然是有的,接下來讓我們來看看親和性排程策略。
親和性排程
親和性排程(Affinity)在定向排程的基礎上,透過靈活的節點親和性(nodeAffinity)、Pod 親和性(podAffinity)、Pod 反親和性(podAntiAffinity)規則,滿足更多樣化的排程場景。
- nodeAffinity
比 nodeSelector 更加強大和靈活,可以讓 Pod 滿足更多樣化的條件排程到指定的節點上,支援“軟性排程”(PreferredDuringSchedulingIgnoreDuringExecution)和“硬性排程”(RequiredDuringSchedulingIgnoredDuringExecution)”,硬性排程比較強硬,不滿足條件則排程不成功,而軟性排程相對溫和,屬於傾向性優先選擇滿足條件的節點,並不強求。
讓我們來看兩個示例,加深理解:
示例 1:我有一個機器學習的應用,必須排程到叢集中帶有 hardware-type: gpu,且區域 kubernetes.io/zone 的值為 cn-shenzhen-1 或 cn-shenzhen-2 標籤的節點上。我們可以透過親和性的硬性排程實現,具體如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: athena
spec:
replicas: 2
selector:
matchLabels:
app: athena
template:
metadata:
labels:
app: athena
spec:
containers:
- name: athena
image: athena:2.0.0
affinity:
nodeAffinity:
# 硬性排程,節點必須滿足所有條件才可以排程
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: hardware-type
# 運算
operator: In
values:
- gpu
- key: kubernetes.io/zone
operator: In
values:
- cn-shenzhen-1
- cn-shenzhen-2
Operator 支援的運算子還有:
Exists(key必須存在,value可以是任意的)
DoesNotExist(key不能存在)
In(key的value必須在提供的值列表中)
NotIn(key的value不能在提供的值列表中)
Gt(key的value必須大於提供的值,僅支援整數)
Lt(key的value必須小於提供的值)
示例 2:我有一個機器學習的應用,傾向於排程到叢集中帶有 hardware-type: gpu,且區域 kubernetes.io/zone 的值為 cn-shenzhen-1 或 cn-shenzhen-2 標籤的節點上。我們可以透過親和性的軟性排程實現,如果不能滿足條件,他也會嘗試去排程其他節點,具體如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: athena
spec:
replicas: 2
selector:
matchLabels:
app: athena
template:
metadata:
labels:
app: athena
spec:
containers:
- name: athena
image: athena:2.0.0
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
# 滿足條件的節點會加分,值支援(1-100),分數越高,優先順序越高
# 不加的話,滿足條件的節點權重也為0,不能保證其優先順序。
- weight: 1
preference:
matchExpressions:
- key: hardware-type
# 運算,支援的運算子跟硬性排程一致
operator: In
values:
- gpu
- key: kubernetes.io/zone
operator: In
values:
- cn-shenzhen-1
- cn-shenzhen-2
- Pod 親和性(podAffinity)和反親和性(podAntiAffinity)
顧名思義,Pod 親和性用來指定哪些 Pod 應該跟哪些 Pod 更加靠近,而 Pod 反親和性通常用來打散 Pod,讓某些 Pod 不在同一節點或區域,同樣也有“軟性排程”(PreferredDuringSchedulingIgnoreDuringExecution)”和“硬性排程”
(RequiredDuringSchedulingIgnoredDuringExecution),接下來我將用一個示例,加深對 Pod 親和性和反親和性的理解:
示例:有兩個微服務 zeus 和 athena 相互呼叫比較頻繁,他們都有兩個副本,出於提升效率和可用性考慮,我想將 zeus 和 athena 的副本打散到兩個不同的可用區(zone),並讓他們的副本必須部署到同一個節點上,假設 zeus 已經部署好了,那 athena 的部署可以這樣實現。
apiVersion: apps/v1
kind: Deployment
metadata:
name: athena
spec:
replicas: 2
selector:
matchLabels:
app: athena
template:
metadata:
labels:
app: athena
spec:
containers:
- name: athena
image: athena:2.0.0
affinity:
# Pod親和性
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: zeus
# 拓撲鍵,表示在相同主機上排程
topologyKey: kubernetes.io/hostname
# Pod反親和性
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: athena
# 拓撲鍵,表示在不同區域上排程
topologyKey: topology.kubernetes.io/zone
結 語
在文章開頭我們提到如何藉助排程策略來提升 K8s 叢集的可用性,相信看完全文的小夥伴都可以悟出其中奧妙,我們可以將高計算、高記憶體的 Pod 排程到指定的節點,避免影響關鍵服務執行,另外為了保障微服務的高可用性,我們通常會打散副本到不同的節點或者可用區。
本文首發:SRE運維手記,作者:亦零一