# Kubernetes之调度篇

我们在日常使用k8s的过程中总会存在一些特殊的调度情景：

* 让master节点上不部署业务的pod
* 让mysql调度到高IO的节点上
* 让coredns的服务均匀的散布在每个节点
* 让内服服务调度在一个节点上，减少访问延迟

这边肯定会有其他场景也会有对pod的调度有特殊要求，这边只是列举了其中几个情况，对于上述遇到的情况我们需要怎么处理，其实k8s给我们提供了丰富的调度策略来满足我们的需求。下面我们来一一说下这些调度策略。

## nodeSelector

你可以约束一个Pod只能在特定的Node(s)上运行，或者优先运行在特定的节点上。有几种方法可以实现这点，推荐的方法都是用标签选择器来进行选择。通常这样的约束不是必须的，因为调度器将自动进行合理的放置（比如，将 pod 分散到节点上，而不是将 pod 放置在可用资源不足的节点上等等），但在某些情况下，你可以需要更多控制 pod 停靠的节点，例如，确保 pod 最终落在连接了 SSD 的机器上，或者将来自两个不同的服务且有大量通信的 pod 放置在同一个可用区

nodeSelector是节点选择约束的最简单推荐形式。nodeSelector是 PodSpec 的一个字段。 它包含键值对的映射。为了使 pod 可以在某个节点上运行，该节点的标签中必须包含这里的每个键值对（它也可以具有其他标签）。最常见的用法的是一对键值对。

### 添加标签到节点

执行 kubectl get nodes 命令获取集群的节点名称。 选择一个你要增加标签的节点，然后执行命令将标签添加到你所选择的节点上。

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

例如，如果你的节点名称为 'kubernetes-foo-node-1.c.a-robinson.internal' 并且想要的标签是 'disktype=ssd'，则可以执行命令。

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

你可以通过重新运行 kubectl get nodes --show-labels，查看节点当前具有了所指定的标签来验证它是否有效。 你也可以使用 命令查看指定节点的标签完整列表

```
[root@VM_0_13_centos prometheus]# kubectl get nodes --show-labels | grep ssd
10.168.1.4      Ready    <none>   48d   v1.16.3-tke.10   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=QCLOUD,beta.kubernetes.io/os=linux,cloud.tencent.com/node-instance-id=ins-4qz8yvgq,disktype=ssd,failure-domain.beta.kubernetes.io/region=gz,failure-domain.beta.kubernetes.io/zone=100003,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.168.1.4,kubernetes.io/os=linux
[root@VM_0_13_centos prometheus]# kubectl describe node 10.168.1.4
Name:               10.168.1.4
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=QCLOUD
                    beta.kubernetes.io/os=linux
                    cloud.tencent.com/node-instance-id=ins-4qz8yvgq
                    disktype=ssd
                    failure-domain.beta.kubernetes.io/region=gz
                    failure-domain.beta.kubernetes.io/zone=100003
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=10.168.1.4
                    kubernetes.io/os=linux
```

### 添加 nodeSelector 字段到 Pod 配置中

选择任何一个你想运行的 Pod 的配置文件，并且在其中添加一个 nodeSelector 部分。 例如，如果下面是我的 pod 配置

```
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  nodeSelector:
    disktype: ssd
```

执行上述yaml可以发现pod已经到到对应的ssd节点了

![upload-image](https://1743139827-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MMy-ccvHJYp-MpGyagk%2Fsync%2Fee7fda6f286d0b1a93ec1cdecce3d9434c0fbb74.png?generation=1606373434154999\&alt=media)

### k8s默认给节点打的标签

```
kubernetes.io/hostname
failure-domain.beta.kubernetes.io/zone
failure-domain.beta.kubernetes.io/region
topology.kubernetes.io/zone
topology.kubernetes.io/region
beta.kubernetes.io/instance-type
node.kubernetes.io/instance-type
kubernetes.io/os
kubernetes.io/arch
```

## 节点的亲和性和反亲和性

nodeSelector 提供了一种非常简单的方法来将 pod 约束到具有特定标签的节点上。亲和/反亲和功能极大地扩展了你可以表达约束的类型。关键的增强点是

* 语言更具表现力（不仅仅是“完全匹配的 AND”）
* 你可以发现规则是“软”/“偏好”，而不是硬性要求，因此，如果调度器无法满足该要求，仍然调度该 pod
* 你可以使用节点上（或其他拓扑域中）的 pod 的标签来约束，而不是使用节点本身的标签，来允许哪些 pod 可以或者不可以被放置在一起。

亲和功能包含两种类型的亲和，即“节点亲和”和“pod 间亲和/反亲和”。节点亲和就像现有的 nodeSelector（但具有上面列出的前两个好处），然而 pod 间亲和/反亲和约束 pod 标签而不是节点标签（在上面列出的第三项中描述，除了具有上面列出的第一和第二属性）

亲和性调度可以分成软策略和硬策略两种方式:

* 软策略就是如果你没有满足调度要求的节点的话，pod 就会忽略这条规则，继续完成调度过程，说白了就是满足条件最好了，没有的话也无所谓了的策略
* 硬策略就比较强硬了，如果没有满足条件的节点的话，就不断重试直到满足条件为止，简单说就是你必须满足我的要求，不然我就不干的策略。

对于亲和性和反亲和性都有这两种规则可以设置： preferredDuringSchedulingIgnoredDuringExecution和requiredDuringSchedulingIgnoredDuringExecution，前面的就是软策略，后面的就是硬策略。

目前有两种类型的节点亲和，分别为 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution。你可以视它们为“硬”和“软”，意思是，前者指定了将 pod 调度到一个节点上必须满足的规则（就像 nodeSelector 但使用更具表现力的语法），后者指定调度器将尝试执行但不能保证的偏好。名称的“IgnoredDuringExecution”部分意味着，类似于 nodeSelector 的工作原理，如果节点的标签在运行时发生变更，从而不再满足 pod 上的亲和规则，那么 pod 将仍然继续在该节点上运行。

```
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 只能放置在具有标签键为 kubernetes.io/e2e-az-name 且 标签值为 e2e-az1 或 e2e-az2 的节点上。另外，在满足这些标准的节点中，具有标签键为 another-node-label-key 且标签值为 another-node-label-value 的节点应该优先使用。这里的匹配逻辑是 label 的值在某个列表中，现在Kubernetes提供的操作符有下面的几种：

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

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

preferredDuringSchedulingIgnoredDuringExecution 中的 weight 字段值的范围是 1-100。对于每个符合所有调度要求（资源请求，RequiredDuringScheduling 亲和表达式等）的节点，调度器将遍历该字段的元素来计算总和，并且如果节点匹配对应的MatchExpressions，则添加“权重”到总和。然后将这个评分与该节点的其他优先级函数的评分进行组合。总分最高的节点是最优选

## pod的亲和性和反亲和性

pod 间亲和与反亲和使你可以基于已经在节点上运行的 pod 的标签来约束 pod 可以调度到的节点，而不是基于节点上的标签。规则的格式为“如果 X 节点上已经运行了一个或多个 满足规则 Y 的pod，则这个 pod 应该（或者在非亲和的情况下不应该）运行在 X 节点”。Y 表示一个具有可选的关联命令空间列表的 LabelSelector；与节点不同，因为 pod 是命名空间限定的（因此 pod 上的标签也是命名空间限定的），因此作用于 pod 标签的标签选择器必须指定选择器应用在哪个命名空间。从概念上讲，X 是一个拓扑域，如节点，机架，云供应商地区，云供应商区域等。你可以使用 topologyKey 来表示它，topologyKey 是节点标签的键以便系统用来表示这样的拓扑域。这里拓扑域可以从默认的标签中选择

pod 亲和性主要解决 pod 可以和哪些 pod 部署在同一个拓扑域中的问题（其中拓扑域用主机标签实现，可以是单个主机，也可以是多个主机组成的 cluster、zone 等等），而 pod 反亲和性主要是解决 pod 不能和哪些 pod 部署在同一个拓扑域中的问题，它们都是处理的 pod 与 pod 之间的关系，比如一个 pod 在一个节点上了，那么我这个也得在这个节点，或者你这个 pod 在节点上了，那么我就不想和你待在同一个节点上。

由于我们这里只有一个集群，并没有区域或者机房的概念，所以我们这里直接使用主机名来作为拓扑域，把 pod 创建在同一个主机上面。

```
[root@VM_0_13_centos prometheus]# kubectl get nodes --show-labels
NAME            STATUS   ROLES    AGE   VERSION          LABELS
10.168.1.4      Ready    <none>   48d   v1.16.3-tke.10   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=QCLOUD,beta.kubernetes.io/os=linux,cloud.tencent.com/node-instance-id=ins-4qz8yvgq,disktype=ssd,failure-domain.beta.kubernetes.io/region=gz,failure-domain.beta.kubernetes.io/zone=100003,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.168.1.4,kubernetes.io/os=linux
10.168.1.5      Ready    <none>   17d   v1.16.3-tke.10   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=QCLOUD,beta.kubernetes.io/os=linux,cloud.tencent.com/node-instance-id=ins-ft3pcr72,failure-domain.beta.kubernetes.io/region=gz,failure-domain.beta.kubernetes.io/zone=100003,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.168.1.5,kubernetes.io/os=linux
10.168.100.22   Ready    <none>   18d   v1.16.3-tke.10   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/instance-type=QCLOUD,beta.kubernetes.io/os=linux,cloud.tencent.com/node-instance-id=ins-iomm694e,failure-domain.beta.kubernetes.io/region=gz,failure-domain.beta.kubernetes.io/zone=100003,kubernetes.io/arch=amd64,kubernetes.io/hostname=10.168.100.22,kubernetes.io/os=linux
```

与节点亲和一样，当前有两种类型的 pod 亲和与反亲和，即 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution，分表表示“硬性”与“软性”要求。请参阅前面节点亲和部分中的描述。requiredDuringSchedulingIgnoredDuringExecution 亲和的一个示例是“将服务 A 和服务 B 的 pod 放置在同一区域，因为它们之间进行大量交流”，而 preferredDuringSchedulingIgnoredDuringExecution 反亲和的示例将是“将此服务的 pod 跨区域分布”（硬性要求是说不通的，因为你可能拥有的 pod 数多于区域数）。

Pod 间亲和通过 PodSpec 中 affinity 字段下的 podAffinity 字段进行指定。而 pod 间反亲和通过 PodSpec 中 affinity 字段下的 podAntiAffinity 字段进行指定

```
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
```

### podAffinity

下面我来实践下pod的亲和性，下面这个例子中的 pod 需要调度到某个指定的主机上，至少有一个节点上运行了这样的 pod：这个 pod 有一个app=redis的 label。

```
[root@VM_0_13_centos ~]# kubectl get pod --show-labels -o wide | grep app=redis
redis-866f79579-2qdfp                  1/1     Running   0          16d   172.16.3.79    10.168.1.4      <none>           <none>            app=redis,pod-template-hash=866f79579
```

测试pod亲和性的yaml文件

```
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: affinity
  labels:
    app: affinity
spec:
  replicas: 3
  revisionHistoryLimit: 15
  template:
    metadata:
      labels:
        app: affinity
        role: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - redis
            topologyKey: kubernetes.io/hostname
```

我们看到这个 pod 运行在了10.168.1.4的节点上面，所以按照上面的亲和性来说，上面我们部署的3个 pod 副本也应该运行在 10.168.1.4节点上：

```
[root@VM_0_13_centos ~]# kubectl get pod -o wide | grep affinity
affinity-547c75bd94-hkx47              1/1     Running   0          19s   172.16.3.91    10.168.1.4      <none>           <none>
affinity-547c75bd94-tg22d              1/1     Running   0          19s   172.16.3.89    10.168.1.4      <none>           <none>
affinity-547c75bd94-vfm7f              1/1     Running   0          19s   172.16.3.90    10.168.1.4      <none>           <none>
```

下面我们把redis的标签改下，然后重建下deloyment看下，看下会发生什么情况

```
[root@VM_0_13_centos ~]# kubectl get pod --show-labels -o wide | grep app=redis
redis-5b4495ddb4-szjtz                 1/1     Running   0          18s     172.16.3.92    10.168.1.4      <none>           <none>            k8s-app=redis,pod-template-hash=5b4495ddb4,qcloud-app=redis
```

```
[root@VM_0_13_centos ~]# kubectl get pod 
NAME                                   READY   STATUS    RESTARTS   AGE
affinity-547c75bd94-8jcb8              0/1     Pending   0          11s
affinity-547c75bd94-9wv9d              0/1     Pending   0          10s
affinity-547c75bd94-bq8qh              0/1     Pending   0          10s
```

我们可以看到处于Pending状态了，这是因为现在没有一个节点上面拥有busybox-pod这个 label 的 pod，而上面我们的调度使用的是硬策略，所以就没办法进行调度了

我们这个地方使用的是kubernetes.io/hostname这个拓扑域，意思就是我们当前调度的 pod 要和目标的 pod 处于同一个主机上面，因为要处于同一个拓扑域下面，为了说明这个问题，我们把拓扑域改成beta.kubernetes.io/os，同样的我们当前调度的 pod 要和目标的 pod 处于同一个拓扑域中，目标的 pod 是不是拥有beta.kubernetes.io/os=linux的标签，而我们这里3个节点都有这样的标签，这也就意味着我们3个节点都在同一个拓扑域中，所以我们这里的 pod 可能会被调度到任何一个节点：

```
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - redis
            topologyKey: beta.kubernetes.io/os
```

```
[root@VM_0_13_centos ~]# kubectl get pod -o wide | grep affinity
affinity-87649f558-6p72d               1/1     Running   0          3m22s   172.16.2.160   10.168.1.5      <none>           <none>
affinity-87649f558-fpcxg               1/1     Running   0          3m22s   172.16.3.94    10.168.1.4      <none>           <none>
affinity-87649f558-h8scw               1/1     Running   0          2m22s   172.16.2.126   10.168.100.22   <none>           <none>
```

### podAntiAffinity

这就是 pod 亲和性的用法，而 pod 反亲和性则是反着来的，比如一个节点上运行了某个 pod，那么我们的 pod 则希望被调度到其他节点上去，同样我们把上面的 podAffinity 直接改成 podAntiAffinity，(pod-antiaffinity-demo.yaml)

```
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: affinity
  labels:
    app: affinity
spec:
  replicas: 3
  revisionHistoryLimit: 15
  template:
    metadata:
      labels:
        app: affinity
        role: test
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - redis
            topologyKey: kubernetes.io/hostname
```

这里的意思就是如果一个节点上面有一个k8s-app=redis这样的 pod 的话，那么我们的 pod 就别调度到这个节点上面来，上面我们k8s-app=redis在10.168.1.4这个节点上，正常来说我们这里的 pod 不会出现在 10.168.1.4节点上

```
[root@VM_0_13_centos ~]# kubectl get pod -o wide | grep redis
redis-5b4495ddb4-szjtz                 1/1     Running   0          23m     172.16.3.92    10.168.1.4      <none>           <none>
[root@VM_0_13_centos ~]# kubectl get pod -o wide | grep affinity
affinity-6868874dbc-5cc95              1/1     Running   0          2m26s   172.16.2.68    10.168.100.22   <none>           <none>
affinity-6868874dbc-g6vb2              1/1     Running   0          2m28s   172.16.2.162   10.168.1.5      <none>           <none>
affinity-6868874dbc-zbpt4              1/1     Running   0          2m30s   172.16.2.67    10.168.100.22   <none>           <none>
```

## 污点和容忍

污点和容忍度（Toleration）相互配合，可以用来避免 Pod 被分配到不合适的节点上。 每个节点上都可以应用一个或多个污点，这表示对于那些不能容忍这些污点的 Pod，是不会被该节点接受的

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

比如我们的Master 节点保留给 Kubernetes 系统组件使用，或者把一组具有特殊资源预留给某些 pod，则污点就很有用了，pod 不会再被调度到 taint 标记过的节点。我们使用kubeadm搭建的集群默认就给 master 节点添加了一个污点标记，所以我们看到我们平时的 pod 都没有被调度到 master 上

```
[tke@VM_0_13_centos ~]$ kubectl describe node 192.168.100.11
Name:               192.168.100.11
Roles:              master
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=QCLOUD
                    beta.kubernetes.io/os=linux
                    cloud.tencent.com/node-instance-id=ins-1zeb156t
                    failure-domain.beta.kubernetes.io/region=cd
                    failure-domain.beta.kubernetes.io/zone=160001
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=192.168.100.11
                    kubernetes.io/node-role-etcd=true
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/master=true
Annotations:        node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
CreationTimestamp:  Wed, 17 Jun 2020 10:51:00 +0800
Taints:             node-role.kubernetes.io/master:NoSchedule
```

我们可以使用上面的命令查看 master 节点的信息，其中有一条关于 Taints 的信息：node-role.kubernetes.io/master:NoSchedule，就表示给 master 节点打了一个污点的标记，其中影响的参数是NoSchedule，表示 pod 不会被调度到标记为 taints 的节点，除了 NoSchedule 外，还有另外两个选项：

* PreferNoSchedule：NoSchedule 的软策略版本，表示尽量不调度到污点节点上去
* NoExecute：该选项意味着一旦 Taint 生效，如该节点内正在运行的 pod 没有对应 Tolerate 设置，会直接被逐出

### 节点添加和去除污点

您可以使用命令 kubectl taint 给节点增加一个污点。比如，

```
kubectl taint nodes node1 key=value:NoSchedule
```

给节点 node1 增加一个污点，它的键名是 key，键值是 value，效果是 NoSchedule。 这表示只有拥有和这个污点相匹配的容忍度的 Pod 才能够被分配到 node1 这个节点。

```
kubectl taint nodes node1 key:NoSchedule-
```

若要移除上述命令所添加的污点，你可以执行：

```
kubectl taint nodes node1 key:NoSchedule-
```

### pod容忍污点

您可以在 PodSpec 中定义 Pod 的容忍度。 下面两个容忍度均与上面例子中使用 kubectl taint 命令创建的污点相匹配， 因此如果一个 Pod 拥有其中的任何一个容忍度都能够被分配到 node1 ：

```
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"
tolerations:
- key: "key"
  operator: "Exists"
  effect: "NoSchedule"
这里是一个使用了容忍度的 Pod：pods/pod-with-toleration.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    env: test
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  tolerations:
  - key: "example-key"
    operator: "Exists"
    effect: "NoSchedule"
```

对于 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

### 多个污点的pod容忍策略

您可以给一个节点添加多个污点，也可以给一个 Pod 添加多个容忍度设置。 Kubernetes 处理多个污点和容忍度的过程就像一个过滤器：从一个节点的所有污点开始遍历， 过滤掉那些 Pod 中存在与之相匹配的容忍度的污点。余下未被过滤的污点的 effect 值决定了 Pod 是否会被分配到该节点，特别是以下情况：

* 如果未被过滤的污点中存在至少一个 effect 值为 NoSchedule 的污点， 则 Kubernetes 不会将 Pod 分配到该节点。
* 如果未被过滤的污点中不存在 effect 值为 NoSchedule 的污点， 但是存在 effect 值为 PreferNoSchedule 的污点， 则 Kubernetes 会 尝试 将 Pod 分配到该节点。
* 如果未被过滤的污点中存在至少一个 effect 值为 NoExecute 的污点， 则 Kubernetes 不会将 Pod 分配到该节点（如果 Pod 还未在节点上运行）， 或者将 Pod 从该节点驱逐（如果 Pod 已经在节点上运行）。

### NoExecute类型pod继续运行时间

通常情况下，如果给一个节点添加了一个 effect 值为 NoExecute 的污点， 则任何不能忍受这个污点的 Pod 都会马上被驱逐， 任何可以忍受这个污点的 Pod 都不会被驱逐。 但是，如果 Pod 存在一个 effect 值为 NoExecute 的容忍度指定了可选属性 tolerationSeconds 的值，则表示在给节点添加了上述污点之后， Pod 还能继续在节点上运行的时间。例如，

```
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoExecute"
  tolerationSeconds: 3600
```

这表示如果这个 Pod 正在运行，同时一个匹配的污点被添加到其所在的节点， 那么 Pod 还将继续在节点上运行 3600 秒，然后被驱逐。 如果在此之前上述污点被删除了，则 Pod 不会被驱逐。

### 节点发生异常pod发生驱逐的原理

一般我们节点发生异常的时候，节点为什么会把pod驱逐走呢，其实这个背后的原理是污点起的作用，当节点出现了异常的情况的时候，控制器会给节点打上一些污点，由于这个节点上的pod没有容忍这些污点从而被驱逐。下面是节点不同异常状态下控制器给节点打的污点类型

```
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 将删除这个污点。
```

如果节点恢复了，节点控制器会删除给节点打上的污点，这样pod就可以调度到这些节点上了。

如果我希望节点发生异常后，pod不马上发生驱逐，想在节点上再运行一段时间，那么这里可以用tolerationSeconds参数来处理，比如，一个使用了很多本地状态的应用程序在网络断开时，仍然希望停留在当前节点上运行一段较长的时间， 愿意等待网络恢复以避免被驱逐。在这种情况下，Pod 的容忍度可能是下面这样的：

```
tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000
```

节点异常后k8s中控制器会为默认给pod加上tolerationSeconds=600这个参数，避免node发生异常pod立马被驱逐

**说明**：Kubernetes 会自动给 Pod 添加一个 key 为 node.kubernetes.io/not-ready 的容忍度 并配置 tolerationSeconds=300，除非用户提供的 Pod 配置中已经已存在了 key 为 node.kubernetes.io/not-ready 的容忍度。

**同样**，Kubernetes 会给 Pod 添加一个 key 为 node.kubernetes.io/unreachable 的容忍度 并配置 tolerationSeconds=300，除非用户提供的 Pod 配置中已经已存在了 key 为 node.kubernetes.io/unreachable 的容忍度。

### DaemonSet的调度和驱逐

我们在集群中使用ds类型的时候会发现，当你部署ds的负载无论节点上有多少污点都可以调度上去，节点异常ds类型的pod也不发生驱逐，这是为什么呢？其实这里的根本原因也是因为ds类型的负载有添加污点的容忍，我们可以看下kube-proxy类型的ds容忍度是怎么设置的。

```
[root@VM_1_4_centos kubernetes]# kubectl get ds kube-proxy -n kube-system -o yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
......
      serviceAccount: kube-proxy
      serviceAccountName: kube-proxy
      terminationGracePeriodSeconds: 30
      tolerations:
      - operator: Exists
      volumes:
      - hostPath:
```

从上面的yaml中可以看出ds类型容忍度是：空的 key 如果再配合 Exists 就能匹配所有的 key 与 value，也是是能容忍所有 node 的所有 Taints

所以这也就是为什么ds类型的pod不会发生驱逐和能调度到所有节点的原因，因为ds类型容忍所有污点。

## TKE上的调度实践

我们在tke集群中如果要配置调度策略，除了修改编写yaml实现，也可以在控制台进行操作，这样对于一些yaml不是很熟悉的同学可以提供更加简便的配置方式。

![upload-image](https://1743139827-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MMy-ccvHJYp-MpGyagk%2Fsync%2Fa27b181583aefb9a7455b9248c0a1f19bc5d9594.png?generation=1606373434219195\&alt=media)

![upload-image](https://1743139827-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MMy-ccvHJYp-MpGyagk%2Fsync%2F2a0669d5f6bc97fd0edef3c07bd7da58260fc547.png?generation=1606373434690293\&alt=media)

![upload-image](https://1743139827-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MMy-ccvHJYp-MpGyagk%2Fsync%2F99d75ca3924e3c337988489d1e0db62dfe69178b.png?generation=1606373434728028\&alt=media)

![upload-image](https://1743139827-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MMy-ccvHJYp-MpGyagk%2Fsync%2F60d4bfb45909cae2ae278ef7de64604964bc87cf.png?generation=1606373434319560\&alt=media)

控制台支持配置3种策略，分别是不使用调度策略、指定节点调度（nodeSelector）、自定义调度策略（节点亲和性），这里控制台不能配置pod的亲和性和反亲和性，如果需要配置只能通过yaml配置。

如果需要修改已经部署的deployment的调度策略可以通过depolyment的更新调度策略来修改调整

![upload-image](https://1743139827-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MMy-ccvHJYp-MpGyagk%2Fsync%2F05fce2ac857b378f5fdf84e63afb42f25db49bb5.png?generation=1606373434563470\&alt=media)

![upload-image](https://1743139827-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MMy-ccvHJYp-MpGyagk%2Fsync%2Fc308b3f8065e317e3c5431ecae4f55f1a3c792a4.png?generation=1606373434456598\&alt=media)

对于pod的亲和性和反亲和性如何配置，大家可以参考集群中的coredns来进行配置

```
apiVersion: apps/v1beta2
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  generation: 1
  labels:
    addonmanager.kubernetes.io/mode: Reconcile
    k8s-app: kube-dns
    kubernetes.io/cluster-service: "true"
    kubernetes.io/name: CoreDNS
  name: coredns
  namespace: kube-system
spec:
......
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: k8s-app
                operator: In
                values:
                - kube-dns
            topologyKey: kubernetes.io/hostname
      containers:
      - args:
        - -conf
        - /etc/coredns/Corefile
        image: ccr.ccs.tencentyun.com/library/coredns:1.2.2
```

## 参考链接

<https://kubernetes.io/zh/docs/concepts/scheduling-eviction/taint-and-toleration/>

<https://www.qikqiak.com/post/kubernetes-affinity-scheduler/>
