Kubernetes排程之親和與反親和
系列目錄
部署pod時,大多數情況下kubernetes的排程程式能將pod排程到叢集中合適的節點上。但有些情況下使用者需要對pod排程到哪個節點上施加更多控制,比如將特定pod部署到擁有SSD儲存節點、將同一個服務的多個後端部署在不同的機器上提高安全性、將通訊頻繁的服務部署在同一個可用區域降低通訊鏈路長度。使用者對pod部署的節點施加控制都與"label selector"有關。
nodeSelector(節點選擇器)
nodeSelector也是標籤選擇器,是最簡單、最直接控制pod部署node的方法,在daemonset用nodeSelector過濾可部署節點,以下是其普通的應用示例。
步驟1:為節點新增標籤
kubectl get nodes,返回叢集中所有node。
kubectl node label
= ,為node選定的node新增標籤。如kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssd 為pod configuration新增節點選擇器:
apiVersion: v1 kind: Pod metadata: name: nginx labels: env: test spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent nodeSelector: disktype: ssd
當執行kubectl create -f https://k8s.io/examples/pods/pod-nginx.yaml命令建立pod時,節點選擇器選中有上述節點,如果沒有符合條件的node則排程失敗,為pod輸出排程失敗事件並指明失敗原因。pod一直處於pending狀態,直到找到合適的節點。
執行kubectl get pods -o wide檢視排程程式為pod選定的節點。
Interlude: built-in node labels(插曲:內建節點標籤)
上例中使用使用者自定義標籤,也可以使用系統自動生成的內建節點標籤。不同kubernetes版本、不同的基礎裝置供應商,預設新增的內建節點標籤可能不同,需查詢相關文件確認,以下是kubernetes1.4的內建節點標籤:
- kubernetes.io/hostname
- failure-domain.beta.kubernetes.io/zone
- failure-domain.beta.kubernetes.io/region
- beta.kubernetes.io/instance-type
- beta.kubernetes.io/os
- beta.kubernetes.io/arch
親和與反親和(Affinity and anti-affinity)
nodeSelector只能基於節點標籤控制pod部署node,並且選擇器只支援“與”邏輯操作。親和與反親和特性目前處於測試階段,相比於節點選擇器,其更靈活,功能更強大,體現在以下三點:
1) 不僅僅是“與”,支援更多的邏輯表示式。
2) nodeSelector是硬性要求,親和與反親和支援軟硬兩種要求。
3) 除了節點標籤,親和與反親和支援根據節點上已經部署的pod進行節點選擇,這一點很重要。比如不想將兩種計算密集型別的pod部署在同一節點上,後部署pod可選擇過濾。
細分成兩種型別的選擇器:"節點親和"與"內部pod親和、反親和"。節點親和與nodeSelector相似,具備上述1、2兩條優點。內部pod親和依賴的是節點上已有pod的標籤而不是節點標籤,兼俱上述三個優點。因為節點親和能完成nodeSelector所工作並且具備額外的優點,因此nodeSelector雖然還能用,但已經不再維護,並且將來可能刪除。
節點親和(測試特性)
節點親和與nodeSelector工作原理相同,都是其於node標籤選擇節點。有兩點不同,第一是節點親和支援軟硬兩種節點選擇requiredDuringSchedulingIgnoredDuringExecution、preferredDuringSchedulingIgnoredDuringExecution。前者是硬性條件必需滿足,後者是軟性條件,屬於偏好部署。第二點是在選擇節點時,節點親和比nodeSelector支援更多更靈活的表示式,示例如下:
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/e2e-az-name
operator: In
values:
- e2e-az1
- e2e-az2
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-node-label-value
containers:
- name: with-node-affinity
image: k8s.gcr.io/pause:2.0
符合條件的節點必需滿足requiredDuringSchedulingIgnoredDuringExecution的條件,在此基礎上,如果有節點滿足
preferredDuringSchedulingIgnoredDuringExecution的條件,則更傾向部署在後者上。查詢表示式中可以使用的操作符有:
In
, NotIn
, Exists
, DoesNotExist
, Gt
, Lt
等。
在requiredDuringSchedulingIgnoredDuringExecution中的matchExpressions可以包含多條選擇表示式,相互之間是"邏輯與"的關係,必需同時滿足。preferredDuringSchedulingIgnoredDuringExecution中的matchExpressions也可以有多條,因為它是軟性條件,因為並非一定要全匹配,匹配的條目越多越符合條件。另外還可以為偏好中的表示式賦予不同的權重weight,可取的值在0-100之間,最後通過計算權重和決定那個節點更符合條件。
如果同時使用了nodeSelector與nodeAffinity,那麼目標節點必需同時滿足這兩個選擇器。
內部pod親和與反親和(測試特性)
內部pod親和與反親和特性由kubernetes1.4版本初次引入,其基於節點上已部署pod的標籤計算親和與反親和,如實現將兩種通訊頻繁pod部署在相同節點,將兩種計算密集型pod部署在不同節點等。
提示:實現內部親和與反親和需要數量可觀的計算步驟,會明顯降低pod排程的速度,叢集規模越大、節點越多降速越明顯,呈指數級增長,需要在使用此特性時考慮對排程速度的影響。
在配置時,內部pod親和用podAffinity欄位表示,內部pod反親和用podAntiAffinity欄位表示,其它與節點親和一樣,也有軟硬兩種選擇器,每種選擇器可以多個過濾條件。
內部pod親和示例:
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: failure-domain.beta.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: kubernetes.io/hostname
containers:
- name: with-pod-affinity
image: k8s.gcr.io/pause:2.0
上例中的topologyKey用來縮小節點選擇範圍,其值可以是任何合法的節點標籤,在大規模叢集中,為此欄位不指定或者指定錯誤值,可能引發巨大的效能、安全問題。因此,對其使用有如下限制:
對於親和與硬性反親和,topologyKey欄位值不能為空。
對於硬性反親和,topoloygKey只能是kubernetes.io/hostname,除非禁止LimitPodHardAntiAffinityTopology允入控制器或者修改其實現。
對於軟體反親和,允許topoloygKey為空,表示對節點拓撲沒有限制。
以上情況外,topologyKey可以是任何合法標籤。
示例1:用反親和特性實現pod位置協商
假設叢集有五個工作節點,部署一個web應用,假設其用redis作記憶體快取,共需要三個副本,通過反親和將三個redis副本分別部署在三個不同的節點上,提高可用性,Deployment配置如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cache
spec:
selector:
matchLabels:
app: store
replicas: 3
template:
metadata:
labels:
app: store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: redis-server
image: redis:3.2-alpine
反親和會阻止同相的redis副本部署在同一節點上。
現在部署三個nginx web前端,要求三個副本不對分別部署在不同的節點上,通過與上列相似的反親和實現。同時需要將三個web前端部署在其上已經部署redis的節點上,降低通訊成本,通過親和實現,配置如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
selector:
matchLabels:
app: web-store
replicas: 3
template:
metadata:
labels:
app: web-store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-store
topologyKey: "kubernetes.io/hostname"
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: web-app
image: nginx:1.12-alpine
參