這次給大家介紹下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
有多個選項的話,則必須同時滿足這些條件才能正常排程 POD。
matchExpressions
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
的同級,還可以定義 namespaces 清單,表示比對哪些 namespace 裡面的 pod,預設情況下,會比對定義的 pod 所在的 namespace;如果定義了這個字段,但是它的值為空,則比對所有的 namespaces。
topologyKey
檢視上面我們定義的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。
$ kubectl taint nodes 192.168.1.40 key=value:NoSchedule
node "192.168.1.40" tainted
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
-
:POD 不會被排程到标記為 taints 節點。NoSchedule
-
:NoSchedule 的軟政策版本。PreferNoSchedule
-
:該選項意味着一旦 Taint 生效,如該節點内正在運作的 POD 沒有對應 Tolerate 設定,會直接被逐出。NoExecute