nodeSelector
提供了一個非常簡單的方式,將 Pod 限定到包含特定標簽的節點上。親和性與反親和性(affinity / anti-affinity)特性則極大地擴展了限定的表達方式。主要的增強點在於:
- 表達方式更加有效(不僅僅是多個精確匹配表達式的“和”關系)
- 可以標識該規則為“soft” / “preference” (軟性的、偏好的)而不是 hard requirement(必須的),此時,如果調度器發現該規則不能被滿足,Pod 仍然可以被調度
- 可以對比節點上(或其他拓撲域 topological domain)已運行的其他 Pod 的標簽,而不僅僅是節點自己的標簽,此時,可以定義類似這樣的規則:某兩類 Pod 不能在同一個節點(或拓撲域)上共存
# 節點親和性
節點親和性(node affinity)的概念與 nodeSelector
相似,可以基於節點的標簽來限定 Pod 可以被調度到哪些節點上。
當前支持兩種類型的節點親和性, requiredDuringSchedulingIgnoredDuringExecution
(hard,目標節點必須滿足此條件) 以及 preferredDuringSchedulingIgnoredDuringExecution
(soft,目標節點最好能滿足此條件)。名字中 IgnoredDuringExecution
意味着:如果 Pod 已經調度到節點上以后,節點的標簽發生改變,使得節點已經不再匹配該親和性規則了,Pod 仍將繼續在節點上執行(這一點與 nodeSelector
相似)。將來,Kubernetes 將會提供 requiredDuringSchedulingRequiredDuringExecution
這個選項,該選項與 requiredDuringSchedulingIgnoredDuringExecution
相似,不同的是,當節點的標簽不在匹配親和性規則之后,Pod 將被從節點上驅逐。
requiredDuringSchedulingIgnoredDuringExecution
的一個例子是,只在 Intel CPU 上運行該 Pod
,preferredDuringSchedulingIgnoredDuringExecution
的一個例子是,盡量在高可用區 XYZ 中運行這個 Pod,但是如果做不到,也可以在其他地方運行該 Pod
。
PodSpec 中通過 affinity.nodeAffinity
字段來定義節點親和性,示例文件如下:
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
此處的親和性規則表明,該 Pod 只能被調度到包含 key 為 kubernetes.io/e2e-az-name
且 value 為 e2e-az1
或 e2e-az2
的標簽的節點上。此外,如果節點已經滿足了前述條件,將優先選擇包含 key 為 another-node-label-key
且 value 為 another-node-label-value
的標簽的節點。
例子中使用了操作符 In
。節點親和性支持如下操作符:In
、NotIn
、Exists
、DoesNotExist
、Gt
、Lt
。使用 NotIn
和 DoesNotExist
可以實現節點反親和性(node anti-affinity)的效果,或者也可以使用 污點 為節點排斥某類 Pod。
如果某個 Pod 同時指定了 nodeSelector
和 nodeAffinity
,則目標節點必須同時滿足兩個條件,才能將 Pod 調度到該節點上。
如果為 nodeAffinity
指定多個 nodeSelectorTerms
,則目標節點只需要滿足任意一個 nodeSelectorTerms
的要求,就可以將 Pod 調度到該節點上。
如果為 nodeSelectorTerms
指定多個 matchExpressions
,則目標節點必須滿足所有的 matchExpressions
的要求,才能將 Pod 調度到該節點上。
當 Pod 被調度到某節點上之后,如果移除或者修改節點的標簽,Pod 將仍然繼續在節點上運行。換句話說,節點親和性規則只在調度該 Pod 時發生作用。
preferredDuringSchedulingIgnoredDuringExecution
中的 weight
字段取值范圍為 1-100。對於每一個滿足調度要求的節點(資源請求、親和性/反親和性規則,等),調度器將遍歷該節點匹配的 preferredDuringSchedulingIgnoredDuringExecution
中所有的weight
並求和。此求和結果將與節點的其他優先級計算的得分合並。得分最高的節點被優先選擇。
# Pod親和性與反親和性
Pod之間的親和性與反親和性(inter-pod affinity and anti-affinity)可以基於已經運行在節點上的 Pod 的標簽(而不是節點的標簽)來限定 Pod 可以被調度到哪個節點上。此類規則的表現形式是:
-
當 X 已經運行了一個或者多個滿足規則 Y 的 Pod 時,待調度的 Pod 應該(或者不應該 - 反親和性)在 X 上運行
-
規則 Y 以 LabelSelector 的形式表述,附帶一個可選的名稱空間列表
與節點不一樣,Pod 是在名稱空間中的(因此,Pod的標簽是在名稱空間中的),針對 Pod 的 LabelSelector 必須同時指定對應的名稱空間
-
X 是一個拓撲域的概念,例如節點、機櫃、雲供應商可用區、雲供應商地域,等。X 以
topologyKey
的形式表達,該 Key代表了節點上代表拓撲域(topology domain)的一個標簽。
-
# pod affinity 的一個例子
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: failure-domain.beta.kubernetes.io/zone
containers:
- name: with-pod-affinity
image: k8s.gcr.io/pause:2.0
該 Pod 的 affinity
定義了一個 Pod 親和性規則和一個 Pod 反親和性規則,例子中, podAffinity
是 requiredDuringSchedulingIgnoredDuringExecution
,而 podAntiAffinity
則是 preferredDuringSchedulingIgnoredDuringExecution
。
- Pod 親和性規則要求,該 Pod 可以被調度到的節點所在的可用區
zone
必須已經有一個已經運行的 Pod 包含標簽 key=security,value=S1,或者更准確地說,節點必須滿足如下條件:- 節點包含 key 為
failure-domain.beta.kubernetes.io/zone
的標簽,假設該標簽的值為V
- 至少有一個包含 key 為
failure-domain.beta.kubernetes.io/zone
且 value 為V
的標簽的節點已經運行了一個包含標簽 key 為security
且 value 為S1
的 Pod
- 節點包含 key 為
- Pod 反親和性規則要求,該 Pod 最好不要被調度到已經運行了包含 key 為
security
且 value 為S2
的標簽的 Pod 的節點上,或者更准確地說,必須滿足如下條件:- 如果
topologyKey
是failure-domain.beta.kubernetes.io/zone
,則,Pod不能被調度到同一個 zone 中的已經運行了包含標簽security: S2
的節點上
- 如果
參考 design doc
可以了解更多 Pod 親和性與反親和性的例子。
原則上, topologyKey
可以是任何合法的標簽 key。然而,處於性能和安全的考慮,仍然對 topologyKey
有如下限制:
- 對親和性以及
requiredDuringSchedulingIgnoredDuringExecution
Pod 反親和性,topologyKey
不能為空 - 對
requiredDuringSchedulingIgnoredDuringExecution
Pod 反親和性,管理控制器LimitPodHardAntiAffinityTopology
被用來限制topologyKey
必須為kubernetes.io/hostname
。如果想要使用其他的自定義 topology,必須修改該管理控制器,或者將其禁用 - 對
preferredDuringSchedulingIgnoredDuringExecution
Pod 反親和性,如果topologyKey
為空,則代表所有的 topology (此時,不局限於kubernetes.io/hostname
、failure-domain.beta.kubernetes.io/zone
和failure-domain.beta.kubernetes.io/region
的組合) - 除了上述的情形以外,
topologyKey
可以是任何合法的標簽 Key
除了 labelSelector
和 topologyKey
以外,還可以指定一個 namespaces
的列表,用作 labelSelector
的作用范圍(與 labelSelector
和 topologyKey
的定義為同一個級別)。如果不定義或者該字段為空,默認為 Pod 所在的名稱空間。
所有與 requiredDuringSchedulingIgnoredDuringExecution
親和性和反親和性關聯的 matchExpressions
必須被滿足,Pod 才能被調度到目標節點。
# 更多實用的例子
Pod 親和性與反親和性結合高級別控制器(例如 ReplicaSet、StatefulSet、Deployment 等)一起使用時,可以非常實用。此時可以很容易的將一組工作復雜調度到同一個 topology,例如,同一個節點。
# 始終在同一個節點
在一個三節點的集群中,部署一個使用 redis 的 web 應用程序,並期望 web-server 盡可能與 redis 在同一個節點上。
下面是 redis deployment 的 yaml 片段,包含三個副本以及 app=store
標簽選擇器。Deployment 中配置了 PodAntiAffinity
,確保調度器不會將三個副本調度到一個節點上:
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
下面是 webserver deployment 的 yaml 片段,配置了 podAntiAffinity
以及 podAffinity
。要求將其副本與 包含 app=store
標簽的 Pod 放在同一個節點上;同時也要求 web-server 的副本不被調度到同一個節點上。
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
如果創建上述兩個 deployment,集群將如下所示:
Node-1 | Node-2 | Node-3 |
---|---|---|
web-server-1 | webserver-2 | webserver-3 |
cache-1 | cache-2 | cache-3 |
web-server
的三個副本都自動與 cach 的副本運行在相同的節點上。
kubectl get pods -o wide
輸出結果如下所示
NAME READY STATUS RESTARTS AGE IP NODE
redis-cache-1450370735-6dzlj 1/1 Running 0 8m 10.192.4.2 kube-node-3
redis-cache-1450370735-j2j96 1/1 Running 0 8m 10.192.2.2 kube-node-1
redis-cache-1450370735-z73mh 1/1 Running 0 8m 10.192.3.1 kube-node-2
web-server-1287567482-5d4dz 1/1 Running 0 7m 10.192.2.3 kube-node-1
web-server-1287567482-6f7v5 1/1 Running 0 7m 10.192.4.3 kube-node-3
web-server-1287567482-s330j 1/1 Running 0 7m 10.192.3.2 kube-node-2
# 始終不在相同的節點上
上面的例子使用了 PodAntiAffinity
規則與 topologyKey: "kubernetes.io/hostname"
來部署 redis 集群,因此沒有任何兩個副本被調度到同一個節點上。參考 ZooKeeper tutorial