Skip to content

官方文档,https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/taint-and-toleration

0. kube-scheduler

1. 什么是调度器

在 Kubernetes中内置了kube-scheduler,它是集群默认的调度器。但kube-scheduler调度器它基本上仅发挥在有限的场景当中,那就是有新的Pod出现了。当我们通过APIServer创建一个Pod时,如果这个Pod尚未绑定到任何节点,那就需要通过调度器来调度这个Pod到对应的节点上运行;

那什么叫尚未绑定到任何节点呢,在Pod规范中有一个nodeName的字段,这个字段指定了该Pod要运行在哪个节点上,如果直接指定该字段的值,也就是直接指明该Pod要在哪个节点运行,那么调度器则不会工作。如果创建的Pod都没有定义nodeName字段的值,也就意味着该字段为空,那这个Pod究竟要运行到哪个节点上,我们的Kubernetes集群没法判定。

所以 kube-scheduler 它会一直监视着APIServer中所有Pod的NodeName字段,当监控到该字段为空时,那么它会为对应的Pod启动调度机制,然后从众多节点中挑选一个最佳的节点。挑选后还需要将选定的节点信息写入到nodeNAME字段,然后返回给APIServer。最后由对应节点上的Kubelet创建并运行该Pod (因为kubeLet也会一直监视着这个字段)

image-20240624165838345

2. 调度器流程

Kubernetes 官方过滤和打分编排源码:https://github.com/kubernetes/kubernetes/blob/281023790fd27eec7bfaa7e26ff1efd45a95fb09/pkg/scheduler/framework/plugins/legacy_registry.go
Kubernetes 官方过滤和打分编排源码:https://github.com/kubernetes/kubernetes/blob/281023790fd27eec7bfaa7e26ff1efd45a95fb09/pkg/scheduler/framework/plugins/legacy_registry.go

一个Pod究竟要调度到哪个节点上,应该有一些挑选标准,或调度算法;kube-scheduLer实现的调度器叫 default-scheduler,如果对调度器有其他要求,Kubernetes也允许自己写一个调度组件来替换默认的kube-scheduLer。

默认调度器在调度一个Pod时,它通过三个步骤来完成调度,(过滤、打分、绑定)

image-20240624170135173

image-20240712094016189

1、过滤阶段(预选策略)

由于Pod内的每一个容器对资源都有不同的需求,所有每个Pod的需求也不同,因此 Pod 在被调度到 Node上之前,会检查候选Node的可用资源能否满足Pod的资源请求。在过滤之后,得出一个Node列表,里面包含了所有可调度节点。如果没有任何一个Node能满足Pod 的资源需求,那么这个 Pod 将一直停留在未调度状态直到调度器能够找到合适的Node。

2、打分阶段(优选策略)

在打分阶段,调度器会为Pod从所有可调度节点中选取一个最合适的 Node。根据当前启用的打分规则,调度器会给每一个可调度节点进行打分,并按综合得分进行排序,如果同时有多个节点得分一致,则随机挑选一个节点。

3、绑定节点

最后调度器会将这个调度决定通知给APIServer,这个过程叫绑定。而后由相应节点的代理程序Kubelet启动Pod。

3. 预选函数

此前我们提到过预选阶段主要用于排除那些不符合Pod申请规范的节点,所以它会通过所有已启用的预选函数对所有节点进行逐一筛查,任何一个预选函数否决,者都会造成该节点被排除掉,反之则会保留进入下一个优选阶段。

1、CheckNodeUnschedulable

检查节点是否被标识为Unschedulable,这个标识表示不可调度,如果Pod能接受此类节点,则保留该节点,否则排除该节点

2、HostName

若Pod资源通过spec.nodeName明确指定了要绑定的目标节点,那么该节点会被保留。

3、PodFitsHostPorts

若容器定义了ports.hostPort属性,那么该预选函数会检查其指定的端口是否已被节点上其他容器或服务所占用,该端口已被占用的节点将统统被排除掉

4、MatchNodeSelector

若Pod资源规范定义了spec.nodeSelector节点选择器字段,则将拥有匹配对应标签的节点保留下来,剩余节点全部排除。

5、PodFitsResources

检查节点是否有足够的CPU或内存资源,能够满足Pod的运行需求。节点会声明其资源的可用容量,而Pod会定义资源需求,于是调度器会判断节点是否有足够可用的资源来运行对应的Pod,如果无法满足则返回失败的原因。(调度器评判节点资源消耗的标准是节点已分配出去的资源使用量,也就是各个容器的Requests之和。)

4. 优选函数

成功通过预选函数过滤后的节点将生成一个可调度列表,随后对节点进行优先级排序阶段。对于每个节点,调度器会使用优选函数分别为其打分(0~10之间的分数),然后将所有的分数进行相加,即为该节点的总得分,最后得分最高者胜出。

1、LeastRequestedPriority

通过计算CPU和Memory的使用率来决定权重,使用率越低的节点权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点。

2、BalancedResourceAllocation

以CPU和内存资源占用率相近的作为评估标准,二者越接近的节点权重越高,该函数不能单独使用,它需要和Least-RequestedPriority组合使用来平衡优化节点资源的使用状态选择在部署当前Pod资源后系统资源更为均衡的节点。

3、NodeAffinityPriority

节点亲和调度机制,它根据Pod资源规范中的spec.nodeSeLector来对给定节点进行匹配度检查,成功匹配到的条目越多则节点得分越高。

4、TaintTolerationPriority

基于Pod资源对节点的污点容调度偏好进行优先级评估,它将Pod对象的tolerations列表与节点的污点进行匹配度检查,成功匹配的条目越多,则节点得分越低。

5、ImageLocalityPriority

镜像亲和调度机制,它根据给定的节点列表上是否拥有运行当前Pod的容器所依赖的镜像文件来计算节点的分值,如果没有所依赖的镜像文件节点得分为日,而存在相关镜像文件的各个节点,被Pod依赖到的镜像文件的体积越大,节点得分越高。

6、SelectorSpreadPriority

尽可能将Pod分散到不同节点上的调度插件,它首先查找标签选择器能匹配当前Pod标签的RS、DepLoyment、StatefuLSet等控制器,而后查找可由这类对象的标签选择器匹配的现存各Pod对象及其所在的节点,而运行此类Pod对象越少的节点得分越高。简单来说:就是将同一标签选择器匹配到的Pod资源打散到不同的节点上运行。

默认调度器能够满足我们绝大多数的要求,能够保证我们的Pod可以被分配到资源充足的节点上运行。但是在实际的线上项目中,可能我们自己会比kubernetes更加了解我们自己的应用,比如我们希望一个Pod只能运行在特定的几个节点上,或者这几个节点只能用来运行特定类型的应用,这就需要我们的调度器能够可控。

1. 节点亲和调度

将一个 Pod 分配到某一个可以满足 Pod 资源请求的节点上,这一过程称之为调度。

在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不满足的需求,因为很多情况下,我们想控制某些Pod到达某些节点上,那么应该怎么做呢?这就要求了解kubernetes对Pod的调度规则,kubernetes提供了四大类调度方式:

  • 自动调度:运行在哪个节点上完全由Scheduler经过一系列的算法计算得出
  • 定向调度:NodeName、NodeSelector
  • 亲和性调度:NodeAffinity、PodAffinity、PodAntiAffinity
  • 污点(容忍)调度:Taints、Toleration

1.1 定向调度

定向调度,指的是利用在pod上声明nodeName或者nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过pod运行失败而已。

NodeName

NodeName用于强制约束将Pod调度到指定的Name的Node节点上。这种方式,其实是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。

  • 首先nodeNamenodeSelector,或亲和性更为直接;

  • 其次nodeName规则的优先级会高于使用nodeSelector或亲和性与非亲和性的规则。

nodeName局限

使用nodeName :为Pod选择节点的方式有一些局限性:

1.如果所指定的节点不存在,则Pod 无法运行,而且在某些情况下可能会被自动删除。

2.如果所指定的节点无法提供Pod所需的资源,则会失败,失败原因也会告知是因为内存还是CPU不足而造成无法运行。

3.在云环境中的节点名称并不是可预测的,也不总是稳定的

例子:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 指定调度到node1节点上
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 指定调度到node1节点上
yaml
#创建Pod
[root@kube-master ~]# kubectl create -f pod-nodename.yaml
pod/pod-nodename created

#查看Pod调度到NODE属性,确实是调度到了node1节点上
[root@kube-master ~]# kubectl get pods pod-nodename -n dev -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP            NODE      ......
pod-nodename   1/1     Running   0          56s   10.244.1.87   node1     ......   

# 接下来,删除pod,修改nodeName的值为node3(并没有node3节点)
[root@kube-master ~]# kubectl delete -f pod-nodename.yaml
pod "pod-nodename" deleted
[root@kube-master ~]# vim pod-nodename.yaml
[root@kube-master ~]# kubectl create -f pod-nodename.yaml
pod/pod-nodename created

#再次查看,发现已经向Node3节点调度,但是由于不存在node3节点,所以pod无法正常运行
[root@kube-master ~]# kubectl get pods pod-nodename -n dev -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP       NODE    ......
pod-nodename   0/1     Pending   0          6s    <none>   node3   ......
#创建Pod
[root@kube-master ~]# kubectl create -f pod-nodename.yaml
pod/pod-nodename created

#查看Pod调度到NODE属性,确实是调度到了node1节点上
[root@kube-master ~]# kubectl get pods pod-nodename -n dev -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP            NODE      ......
pod-nodename   1/1     Running   0          56s   10.244.1.87   node1     ......   

# 接下来,删除pod,修改nodeName的值为node3(并没有node3节点)
[root@kube-master ~]# kubectl delete -f pod-nodename.yaml
pod "pod-nodename" deleted
[root@kube-master ~]# vim pod-nodename.yaml
[root@kube-master ~]# kubectl create -f pod-nodename.yaml
pod/pod-nodename created

#再次查看,发现已经向Node3节点调度,但是由于不存在node3节点,所以pod无法正常运行
[root@kube-master ~]# kubectl get pods pod-nodename -n dev -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP       NODE    ......
pod-nodename   0/1     Pending   0          6s    <none>   node3   ......

NodeSelector

NodeSelector用于将pod调度到添加了指定标签的node节点上。它是通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。

1.首先分别为node节点添加标签

shell
[root@kube-master ~]# kubectl label nodes node1 nodeenv=pro
node/node2 labeled
[root@kube-master ~]# kubectl label nodes node2 nodeenv=test
node/node2 labeled
[root@kube-master ~]# kubectl label nodes node1 nodeenv=pro
node/node2 labeled
[root@kube-master ~]# kubectl label nodes node2 nodeenv=test
node/node2 labeled

2.创建一个pod-nodeselector.yaml文件,并使用它创建Pod

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeSelector: 
    nodeenv: pro # 指定调度到具有nodeenv=pro标签的节点上
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeselector
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeSelector: 
    nodeenv: pro # 指定调度到具有nodeenv=pro标签的节点上
yaml
#创建Pod
[root@kube-master ~]# kubectl create -f pod-nodeselector.yaml
pod/pod-nodeselector created

#查看Pod调度到NODE属性,确实是调度到了node1节点上
[root@kube-master ~]# kubectl get pods pod-nodeselector -n dev -o wide
NAME               READY   STATUS    RESTARTS   AGE     IP          NODE    ......
pod-nodeselector   1/1     Running   0          47s   10.244.1.87   node1   ......

# 接下来,删除pod,修改nodeSelector的值为nodeenv: xxxx(不存在打有此标签的节点)
[root@kube-master ~]# kubectl delete -f pod-nodeselector.yaml
pod "pod-nodeselector" deleted
[root@kube-master ~]# vim pod-nodeselector.yaml
[root@kube-master ~]# kubectl create -f pod-nodeselector.yaml
pod/pod-nodeselector created

#再次查看,发现pod无法正常运行,Node的值为none
[root@kube-master ~]# kubectl get pods -n dev -o wide
NAME               READY   STATUS    RESTARTS   AGE     IP       NODE    
pod-nodeselector   0/1     Pending   0          2m20s   <none>   <none>

# 查看详情,发现node selector匹配失败的提示
[root@kube-master ~]# kubectl describe pods pod-nodeselector -n dev
.......
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.
#创建Pod
[root@kube-master ~]# kubectl create -f pod-nodeselector.yaml
pod/pod-nodeselector created

#查看Pod调度到NODE属性,确实是调度到了node1节点上
[root@kube-master ~]# kubectl get pods pod-nodeselector -n dev -o wide
NAME               READY   STATUS    RESTARTS   AGE     IP          NODE    ......
pod-nodeselector   1/1     Running   0          47s   10.244.1.87   node1   ......

# 接下来,删除pod,修改nodeSelector的值为nodeenv: xxxx(不存在打有此标签的节点)
[root@kube-master ~]# kubectl delete -f pod-nodeselector.yaml
pod "pod-nodeselector" deleted
[root@kube-master ~]# vim pod-nodeselector.yaml
[root@kube-master ~]# kubectl create -f pod-nodeselector.yaml
pod/pod-nodeselector created

#再次查看,发现pod无法正常运行,Node的值为none
[root@kube-master ~]# kubectl get pods -n dev -o wide
NAME               READY   STATUS    RESTARTS   AGE     IP       NODE    
pod-nodeselector   0/1     Pending   0          2m20s   <none>   <none>

# 查看详情,发现node selector匹配失败的提示
[root@kube-master ~]# kubectl describe pods pod-nodeselector -n dev
.......
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.

1.2 什么是节点亲和

节点亲和是指,Pod自身对期望运行的某类节点的倾向性,倾向于运行指定的节点即为“亲和"关系,否则即为”反亲和"关系。

要想实现亲和调度,首先需要在节点上定义便签,而后在Pod对象上通过标签选择器来选择对应的节点标签。而支持这种调度机制的有NodeSeLectorNodeAffinity调度插件。

nodeSelector节点选择器,只能选择拥有指定标签的节点,其实现逻辑较为简单,控制粒度不够细。而nodeAffinity节点亲和,提供对选择逻辑,更强的控制能力。

1.3 节点亲和策略

在Pod上定义节点亲和(nodeAffinity)时有两类亲和关系;(required)强制/硬亲和、(preferred)首选/软亲和。

强制亲和:限定了调度Pod资源时必须满足的规则,如没有可用节点时Pod对象会被置为Pending状态,直到满足规则的节点出现软亲和:相对来强制亲和来说它更加的柔和,它倾向将Pod运行在某类特定的节点上,但无法满足调度需求时,调度器将选择一个无法匹配规则的节点,而非将Pod对象置为Pending状态。

requiredDuringSchedulingIgnoredDuringExecution:调度器只有在规则被满足的时候才能执行调度,强制策略。

preferredDuringSchedulingIgnoredDuringExecution:调度器会尝试寻找满足对应规则的节点,软性亲和。

NodeAffinity

yaml
affinity:
        nodeAffinity:   #节点亲和力配置
          requiredDuringSchedulingIgnoredDuringExecution:  #硬亲和力配置
            nodeSelectorTerms:   #节点选择器配置,只能配置一个
            - matchExpressions:  #匹配条件设置,可以配置多个,如果是多个他们是或的关系,所有条件都可以被匹配
              - key: node        #匹配的node的key设置,可以配置多个,如果配置多个key他们的关系为and,即需要满足所有的条件才会被匹配
                operator: In     #匹配方式,有多种
                values:          #key值,可以写多个
                - "68"
                - "69"
              - key: ip
                operator: In
                values:
                - "68"
            - matchExpressions:
              - key: node
                operator: In
                values:
                - "70"
          preferredDuringSchedulingIgnoredDuringExecution:  #软亲和力配置
          - weight: 1   #软亲和力的权重,权重越高优先级越大,范围1-100
            preference: #软亲和力配置项,和weight同级
              matchExpressions: #匹配条件设置
              - key: node       #匹配的node的key设置,与硬亲和力一致
                operator: In
                values:
                - "71"
affinity:
        nodeAffinity:   #节点亲和力配置
          requiredDuringSchedulingIgnoredDuringExecution:  #硬亲和力配置
            nodeSelectorTerms:   #节点选择器配置,只能配置一个
            - matchExpressions:  #匹配条件设置,可以配置多个,如果是多个他们是或的关系,所有条件都可以被匹配
              - key: node        #匹配的node的key设置,可以配置多个,如果配置多个key他们的关系为and,即需要满足所有的条件才会被匹配
                operator: In     #匹配方式,有多种
                values:          #key值,可以写多个
                - "68"
                - "69"
              - key: ip
                operator: In
                values:
                - "68"
            - matchExpressions:
              - key: node
                operator: In
                values:
                - "70"
          preferredDuringSchedulingIgnoredDuringExecution:  #软亲和力配置
          - weight: 1   #软亲和力的权重,权重越高优先级越大,范围1-100
            preference: #软亲和力配置项,和weight同级
              matchExpressions: #匹配条件设置
              - key: node       #匹配的node的key设置,与硬亲和力一致
                operator: In
                values:
                - "71"

operator:标签匹配的方式

  • In:相当于key = value的形式
  • NotIn:相当于key != value的形式
  • Exists:节点存在label的key为指定的值即可,不能配置values字段
  • DoesNotExist:节点不存在label的key为指定的值即可,不能配置values字段
  • Gt:大于value指定的值
  • Lt:小于value指定的值

❌ 注意

节点清和力配置注意事项

  1. 硬亲和力与软亲和力只能选择一个进行设置,如果同时设置软亲和力将不生效
  2. valuse的值最好使用字符串的形式配置加"",否则在一些情况下会报错,除了Gt与Lt
  3. 亲和力配置不要写的过于复杂,否则会极大的降低k8s调度Pod的效率,也不利于运维人员进行维护

硬限制

requiredDuringSchedulingIgnoredDuringExecution

创建pod-nodeaffinity-required.yaml

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
        nodeSelectorTerms:
        - matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
        nodeSelectorTerms:
        - matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
yaml
# 创建pod
[root@kube-master ~]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created

# 查看pod状态 (运行失败)
[root@kube-master ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP       NODE    ...... 
pod-nodeaffinity-required   0/1     Pending   0          16s   <none>   <none>  ......

# 查看Pod的详情
# 发现调度失败,提示node选择失败
[root@kube-master ~]# kubectl describe pod pod-nodeaffinity-required -n dev
......
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.

#接下来,停止pod
[root@kube-master ~]# kubectl delete -f pod-nodeaffinity-required.yaml
pod "pod-nodeaffinity-required" deleted

# 修改文件,将values: ["xxx","yyy"]------> ["pro","yyy"]
[root@kube-master ~]# vim pod-nodeaffinity-required.yaml

# 再次启动
[root@kube-master ~]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created

# 此时查看,发现调度成功,已经将pod调度到了node1上
[root@kube-master ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE  ...... 
pod-nodeaffinity-required   1/1     Running   0          11s   10.244.1.89   node1 ......
# 创建pod
[root@kube-master ~]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created

# 查看pod状态 (运行失败)
[root@kube-master ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP       NODE    ...... 
pod-nodeaffinity-required   0/1     Pending   0          16s   <none>   <none>  ......

# 查看Pod的详情
# 发现调度失败,提示node选择失败
[root@kube-master ~]# kubectl describe pod pod-nodeaffinity-required -n dev
......
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 3 node(s) didn't match node selector.

#接下来,停止pod
[root@kube-master ~]# kubectl delete -f pod-nodeaffinity-required.yaml
pod "pod-nodeaffinity-required" deleted

# 修改文件,将values: ["xxx","yyy"]------> ["pro","yyy"]
[root@kube-master ~]# vim pod-nodeaffinity-required.yaml

# 再次启动
[root@kube-master ~]# kubectl create -f pod-nodeaffinity-required.yaml
pod/pod-nodeaffinity-required created

# 此时查看,发现调度成功,已经将pod调度到了node1上
[root@kube-master ~]# kubectl get pods pod-nodeaffinity-required -n dev -o wide
NAME                        READY   STATUS    RESTARTS   AGE   IP            NODE  ...... 
pod-nodeaffinity-required   1/1     Running   0          11s   10.244.1.89   node1 ......

软限制

preferredDuringSchedulingIgnoredDuringExecution

创建pod-nodeaffinity-preferred.yaml

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-preferred
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      preferredDuringSchedulingIgnoredDuringExecution: # 软限制
      - weight: 1
        preference:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签(当前环境没有)
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
apiVersion: v1
kind: Pod
metadata:
  name: pod-nodeaffinity-preferred
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    nodeAffinity: #设置node亲和性
      preferredDuringSchedulingIgnoredDuringExecution: # 软限制
      - weight: 1
        preference:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签(当前环境没有)
          - key: nodeenv
            operator: In
            values: ["xxx","yyy"]
yaml
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-nodeaffinity-preferred.yaml
pod/pod-nodeaffinity-preferred created

# 查看pod状态 (运行成功)
[root@k8s-master01 ~]# kubectl get pod pod-nodeaffinity-preferred -n dev
NAME                         READY   STATUS    RESTARTS   AGE
pod-nodeaffinity-preferred   1/1     Running   0          40s
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-nodeaffinity-preferred.yaml
pod/pod-nodeaffinity-preferred created

# 查看pod状态 (运行成功)
[root@k8s-master01 ~]# kubectl get pod pod-nodeaffinity-preferred -n dev
NAME                         READY   STATUS    RESTARTS   AGE
pod-nodeaffinity-preferred   1/1     Running   0          40s

💡 说明

NodeAffinity规则设置的注意事项:

​ 1.如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node上

​ 2.如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可

​ 3.如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的才能匹配成功

​ 4.如果一个pod所在的Node在Pod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求,则系统将忽略此变化

1.4 节点亲和操作符

可以使用 operator 字段来为 Kubernetes 设置在解释规则时要使用的逻辑操作符

操作符含义
In匹配节点标签对应的值是否在列表中,如果在则条件成立;
NotIn匹配节点标签值是否不在列表中,如果不在则条件成立;
Exists匹配节点的标签名称只要存在,则条件成立;key必须存在,value可以是任意的
DoesNotExist匹配不到对应标签名称,则条件成立;key不能存在
Gt匹配到的标签值,如果大于则条件成立;比如CPU核心数大于2,则...
Lt匹配到的标签值,如果小于则条件成立;比如CPU核心数小于1,则...

NodeAffinity的可配置项:

yaml
pod.spec.affinity.nodeAffinity
  requiredDuringSchedulingIgnoredDuringExecution  Node节点必须满足指定的所有规则才可以,相当于硬限制
    nodeSelectorTerms  节点选择列表
      matchFields   按节点字段列出的节点选择器要求列表
      matchExpressions   按节点标签列出的节点选择器要求列表(推荐)
        key    键
        values 值
        operat or 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
  preferredDuringSchedulingIgnoredDuringExecution 优先调度到满足指定的规则的Node,相当于软限制 (倾向)
    preference   一个节点选择器项,与相应的权重相关联
      matchFields   按节点字段列出的节点选择器要求列表
      matchExpressions   按节点标签列出的节点选择器要求列表(推荐)
        key    键
        values 值
        operator 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
	weight 倾向权重,在范围1-100。
pod.spec.affinity.nodeAffinity
  requiredDuringSchedulingIgnoredDuringExecution  Node节点必须满足指定的所有规则才可以,相当于硬限制
    nodeSelectorTerms  节点选择列表
      matchFields   按节点字段列出的节点选择器要求列表
      matchExpressions   按节点标签列出的节点选择器要求列表(推荐)
        key    键
        values 值
        operat or 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
  preferredDuringSchedulingIgnoredDuringExecution 优先调度到满足指定的规则的Node,相当于软限制 (倾向)
    preference   一个节点选择器项,与相应的权重相关联
      matchFields   按节点字段列出的节点选择器要求列表
      matchExpressions   按节点标签列出的节点选择器要求列表(推荐)
        key    键
        values 值
        operator 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
	weight 倾向权重,在范围1-100。

1.5 表达式

yaml
关系符的使用说明:

- matchExpressions:
  - key: nodeenv              # 匹配存在标签的key为nodeenv的节点
    operator: Exists
  - key: nodeenv              # 匹配标签的key为nodeenv,且value是"xxx"或"yyy"的节点
    operator: In
    values: ["xxx","yyy"]
  - key: nodeenv              # 匹配标签的key为nodeenv,且value大于"xxx"的节点
    operator: Gt
    values: "xxx"
关系符的使用说明:

- matchExpressions:
  - key: nodeenv              # 匹配存在标签的key为nodeenv的节点
    operator: Exists
  - key: nodeenv              # 匹配标签的key为nodeenv,且value是"xxx"或"yyy"的节点
    operator: In
    values: ["xxx","yyy"]
  - key: nodeenv              # 匹配标签的key为nodeenv,且value大于"xxx"的节点
    operator: Gt
    values: "xxx"

多个matchExpressions

如果在nodeSelectorTerms中指定了matchExpressions的话,只要有一个条件满足,Pod就可以被调度到对应的节点上。

yaml
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64

案例:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 2
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            # 满足条件的节点会加分,值支持(1-100),分数越高,优先级越高
            # 不加的话,满足条件的节点权重也为0,不能保证其优先级。
            - weight: 1
              preference:
                matchExpressions:
                  - key: hardware-type
                    operator: In
                    values:
                      - gpu
                  - key: kubernetes.io/zone
                    operator: In
                    values:
                      - cn-shenzhen-1
                      - cn-shenzhen-2
apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 2
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            # 满足条件的节点会加分,值支持(1-100),分数越高,优先级越高
            # 不加的话,满足条件的节点权重也为0,不能保证其优先级。
            - weight: 1
              preference:
                matchExpressions:
                  - key: hardware-type
                    operator: In
                    values:
                      - gpu
                  - key: kubernetes.io/zone
                    operator: In
                    values:
                      - cn-shenzhen-1
                      - cn-shenzhen-2

多个条件(key)

如果在matchExpressions中指定了多个条件,则matchExpressions所有条件都满足时,Pod才可以被调度到节点上。

yaml
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64

同时指定nodeSelector 和 nodeAffinity

如果你同时指定了nodeSelector和nodeAffinity,两者必须都要满足,才能将Pod调度到候选节点上。

yaml
#节点为linux,同时arch必须为amd64,并且disktype为hdd
spec:
  nodeSelector:
    kubernetes.io/os: linux
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64
          - key: disktype
            operator: In
            values:
            - hdd
#节点为linux,同时arch必须为amd64,并且disktype为hdd
spec:
  nodeSelector:
    kubernetes.io/os: linux
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values:
            - amd64
          - key: disktype
            operator: In
            values:
            - hdd

2. Pod亲和性与反亲和性调度

https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/assign-pod-node/

https://kubernetes.io/zh-cn/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/

https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/?spm=a2c4g.11186623.2.34.7f4d38f6C1WPWj#affinity-and-anti-affinity

2.1 什么是Pod亲和与反亲和

文档

介绍了两种定向调度的方式,使用起来非常方便,但是也有一定的问题,那就是如果没有满足条件的Node,那么Pod将不会被运行,即使在集群中还有可用Node列表也不行,这就限制了它的使用场景。

基于上面的问题,kubernetes还提供了一种亲和性调度(Affinity)。它在NodeSelector的基础之上的进行了扩展,可以通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。

Affinity主要分为三类:

  • nodeAffinity(node亲和性): 以node为目标,解决pod可以调度到哪些node的问题
  • podAffinity(pod亲和性) : 以pod为目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题
  • podAntiAffinity(pod反亲和性) : 以pod为目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题

💡 说明

关于亲和性(反亲和性)使用场景的说明:

亲和性:如果两个应用频繁交互,那就有必要利用亲和性让两个应用的尽可能的靠近,这样可以减少因网络通信而带来的性能损耗。

反亲和性:当应用的采用多副本部署时,有必要采用反亲和性让各个应用实例打散分布在各个node上,这样可以提高服务的高可用性。

bash
[root@kube-master ~]# kubectl explain pod.spec.affinity
KIND:     Pod
VERSION:  v1

RESOURCE: affinity <Object>

DESCRIPTION:
     If specified, the pod's scheduling constraints

     Affinity is a group of affinity scheduling rules.

FIELDS:
   nodeAffinity	<Object>
     Describes node affinity scheduling rules for the pod.

   podAffinity	<Object>
     Describes pod affinity scheduling rules (e.g. co-locate this pod in the
     same node, zone, etc. as some other pod(s)).

   podAntiAffinity	<Object>
     Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod
     in the same node, zone, etc. as some other pod(s)).
[root@kube-master ~]# kubectl explain pod.spec.affinity
KIND:     Pod
VERSION:  v1

RESOURCE: affinity <Object>

DESCRIPTION:
     If specified, the pod's scheduling constraints

     Affinity is a group of affinity scheduling rules.

FIELDS:
   nodeAffinity	<Object>
     Describes node affinity scheduling rules for the pod.

   podAffinity	<Object>
     Describes pod affinity scheduling rules (e.g. co-locate this pod in the
     same node, zone, etc. as some other pod(s)).

   podAntiAffinity	<Object>
     Describes pod anti-affinity scheduling rules (e.g. avoid putting this pod
     in the same node, zone, etc. as some other pod(s)).

PodAffinity

PodAffinity主要实现以运行的Pod为参照,实现让新创建的Pod跟参照pod在一个区域的功能。

看一下PodAffinity的可配置项:

yaml
pod.spec.affinity.podAffinity

spec:
      affinity:
        podAntiAffinity:   #Pod反亲和配置与亲和配置一致,可以与Pod亲和一起存在
        podAffinity:       #Pod亲和配置,可以与Pod反亲和一起存在
          requiredDuringSchedulingIgnoredDuringExecution:  #硬亲和,与软亲和只能选择其中一种
          - labelSelector:               #Pod标签选择器
              matchExpressions:   #和节点亲和力配置一致,只能配置1个
              - key: name         #pod的标签设置可以设置多个,多个key必须存在于1个Pod
                operator: In      #配置和节点亲和力一致,但是没有Gt和Lt
                values:
                - "blackbox"
                - "blackbox-1"
            namespaces:     # 和哪个命名空间的Pod进行匹配,为空为当前命名空间
            - helm
            topologyKey: kubernetes.io/hostname  #匹配的拓扑域的key,也就是节点上label的key,key和value相同的为同一个域,可以用于标注不同的机房和地区
          preferredDuringSchedulingIgnoredDuringExecution:  #软亲和,与硬亲和只能选择其中一种
          - weight: 100    #权重1-100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: security
                  operator: In
                  values:
                  - S2
              namespaces:
              - default
              topologyKey: failure-domain.beta.kubernetes.io/zone #根据实际情况修改
pod.spec.affinity.podAffinity

spec:
      affinity:
        podAntiAffinity:   #Pod反亲和配置与亲和配置一致,可以与Pod亲和一起存在
        podAffinity:       #Pod亲和配置,可以与Pod反亲和一起存在
          requiredDuringSchedulingIgnoredDuringExecution:  #硬亲和,与软亲和只能选择其中一种
          - labelSelector:               #Pod标签选择器
              matchExpressions:   #和节点亲和力配置一致,只能配置1个
              - key: name         #pod的标签设置可以设置多个,多个key必须存在于1个Pod
                operator: In      #配置和节点亲和力一致,但是没有Gt和Lt
                values:
                - "blackbox"
                - "blackbox-1"
            namespaces:     # 和哪个命名空间的Pod进行匹配,为空为当前命名空间
            - helm
            topologyKey: kubernetes.io/hostname  #匹配的拓扑域的key,也就是节点上label的key,key和value相同的为同一个域,可以用于标注不同的机房和地区
          preferredDuringSchedulingIgnoredDuringExecution:  #软亲和,与硬亲和只能选择其中一种
          - weight: 100    #权重1-100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: security
                  operator: In
                  values:
                  - S2
              namespaces:
              - default
              topologyKey: failure-domain.beta.kubernetes.io/zone #根据实际情况修改
yaml
topologyKey用于指定调度时作用域,例如:
    如果指定为kubernetes.io/hostname,那就是以Node节点为区分范围
	如果指定为beta.kubernetes.io/os,则以Node节点的操作系统类型来区分
topologyKey用于指定调度时作用域,例如:
    如果指定为kubernetes.io/hostname,那就是以Node节点为区分范围
	如果指定为beta.kubernetes.io/os,则以Node节点的操作系统类型来区分

operator:标签匹配的方式

  • In:相当于key = value的形式
  • NotIn:相当于key != value的形式
  • Exists:节点存在label的key为指定的值即可,不能配置values字段
  • DoesNotExist:节点不存在label的key为指定的值即可,不能配置values字段

案例

演示下requiredDuringSchedulingIgnoredDuringExecution,

1)首先创建一个参照Pod,pod-podaffinity-target.yaml

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-target
  namespace: dev
  labels:
    podenv: pro #设置标签
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 将目标pod名确指定到node1上
apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-target
  namespace: dev
  labels:
    podenv: pro #设置标签
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  nodeName: node1 # 将目标pod名确指定到node1上
bash
# 启动目标pod
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-target.yaml
pod/pod-podaffinity-target created

# 查看pod状况
[root@k8s-master01 ~]# kubectl get pods  pod-podaffinity-target -n dev
NAME                     READY   STATUS    RESTARTS   AGE
pod-podaffinity-target   1/1     Running   0          4s
# 启动目标pod
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-target.yaml
pod/pod-podaffinity-target created

# 查看pod状况
[root@k8s-master01 ~]# kubectl get pods  pod-podaffinity-target -n dev
NAME                     READY   STATUS    RESTARTS   AGE
pod-podaffinity-target   1/1     Running   0          4s

2)创建pod-podaffinity-required.yaml

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    podAffinity: #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
          - key: podenv
            operator: In
            values: ["xxx","yyy"]
        topologyKey: kubernetes.io/hostname
apiVersion: v1
kind: Pod
metadata:
  name: pod-podaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    podAffinity: #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配env的值在["xxx","yyy"]中的标签
          - key: podenv
            operator: In
            values: ["xxx","yyy"]
        topologyKey: kubernetes.io/hostname

上面配置表达的意思是:新Pod必须要与拥有标签nodeenv=xxx或者nodeenv=yyy的pod在同一Node上,显然现在没有这样pod

bash
# 启动pod
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created

# 查看pod状态,发现未运行
[root@k8s-master01 ~]# kubectl get pods pod-podaffinity-required -n dev
NAME                       READY   STATUS    RESTARTS   AGE
pod-podaffinity-required   0/1     Pending   0          9s

# 查看详细信息
[root@k8s-master01 ~]# kubectl describe pods pod-podaffinity-required  -n dev
......
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 2 node(s) didn't match pod affinity rules, 1 node(s) had taints that the pod didn't tolerate.

# 接下来修改  values: ["xxx","yyy"]----->values:["pro","yyy"]
# 意思是:新Pod必须要与拥有标签nodeenv=xxx或者nodeenv=yyy的pod在同一Node上
[root@k8s-master01 ~]# vim pod-podaffinity-required.yaml

# 然后重新创建pod,查看效果
[root@k8s-master01 ~]# kubectl delete -f  pod-podaffinity-required.yaml
pod "pod-podaffinity-required" de leted
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created

# 发现此时Pod运行正常
[root@k8s-master01 ~]# kubectl get pods pod-podaffinity-required -n dev
NAME                       READY   STATUS    RESTARTS   AGE   LABELS
pod-podaffinity-required   1/1     Running   0          6s    <none>
# 启动pod
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created

# 查看pod状态,发现未运行
[root@k8s-master01 ~]# kubectl get pods pod-podaffinity-required -n dev
NAME                       READY   STATUS    RESTARTS   AGE
pod-podaffinity-required   0/1     Pending   0          9s

# 查看详细信息
[root@k8s-master01 ~]# kubectl describe pods pod-podaffinity-required  -n dev
......
Events:
  Type     Reason            Age        From               Message
  ----     ------            ----       ----               -------
  Warning  FailedScheduling  <unknown>  default-scheduler  0/3 nodes are available: 2 node(s) didn't match pod affinity rules, 1 node(s) had taints that the pod didn't tolerate.

# 接下来修改  values: ["xxx","yyy"]----->values:["pro","yyy"]
# 意思是:新Pod必须要与拥有标签nodeenv=xxx或者nodeenv=yyy的pod在同一Node上
[root@k8s-master01 ~]# vim pod-podaffinity-required.yaml

# 然后重新创建pod,查看效果
[root@k8s-master01 ~]# kubectl delete -f  pod-podaffinity-required.yaml
pod "pod-podaffinity-required" de leted
[root@k8s-master01 ~]# kubectl create -f pod-podaffinity-required.yaml
pod/pod-podaffinity-required created

# 发现此时Pod运行正常
[root@k8s-master01 ~]# kubectl get pods pod-podaffinity-required -n dev
NAME                       READY   STATUS    RESTARTS   AGE   LABELS
pod-podaffinity-required   1/1     Running   0          6s    <none>

PodAntiAffinity

PodAntiAffinity主要实现以运行的Pod为参照,让新创建的Pod跟参照pod不在一个区域中的功能。

它的配置方式和选项跟PodAffinty是一样的

1)继续使用上个案例中目标pod

bash
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide --show-labels
NAME                     READY   STATUS    RESTARTS   AGE     IP            NODE    LABELS
pod-podaffinity-required 1/1     Running   0          3m29s   10.244.1.38   node1   <none>     
pod-podaffinity-target   1/1     Running   0          9m25s   10.244.1.37   node1   podenv=pro
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide --show-labels
NAME                     READY   STATUS    RESTARTS   AGE     IP            NODE    LABELS
pod-podaffinity-required 1/1     Running   0          3m29s   10.244.1.38   node1   <none>     
pod-podaffinity-target   1/1     Running   0          9m25s   10.244.1.37   node1   podenv=pro

2)创建pod-podantiaffinity-required.yaml

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-podantiaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    podAntiAffinity: #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配podenv的值在["pro"]中的标签
          - key: podenv
            operator: In
            values: ["pro"]
        topologyKey: kubernetes.io/hostname
apiVersion: v1
kind: Pod
metadata:
  name: pod-podantiaffinity-required
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  affinity:  #亲和性设置
    podAntiAffinity: #设置pod亲和性
      requiredDuringSchedulingIgnoredDuringExecution: # 硬限制
      - labelSelector:
          matchExpressions: # 匹配podenv的值在["pro"]中的标签
          - key: podenv
            operator: In
            values: ["pro"]
        topologyKey: kubernetes.io/hostname

上面新Pod必须要与拥有标签nodeenv=pro的pod不在同一Node上

bash
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-podantiaffinity-required.yaml
pod/pod-podantiaffinity-required created

# 查看pod
# 发现调度到了node2上
[root@k8s-master01 ~]# kubectl get pods pod-podantiaffinity-required -n dev -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE   .. 
pod-podantiaffinity-required   1/1     Running   0          30s   10.244.1.96   node2  ..
# 创建pod
[root@k8s-master01 ~]# kubectl create -f pod-podantiaffinity-required.yaml
pod/pod-podantiaffinity-required created

# 查看pod
# 发现调度到了node2上
[root@k8s-master01 ~]# kubectl get pods pod-podantiaffinity-required -n dev -o wide
NAME                           READY   STATUS    RESTARTS   AGE   IP            NODE   .. 
pod-podantiaffinity-required   1/1     Running   0          30s   10.244.1.96   node2  ..

2.2 Pod亲和与反亲和位置拓扑

定义位置的方法很简单,无外乎就是在节点上选定一个特定的节点标签,可以理解为拓扑标签/位置标签,当我们选择一个节点标签作为位置判定逻辑时,具有同一标签值的就是同一位置,具有不同标签值的就是不同位置。

配置

yaml
apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  # 配置一个拓扑分布约束
  topologySpreadConstraints:
    - maxSkew: <integer> # 必须大于0,全局最小差值,就是各个拓扑域之间 Pod 的最大差值
      minDomains: <integer> # 可选;自从 v1.25 开始成为 Beta
      topologyKey: <string> # 是节点标签的键
      whenUnsatisfiable: <string> # 不满足分布约束时如何处理
      labelSelector: <object> # 用于查找匹配的 Pod
      matchLabelKeys: <list> # 可选;自从 v1.27 开始成为 Beta
      nodeAffinityPolicy: [Honor|Ignore] # 可选;自从 v1.26 开始成为 Beta
      nodeTaintsPolicy: [Honor|Ignore] # 可选;自从 v1.26 开始成为 Beta
  ### 其他 Pod 字段置于此处
apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  # 配置一个拓扑分布约束
  topologySpreadConstraints:
    - maxSkew: <integer> # 必须大于0,全局最小差值,就是各个拓扑域之间 Pod 的最大差值
      minDomains: <integer> # 可选;自从 v1.25 开始成为 Beta
      topologyKey: <string> # 是节点标签的键
      whenUnsatisfiable: <string> # 不满足分布约束时如何处理
      labelSelector: <object> # 用于查找匹配的 Pod
      matchLabelKeys: <list> # 可选;自从 v1.27 开始成为 Beta
      nodeAffinityPolicy: [Honor|Ignore] # 可选;自从 v1.26 开始成为 Beta
      nodeTaintsPolicy: [Honor|Ignore] # 可选;自从 v1.26 开始成为 Beta
  ### 其他 Pod 字段置于此处

参数解释

bash
maxSkew 描述这些 Pod 可能被均匀分布的程度。你必须指定此字段且该数值必须大于零。 例如,如果你有 3 个可用区,分别有 2、2  1 个匹配的 Pod,则 MaxSkew 设为 1 且全局最小值为 1

minDomains 表示符合条件的域的最小数量。此字段是可选的。域是拓扑的一个特定实例。 符合条件的域是其节点与节点选择器匹配的域。

topologyKey 是节点标签的键。参考节点 Pod 亲和性。

whenUnsatisfiable 指示如果 Pod 不满足分布约束时如何处理:DoNotSchedule/ScheduleAnyway 

labelSelector 用于查找匹配的 Pod

matchLabelKeys 是一个 Pod 标签键的列表,这些键值标签与 labelSelector 进行逻辑与运算。matchLabelKeys  labelSelector 中禁止存在相同的键。 未设置 labelSelector 时无法设置 matchLabelKeys。Pod 标签中不存在的键将被忽略。 null 或空列表意味着仅与 labelSelector 匹配。

nodeAffinityPolicy 表示我们在计算 Pod 拓扑分布偏差时将如何处理 Pod  nodeAffinity/nodeSelector。 选项为:

Honor:只有与 nodeAffinity/nodeSelector 匹配的节点才会包括到计算中。

Ignore:nodeAffinity/nodeSelector 被忽略。所有节点均包括到计算中。

如果此值为 nil,此行为等同于 Honor 策略。

nodeTaintsPolicy 表示我们在计算 Pod 拓扑分布偏差时将如何处理节点污点。选项为:

Honor:包括不带污点的节点以及污点被新 Pod 所容忍的节点。

Ignore:节点污点被忽略。包括所有节点。

如果此值为 null,此行为等同于 Ignore 策略。
maxSkew 描述这些 Pod 可能被均匀分布的程度。你必须指定此字段且该数值必须大于零。 例如,如果你有 3 个可用区,分别有 2、2  1 个匹配的 Pod,则 MaxSkew 设为 1 且全局最小值为 1

minDomains 表示符合条件的域的最小数量。此字段是可选的。域是拓扑的一个特定实例。 符合条件的域是其节点与节点选择器匹配的域。

topologyKey 是节点标签的键。参考节点 Pod 亲和性。

whenUnsatisfiable 指示如果 Pod 不满足分布约束时如何处理:DoNotSchedule/ScheduleAnyway 

labelSelector 用于查找匹配的 Pod

matchLabelKeys 是一个 Pod 标签键的列表,这些键值标签与 labelSelector 进行逻辑与运算。matchLabelKeys  labelSelector 中禁止存在相同的键。 未设置 labelSelector 时无法设置 matchLabelKeys。Pod 标签中不存在的键将被忽略。 null 或空列表意味着仅与 labelSelector 匹配。

nodeAffinityPolicy 表示我们在计算 Pod 拓扑分布偏差时将如何处理 Pod  nodeAffinity/nodeSelector。 选项为:

Honor:只有与 nodeAffinity/nodeSelector 匹配的节点才会包括到计算中。

Ignore:nodeAffinity/nodeSelector 被忽略。所有节点均包括到计算中。

如果此值为 nil,此行为等同于 Honor 策略。

nodeTaintsPolicy 表示我们在计算 Pod 拓扑分布偏差时将如何处理节点污点。选项为:

Honor:包括不带污点的节点以及污点被新 Pod 所容忍的节点。

Ignore:节点污点被忽略。包括所有节点。

如果此值为 null,此行为等同于 Ignore 策略。

基于节点

如果使用节点的 kubernetes.io/hostname 标签作为划分标准,由于每个节点的标签值都不同,所以他们有着不同的位置

image-20240625155742257

基于区域划分

如果基于区域标签划分节点位置,同一位置就表示节点在同一区域,而不同位置则表示在节点在不同的区域;下图node01和node02属于同一位置,而node03和node04属于另一个意义上的同一位置。

image-20240625160146594

假设PodA运行在beijing区域上,PodB与PodA有亲和关系,那么PodB则应该运行在beijing区域,反之则运行在shanghai区域。所以不管是亲和还是反亲和,它应该是有一个参照系,就是参考某个定义好的Pod,只要它在的地方我不在就是反亲和,或者我必须与它在同一位置,那就是亲和关系。

案例:

有两个微服务zeus和athena相互调用比较频繁,他们都有两个副本,出于提升效率和可用性考虑,我想将zeus和athena的副本打散到两个不同的可用区(zone),并让他们的副本必须部署到同一个节点上,假设zeus已经部署好了,那athena的部署可以这样实现

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 2
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: zeus
            topologyKey: kubernetes.io/hostname
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: athena
            topologyKey: topology.kubernetes.io/zone
apiVersion: apps/v1
kind: Deployment
metadata:
  name: athena
spec:
  replicas: 2
  selector:
    matchLabels:
      app: athena
  template:
    metadata:
      labels:
        app: athena
    spec:
      containers:
      - name: athena
        image: athena:2.0.0
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: zeus
            topologyKey: kubernetes.io/hostname
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: athena
            topologyKey: topology.kubernetes.io/zone

3. 污点和容忍

文档

3.1 污点(Taints)

前面的调度方式都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上,其实我们也可以站在Node的角度上,通过在Node上添加污点属性,来决定是否允许Pod调度过来。

污点是节点级的属性,我们可以在一个节点上给它设定一组特殊的属性,为了能更加形象的描述这组属性,我们将其称为污点。一旦对应的节点拥有了污点,Pod则无法调度到该节点上,除非它能容忍这些节点的污点,则可以正常调度到拥有污点的节点上。(污点(Taint) 是应用在节点之上的,从这个名字就可以看出来,是为了排斥pod 所存在的)

image-20240623214709906

Node被设置上污点之后就和Pod之间存在了一种相斥的关系,进而拒绝Pod调度进来,甚至可以将已经存在的Pod驱逐出去。

污点的格式为:key=value:effect, key和value是污点的标签,effect描述污点的作用,支持如下三个选项:

  • PreferNoSchedule:尽量不要调度,除非没有其他节点可调度
  • NoSchedule:一定不能被调度,但不会影响当前Node上已存在的Pod
  • NoExecute:不仅不会调度, 还会驱逐 Node 上已有的 Pod
  • 无法匹配条件时,Pod对象将会被驱逐;属于强制约束关系;

image-20240621174447129

设置方式

shell
# 设置污点
kubectl taint nodes node1 key=value:effect

#查看
kubectl describe node nodename | grep Taints -A 10

# 去除污点
kubectl taint nodes node1 key:effect-

# 去除所有污点
kubectl taint nodes node1 key-
# 设置污点
kubectl taint nodes node1 key=value:effect

#查看
kubectl describe node nodename | grep Taints -A 10

# 去除污点
kubectl taint nodes node1 key:effect-

# 去除所有污点
kubectl taint nodes node1 key-
bash
## 设置污点并不允许 Pod 调度到该节点
$ kubectl taint node kube-master key1=value1:NoSchedule

## 设置污点尽量阻止污点调度到该节点
$ kubectl taint node kube-master key2=value2:PreferNoSchedule

## 设置污点,不允许普通 Pod 调度到该节点,且将该节点上已经存在的 Pod 进行驱逐
$ kubectl taint node kube-master key3=value3:NoExecute
## 设置污点并不允许 Pod 调度到该节点
$ kubectl taint node kube-master key1=value1:NoSchedule

## 设置污点尽量阻止污点调度到该节点
$ kubectl taint node kube-master key2=value2:PreferNoSchedule

## 设置污点,不允许普通 Pod 调度到该节点,且将该节点上已经存在的 Pod 进行驱逐
$ kubectl taint node kube-master key3=value3:NoExecute

案例

  1. 准备节点node1(为了演示效果更加明显,暂时停止node2-3节点)
  2. 为node1节点设置一个污点: tag=heima:PreferNoSchedule;然后创建pod1( pod1 可以 )
  3. 修改为node1节点设置一个污点: tag=heima:NoSchedule;然后创建pod2( pod1 正常 pod2 失败 )
  4. 修改为node1节点设置一个污点: tag=heima:NoExecute;然后创建pod3 ( 3个pod都失败 )
bash
# 为node1设置污点(PreferNoSchedule)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=heima:PreferNoSchedule

# 创建pod1
[root@k8s-master01 ~]# kubectl run taint1 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE     IP           NODE   
taint1-7665f7fd85-574h4   1/1     Running   0          2m24s   10.244.1.59   node1    

# 为node1设置污点(取消PreferNoSchedule,设置NoSchedule)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag:PreferNoSchedule-
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=heima:NoSchedule

# 创建pod2
[root@k8s-master01 ~]# kubectl run taint2 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods taint2 -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE     IP            NODE
taint1-7665f7fd85-574h4   1/1     Running   0          2m24s   10.244.1.59   node1 
taint2-544694789-6zmlf    0/1     Pending   0          21s     <none>        <none>   

# 为node1设置污点(取消NoSchedule,设置NoExecute)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag:NoSchedule-
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=heima:NoExecute

# 创建pod3
[root@k8s-master01 ~]# kubectl run taint3 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED 
taint1-7665f7fd85-htkmp   0/1     Pending   0          35s   <none>   <none>   <none>    
taint2-544694789-bn7wb    0/1     Pending   0          35s   <none>   <none>   <none>     
taint3-6d78dbd749-tktkq   0/1     Pending   0          6s    <none>   <none>   <none>
# 为node1设置污点(PreferNoSchedule)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=heima:PreferNoSchedule

# 创建pod1
[root@k8s-master01 ~]# kubectl run taint1 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE     IP           NODE   
taint1-7665f7fd85-574h4   1/1     Running   0          2m24s   10.244.1.59   node1    

# 为node1设置污点(取消PreferNoSchedule,设置NoSchedule)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag:PreferNoSchedule-
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=heima:NoSchedule

# 创建pod2
[root@k8s-master01 ~]# kubectl run taint2 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods taint2 -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE     IP            NODE
taint1-7665f7fd85-574h4   1/1     Running   0          2m24s   10.244.1.59   node1 
taint2-544694789-6zmlf    0/1     Pending   0          21s     <none>        <none>   

# 为node1设置污点(取消NoSchedule,设置NoExecute)
[root@k8s-master01 ~]# kubectl taint nodes node1 tag:NoSchedule-
[root@k8s-master01 ~]# kubectl taint nodes node1 tag=heima:NoExecute

# 创建pod3
[root@k8s-master01 ~]# kubectl run taint3 --image=nginx:1.17.1 -n dev
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME                      READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED 
taint1-7665f7fd85-htkmp   0/1     Pending   0          35s   <none>   <none>   <none>    
taint2-544694789-bn7wb    0/1     Pending   0          35s   <none>   <none>   <none>     
taint3-6d78dbd749-tktkq   0/1     Pending   0          6s    <none>   <none>   <none>

💡 说明

使用kubeadm搭建的集群,默认就会给master节点添加一个污点标记,所以pod就不会调度到master节点上.

3.2 容忍(Toleration)

容忍度(tolerations)是定义在Pod对象上的键值数据,用于配置该Pod可以容忍哪些节点的污点以及污点的效用标识,这样就可以将Pod调度到那些有污点的节点上。(容忍度(Toleration)是应用于 Pod 上的,允许(但并不要求)Pod 调度到带有与之匹配的污点的节点上。)

容忍作用与Pod资源配置在tolerations字段,用于容忍配置在node节点的Taint污点,可以让Pod部署在有污点的node节点

污点就是拒绝,容忍就是忽略,Node通过污点拒绝pod调度上去,Pod通过容忍忽略拒绝

image-20240621181204412

bash
[root@kube-master ~]# kubectl explain pod.spec.tolerations
[root@kube-master ~]# kubectl explain pod.spec.tolerations

Pod 设置容忍

为了使某些 Pod 禁止调度到某些特定节点上,就可以对节点设置污点 taints。当然,如果希望有些 Pod 能够忽略节点的污点,继续能够调度到该节点,就可以对 Pod 设置容忍,让 Pod 能够容忍节点上设置的污点

对一个节点设置污点

bash
$ kubectl taint node k8s-master key=value:NoSchedule
$ kubectl taint node k8s-master key=value:NoSchedule

对 Pod 设置容忍,以下两种方式都可:

yaml
## 容忍的 key、value 和对应 effect 也必须和污点 taints 保持一致
......
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"

## 容忍 tolerations 的 key 和要污点 taints 的 key 一致,且设置的 effect 也相同,不需要设置 value
......
tolerations:
- key: "key"
  operator: "Exists"
  effect: "NoSchedule"
## 容忍的 key、value 和对应 effect 也必须和污点 taints 保持一致
......
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"

## 容忍 tolerations 的 key 和要污点 taints 的 key 一致,且设置的 effect 也相同,不需要设置 value
......
tolerations:
- key: "key"
  operator: "Exists"
  effect: "NoSchedule"

设置容忍时间

yaml
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "PreferNoSchedule"
  tolerationSeconds: 3600
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "PreferNoSchedule"
  tolerationSeconds: 3600

如果这个 Pod 已经在这个带污点且 effect 为 NoExecute 的 node 上。这个 pod 可以一直运行到 3600s 后再被踢掉。如果这时候 Node 的污点被移除了,这个 Pod 就不会被踢掉。

❌ 注意

存在两种特殊情况:

如果一个容忍度的 key 为空且 operator 为 Exists, 表示这个容忍度与任意的 key 、value 和 effect 都匹配,即这个容忍度能容忍任意 taint。

如果 effect 为空,则可以与所有键名 key1 的效果相匹配

案例

Operator 默认是 Equal,可设置为 EqualExists 两种,按这两种进行示例:

Operator 是 Exists

容忍任何污点

例如一个空的key,将匹配所有的key、value、effect。即容忍任何污点。

yaml
tolerations:
- operator: "Exists"
tolerations:
- operator: "Exists"

容忍某 key 值的污点

例如一个空的 effect,并且 key 不为空,那么将匹配所有与 key 相同的 effect:

yaml
tolerations:
- key: "key"
  operator: "Exists"
tolerations:
- key: "key"
  operator: "Exists"
Operator 是 Equal

Node 上有一个污点

Node 和 Pod 的 key 为 key1、value1 与 effect 相同则能调度:

yaml
#污点
key1=value1:NoSchedule

#Pod设置
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
#污点
key1=value1:NoSchedule

#Pod设置
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"

Node 上有多个污点

Node 的污点的 key、value、effect 和 Pod 容忍都相同则能调度:

yaml
# 设置污点
key1=value1:NoSchedule
key2=value2:NoExecute

# Pod设置容忍
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key2"
  operator: "Equal"
  value: "value2"
  effect: "NoExecute"
# 设置污点
key1=value1:NoSchedule
key2=value2:NoExecute

# Pod设置容忍
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key2"
  operator: "Equal"
  value: "value2"
  effect: "NoExecute"

Node 的污点和 Pod 的大部分都相同,不同的是 Node 污点 effect 为 PreferNoSchedule 的,可能会调度:

yaml
# 污点
key1=value1:NoSchedule
key2=value2:NoExecute
key3=value3:PreferNoSchedule

# Pod设置容忍
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key3"
  operator: "Equal"
  value: "value3"
  effect: "PreferNoSchedule"
# 污点
key1=value1:NoSchedule
key2=value2:NoExecute
key3=value3:PreferNoSchedule

# Pod设置容忍
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key3"
  operator: "Equal"
  value: "value3"
  effect: "PreferNoSchedule"

Node 的污点和 Pod 的大部分都相同,不同的是 Node 污点 effect 为 NoSchedule 和 NoExecute 的,不会被调度:

yaml
# 污点
key1=value1:NoSchedule
key2=value2:NoExecute
key3=value3:PreferNoSchedule

# Pod设置容忍
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key2"
  operator: "Equal"
  value: "value2"
  effect: "NoExecute"
# 污点
key1=value1:NoSchedule
key2=value2:NoExecute
key3=value3:PreferNoSchedule

# Pod设置容忍
tolerations:
- key: "key1"
  operator: "Equal"
  value: "value1"
  effect: "NoSchedule"
- key: "key2"
  operator: "Equal"
  value: "value2"
  effect: "NoExecute"
  1. 上一小节,已经在node1节点上打上了NoExecute的污点,此时pod是调度不上去的
  2. 本小节,可以通过给pod添加容忍,然后将其调度上去

创建pod-toleration.yaml

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  tolerations:      # 添加容忍
  - key: "tag"        # 要容忍的污点的key
    operator: "Equal" # 操作符
    value: "heima"    # 容忍的污点的value
    effect: "NoExecute"   # 添加容忍的规则,这里必须和标记的污点规则相同
apiVersion: v1
kind: Pod
metadata:
  name: pod-toleration
  namespace: dev
spec:
  containers:
  - name: nginx
    image: nginx:1.17.1
  tolerations:      # 添加容忍
  - key: "tag"        # 要容忍的污点的key
    operator: "Equal" # 操作符
    value: "heima"    # 容忍的污点的value
    effect: "NoExecute"   # 添加容忍的规则,这里必须和标记的污点规则相同
yaml
# 添加容忍之前的pod
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED 
pod-toleration   0/1     Pending   0          3s    <none>   <none>   <none>           

# 添加容忍之后的pod
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED
pod-toleration   1/1     Running   0          3s    10.244.1.62   node1   <none>
# 添加容忍之前的pod
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP       NODE     NOMINATED 
pod-toleration   0/1     Pending   0          3s    <none>   <none>   <none>           

# 添加容忍之后的pod
[root@k8s-master01 ~]# kubectl get pods -n dev -o wide
NAME             READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED
pod-toleration   1/1     Running   0          3s    10.244.1.62   node1   <none>

💡 说明

内置以下污点:

  • node.kubernetes.io/not-ready: 节点尚未准备好。这对应于 NodeCondition Ready 为 false。
  • node.kubernetes.io/unreachable: 无法从节点控制器访问节点。这对应于 NodeCondition 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: 节点不可调度。

污点和容忍度的作用

Taint(污点)和 Toleration(容忍)可以作用于node和 pod 上,其目的是优化pod在集群间的调度,这跟节点亲和性类似,只不过它们作用的方式相反,具有taint的node和pod是互斥关系,而具有节点亲和性关系的node和pod是相吸的。另外还有可以给node节点设置label,通过给pod设置nodeSelector将pod调度到具有匹配标签的节点上。

Taint 和 toleration 相互配合,可以用来避免pod被分配到不合适的节点上。每个节点上都可以应用一个或多个taint,这表示对于那些不能容忍这些taint的 pod,是不会被该节点接受的。如果将toleration应用于pod上,则表示这些pod可以(但不要求)被调度到具有相应taint的节点上。

污点与标签区别

由于标签和污点工作原理的不同,他们的适用场景也不一样。

标签通常用于为 Pod 指定分组,规定了 Pod 只能调度到这些分组里的 node 中,这是一种强制的做法。

污点通常用于将 Node 设置为专用节点,默认情况下普通 Pod 是无法调度过来,仅当你在 Pod 中指定了对应的容忍度才能调度。

容忍与nodeSelector区别

污点在k8s中相当于给node设置了一个锁,容忍相当于这个锁的钥匙,调度到这个节点的Pod需要使用容忍来解开这个锁,但是污点与容忍并不会固定的把某些Pod调度到这个节点。如果Pod第一次调度被调度到了没有污点的节点,他就会被分配到这个节点,容忍的配置就无效了,如果这个Pod正好被调度到有污点的node,他的容忍配置会被解析,验证这个Pod是否可以容忍node的污点。

nodeSelector固定调度某些Pod到指定的一些节点,他的作用是强制性的,如果集群中没有符合的node,Pod会一直处于等待调度的阶段。

3.3 Node添加污点

容忍配置

yaml
[root@k8s-master01 ~]# kubectl explain pod.spec.tolerations
......
FIELDS:
   key       # 对应着要容忍的污点的键,空意味着匹配所有的键
   value     # 对应着要容忍的污点的值
   operator  # key-value的运算符,支持Equal和Exists(默认)
   effect    # 对应污点的effect,空意味着匹配所有影响
   tolerationSeconds   # 容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间
[root@k8s-master01 ~]# kubectl explain pod.spec.tolerations
......
FIELDS:
   key       # 对应着要容忍的污点的键,空意味着匹配所有的键
   value     # 对应着要容忍的污点的值
   operator  # key-value的运算符,支持Equal和Exists(默认)
   effect    # 对应污点的effect,空意味着匹配所有影响
   tolerationSeconds   # 容忍时间, 当effect为NoExecute时生效,表示pod在Node上的停留时间

在实际生产环境中,污点通常用于描述具体的部署规划,它们的键名如:node-type、node-role、node-project等。kubectltaint命令可以管理Node对象的污点信息,语法格式如下:

语法

语法:kubectl taint nodes <node-name> <key=value>:<effect>

# 示例:kubectl taint nodes node01 node-type=prod:NoExecute
语法:kubectl taint nodes <node-name> <key=value>:<effect>

# 示例:kubectl taint nodes node01 node-type=prod:NoExecute
bash
#添加
[root@kube-master ~]# kubectl taint nodes kube-node03 node-type=prod:NoSchedule
node/kube-node03 tainted
#添加
[root@kube-master ~]# kubectl taint nodes kube-node03 node-type=prod:NoSchedule
node/kube-node03 tainted
bash
#查看
[root@kube-master ~]# kubectl describe nodes  kube-node03 |grep Taints
Taints:             node-type=prod:NoSchedule
#查看
[root@kube-master ~]# kubectl describe nodes  kube-node03 |grep Taints
Taints:             node-type=prod:NoSchedule
bash
#删除,只需在effect后面加上-号即可
[root@kube-master ~]# kubectl taint nodes kube-node03 node-type=prod:NoSchedule-
node/kube-node03 untainted

#查看
[root@kube-master ~]# kubectl describe nodes  kube-node03 |grep Taints
Taints:             <none>
#删除,只需在effect后面加上-号即可
[root@kube-master ~]# kubectl taint nodes kube-node03 node-type=prod:NoSchedule-
node/kube-node03 untainted

#查看
[root@kube-master ~]# kubectl describe nodes  kube-node03 |grep Taints
Taints:             <none>
bash
## 设置污点并不允许 Pod 调度到该节点
$ kubectl taint node kube-node03 key1=value1:NoSchedule

## 设置污点尽量阻止污点调度到该节点
$ kubectl taint node kube-node03 key2=value2:PreferNoSchedule

## 设置污点,不允许普通 Pod 调度到该节点,且将该节点上已经存在的 Pod 进行驱逐
$ kubectl taint node kube-node03 key3=value3:NoExecute
## 设置污点并不允许 Pod 调度到该节点
$ kubectl taint node kube-node03 key1=value1:NoSchedule

## 设置污点尽量阻止污点调度到该节点
$ kubectl taint node kube-node03 key2=value2:PreferNoSchedule

## 设置污点,不允许普通 Pod 调度到该节点,且将该节点上已经存在的 Pod 进行驱逐
$ kubectl taint node kube-node03 key3=value3:NoExecute

3.4 Pod添加容忍度

Pod 的容忍度在 PodSpec 中定义,根据使用的操作符不同,主要有两种可用形式:

  • 一种是与污点信息完全匹配的等值关系。将operator指定为Equal,则它们的勺key、value、effect 应该相等;
  • 一种是判断污点信息存在性的匹配方式。将operator指定为 Exists(此时容忍度无需指定value);

4. 实践

4.1 Pod亲和性

1.添加标签

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

#查看
kubectl get node --show-labels

#删除
kubectl label nodes <your-node-name> key-
kubectl label nodes <your-node-name> key=value

#查看
kubectl get node --show-labels

#删除
kubectl label nodes <your-node-name> key-

2.创建deployment

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: redis-server
        image: redis:3.2-alpine

4.2 node亲和性

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: instance-type
                operator: In
                values:
                - memory
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            preference:
              matchExpressions:
              - key: disktype
                operator: In
                values:
                - ssd
      containers:
      - name: redis-server
        image: redis:3.2-alpine
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-cache
spec:
  selector:
    matchLabels:
      app: store
  replicas: 3
  template:
    metadata:
      labels:
        app: store
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: instance-type
                operator: In
                values:
                - memory
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            preference:
              matchExpressions:
              - key: disktype
                operator: In
                values:
                - ssd
      containers:
      - name: redis-server
        image: redis:3.2-alpine