淺入Kubernetes(10):控制節點的部署,選擇器、親和性、汙點

痴者工良發表於2021-04-23

在前面的學習中,我們學到了 Deployment 部署,以及副本數(ReplicaSet),但是 Pod 部署到哪個 Worker 節點是隨機,即使有 3個 Woker 和設定 3個 副本,不一定每個 Node 剛剛好執行一個 Pod,也可能其中 Node 執行著三個副本。

在本篇我們將探究 Kubernetes 中的 DaemonSet、容忍度、親和性、Label、選擇器等概念,以便控制 pod 的部署。

標籤和nodeSelector

標籤(Label)是附加到 Kubernetes 物件上的鍵值對,如果用 json 表示附加到 metadata 的 label:

"metadata": {
  "labels": {
    "key1" : "value1",
    "key2" : "value2"
  }
}

yaml:

metadata:
  labels:
    key1: "value1"
    key2: "value2"

標籤主要是用於表示對使用者有意義的物件的屬性標識。

可以給節點設定一些 Label,例如在 kube-system 名稱空間中,執行著 Kubernetes 的核心元件,我們可以檢視此名稱空間中所有元件的 Label。

kubectl get nodes --namespace=kube-system --show-labels
beta.kubernetes.io/arch=amd64,
beta.kubernetes.io/os=linux,
kubernetes.io/arch=amd64,
... ...

我們也可以手動給一個 Node 新增標籤。

kubectl label nodes <node-name> <label-key>=<label-value>

例如我們給節點設定一個 disksize,表示節點的硬碟是否夠大。

kubectl label nginx disksize=big

然後我們在編寫 yaml 檔案時,希望這個 pod 在容量大的 Node 上執行,可以這樣寫:

  nodeSelector:
    disksize=big

順便聊一下官方的一個例子,設定 Node 的 Label,表示硬碟是 ssd。

kubectl label nodes kubernetes-foo-node-1.c.a-robinson.internal disktype=ssd

在 yaml 檔案的節點選擇器中,新增選擇。

spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd

Label 可以在多個地方使用,例如在 Node 上新增 Label,標識此 Node;而在 NodeSelector 裡使用,可以選擇合適的 Node 執行 Pod;在 metadata 中使用,可以對後設資料加以描述。

在 metadata 中新增的 Label,可以在命令查詢時做篩選。

查詢 pod 的 Label:

kubectl get pods --show-labels

查詢符合條件的 pod(參考 LABELS 欄位,可以根據裡面的標籤選擇):

kubectl get pods -l app=nginx

標籤選擇

在前面,我們學習了 nodeSelector ,可以幫助我們選擇合適的 Node 執行 Pod,實際上 Kubernets 的標籤選擇是豐富多樣的,例如:

  nodeSelector:
    disktype: ssd
    disksize: big

則表示節點選擇器是等值選擇,表示式是 disktype=ssd && disksize=big

標籤選擇有等值和集合兩種,其中等值選擇有 ===!= 三種,=== 無區別。在多個需求(多個label)的情況下,相對於使用 && 運算子,但是選擇器不存在 || 這種邏輯或運算子。

yaml 只支援 {key}:{value} 這種形式,而我們使用命令形式時,則可使用以上三種運算子。

kubectl get nodes -l disktype=ssd,disksize!=big
# 多個條件使用 逗號","" 隔開,而不是 "&&"。

對於集合選擇方式,支援三種操作符:innotinexists。不過別理解成是從集合中選擇,下面舉個例子。

假如有三個 Node,其 disksize 有 big、medium、small,我們要部署一個 pod,在 big、medium 中都可以執行,則:

... -l disksize in (big,medium)
... -l disksize notin (small)
# 不在 small 中執行

而 exists 則跟 != 類似,但是 exists 表示只要存在這個 label 即可,而不論其設定了是什麼值。

-l disksize
# 等同 -l disksize in (big,medium,small)

我們也可以使用 '' 把選擇表示式包起來。

kubectl get pods -l 'app=nginx'

前面已經提到了 yaml 的 nodeSelector 和 命令式的選擇,這裡我們介紹 yaml 的 selector。

前面我們提到在 Deployment 的 metadata 中加上 Label,即 pod 加上 Label,我們也在 kubectl get pods 中使用 Label 選擇過濾 pod。同樣,當我們建立 Service 或者使用 ReplicationController 時,也可以使用標籤選擇合適的 pod。

假如我們已經部署了 nginx,那麼查詢 kubectl get pods --show-labels 時,其 pod 的 LABELS 會有 app=nginx,那麼我們可以這樣選擇:

  selector:
    app: nginx

完整版本:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 6666
status:
  loadBalancer:
    ingress:
      - ip: 192.0.2.127

selector 還支援以下選擇方式 matchLabelsmatchExpressions

matchLabels 是由 {key,value} 對組成的對映。 matchLabels 對映中的單個 {key,value } 等同於 matchExpressions 的元素, 其 key 欄位為 "key",operator 為 "In",而 values 陣列僅包含 "value"。

matchExpressions 是 Pod 選擇算符需求的列表。 有效的運算子包括 InNotInExistsDoesNotExist。 在 InNotIn 的情況下,設定的值必須是非空的。 來自 matchLabelsmatchExpressions 的所有要求都按邏輯與的關係組合到一起 -- 它們必須都滿足才能匹配。

示例如下:

selector:
  matchLabels:
    component: redis
  matchExpressions:
    - {key: tier, operator: In, values: [cache]}
    - {key: environment, operator: NotIn, values: [dev]}

這裡就不在詳細說這些選擇規則了,前面提到的已經夠用了,讀者可以查閱官方文件學習更多複雜的操作:https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/labels/

親和性和反親和性

前面我們學習了 nodeSelector ,使用 nodeSelector 選擇合適的 Label,可以表達我們約束的型別。

親和性則類似於 nodeSelector,可以根據節點上的標籤約束 pod 可以排程到哪些節點。

pod 親和性有兩種別為:

  • requiredDuringSchedulingIgnoredDuringExecution

    硬需求,將 pod 排程到一個節點必須滿足的規則。

  • preferredDuringSchedulingIgnoredDuringExecution

    嘗試執行但是不能保證偏好。

這是官方的一個例子:

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

親和性的約束相對於:

... ... -l kubernetes.io/e2e-az-name in (e2e-az1,e2e-az2)

affinity 設定親密關係,nodeAffinity 設定節點親密關係,最後才到 親和性,它們表示必須滿足和儘量滿足。

如果我們設定了多個 nodeSelectorTerms :

requiredDuringSchedulingIgnoredDuringExecution:
  nodeSelectorTerms:
  ...
  nodeSelectorTerms:

則只需要滿足其中一種即可排程 pod 到 node 上。

如果你同時指定了 nodeSelectornodeAffinity兩者必須都要滿足, 才能將 Pod 排程到候選節點上。

節點親和性語法支援下面的操作符: InNotInExistsDoesNotExistGtLt

Pod 親和性與反親和性的合法操作符有 InNotInExistsDoesNotExist

通過 -Affinity 可以設定親和性,例如節點親和性 nodeAffinity,而且設定反親和性使用 -AntiAffinity,例如 nodeAntiAffinity

反親和性跟親和性一樣,都有 requiredDuringSchedulingIgnoredDuringExecution 硬限制和 preferredDuringSchedulingIgnoredDuringExecution 軟限制,只是反親和性是相反的表示,如果符合條件則不能排程。

關於親和性和反親和性的說明就到這裡,著兩者的配置比較多和複雜,讀者可以參考官方文件,這裡不在贅述。

汙點和容忍度

前面提到親和性和反親和性,我們加以通過 pod 選擇合適的 node,或者 service 選擇合適的 pod,這些擁有 Label 的物件都是被選擇的。

這裡,我們介紹汙點和容忍度,它們可以排斥 “被選擇” 的命運。

節點汙點(taint) 可以排斥一類特定的 pod,而 容忍度(Tolerations)則表示能夠容忍這個物件的汙點。

當節點新增一個汙點後,除非 pod 宣告能夠容忍這個汙點,否則 pod 不會被排程到這個 節點上。

系統會 儘量 避免將 Pod 排程到存在其不能容忍汙點的節點上, 但這不是強制的。Kubernetes 處理多個汙點和容忍度的過程就像一個過濾器:從一個節點的所有汙點開始遍歷, 過濾掉那些 Pod 中存在與之相匹配的容忍度的汙點。

但是如果你只有一個 worker,那麼設定了汙點,那 pod 也只能選擇在這個節點上執行。

新增汙點格式:

kubectl taint node [node] key=value:[effect]

更新汙點或覆蓋:

kubectl taint node [node] key=value:[effect] --overwrite=true

使用 kubectl taint 給節點增加一個汙點。

kubectl taint nodes node1 key1=value1:NoSchedule

移除汙點:

kubectl taint nodes node1 key1=value1:NoSchedule-

其中,汙點需要設定 label ,並設定這個 label 的效果為 NoSchedule。

汙點的效果稱為 effect ,節點的汙點可以設定為以下三種效果:

  • NoSchedule:不能容忍此汙點的 Pod 不會被排程到節點上;不會影響已存在的 pod。
  • PreferNoSchedule:Kubernetes 會避免將不能容忍此汙點的 Pod 安排到節點上。
  • NoExecute:如果 Pod 已在節點上執行,則會將該 Pod 從節點中逐出;如果尚未在節點上執行,則不會將其安排到節點上。

但是某些系統建立的 Pod 可以容忍所有 NoExecuteNoSchedule 汙點,因此不會被逐出,例如 master 節點是不能被部署 pod 的,但是 kube-system 名稱空間卻有很多系統 pod。當然通過修改汙點,可以讓 戶 pod 部署到 master 節點中。

查詢節點的汙點:

kubectl describe nodes | grep Taints
Taints:             node-role.kubernetes.io/master:NoSchedule
Taints:             key1=value1:NoSchedule

系統預設汙點

我們去除 master 的汙點:

kubectl taint node instance-1 node-role.kubernetes.io/master:NoSchedule-

然後部署 nginx pod。

kubectl create deployment nginxtaint --image=nginx:latest --replicas=3

檢視 pod:

kubectl get pods -o wide

結果筆者查到三個副本都在 master 節點上。

為了保證叢集安全,我們需要恢復 master 的汙點。

kubectl taint node instance-1 node-role.kubernetes.io/master:NoSchedule

當某種條件為真時,節點控制器會自動給節點新增一個汙點。當前內建的汙點包括:

  • node.kubernetes.io/not-ready:節點未準備好。這相當於節點狀態 Ready 的值為 "False"。
  • node.kubernetes.io/unreachable:節點控制器訪問不到節點. 這相當於節點狀態 Ready 的值為 "Unknown"。
  • node.kubernetes.io/out-of-disk:節點磁碟耗盡。
  • node.kubernetes.io/memory-pressure:節點存在記憶體壓力。
  • node.kubernetes.io/disk-pressure:節點存在磁碟壓力。
  • node.kubernetes.io/network-unavailable:節點網路不可用。
  • node.kubernetes.io/unschedulable: 節點不可排程。
  • node.cloudprovider.kubernetes.io/uninitialized:如果 kubelet 啟動時指定了一個 "外部" 雲平臺驅動, 它將給當前節點新增一個汙點將其標誌為不可用。在 cloud-controller-manager 的一個控制器初始化這個節點後,kubelet 將刪除這個汙點。

容忍度

一個 node 可以設定汙點,排斥 pod,但是 pod 也可以設定 容忍度,容忍 node 的汙點。

tolerations:
- key: "key1"
  operator: "Exists"
  effect: "NoSchedule"

也可以設定 value。

tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"

operator 的預設值是 Equal

一個容忍度和一個汙點相“匹配”是指它們有一樣的鍵名和效果,並且:

  • 如果 operatorExists

    此時容忍度不能指定 value,如果存在 key 為 key1 的 label,且汙點效果為 NoSchedule,則容忍。

  • 如果 operatorEqual ,則它們的 value 應該相等

如果 effect 留空,則表示只要是 label 為 key1 的節點,都可以容忍。

如果:

tolerations:
  operator: "Exists"

則表示此 pod 能夠容忍任意的汙點,無論 node 怎麼設定 keyvalueeffect ,此 pod 都不會介意。

如果要在 master 上也能部署 pod,則可以修改 pod 的容忍度:

    spec:
      tolerations:
      # this toleration is to have the daemonset runnable on master nodes
      # remove it if your masters can't run pods
      - key: node-role.kubernetes.io/master
        effect: NoSchedule

DaemonSet

在 Kubernetes 中,有三個 -Set ,分別是 ReplicaSet、DaemonSet、StatefulSets。而 負載型別有 Deployments、ReplicaSet、DaemonSet、StatefulSets等(或者說有這幾個控制器)。

前面已經介紹過 Deployments ,而 kind: ReplicaSet 一般是沒必要的,可以在 kind: Deployment 加上 replicas:

kind: DaemonSet 需要使用一個 yaml 來描述,但是整體跟 Deployment 一樣。

DaemonSet 可以確保一個節點只執行一個 Pod 副本,假如有個 nginx 的 pod,當新的 Node 加入叢集時,會自動在這個 Node 上部署一個 pod;當節點從叢集中移開時,這個 Node 上的 Pod 會被回收;如果 DaemontSet 配置被刪除,則也會刪除所有由它建立的 Pod。

DaemonSet 的一些典型用法:

  • 在每個節點上執行叢集守護程式
  • 在每個節點上執行日誌收集守護程式
  • 在每個節點上執行監控守護程式

在 yaml 中,要配置 Daemont,可以使用 tolerations,配置示例:

kind: DaemontSet
... ...

其它地方跟 Deployment 一致。

相關文章