NodeName、nodeSelector、nodeAffinity、podAffinity、Taints以及Tolerations用法

1. NodeName

Pod.spec.nodeName用于强制约束将Pod调度到指定的Node节点上,这里说是“调度”,但其实指定了nodeName的Pod会直接跳过Scheduler的调度逻辑,直接写入PodList列表,该匹配规则是强制匹配
例子:
我的预期是将该pod运行于节点名称为 192.168.56.11 这个节点:

1.1 例如(test-nodename.yaml)
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: with-nodename-busybox-pod
  name: with-nodename
spec:
  nodeName: 192.168.56.11 #通过这里指定
  containers:
  - command:
    - sleep
    - "3600"
    image: busybox
    imagePullPolicy: IfNotPresent
    name: test-busybox
1.2 查看创建事件、与预期相符:
Events:
  Type    Reason                 Age   From                    Message
  ----    ------                 ----  ----                    -------
  Normal  Scheduled              3m    default-scheduler       Successfully assigned with-pod-affinity to 192.168.56.11
  Normal  SuccessfulMountVolume  3m    kubelet, 192.168.56.11  MountVolume.SetUp succeeded for volume "default-token-5htws"
  Normal  Pulling                3m    kubelet, 192.168.56.11  pulling image "nginx"
  Normal  Pulled                 1m    kubelet, 192.168.56.11  Successfully pulled image "nginx"
  Normal  Created                1m    kubelet, 192.168.56.11  Created container
  Normal  Started                1m    kubelet, 192.168.56.11  Started container

2. NodeSelector

Pod.spec.nodeSelector是通过kubernetes的label-selector机制进行节点选择,由scheduler调度策略MatchNodeSelector进行label匹配,调度pod到目标节点,该匹配规则是强制约束。使用节点选择器的步骤为:

2.1 Node添加label标记,标记规则:
kubectl label nodes <node-name> <label-key>=<label-value>
kubectl label nodes 192.168.56.14 server_type=game_server
2.2 确认标记
[root@linux-node1 ~]# kubectl get nodes 192.168.56.14 --show-labels
NAME            STATUS    ROLES     AGE       VERSION   LABELS
192.168.56.14   Ready     node      82d       v1.10.1   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/hostname=192.168.56.14,node-role.kubernetes.io/node=true,server_type=game_server
2.3 例如(test-nodeselector.yaml)
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: with-nodeselector-busybox-pod
  name: with-node-selector
spec:
  containers:
  - command:
    - sleep
    - "3600"
    image: busybox
    imagePullPolicy: IfNotPresent
    name: test-busybox
  # 通过节点标签进行预设该pod将会运行于有此标签的节点上面,
  # 如果多个节点拥有此标签,调度器将会择优进行调度
  nodeSelector:
    server_type: game_server
2.4 查看创建事件:
Events:
  Type    Reason                 Age   From                    Message
  ----    ------                 ----  ----                    -------
  Normal  Scheduled              16s   default-scheduler       Successfully assigned with-node-selector to 192.168.56.14
  Normal  SuccessfulMountVolume  16s   kubelet, 192.168.56.14  MountVolume.SetUp succeeded for volume "default-token-5htws"
  Normal  Pulling                11s   kubelet, 192.168.56.14  pulling image "busybox"

通过上面的例子我们可以感受到nodeSelector的方式比较直观,但是还够灵活,控制粒度偏大,下面我们再看另外一种更加灵活的方式:nodeAffinity

3. NodeAffinity

nodeAffinity就是节点亲和性,相对应的是Anti-Affinity,就是反亲和性,这种方法比上面的nodeSelector更加灵活,它可以进行一些简单的逻辑组合了,不只是简单的相等匹配。
调度可以分成软策略和硬策略两种方式,软策略就是如果你没有满足调度要求的节点的话,POD 就会忽略这条规则,继续完成调度过程。 nodeAffinity就有两上面两种策略:

# 软策略:(满足条件最好了,没有的话也无所谓了的策略)
preferredDuringSchedulingIgnoredDuringExecution
# 硬策略:(你必须满足我的要求,不然我就不干)
requiredDuringSchedulingIgnoredDuringExecution
3.1 例如(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
    imagePullPolicy: IfNotPresent
  affinity:
    nodeAffinity:
      # 硬性策略
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: NotIn
            values:
            - 192.168.56.11
            - 192.168.56.12
      # 软性策略
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: server_type
            operator: In
            values:
              - game_server

上面的Pod我们进行一下解读:
1、该pod 通过硬性策略约束后将禁止调度到节点11,12上面去
2、排除节点11,12后,选择其他节点含有label为server_type:game_server的节点运行
3、如果其他节点也没有含有label server_type:game_server, 那么将会调度到任意节点运行
同样的我们可以使用descirbe命令查看具体的调度情况是否满足我们的要求。这里的匹配逻辑是 label 的值在某个列表中,现在Kubernetes提供的操作符有下面的几种:

  • In:label 的值在某个列表中
  • NotIn:label 的值不在某个列表中
  • Gt:label 的值大于某个值
  • Lt:label 的值小于某个值
  • Exists:某个 label 存在
  • DoesNotExist:某个 label 不存在

如果nodeSelectorTerms下面有多个选项的话,满足任何一个条件就可以了;如果matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 POD。

4. PodAffinity

上面三种方式都是让POD去选择节点的,有的时候我们也希望能够根据 POD 之间的关系进行调度,Kubernetes在1.4版本引入的podAffinity概念就可以实现我们这个需求。

nodeAffinity类似,podAffinity也有requiredDuringSchedulingIgnoredDuringExecution(硬性策略)preferredDuringSchedulingIgnoredDuringExecution(软性策略)两种调度策略,唯一不同的是如果要使用互斥性,我们需要使用podAntiAffinity字段。 如下例子,我们希望【with-pod-affinity】和【with-nodename-busybox-pod】能够就近部署,而不希望和【node-affinity-pod】部署在同一个拓扑域下面:

4.1 例如(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:
            - with-nodename-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我们解读一下:
1、POD 需要调度到某个指定的主机上,至少有一个节点上运行了这样的 POD:这个 POD 有一个app=busybox-pod的 label。
2、podAntiAffinity则是希望最好不要调度到这样的节点:这个节点上运行了某个 POD,而这个 POD 有app=node-affinity-pod的 label。
3、根据前面两个 POD 的定义,我们可以预见上面这个 POD 应该会被调度到192.168.56.11的节点上,因为前面实践的时候【with-nodename-busybox-pod】被调度到了192.168.56.11节点,而【node-affinity-pod】被调度到了192.168.56.11以为的节点,正好满足上面的需求。
通过describe查看:

Events:
  Type    Reason                 Age   From                    Message
  ----    ------                 ----  ----                    -------
  Normal  Scheduled              53m   default-scheduler       Successfully assigned with-pod-affinity to 192.168.56.11
  Normal  SuccessfulMountVolume  52m   kubelet, 192.168.56.11  MountVolume.SetUp succeeded for volume "default-token-5htws"
  Normal  Pulling                52m   kubelet, 192.168.56.11  pulling image "nginx"
  Normal  Pulled                 51m   kubelet, 192.168.56.11  Successfully pulled image "nginx"
  Normal  Created                51m   kubelet, 192.168.56.11  Created container
  Normal  Started                51m   kubelet, 192.168.56.11  Started container

在labelSelector和 topologyKey的同级,还可以定义 namespaces 列表,表示匹配哪些 namespace 里面的 pod,默认情况下,会匹配定义的 pod 所在的 namespace;如果定义了这个字段,但是它的值为空,则匹配所有的 namespaces。

so,以上我们创建的四个例子结果为下:

[root@linux-node1 affinity_study]# kubectl get pods -o wide
NAME                          READY     STATUS    RESTARTS   AGE       IP           NODE
with-node-affinity            1/1       Running   2          10h       10.2.70.16   192.168.56.14
with-node-selector            1/1       Running   0          8m        10.2.70.15   192.168.56.14
with-nodename                 1/1       Running   1          11h       10.2.88.9    192.168.56.11
with-pod-affinity             1/1       Running   0          10h       10.2.88.10   192.168.56.11

5. 污点(Taints)与容忍(tolerations)

对于nodeAffinity无论是硬策略还是软策略方式,都是调度 POD 到预期节点上,而Taints恰好与之相反,如果一个节点标记为 Taints ,除非 POD 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度pod。

比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊资源预留给某些 POD,则污点就很有用了,POD 不会再被调度到 taint 标记过的节点。taint 标记节点举例如下:

5.1 设置污点
kubectl taint node [node] key=value[effect]   
     其中[effect] 可取值: [ NoSchedule | PreferNoSchedule | NoExecute ]
      NoSchedule :一定不能被调度。POD 不会被调度到标记为 taints 节点。
      PreferNoSchedule:尽量不要调度。NoSchedule 的软策略版本。
      NoExecute:不仅不会调度,还会驱逐Node上已有的Pod。
 示例:kubectl taint node 192.168.56.11 key1=value1:NoSchedule
5.2 去除污点
#比如设置污点:
    kubectl taint nodes 192.168.56.11 key1=value1:NoSchedule
    kubectl taint nodes 192.168.56.11 key2=value2:NoExecute
    kubectl taint nodes 192.168.56.11 key3=value3:PreferNoSchedule
 #去除指定key及其effect:
    kubectl taint nodes 192.168.56.11 key:[effect]-    #(这里的key不用指定value)

 #去除指定key所有的effect: 
     kubectl taint nodes 192.168.56.11 key-

 #示例:
     kubectl taint node 192.168.56.11 key1:NoSchedule-
     kubectl taint node 192.168.56.11 key2:NoExecute-
     kubectl taint node 192.168.56.11 key3:PreferNoSchedule-
     kubectl taint node 192.168.56.11 key3- (去除指定key的所有effct)
5.3 实践
# 5.3.1 给节点设置污点

首先我的环境中还没有设置污点,我们执行以下例子(nginx-deployment.yaml):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-pod
  labels:
    app: nginx-pod
spec:
  replicas: 10 # 这里为了实践效果特意设定了10个副本
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

# 部署结果:
[root@linux-node1 ~]# kubectl get pods -o wide | grep nginx
nginx-pod-7d9f9876cc-95fj2   1/1       Running   0          1m        10.2.70.26   192.168.56.14
nginx-pod-7d9f9876cc-jclvf   1/1       Running   0          1m        10.2.44.28   192.168.56.13
nginx-pod-7d9f9876cc-k2m6x   1/1       Running   0          1m        10.2.44.31   192.168.56.13
nginx-pod-7d9f9876cc-l74hf   1/1       Running   0          1m        10.2.88.15   192.168.56.11
nginx-pod-7d9f9876cc-n8g6c   1/1       Running   0          1m        10.2.44.29   192.168.56.13
nginx-pod-7d9f9876cc-p2cft   1/1       Running   0          1m        10.2.88.14   192.168.56.11
nginx-pod-7d9f9876cc-p7hvt   1/1       Running   0          1m        10.2.69.25   192.168.56.12
nginx-pod-7d9f9876cc-rjj97   1/1       Running   0          1m        10.2.70.27   192.168.56.14
nginx-pod-7d9f9876cc-t2wlw   1/1       Running   0          1m        10.2.69.26   192.168.56.12
nginx-pod-7d9f9876cc-whkx9   1/1       Running   0          1m        10.2.44.30   192.168.56.13

以上例子可以看出在我的环境中已经分配到了每个节点包括我们接下来准备设置污点的节点192.168.56.11。

下面我把master节点192.168.56.11 设置污点(这里我用的effect是NoSchedule)

[root@linux-node1 affinity_study]# kubectl taint nodes 192.168.56.11 server_type=k8s_system:NoSchedule
node "192.168.56.11" tainted

# 然后部署下面这个设置例子:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-pod-taints
  labels:
    app: nginx-pod-taints
spec:
  replicas: 10 # 这里为了实践效果特意设定了10个副本
  selector:
    matchLabels:
      app: nginx-taints
  template:
    metadata:
      labels:
        app: nginx-taints
    spec:
      containers:
      - name: nginx-taints
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80
# 部署结果:
[root@linux-node1 ~]# kubectl get pods -o wide | grep nginx-pod-taints
nginx-pod-taints-57757d7677-5h6nj   1/1       Running   0          2m        10.2.69.31   192.168.56.12
nginx-pod-taints-57757d7677-dkd8h   1/1       Running   0          2m        10.2.70.34   192.168.56.14
nginx-pod-taints-57757d7677-f2vdn   1/1       Running   0          2m        10.2.44.38   192.168.56.13
nginx-pod-taints-57757d7677-fnx2n   1/1       Running   0          2m        10.2.44.36   192.168.56.13
nginx-pod-taints-57757d7677-gsc5g   1/1       Running   0          2m        10.2.70.32   192.168.56.14
nginx-pod-taints-57757d7677-kjl89   1/1       Running   0          2m        10.2.44.35   192.168.56.13
nginx-pod-taints-57757d7677-mqx27   1/1       Running   0          2m        10.2.70.33   192.168.56.14
nginx-pod-taints-57757d7677-skdd4   1/1       Running   0          2m        10.2.69.32   192.168.56.12
nginx-pod-taints-57757d7677-spwfh   1/1       Running   0          2m        10.2.69.30   192.168.56.12
nginx-pod-taints-57757d7677-tpcm8   1/1       Running   0          2m        10.2.44.37   192.168.56.13

以上例子可以看出我们将master节点设置污点之后调度器并没有把pod调度到master节点上。
这里我选择的effect是NoSchedule,也就是一定不要调度到该节点。

# 5.3.2 给部署配置设置容忍性

首先 我们上面设置了污点
即:

taints:
- effect: NoSchedule
  key: server_type
  value: k8s_system

然后我们部署以下例子(test-tolertations.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-pod-taints-tolerations
  labels:
    app: nginx-pod-taints-tolerations
spec:
  replicas: 10 # 这里为了实践效果特意设定了10个副本
  selector:
    matchLabels:
      app: nginx-taints-tolerations
  template:
    metadata:
      labels:
        app: nginx-taints-tolerations
    spec:
      # 设置容忍性
      tolerations:
      - key: "server_type"
        operator: "Equal"
        value: "k8s_system"
        effect: "NoSchedule"
      containers:
      - name: nginx-taints-tolerations
        image: nginx:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 80

# 部署结果:
[root@linux-node1 affinity_study]# kubectl get pods -o wide  | grep nginx-pod-taints-tolerations
nginx-pod-taints-tolerations-5b4988bd4-2pk46   1/1       Running   0          29m       10.2.69.33   192.168.56.12
nginx-pod-taints-tolerations-5b4988bd4-854xx   1/1       Running   0          29m       10.2.70.35   192.168.56.14
nginx-pod-taints-tolerations-5b4988bd4-88xrt   1/1       Running   0          29m       10.2.69.34   192.168.56.12
nginx-pod-taints-tolerations-5b4988bd4-8czpg   1/1       Running   0          29m       10.2.88.17   192.168.56.11
nginx-pod-taints-tolerations-5b4988bd4-czdss   1/1       Running   0          21m       10.2.44.41   192.168.56.13
nginx-pod-taints-tolerations-5b4988bd4-dj6mw   1/1       Running   0          29m       10.2.88.16   192.168.56.11
nginx-pod-taints-tolerations-5b4988bd4-g4ltd   1/1       Running   0          29m       10.2.44.39   192.168.56.13
nginx-pod-taints-tolerations-5b4988bd4-npthj   1/1       Running   0          29m       10.2.44.40   192.168.56.13
nginx-pod-taints-tolerations-5b4988bd4-ptlqg   1/1       Running   0          29m       10.2.88.18   192.168.56.11
nginx-pod-taints-tolerations-5b4988bd4-t9gs6   1/1       Running   0          29m       10.2.69.35   192.168.56.12

以上实践我们解读一下:
1. 在节点master 192.168.56.11上面设置了污点;但期望该节点能容忍调度;
2. 于是通过设置tolerations来实现,其中Pod要容忍的有污点的Node的key是server_type Equal k8s_system,效果是NoSchedule.
3. 通过设置容忍机制结果与预期相符。

对于tolerations属性的写法:

其中的key、value、effect 与Node的Taint设置需保持一致, 还有以下几点说明:
     1、如果operator的值是Exists,则value属性可省略。
     2、如果operator的值是Equal,则表示其key与value之间的关系是equal(等于)。
     3、如果不指定operator属性,则默认值为Equal。
另外,还有两个特殊值:
     1、空的key 如果再配合Exists 就能匹配所有的key与value ,也是是能容忍所有node的所有Taints。
     2、空的effect 匹配所有的effect。
# 5.3.3 其他

一个node上可以有多个污点:

[root@linux-node1 affinity_study]# kubectl describe node/192.168.56.11
........
........
Taints:             server_type=k8s_system:NoSchedule
                    test=wahaha:PreferNoSchedule
........
........

如果按照我上面的例子来的话结果将不会有任何pod调度到该节点,因为上述我只容忍了一个污点;所以可以新加一个污点容忍:

[root@linux-node1 affinity_study]# more test-tolertations.yaml
........
........      
    tolerations:
      - key: "server_type"
        operator: "Equal"
        value: "k8s_system"
        effect: "NoSchedule"
      - key: "test"
        operator: "Equal"
        value: "wahaha"
        effect: "PreferNoSchedule"
........
........

重新部署后结果也可以预见 只要两个容忍满足就可以调度过去啦~

设置容忍的效果:

  • 如果在设置node的Taints(污点)之前,就已经运行了一些Pod,那么这些Pod是否还能继续在此Node上运行? 这就要看设置Taints污点时的effect(效果)了。
  • 如果effect的值是NoSchedule或PreferNoSchedule,那么已运行的Pod仍然可以运行,只是新Pod(如果没有容忍)不会再往上调度。
  • 如果 pod 不能忍受effect 值为 NoExecute 的 taint,那么 pod 将马上被驱逐
  • 如果 pod 能够忍受effect 值为 NoExecute 的 taint,但是在 toleration 定义中没有指定 tolerationSeconds,则 pod 还会一直在这个节点上运行。
  • 如果 pod 能够忍受effect 值为 NoExecute 的 taint,而且指定了 tolerationSeconds,则 pod 还能在这个节点上继续运行这个指定的时间长度。 虽然是立刻被驱逐,但是K8S为了彰显人性化,又给具有NoExecute效果的污点, 在容忍属性中有一个可选的tolerationSeconds字段,用来设置这些Pod还可以在这个Node之上运行多久,给它们一点宽限的时间,到时间才驱逐。

不同的部署启动方式:

  • 如果是以Pod来启动的,那么Pod被驱逐后, 将不会再被运行,就等于把它删除了。
  • 如果是deployment/rc,那么删除的pod会再其它节点运行。
  • 如果是DaemonSet在此Node上启动的Pod,那么也不会再被运行,直到Node上的NoExecute污被去除或者Pod容忍。

    #设置Pod的宽限时间
    spec:
    tolerations: #设置容忍性

    • key: “test”
      operator: “Equal” #如果操作符为Exists,那么value属性可省略
      value: “16”
      effect: “wahaha”
      tolerationSeconds: 180

      如果运行此Pod的Node,被设置了具有NoExecute效果的Taint(污点),这个Pod将在存活180s后才被驱逐。

      如果没有设置tolerationSeconds字段,将永久运行。

通过对Taints和Tolerations的了解,可以知道,通过它们可以让某些特定应用,独占一个Node,给特定的Node设置一个Taint,只让某些特定的应用来容忍这些污点,容忍后就有可能会被调度到此特定Node,但是也不一定会调度给此特定Node,设置容忍并不阻止调度器调度给其它Node,那么如何让特定应用的Node,只能被调度到此特定的Node呢,这就要结合NodeAffinity节点亲和性,给Node打个标签,然后在Pod属性里设置NodeAffinity到Node。如此就能达到要求了。