六、Pod高级实战:基于亲和性、污点、容忍度的多种调度策略

6.1 标签

6.1.1 什么是标签?

标签其实就是一对键值对(Key/value),被关联到对象上,比如Pod;

标签的作用其实就是“见名知意”,一眼就知道这个资源的特点,资源的作用;

标签可以用来划分特定的对象(比如:版本、服务类型…)

  • 每个对象可以有多个标签,但是 key 的值必须是唯一的

创建标签之后,也可以方便我们对资源进行分组管理。

如果对Pod打标签,之后就可使用标签来查看、删除指定的Pod

6.1.2 查看资源标签
# 新建几个Pod资源
[root@k8s-master1 ~]# cd pod
[root@k8s-master1 pod]# kubectl apply -f pod-tomcat.yaml
[root@k8s-master1 pod]# kubectl apply -f nginx-deploy.yaml
# 新建完成之后,查看一下
[root@k8s-master1 pod]# kubectl get pods

# 查看在此命名空间下所有Pod资源标签(-n default 忽略)
[root@k8s-master1 pod]# kubectl get pods --show-labels

# 在此命名空间下查看某个Pod资源的标签
[root@k8s-master1 pod]# kubectl get pods tomcat-test --show-labels

# 查看在此命名空间下标签为c的Pod资源
[root@k8s-master1 pod]# kubectl get pods -l c
# 查看在此命名空间下标签为app的Pod资源
[root@k8s-master1 pod]# kubectl get pods -l app
# 查看在此命名空间下标签为releases的Pod资源
[root@k8s-master1 pod]# kubectl get pods -l release

# 查看在此命名空间下标签为releases的Pod资源的Key值(-L)
[root@k8s-master1 pod]# kubectl get pods -L release
NAME          READY   STATUS    RESTARTS   AGE    RELEASE
tomcat-test   1/1     Running   0          2m4s   v1


# 查看所有命名空间下Pod资源的标签
[root@k8s-master1 pod]# kubectl get pods --all-namespaces --show-labels
6.1.3 打标签示例
# 实操测试
[root@k8s-master1 ~]# cd pod
# 先创建一个Pod资源
[root@k8s-master1 pod]# kubectl apply -f pod-first.yaml
pod/tomcat-test created

# 查看Pod资源状态
[root@k8s-master1 pod]# kubectl get pods
NAME          READY   STATUS    RESTARTS   AGE
tomcat-test   1/1     Running   0          4s
# 查看Pod资源此时所拥有的所有标签
[root@k8s-master1 pod]# kubectl get pods --show-labels
NAME          READY   STATUS    RESTARTS   AGE   LABELS
tomcat-test   1/1     Running   0          10s   app=tomcat

# 第一种方法:利用命令去增添此Pod资源的
[root@k8s-master1 pod]# kubectl label pods tomcat-test release=v1

# 第二种方法:去yaml文件里面添加标签,然后apply -f 更新文件
[root@k8s-master1 pod]# vim pod-first.yaml
...
metadata:
  labels:
    app: tomcat
    a: b
    c: d
[root@k8s-master1 pod]# kubectl apply -f pod-first.yaml

[root@k8s-master1 pod]# kubectl get pods --show-labels
NAME          READY   STATUS    RESTARTS   AGE     LABELS
tomcat-test   1/1     Running   0          2m20s   a=b,app=tomcat,c=d,release=v1
6.1.4 删除标签
# 单个资源
kubectl label <资源类型ps:pods,nodes> <资源名称> <标签键>-	# 标签后面加个‘-’,一定没有空格!

# 批量删除
kubectl label <资源类型> --all <标签键>-

6.2 node 节点选择器

我们在创建Pod资源的时候,Pod会根据schduler进行调度,那么默认会调度到随机的一个工作节点,

如果,我们想要Pod调度到指定的节点或者调度到一些具有相同特点的node节点,怎么办呢?

可以使用Pod中的nodeName或者nodeSelector字段指定要调度到的node节点。

6.2.1 nodeName

指定Pod节点运行在哪个具体的node上,示例如下:

n1 && n2 ~]# ctr -n k8s.io images import tomcat.tar.gz
n1 && n2 ~]# ctr -n k8s.io images import busybox.tar.gz

[root@k8s-master1 pod]# vim test_nodeName.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: myapp
    env: dev
  name: test-nodename
  namespace: default
spec:
  nodeName: k8s-node1.kaser.org		# 指示调度器schduler调度到node1上
  containers:
    - name: tomcat-pod-java				# 第一个容器:tomcat
      image: tomcat:8.5-jre8-alpine
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
    - name: busybox						# 第二个容器:busybox
      image: busybox:latest
      imagePullPolicy: IfNotPresent
      command:
        - "/bin/sh"
        - "-c"
        - "sleep 3600"
        
[root@k8s-master1 pod]# kubectl apply -f test_nodeName.yaml
# 查看是否调度到node1节点上
[root@k8s-master1 pod]# kubectl get pods -owide
...... k8s-node1.kaser.org
6.2.2 nodeSelector

nodeSelector 是 Kubernetes 中最简单、最直接的节点选择约束机制,它允许你将 Pod 调度到具有特定标签的节点上。

Pod → nodeSelector → 匹配节点标签 → 调度到符合条件的节点,示例如下:

[root@k8s-master1 pod]# cp test_nodeName.yaml test_nodeName_1.yaml
[root@k8s-master1 pod]# vim test_nodeName_1.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: myapp
    env: dev
  name: test-nodename-1
  namespace: default
spec:
  nodeSelector:
    disk: ceph				# 设置节点标签 disk: ceph
  containers:
    - name: tomcat-pod-java
      image: tomcat:8.5-jre8-alpine
      imagePullPolicy: IfNotPresent
      ports:
        - containerPort: 8080
    - name: busybox
      image: busybox:latest
      imagePullPolicy: IfNotPresent
      command:
        - "/bin/sh"
        - "-c"
        - "sleep 3600"

[root@k8s-master1 pod]# kubectl apply -f test_nodeName_1.yaml
[root@k8s-master1 pod]# kubectl get pods
NAME              READY   STATUS    RESTARTS   AGE
test-nodename-1   0/2     Pending   0          5s

[root@k8s-master1 pod]# kubectl describe pods test-nodename-1
  Warning  FailedScheduling  6m17s  default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) didn't match Pod's node affinity/selector. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.
# 显示这个k8s集群的三个几点都没有找到这个标签disk: ceph

# 自我测试一下,发现什么都没有
[root@k8s-master1 pod]# kubectl get nodes --show-labels | grep ceph

# 给 k8s-node2.kaser.org 打节点标签
[root@k8s-master1 pod]# kubectl label nodes k8s-node2.kaser.org disk=ceph

# 删除原先的Pod资源并apply重新加载yaml文件
[root@k8s-master1 pod]# kubectl delete pod test-nodename-1
[root@k8s-master1 pod]# kubectl apply -f test_nodeName_1.yaml

# 此时状态就由 Pending --> Running
[root@k8s-master1 pod]# kubectl get pods
NAME              READY   STATUS    RESTARTS   AGE
test-nodename-1   2/2     Running   0          2s

# 检查是否将其调度到node2的节点上去
[root@k8s-master1 pod]# kubectl get pods -owide
......k8s-node2.kaser.org

# 
[root@k8s-master1 pod]# kubectl get nodes -l disk
NAME                  STATUS   ROLES   AGE   VERSION
k8s-node2.kaser.org   Ready    work    23h   v1.31.3

6.3 亲和性 Affinity

6.3.1 node Affinity node节点亲和性

节点亲和性是 K8s 中的一种调度机制,用于控制 Pod 被调度到特定节点的策略。

它基于节点的标签(Lables)定义规则,使调度过程更灵活灵活、精细,适用于需要特定节点资源。

# affinity
[root@k8s-master1 ~]# kubectl explain pods.spec.affinity
...
FIELDS:
  nodeAffinity
  podAffinity
  podAntiAffinity
# nodeAffinity
[root@k8s-master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity
  preferredDuringSchedulingIgnoredDuringExecution
  # prefered表示有节点尽量满足这个位置定义的亲和性,这不是一个必须的条件,软亲和性
  requiredDuringSchedulingIgnoredDuringExecution
  # require表示必须有节点满足这个位置定义的亲和性,这是个硬性条件,硬亲和性
6.3.1.1 软亲和性
# preferredDuringSchedulingIgnoredDuringExecution
[root@k8s-master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.preferredDuringSchedulingIgnoredDuringExecution
...
  preference    <NodeSelectorTerm> -required-
  weight        <integer> -required-

软亲和性示例:

# 上传资源文件至工作节点,并手动导入
n1 && n2 ]# ctr -n k8s.io images import myapp-v1.tar.gz
# 编写yaml文件
[root@k8s-master1 pod]# vim pod-nodeaffinity-demo-2.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: myapp
    env: dev
  name: pod-nodeaffinity-demo-2
  namespace: default
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:	# 软亲和性
      - preference:
          matchExpressions:
          - key: zone1
            operator: In
            values:
            - foo1					# 这里注意,两个新添的前面都要有 -,要不然会报错
            - bar1
        weight: 20					# 这里是权重20
      - preference:
          matchExpressions:
          - key: zone2
            operator: In
            values:
            - foo2
            - bar2
        weight: 10					# 这里是权重10,比上面的权重小
  containers:
  - image: docker.io/ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    name: myapp
# 实验1:软亲和性的特性(即使没有对应的标签,也会进行调度)
[root@k8s-master1 pod]# kubectl apply -f pod-nodeaffinity-demo-2.yaml
[root@k8s-master1 pod]# kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
pod-nodeaffinity-demo-2   1/1     Running   0          8s	# Running状态

# 查看调度到了哪个工作节点上
[root@k8s-master1 pod]# kubectl get pods -owide
k8s- node2.kaser.org   <none>           <none>
# 实验2:软亲和性的关键参数weight权重
# weight权重是相对权重!	-- 权重越高,Pod调度的几率就越大

# 先删除Pod资源以免影响实验效果
[root@k8s-master1 pod]# kubectl delete pod pod-nodeaffinity-demo-2

[root@k8s-master1 pod]# kubectl get nodes
NAME                    STATUS   ROLES           AGE   VERSION
k8s-master1.kaser.org   Ready    control-plane   13d   v1.31.3
k8s-node1.kaser.org     Ready    work            13d   v1.31.3
k8s-node2.kaser.org     Ready    work            13d   v1.31.3

# 查看yaml文件的权重部分
[root@k8s-master1 pod]# more pod-nodeaffinity-demo-2.yaml

      - preference:
          matchExpressions:
          - key: zone1
            operator: In
            values:
            - foo1
            - bar1
        weight: 20
      - preference:
          matchExpressions:
          - key: zone2
            operator: In
            values:
            - foo2
            - bar2
        weight: 10

# 并给节点打标签!
[root@k8s-master1 pod]# kubectl label nodes k8s-node1.kaser.org zone1=foo1
[root@k8s-master1 pod]# kubectl label nodes k8s-node2.kaser.org zone2=foo2

# 此时权重:node1 -- 20;node2 -- 10
# 一会测试看看是否将Pod调度到node1(权重高:20>10,优先调度)上
[root@k8s-master1 pod]# kubectl apply -f pod-nodeaffinity-demo-2.yaml
# 成功!
[root@k8s-master1 pod]# kubectl get pods -owide
k8s-node1.kaser.org   <none>           <none>
# 为了下面的实验效果,将一部分资源删除
[root@k8s-master1 pod]# kubectl label nodes k8s-node1.kaser.org zone1-
[root@k8s-master1 pod]# kubectl label nodes k8s-node2.kaser.org zone2-
[root@k8s-master1 pod]# kubectl delete pod pod-nodeaffinity-demo-2
6.3.1.2 硬亲和性
# requiredDuringSchedulingIgnoredDuringExecution
[root@k8s-master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution
...
  nodeSelectorTerms     <[]NodeSelectorTerm> -required-
# nodeSelectorTerms
[root@k8s-master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms
...
  matchExpressions      <[]NodeSelectorRequirement>		# 匹配表达式的
  matchFields   <[]NodeSelectorRequirement>				# 匹配字段的

image-20260110140206007

image-20260110140238937

# matchExpressions
[root@k8s-master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.matchExpressions
...
FIELDS:
  key   <string> -required-				# 检查label

  operator      <string> -required-		# 坐等值选则还是不等值选则
    Possible enum values:
     - `"DoesNotExist"`
     - `"Exists"`
     - `"Gt"`
     - `"In"`
     - `"Lt"`
     - `"NotIn"`

  values        <[]string>				# 给定值
# matchFields
[root@k8s-master1 ~]# kubectl explain pods.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.matchFields
...
FIELDS:
  key   <string> -required-
  
  operator      <string> -required-
    Possible enum values:
     - `"DoesNotExist"`
     - `"Exists"`
     - `"Gt"`
     - `"In"`
     - `"Lt"`
     - `"NotIn"`

  values        <[]string>

硬亲和性示例:

# 上传资源文件至工作节点,并手动导入	(上面的演示实验要是上传了,就不用管了)
n1 && n2 ]# ctr -n k8s.io images import myapp-v1.tar.gz

# 控制节点写下yaml文件
[root@k8s-master1 ~]# vim pod-nodeaffinity-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: myapp
    env: dev
  name: pod-nodeaffinity-demo
  namespace: default
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: zone
            operator: In
            values:
            - foo
            - bar
  containers:
  - image: docker.io/ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    name: myapp

[root@k8s-master1 ~]# kubectl apply -f pod-nodeaffinity-demo.yaml
# 发现Pod资源处于Pending状态
[root@k8s-master1 ~]# kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
pod-nodeaffinity-demo   0/1     Pending   0          11m

# 查看Pod的详细信息显示,三个节点都匹配不到:不存在的标签key=foo,key=bar(会导致 Pod 无法调度)
[root@k8s-master1 ~]# kubectl describe pods pod-nodeaffinity-demo
Warning  FailedScheduling  2m59s (x2 over 7m59s)  default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) didn't match Pod's node affinity/selector. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.

# 由于使用的是硬亲和性,条件是必须得满足,因此打标签测试

# 查看nodes
[root@k8s-master1 ~]# kubectl get nodes
NAME                    STATUS   ROLES           AGE   VERSION
k8s-master1.kaser.org   Ready    control-plane   13d   v1.31.3
k8s-node1.kaser.org     Ready    work            13d   v1.31.3
k8s-node2.kaser.org     Ready    work            13d   v1.31.3
# 给node1打标签
[root@k8s-master1 ~]# kubectl label nodes k8s-node1.kaser.org zone=foo

# 查看Pod状态
[root@k8s-master1 ~]# kubectget pods
NAME                    READY   STATUS    RESTARTS   AGE
pod-nodeaffinity-demo   1/1     Running   0          18m

# 查看被调度node1上,成功!
[root@k8s-master1 ~]# kubectl get pods -owide
k8s-node1.kaser.org   <none>           <none>
# 删除资源,避免影响下面的实验:
[root@k8s-master1 pod]# kubectl label nodes k8s-node1.kaser.org zone-
[root@k8s-master1 pod]# kubectl delete pod pod-nodeaffinity-demo
6.3.2 pod Affinity pod节点亲和性

第一个pod随机选则一个节点,做为评判后续的pod能否到达这个pod所在的节点上的运行方式,这就称为pod亲和性。

我们怎么判定哪些节点是相同位置的,哪些节点是不同位置的;我们在定义pod亲和性时需要有一个前提,哪些pod在同一个位置,哪些pod不在同一个位置,这个位置是怎么定义的,标准是什么?以节点名称为标准,这个节点名称相同的表示是同一个位置,节点名称不相同的表示不是一个位置。

  • podaffinity:
    • pod和pod更倾向腻在一起,把相近的pod结合到相近的位置,如同一区域,同一机架,这样的话pod和pod之间更好通信,比方说有两个机房,这两个机房部署的集群有1000台主机,那么我们希望把nginx和tomcat都部署同一个地方的node节点上,可以提高通信效率;
  • podunaffinity:
    • pod和pod更倾向不腻在一起,如果部署两套程序,那么这两套程序更倾向于反亲和性,这样相互之间不会有影响。
# 演示
[root@k8s-master1 pod]# kubectl explain pods.spec.affinity.podAffinity
...
# 软亲和性:
  preferredDuringSchedulingIgnoredDuringExecution       <[]WeightedPodAffinityTerm>
# 硬亲和性:
  requiredDuringSchedulingIgnoredDuringExecution        <[]PodAffinityTerm>
# 以硬亲和性为例(只列出重要的):
[root@k8s-master1 pod]# kubectl explain pods.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution
...
  labelSelector <LabelSelector>
...
  topologyKey   <string> -required-		# 位置拓扑的键
# labelSelector
[root@k8s-master1 pod]# kubectl explain pods.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution.labelSelector
...
  matchExpressions      <[]LabelSelectorRequirement>		# 重要!
  matchLabels   <map[string]string>
# matchExpressions
[root@k8s-master1 pod]# kubectl explain pods.spec.affinity.podAffinity.requiredDuringSchedulingIgnoredDuringExecution.labelSelector.matchExpressions
...
  key   <string> -required-
  operator      <string> -required-
  values        <[]string>
topologyKey:
位置拓扑的键,这个是必须字段
怎么判断是不是同一个位置:
rack=rack1
row=row1
使用rack的键是同一个位置
使用row的键是同一个位置
labelSelector:
我们要判断pod跟别的pod亲和,跟哪个pod亲和,需要靠labelSelector,通过labelSelector选则一组能作为亲和对象的pod资源
namespace:
labelSelector需要选则一组资源,那么这组资源是在哪个名称空间中呢,通过namespace指定,如果不指定namespaces,那么就是当前创建pod的名称空间

Pod节点亲和性示例:

# 定义两个Pod,第一个Pod作为基准,第二个Pod跟着它走
[root@k8s-master1 pod]# vim pod-required-affinity-demo-1.yaml
# 此时pod-first并没有指定要调度到哪个节点(因此随机调度)
[root@k8s-master1 pod]# kubectl apply -f pod-required-affinity-demo-1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-first
  namespace: default
  labels:
    app2: myapp2
    env: dev
spec:
  containers:
  - name: myapp
    image: docker.io/ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent

# 此时调度到了node2上
[root@k8s-master1 pod]# kubectl get pods -owide
pod-first   1/1     k8s-node2.kaser.org

# 编写pod-second的yaml文件
# 下面表示创建的pod必须与拥有app2=myapp2标签的pod在一个节点上
[root@k8s-master1 pod]# vim pod-required-affinity-demo-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-second
  namespace: default
  labels:
    app2: backend
    env: dev
spec:
  containers:
  - name: busybox
    image: docker.io/library/busybox:latest
    imagePullPolicy: IfNotPresent
    command: ["sh","-c","sleep 3600"]
  affinity:								# 注意!!!
    podAffinity:						# pod节点亲和性
      requiredDuringSchedulingIgnoredDuringExecution:	# 硬亲和
      - labelSelector:					# 标签选择器
          matchExpressions:				# 跟随 key=myapp2 的pod
          - {key: "app2",operator: "In",values: ["myapp2"]}
        topologyKey: kubernetes.io/hostname	# 位置拓扑的键

##############################################################
# 示例:寻找位置拓扑的键 --> topologyKey: kubernetes.io/hostname
[root@k8s-master1 ~]# kubectl get nodes --show-labels
...kubernetes.io/hostname=k8s-master1.kaser.org...
...kubernetes.io/hostname=k8s-node1.kaser.org...
...kubernetes.io/hostname=k8s-node2.kaser.org...
#############################################################

[root@k8s-master1 pod]# kubectl apply -f pod-required-affinity-demo-2.yaml
# 因此,构成pod亲和性
[root@k8s-master1 pod]# kubectl get pods -owide
pod-first    k8s-node2.kaser.org
pod-second   k8s-node2.kaser.org
6.3.3 pod AntiAffinity pod节点反亲和性

pod节点反亲和性示例1:

# 定义两个pod,第一个pod作为基准,第二个跟它调度相反节点
[root@k8s-master1 pod]# vim pod-required-anti-affinity-demo-1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-first
  labels:
    app1: myapp1
    tier: frontend
spec:
    containers:
    - name: myapp
      image: ikubernetes/myapp:v1
      imagePullPolicy: IfNotPresent

[root@k8s-master1 pod]# kubectl apply -f pod-required-anti-affinity-demo-1.yaml
pod-first    k8s-node2.kaser.org
[root@k8s-master1 pod]# vim pod-required-anti-affinity-demo-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-second
  labels:
    app: backend
spec:
  containers:
  - name: busybox
    image: docker.io/library/busybox:latest
    imagePullPolicy: IfNotPresent
    command: ["sh","-c","sleep 3600"]
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - {key: app1,operator: In,values: ["myapp1"]}
        topologyKey: kubernetes.io/hostname

[root@k8s-master1 pod]# kubectl apply -f pod-required-anti-affinity-demo-2.yaml

[root@k8s-master1 pod]# kubectl get pods -owide
pod-first    k8s-node2.kaser.org
pod-second   k8s-node1.kaser.org

# 删除资源,以免影响后续实验
[root@k8s-master1 pod]# kubectl delete pods pod-first pod-second --force

pod节点反亲和性示例2:

# 换一个topologykey zone
[root@k8s-master1 pod]# kubectl get nodes
NAME                    STATUS   ROLES           AGE   VERSION
k8s-master1.kaser.org   Ready    control-plane   14d   v1.31.3
k8s-node1.kaser.org     Ready    work            13d   v1.31.3
k8s-node2.kaser.org     Ready    work            13d   v1.31.3

# 打两个标签
[root@k8s-master1 pod]# kubectl label nodes k8s-node1.kaser.org zone=foo
[root@k8s-master1 pod]# kubectl label nodes k8s-node2.kaser.org zone=foo

# 编写yaml
[root@k8s-master1 pod]# vim pod-first-required-anti-affinity-demo-1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-first
  labels:
    app3: myapp3
    tier: frontend
spec:
    containers:
    - name: myapp
      image: ikubernetes/myapp:v1
      imagePullPolicy: IfNotPresent
      
[root@k8s-master1 pod]# kubectl apply -f pod-first-required-anti-affinity-demo-1.yaml

# pod资源被调度到node2
[root@k8s-master1 pod]# kubectl get pods -owide
pod-first   k8s-node2.kaser.org   <none>           <none>

# 编写yaml
[root@k8s-master1 pod]# vim pod-first-required-anti-affinity-demo-2.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-second
  labels:
    app: backend
spec:
  containers:
  - name: busybox
    image: docker.io/library/busybox:latest
    imagePullPolicy: IfNotPresent
    command: ["sh","-c","sleep 3600"]
  affinity:
    podAntiAffinity:			# pod反亲和性
      requiredDuringSchedulingIgnoredDuringExecution:	# 硬亲和性
      - labelSelector:
          matchExpressions:
          - {key: "app3",operator: "In",values: ["myapp3"]}
        topologyKey: zone

[root@k8s-master1 pod]# kubectl apply -f pod-first-required-anti-affinity-demo-2.yaml

# 查看状态(发现状态为Pending!)
# 首先:pod反亲和性的标签选择性和topologkey都是互斥的,因此Pending
[root@k8s-master1 pod]# kubectl get pods -owide
pod-first    Running   k8s-node2.kaser.org
pod-second   Pending   <none>
# 详细查看
[root@k8s-master1 pod]# kubectl describe pods pod-second
...
Warning  FailedScheduling  21s   default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }, 2 node(s) didn't match pod anti-affinity rules. preemption: 0/3 nodes are available: 1 Preemption is not helpful for scheduling, 2 No preemption victims found for incoming pod.
podaffinity			pod节点亲和性,pod倾向于哪个pod
poduntiaffinity		pod反亲和性
nodeaffinity		node节点亲和性,pod倾向于哪个node

6.4 污点 Taints、容忍度 Tolerations

6.4.1 污点 Taints
  • 作用对象:节点Node
  • 目的:排斥不满足条件的Pod,防止调度到特定节点
  • 组成:<key>=<value>:<effect>
    • key:污点标识
    • value:可选值(可为空)
    • effect:排斥行为类型(关键)
Effect类型行为
NoSchedule禁止新 Pod 调度,担已运行的不影响
PreferNoSchedule尽量避免调度(软性排斥,调度器会优先选择其他节点)
NoExecute禁止新 Pod 调度并驱逐已运行的不兼容的 Pod(硬性排斥、触发驱逐)
6.4.2 容忍度 Tolerations
  • 作用对象:Pod
  • 目的:声明Pod可以容忍节点的特定污点
  • 匹配规则:
tolerations:
- key: "key1"           # 污点 key
  operator: "Equal"     # 匹配操作符(Equal/Exists)
  value: "value1"       # 污点 value(仅当 operator=Equal 时有效)
  effect: "NoSchedule"  # 污点 effect
  tolerationSeconds: 30 # 仅对 NoExecute 有效:容忍后继续运行的秒数
6.4.3 核心机制详解
6.4.3.1 调度阶段匹配规则
  • 必要条件:Pod 要调度到节点,必须容忍该节点所有污点
    • 节点有污点 A 和 B → Pod 需同时容忍 A 和 B
  • effect 精确匹配
    • Pod 的容忍 effect: NoSchedule 无法匹配 节点的 effect: NoExecute
    • 示例:Master 节点污点 control-plane:NoSchedule 需要 Pod 显式声明 effect: NoSchedule 的容忍
6.4.3.2 运行阶段行为(NoExecute 专属)
  • 场景:节点新增 NoExecute 污点时
    • 无容忍的 Pod:立即驱逐
    • 有容忍但无 tolerationSeconds:永不驱逐
    • 有容忍且指定 tolerationSeconds:容忍期结束后驱逐
tolerations:
- key: "node.kubernetes.io/not-ready"
  effect: "NoExecute"
  tolerationSeconds: 300  # 节点失联后 5 分钟内不驱逐
6.4.4 特殊匹配规则
operator匹配条件示例场景
Equal完全匹配 key=value:effectGPU 节点专用 Pod
Exists仅匹配 keyeffect(忽略 value容忍所有 control-plane 污点
空 key + Exists匹配所有指定 effect 的污点(无论 key/value)容忍所有 NoExecute 污点

正确示例:
节点污点:disk=ssd:NoSchedule
Pod 容忍:

- key: "disk"
  operator: "Equal"
  value: "ssd"
  effect: "NoSchedule"
6.4.5 示例
[root@k8s-master1 pod]# kubectl get nodes
NAME                    STATUS   ROLES           AGE   VERSION
k8s-master1.kaser.org   Ready    control-plane   14d   v1.31.3
k8s-node1.kaser.org     Ready    work            14d   v1.31.3
k8s-node2.kaser.org     Ready    work            14d   v1.31.3

[root@k8s-master1 pod]# kubectl explain nodes.spec.taints
...
  effect        <string> -required-
     - `"NoExecute"`		# 最低容忍度
     	即影响pod调度过程,又影响现存pod资源
     	(简单来说:没符合条件且没被调度的pod来不了,没符合条件且被调度的pod被驱逐)
     - `"NoSchedule"`		# 其次容忍度
     	只影响pod调度过程,现存的pod不影响
     - `"PreferNoSchedule"`	# 最高容忍度
     	最好不调度,但是也可以,是NoScheduler的更柔性版本

  key   <string> -required-
...
  value <string>
    The taint value corresponding to the taint key.
    
    
[root@k8s-master1 pod]# kubectl describe nodes k8s-master1.kaser.org | grep Taints
Taints:             node-role.kubernetes.io/control-plane:NoSchedule
# master的污点为 :NoScheduler
# 因此,创建的pod都不会调度到这个master上,因为我们创建的pod没有容忍度

# 选一个再来看看,在master的pod有什么容忍度?
[root@k8s-master1 pod]# kubectl get pods -n kube-system -owide | grep master
...
# 在筛选出来里面的挑一个查看
[root@k8s-master1 pod]# kubectl describe pods kube-apiserver-k8s-master1.kaser.org -n kube-system | grep Tolerations
Tolerations:       :NoExecute op=Exists

# 可以看到这个pod的容忍度是NoExecute,则可以调度到

示例1:

# 将node2当成生产环境专用节点,其余node是测试节点
[root@k8s-master1 pod]# kubectl get nodes
NAME                    STATUS   ROLES           AGE   VERSION
k8s-master1.kaser.org   Ready    control-plane   14d   v1.31.3
k8s-node1.kaser.org     Ready    work            14d   v1.31.3
k8s-node2.kaser.org     Ready    work            14d   v1.31.3

# 给node2打污点,pod如果不容忍就不会调度到node2上,只会调度到node1上
[root@k8s-master1 pod]# kubectl taint node k8s-node2.kaser.org node-type=production:NoSchedule

[root@k8s-master1 pod]# vim pod-taint.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-taint
  labels:
    tomcat: pod-tomcat
spec:
  containers:
  - name: pod-taint
    ports:
    - containerPort: 8080
    image: docker.io/xianchao/tomcat-8.5-jre8:v1
    imagePullPolicy: IfNotPresent

[root@k8s-master1 pod]# kubectl apply -f pod-taint.yaml
# 确实调度到node1上,因为没有做任何的容忍度处理
[root@k8s-master1 pod]# kubectl get pods -owide
pod-taint   Running		k8s-node1.kaser.org

示例2:

# 将node1也打上污点,看看有什么效果
[root@k8s-master1 pod]# kubectl taint nodes k8s-node1.kaser.org node-type=dev:NoExecute

# pod状态由Running-->Pending
[root@k8s-master1 pod]# kubectl get pods -owide
pod-taint   Pending		<none>
# 编写yaml文件
[root@k8s-master1 pod]# vim pod-taint-1.yaml
apiVersion: v1
kind: Pod
metadata:
  name: myapp-deploy
  labels:
    app: myapp
spec:
  containers:
  - name: myapp
    ports:
    - name: http
      containerPort: 80
    image: docker.io/ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
  tolerations:				# 容忍度的设置
  - key: "node-type"
    operator: "Equal"
    value: "production"
    effect: "NoExecute"
    tolerationSeconds: 3600

[root@k8s-master1 pod]# kubectl apply -f pod-taint-1.yaml

[root@k8s-master1 pod]# kubectl get pods
NAME           READY   STATUS    RESTARTS   AGE
myapp-deploy   0/1     Pending   0          16s
# 查看状态发现还是Pending;
# 是由于我们使用的是equal(等值匹配)
# 因此key和value,effect必须和node节点定义的污点完全匹配才可以,此时将“NoExecute”改为“NoScheduel”,并将容忍期删去即可

[root@k8s-master1 pod]# vim pod-taint-1.yaml
  tolerations:				# 容忍度的设置
  - key: "node-type"
    operator: "Equal"
    value: "production"
    effect: "NoSchedule"

[root@k8s-master1 pod]# kubectl delete pods myapp-deploy
[root@k8s-master1 pod]# kubectl apply -f pod-taint-1.yaml
# 再次查看,发现可以调度到node2上了
[root@k8s-master1 pod]# kubectl get pods -owide
myapp-deploy   Running   k8s-node2.kaser.org

示例3:

# 修改如下部分:
tolerations:
- key: "node-type"
  operator: "Exists"	# 不完全匹配,仅匹配key和effect(可以直接忽略value)
  value: ""
  effect: "NoSchedule"


[root@k8s-master1 pod]# kubectl delete pods myapp-deploy
[root@k8s-master1 pod]# vim pod-taint-1.yaml
...
  tolerations:
  - key: "node-type"
    operator: "Exists"		# Equal --> Exists,完全匹配 --> 不完全匹配
    value: ""
    effect: "NoSchedule"
    
[root@k8s-master1 pod]# kubectl apply -f pod-taint-1.yaml
[root@k8s-master1 pod]# kubectl get pods -owide
myapp-deploy   1/1     Running   k8s-node2.kaser.org



# 修改如下部分:
  tolerations:
  - key: "node-type"
    operator: "Exists"
    value: ""
    effect: ""			# 将effect删去

[root@k8s-master1 pod]# kubectl delete pods myapp-deploy
[root@k8s-master1 pod]# vim pod-taint-1.yaml
...
  tolerations:
  - key: "node-type"
    operator: "Exists"
    value: ""
    effect: ""

[root@k8s-master1 pod]# kubectl apply -f pod-taint-1.yaml

# 此时发现,还是还是可以运行Running且调度到node1(由于两个node节点都有node-type的key)
[root@k8s-master1 pod]# kubectl get pods -owide
myapp-deploy   1/1     Running   k8s-node1.kaser.org


# 为了避免影响后面实验,将资源与污点删去
[root@k8s-master1 pod]# kubectl taint nodes k8s-node1.kaser.org node-type:NoExecute-
[root@k8s-master1 pod]# kubectl taint nodes k8s-node2.kaser.org node-type-

更多推荐