這次給大家介紹下k8s的親和性調度:nodeSelector、nodeAffinity、podAffinity、Taints以及Tolerations用法。
一般情況下我們部署的 POD 是通過集群自動調度選擇某個節點的,默認情況下調度器考慮的是資源足夠,並且負載盡量平均,但是有的時候我們需要能夠更加細粒度的去控制 POD 的調度,比如我們內部的一些服務 gitlab 之類的也是跑在Kubernetes
集群上的,我們就不希望對外的一些服務和內部的服務跑在同一個節點上了,害怕內部服務對外部的服務產生影響;有的時候呢我們兩個服務直接交流比較頻繁,又希望能夠將這兩個服務的 POD 調度到同樣的節點上。這就需要用到 Kubernetes 里面的一個概念:親和性,親和性主要分為兩類:nodeAffinity
和podAffinity
。
nodeSelector
我們知道label
是kubernetes
中一個非常重要的概念,用戶可以非常靈活的利用 label 來管理集群中的資源,比如最常見的一個就是 service 通過匹配 label 去選擇 POD 的。而 POD 的調度也可以根據節點的 label 進行特定的部署。
我們可以通過下面的命令查看我們的 node 的 label:
$ kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
192.168.1.140 Ready <none> 42d v1.8.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.1.140
192.168.1.161 Ready <none> 118d v1.8.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/cluster-service=true,kubernetes.io/hostname=192.168.1.161
192.168.1.170 Ready <none> 118d v1.8.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/cluster-service=true,kubernetes.io/hostname=192.168.1.170
192.168.1.172 Ready <none> 114d v1.8.1 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/cluster-service=true,kubernetes.io/hostname=192.168.1.172
現在我們先給節點192.168.1.140增加一個source=qikqiak
的標簽,命令如下:
$ kubectl label nodes 192.168.1.140 source=qikqiak
node "192.168.1.140" labeled
我們可以通過上面的--show-labels
參數可以查看上述標簽是否生效。當 node 被打上了相關標簽后,在調度的時候就可以使用這些標簽了,只需要在 POD 的 spec 字段中添加nodeSelector
字段,里面是我們需要被調度的節點的 label。例如,下面是我們之前的一個默認的 busybox POD 的 YAML 文件:
apiVersion: v1
kind: Pod
metadata:
labels:
app: busybox-pod
name: test-busybox
spec:
containers:
- command:
- sleep
- "3600"
image: busybox
imagePullPolicy: Always
name: test-busybox
然后我需要讓上面的 POD 被調度到140的節點上,那么最簡單的方法就是去匹配140上面的 label,如下:
apiVersion: v1
kind: Pod
metadata:
labels:
app: busybox-pod
name: test-busybox
spec:
containers:
- command:
- sleep
- "3600"
image: busybox
imagePullPolicy: Always
name: test-busybox
nodeSelector:
source: qikqiak
然后我們可以通過 describe 命令查看調度結果:
$ kubectl describe pod test-busybox
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 49s default-scheduler Successfully assigned test-busybox to 192.168.1.140
Normal SuccessfulMountVolume 49s kubelet, 192.168.1.140 MountVolume.SetUp succeeded for volume "default-token-hmpbz"
Normal Pulling 49s kubelet, 192.168.1.140 pulling image "busybox"
Normal Pulled 41s kubelet, 192.168.1.140 Successfully pulled image "busybox"
Normal Created 41s kubelet, 192.168.1.140 Created container
Normal Started 41s kubelet, 192.168.1.140 Started container
我們可以看到 Events 下面的信息,上面的 POD 被正確的調度到了140節點。通過上面的例子我們可以感受到nodeSelector
的方式比較直觀,但是還夠靈活,控制粒度偏大,下面我們再看另外一種更加靈活的方式:nodeAffinity
。
nodeAffinity
nodeAffinity
就是節點親和性,相對應的是Anti-Affinity
,就是反親和性,這種方法比上面的nodeSelector
更加靈活,它可以進行一些簡單的邏輯組合了,不只是簡單的相等匹配。 調度可以分成軟策略和硬策略兩種方式,軟策略就是如果你沒有滿足調度要求的節點的話,POD 就會忽略這條規則,繼續完成調度過程,說白了就是滿足條件最好了,沒有的話也無所謂了的策略;而硬策略就比較強硬了,如果沒有滿足條件的節點的話,就不斷重試直到滿足條件為止,簡單說就是你必須滿足我的要求,不然我就不干的策略。 nodeAffinity
就有兩上面兩種策略:preferredDuringSchedulingIgnoredDuringExecution
和requiredDuringSchedulingIgnoredDuringExecution
,前面的就是軟策略,后面的就是硬策略。
如下例子:(test-node-affinity.yaml)
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
labels:
app: node-affinity-pod
spec:
containers:
- name: with-node-affinity
image: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: NotIn
values:
- 192.168.1.140
- 192.168.1.161
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: source
operator: In
values:
- qikqiak
上面這個 POD 首先是要求 POD 不能運行在140和161兩個節點上,如果有個節點滿足source=qikqiak
的話就優先調度到這個節點上,同樣的我們可以使用descirbe
命令查看具體的調度情況是否滿足我們的要求。這里的匹配邏輯是 label 的值在某個列表中,現在Kubernetes
提供的操作符有下面的幾種:
- In:label 的值在某個列表中
- NotIn:label 的值不在某個列表中
- Gt:label 的值大於某個值
- Lt:label 的值小於某個值
- Exists:某個 label 存在
- DoesNotExist:某個 label 不存在
如果
nodeSelectorTerms
下面有多個選項的話,滿足任何一個條件就可以了;如果matchExpressions
有多個選項的話,則必須同時滿足這些條件才能正常調度 POD。
podAffinity
上面兩種方式都是讓 POD 去選擇節點的,有的時候我們也希望能夠根據 POD 之間的關系進行調度,Kubernetes
在1.4版本引入的podAffinity
概念就可以實現我們這個需求。
和nodeAffinity
類似,podAffinity
也有requiredDuringSchedulingIgnoredDuringExecution
和 preferredDuringSchedulingIgnoredDuringExecution
兩種調度策略,唯一不同的是如果要使用互斥性,我們需要使用podAntiAffinity
字段。 如下例子,我們希望with-pod-affinity
和busybox-pod
能夠就近部署,而不希望和node-affinity-pod
部署在同一個拓撲域下面:(test-pod-affinity.yaml)
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
labels:
app: pod-affinity-pod
spec:
containers:
- name: with-pod-affinity
image: nginx
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- busybox-pod
topologyKey: kubernetes.io/hostname
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- node-affinity-pod
topologyKey: kubernetes.io/hostname
上面這個例子中的 POD 需要調度到某個指定的主機上,至少有一個節點上運行了這樣的 POD:這個 POD 有一個app=busybox-pod
的 label。podAntiAffinity
則是希望最好不要調度到這樣的節點:這個節點上運行了某個 POD,而這個 POD 有app=node-affinity-pod
的 label。根據前面兩個 POD 的定義,我們可以預見上面這個 POD 應該會被調度到140的節點上,因為busybox-pod
被調度到了140節點,而node-affinity-pod
被調度到了140以為的節點,正好滿足上面的需求。通過describe
查看:
$ kubectl describe pod with-pod-affinity
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 8s default-scheduler Successfully assigned with-pod-affinity to 192.168.1.140
Normal SuccessfulMountVolume 7s kubelet, 192.168.1.140 MountVolume.SetUp succeeded for volume "default-token-lcl77"
Normal Pulling 7s kubelet, 192.168.1.140 pulling image "nginx"
上面的事件信息也驗證了我們的想法。
在
labelSelector
和topologyKey
的同級,還可以定義 namespaces 列表,表示匹配哪些 namespace 里面的 pod,默認情況下,會匹配定義的 pod 所在的 namespace;如果定義了這個字段,但是它的值為空,則匹配所有的 namespaces。
查看上面我們定義的3個 POD 結果:
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
test-busybox 1/1 Running 0 8m 172.30.95.18 192.168.1.140
with-node-affinity 1/1 Running 0 10m 172.30.81.25 192.168.1.172
with-pod-affinity 1/1 Running 0 8m 172.30.95.17 192.168.1.140
親和性/反親和性調度策略比較如下:
調度策略 | 匹配標簽 | 操作符 | 拓撲域支持 | 調度目標 |
---|---|---|---|---|
nodeAffinity | 主機 | In, NotIn, Exists, DoesNotExist, Gt, Lt | 否 | 指定主機 |
podAffinity | POD | In, NotIn, Exists, DoesNotExist | 是 | POD與指定POD同一拓撲域 |
podAnitAffinity | POD | In, NotIn, Exists, DoesNotExist | 是 | POD與指定POD不在同一拓撲域 |
污點(Taints)與容忍(tolerations)
對於nodeAffinity
無論是硬策略還是軟策略方式,都是調度 POD 到預期節點上,而Taints
恰好與之相反,如果一個節點標記為 Taints ,除非 POD 也被標識為可以容忍污點節點,否則該 Taints 節點不會被調度pod。
比如用戶希望把 Master 節點保留給 Kubernetes 系統組件使用,或者把一組具有特殊資源預留給某些 POD,則污點就很有用了,POD 不會再被調度到 taint 標記過的節點。taint 標記節點舉例如下:
$ kubectl taint nodes 192.168.1.40 key=value:NoSchedule
node "192.168.1.40" tainted
如果仍然希望某個 POD 調度到 taint 節點上,則必須在 Spec 中做出Toleration
定義,才能調度到該節點,舉例如下:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
effect 共有三個可選項,可按實際需求進行設置:
NoSchedule
:POD 不會被調度到標記為 taints 節點。PreferNoSchedule
:NoSchedule 的軟策略版本。NoExecute
:該選項意味着一旦 Taint 生效,如該節點內正在運行的 POD 沒有對應 Tolerate 設置,會直接被逐出。