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 有如下限制:
- 對親和性以及
requiredDuringSchedulingIgnoredDuringExecutionPod 反親和性,topologyKey不能為空 - 對
requiredDuringSchedulingIgnoredDuringExecutionPod 反親和性,管理控制器LimitPodHardAntiAffinityTopology被用來限制topologyKey必須為kubernetes.io/hostname。如果想要使用其他的自定義 topology,必須修改該管理控制器,或者將其禁用 - 對
preferredDuringSchedulingIgnoredDuringExecutionPod 反親和性,如果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
