Kubernetes Pod Scheduler 详解

Kubernetes Pod Scheduler

学习参考:调度、抢占和驱逐

环境准备

bash 复制代码
root@master30:~# kubectl create ns scheduler
root@master30:~# kubectl config set-context --current --namespace scheduler

一、调度介绍

K8s 调度:把 Pod 分配到合适的 Node 节点上运行 。 kube‑scheduler 是集群默认调度器 ,通过 Watch 监听还没有分配节点的 Pending 状态 Pod,选出合适节点。

二、直接创建 Pod 过程

精简流程

  1. 客户端发送创建Pod请求至kube-apiserver;
  2. apiserver完成认证、授权、准入校验,合法则把Pod存入etcd,Pod为Pending;
  3. kube-scheduler监听到未调度Pod,执行过滤、打分选出最优节点,向apiserver发起绑定;
  4. apiserver更新Pod,写入节点名称;
  5. 对应节点kubelet感知Pod绑定到本机,调用CNI准备网络、CSI准备存储;
  6. kubelet调用容器运行时拉取镜像、启动容器;
  7. kubelet检测容器正常运行,通知apiserver将Pod状态更新为Running。

三、通过控制器创建 Pod 过程

精简版本

重要区分:**controller‑manager 只负责增删 Pod,不做调度,不启动容器;调度交给 kube‑scheduler**

  1. 用户通过kubectl提交Deployment创建请求至apiserver;
  2. apiserver完成认证授权准入校验后将Deployment存入etcd。
  3. kube-controller-manager内的Deployment控制器监听到Deployment,对比期望副本数与现有Pod数量,缺失则调用apiserver创建Pending状态Pod存入etcd。
  4. kube-scheduler监听到未调度Pod,筛选选出最优节点并发起绑定;
  5. apiserver更新Pod绑定节点名称。
  6. 对应节点kubelet感知Pod调度到本机,准备存储与网络,调用容器运行时拉取镜像启动容器;
  7. kubelet检测容器正常运行后更新Pod状态为Running。

精简链路: Client → kube‑apiserver → etcd → kube‑controller‑manager → kube‑scheduler → kubelet → containerd

四、Pod 调度过程

Kubernetes 使用以下策略挑选出适合运行pod的最佳节点:

  1. 过滤,将满足 Pod 调度需求的所有节点选出来。
  2. 打分,根据当前启用的打分规则,调度器会给每一个可调度节点进行打分,调度器将 Pod 调度到得分最高的节点上。 如果存在多个得分最高的节点 ,kube-scheduler 会从中随机选取一个。

4.1 过滤

Kubernetes 使用**断言(Predicates)**过滤出适合调度的节点:

  • 常用过滤规则:
    • PodFitsResources:节点 CPU 内存满足 Pod requests 资源
    • MatchNodeSelector:匹配 nodeSelector 节点标签
    • PodToleratesNodeTaints:Pod 容忍度匹配节点污点
    • PodFitsHostPorts:节点端口不冲突
    • PodFitsHostPorts:Pod 要使用的 hostPort,节点上没有被占用冲突
    • CheckVolumeBinding:PVC 存储可以绑定到节点存储

4.2 计分

Kubernetes 使用根据以下 优先级 Priorities 对主机进行评分:

  • 常用打分策略:
    • LeastRequestedPriority:优先选资源占用更少、更空闲的节点

    • SelectorSpreadPriority:同一个控制器下的 Pod,尽量分散到不同节点(只是打分偏好,不是强制打散)

    • NodeAffinityPriority:nodeAffinity 里面 preferred 软亲和的权重,在这里计入总分

    • ImageLocalityPriority:优先选本地已经存在该镜像的节点,减少拉镜像耗时

    • TaintTolerationPriority:污点容忍度打分

    • EvenPodsSpreadPriority:Pod 拓扑分布约束打分

✅区分一句话:过滤 = 必须满足;打分 = 尽量优先,不强求。


五、4种控制 pod 运行位置的方式

参考学习:将 Pod 指派给节点

用途 :人为干预,指定 Pod 可以跑在哪些节点;例如 SSD 磁盘节点、GPU 节点。

**4 种方式:**与节点名匹配nodeName、与节点标签匹配nodeSelector、亲和 / 反亲和、Pod 拓扑分布约束。

5.1 nodeName(直接指定节点名,优先级最高)

直接写spec.nodeName: 节点主机名,绕过调度器,强制 Pod 运行在指定节点。

⚠️缺点:

  1. 节点不存在 ,Pod 无法运行甚至删除
  2. 节点资源不足 ,Pod 会失败(OutOfcpu/OutOfmemory)
  3. 云环境节点名字不稳定,不推荐生产使用。

示例:

bash 复制代码
root@master30:~# kubectl create deployment webapp --image nginx --replicas 2 -o yaml --dry-run=client > deploy-with-nodeName.yaml
root@master30:~# vim deploy-with-nodeName.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: webapp
  name: webapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  strategy: {}
  template:
    metadata:
      labels:
        app: webapp
    spec:
      # 添加nodeName配置
      nodeName: worker32.laoma.cloud
      containers:
      - image: hub.laoma.cloud/library/nginx
        name: nginx
root@master30:~# kubectl apply -f deploy-with-nodeName.yaml
root@master30 ~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME STATUS NODE
webapp-55c6b896bc-ns9m5 Running worker32.laoma.cloud
webapp-55c6b896bc-qr892 Running worker32.laoma.cloud
# 清理资源
root@master30:~# kubectl delete deployments.apps webapp 

5.2 nodeSelector(节点标签硬性匹配,简单推荐)

Pod 的 spec.nodeSelector,键值对 ,节点必须全部拥有这些标签才可以调度 Pod,不满足 Pod 就 Pending。

⚠重要特性:只有新建 Pod 生效;已经运行的 Pod,节点标签修改 / 删除,不会驱逐 Pod。

提示:nodeSelector 是节点选择约束的推荐形式。

bash 复制代码
# 查看node标签
root@master30:~# kubectl get node --show-labels 
NAME                  STATUS   ROLES           AGE    VERSION   LABELS
master30.laoma.cloud   Ready    control-plane   7d8h   v1.30.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=master30.laoma.cloud,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/exclude-from-external-load-balancers=
worker31.laoma.cloud   Ready    <none>          7d8h   v1.30.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker31.laoma.cloud,kubernetes.io/os=linux
worker32.laoma.cloud   Ready    <none>          7d8h   v1.30.2   beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.io/arch=amd64,kubernetes.io/hostname=worker32.laoma.cloud,kubernetes.io/os=linux

# 给节点打标签,标注worker31 disktype是ssd
root@master30:~# kubectl label node worker31.laoma.cloud disktype=ssd
root@master30:~# kubectl get node -L disktype
NAME                   STATUS   ROLES           AGE   VERSION   DISKTYPE
master30.laoma.cloud   Ready    control-plane   11d   v1.30.2   
worker31.laoma.cloud   Ready    <none>          11d   v1.30.2   ssd
worker32.laoma.cloud   Ready    <none>          11d   v1.30.2   

在Pod模板的spec里通过nodeSelector指定将此Pod部署到具有label为 disktype=ssd 的Node上。

bash 复制代码
root@master30:~# vim deploy-with-nodeSelector.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: webapp
  name: webapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webapp
  template:
    metadata:
      labels:
        app: webapp
    spec:
      # 添加 nodeSelector
      nodeSelector:
        disktype: ssd
      containers:
      - image: hub.laoma.cloud/library/nginx
        name: nginx
root@master30:~# kubectl apply -f deploy-with-nodeSelector.yaml
root@master30:~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME NODE
webapp-7d675f9d9c-rz9hx Running worker31.laoma.cloud
webapp-7d675f9d9c-zrj97 Running worker31.laoma.cloud

# 删除worker31标签
root@master30:~# kubectl label node worker31.laoma.cloud disktype-
root@master30:~# kubectl get node -L disktype
NAME                   STATUS   ROLES           AGE   VERSION   DISKTYPE
master30.laoma.cloud   Ready    control-plane   11d   v1.30.2   
worker31.laoma.cloud   Ready    <none>          11d   v1.30.2   
worker32.laoma.cloud   Ready    <none>          11d   v1.30.2   

# 此时不会触发重新部署deployment

# 删除deployment的nodeSelector
root@master30:~# kubectl patch deployment webapp --type json \
-p '[{"op":"remove","path":"/spec/template/spec/nodeSelector"}]'
# 自动触发重新部署
root@master30:~# kubectl get pods -o wide | awk '{print $1,$3,$7}'
NAME NODE
webapp-7fb6dc88f9-hj6m6 Running worker31.laoma.cloud
webapp-7fb6dc88f9-qcvtr Running worker32.laoma.cloud
# 清理资源
root@master30:~# kubectl delete deployments.apps webapp 

5.3 Affinity and AntiAffinity亲和与反亲和

亲和性为两大类:

  1. nodeAffinity 节点亲和:看节点标签
  2. podAffinity / podAntiAffinity Pod 亲和 / 反亲和:看节点上已经运行的其他 Pod 标签

后缀统一 IgnoredDuringExecution:调度的时候检查规则;Pod 已经运行后,标签变化,不会驱逐 Pod。

5.3.1 nodeAffinity 节点亲和

两种模式:

  1. ==requiredDuringSchedulingIgnoredDuringExecution:硬亲和,必须满足,不满足 Pod Pending,==功能类似 nodeSelector
  2. preferredDuringSchedulingIgnoredDuringExecution:软亲和,优先偏好;设置 weight 权重 1‑100;符合就给节点加分;不满足也可以调度
逻辑规则
  1. nodeSelector + nodeAffinity 同时写:两个条件必须全部满足
  2. 多个nodeSelectorTerms:满足任意一个 term 即可(或)
  3. 同一个 term 内部多个matchExpressions:全部表达式必须满足(且)
完整示例 yaml
bash 复制代码
root@master30:~# vim deploy-with-nodeAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: CPU
                operator: In
                values:
                - L1
                - L2
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            preference:
              matchExpressions:
              - key: MEM
                operator: In
                values:
                - L1
                - L2
      containers:
      - image: hub.laoma.cloud/library/nginx
        name: nginx

示例说明:

  • 硬规则:Pod 只能调度到带有标签 CPU=L1 或者 CPU=L2 的节点;没有该标签 → Pod Pending。
  • 软规则:在满足硬规则的候选节点池中,如果节点拥有MEM=L1/MEM=L2,给该节点加权重weight=1,优先调度到此节点。

如果你修改或删除了 pod 所调度到的节点的标签,Pod 不会被删除。 换句话说,亲和性选择只在 Pod 调度期间有效。

验证1:节点未打标签

bash 复制代码
root@master30:~# kubectl apply -f deploy-with-nodeAffinity.yaml
root@master30:~# kubectl get pods
NAME                  READY   STATUS    RESTARTS   AGE
web-854cd4986-598lb   0/1     Pending   0          2s
web-854cd4986-mp266   0/1     Pending   0          2s
# Pod全部Pending,不满足硬亲和条件

验证2:节点打 CPU 标签

bash 复制代码
root@master30:~# kubectl label nodes worker31.laoma.cloud CPU=L1
root@master30:~# kubectl label nodes worker32.laoma.cloud CPU=L2

# 再次查看pod调度情况
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-854cd4986-598lb Running worker31.laoma.cloud
web-854cd4986-mp266 Running worker31.laoma.cloud
# worker31打上标签后,满足pod调度条件,此时所有pod调度到worker31节点。

# 重新部署
root@master30:~# kubectl rollout restart deployment web
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-85c8ffff8-q2j7r Running worker31.laoma.cloud
web-85c8ffff8-rkq7s Running worker32.laoma.cloud、
# worker31和worker32都打上标签后,满足pod调度条件,此时pod均衡调度到worker31和worker32节点。

⚠重点:软亲和只是打分偏好,不保证一定打散 Pod;分数相同会随机选择,会出现 Pod 扎堆 。想要强制打散副本,使用 Pod 反亲和podAntiAffinity。

验证3:worker32节点打标签MEM,触发软亲和权重

bash 复制代码
root@master30:~# kubectl label nodes worker32.laoma.cloud MEM=L2
# 重新部署
root@master30:~# kubectl rollout restart deployment web

# 只在worker32上运行
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web-6c45779498-glxbg Running worker32.laoma.cloud
web-6c45779498-sn996 Running worker32.laoma.cloud
清理环境
bash 复制代码
root@master30:~# kubectl delete deployments.apps web
root@master30:~# kubectl label nodes worker31.laoma.cloud CPU-
root@master30:~# kubectl label nodes worker32.laoma.cloud CPU-
root@master30:~# kubectl label nodes worker32.laoma.cloud MEM-
软亲和 weight 权重打分规则
  1. weight范围:1‑100,数值越大优先级越高。
  2. 调度器遍历所有preferredDuringSchedulingIgnoredDuringExecution规则;节点每命中一条规则,就累加对应的 weight 权重。
  3. 累加出来的总权重,会和调度器其他打分函数结果合并;节点最终总分越高,越优先被选中。

例如,考虑下面的 Pod 规约:

yaml 复制代码
apiVersion: v1
kind: Pod
metadata:
  name: with-affinity-anti-affinity
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/os
            operator: In
            values:
            - linux
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        preference:
          matchExpressions:
          - key: label-1
            operator: In
            values:
            - key-1
      - weight: 50
        preference:
          matchExpressions:
          - key: label-2
            operator: In
            values:
            - key-2
  containers:
  - name: with-node-affinity
    image: hub.laoma.cloud/library/nginx

候选节点 A 拥有label‑1=key‑1,得 1 分;候选节点 B 拥有label‑2=key‑2,得 50 分;B 总分更高,优先调度 B。

5.3.2 Inter-pod affinity and anti-affinity

💡核心区别: nodeAffinity:看节点标签 做调度判断 podAffinity/podAntiAffinity:不看节点标签,看同一个拓扑域内已经运行的其他 Pod 的标签

两个概念
  1. podAffinity(Pod 亲和) :希望新 Pod,和满足标签条件的 Pod跑到同一个拓扑域(挨在一起)
  2. podAntiAffinity(Pod 反亲和) :希望新 Pod,不要和满足标签条件的 Pod跑到同一个拓扑域(打散副本,提高高可用)
关键字段 topologyKey

topologyKey:节点上的标签,用来定义 "拓扑域范围"

  • kubernetes.io/hostname:拓扑域 = 单个节点(以一台机器作为判断范围)
  • topology.kubernetes.io/zone:拓扑域 = 一个可用区(同一机房 / 机架)

⚠重要限制

  1. 硬、软的 podAffinity/podAntiAffinity,topologyKey不能为空。
  2. 集群所有节点必须拥有这个 topologyKey 对应的标签,否则调度行为会异常。
  3. 缺点:大规模集群(几百节点以上),Pod 亲和 / 反亲和会大量遍历 Pod 信息,消耗调度性能,不推荐大规模使用。
两种规则模式(和 nodeAffinity 一样)
  1. requiredDuringSchedulingIgnoredDuringExecution【硬规则】:条件不满足,Pod 直接 Pending
  2. preferredDuringSchedulingIgnoredDuringExecution【软规则】:尽量满足,给节点加 weight 权重打分,不满足也可以调度

后缀 IgnoredDuringExecution:只在 Pod 新建调度那一刻生效;Pod 已经运行后,其他 Pod 标签变化,不会驱逐已经运行的 Pod。

语法说明

本示例定义了一条 Pod 亲和性规则和一条 Pod 反亲和性规则。

bash 复制代码
root@master30:~# vim pod-with-podaffinity.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-podaffinity
spec:
  affinity:
    podAffinity:  # Pod亲和:想跟符合条件Pod同拓扑域
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity:   # Pod反亲和:尽量避开符合条件Pod的拓扑域
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: topology.kubernetes.io/zone
  containers:
  - name: pod-with-podaffinity
    image: hub.laoma.cloud/library/nginx
  • podAffinity 解读(硬) : 当前拓扑可用区zone里面,必须至少已经跑了一个标签security=S1的 Pod,新 Pod 才允许调度进这个可用区。该可用区没有这个 Pod → 不能调度。
  • podAntiAffinity 解读(软) : 如果该可用区已经存在security=S2的 Pod,尽量不要把新 Pod 调度到此可用区;不是强制禁止。

支持运算符:In、NotIn、Exists、DoesNotExist

示例 1:根据节点拓扑--亲和性调度
bash 复制代码
# node节点打标签
root@master30:~# kubectl label node worker31.laoma.cloud \
topology.kubernetes.io/zone=v
root@master30:~# kubectl label node worker32.laoma.cloud \
topology.kubernetes.io/zone=v

# 创建两个pod
root@master30:~# vim deploy-with-podaffinity.yaml
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web1
spec:
  selector:
    matchLabels:
      app: web1
  replicas: 1
  template:
    metadata:
      labels:
        app: web1
    spec:
      nodeName: worker31.laoma.cloud
      containers:
      - name: web-server
        image: hub.laoma.cloud/library/nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web2
spec:
  selector:
    matchLabels:
      app: web2
  replicas: 1
  template:
    metadata:
      labels:
        app: web2
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web1
            topologyKey: topology.kubernetes.io/zone
      containers:
      - name: web-server
        image: hub.laoma.cloud/library/nginx
root@master30:~# kubectl apply -f deploy-with-podaffinity.yaml 
root@master30 ~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web1-849b694c75-t2spf Running worker31.laoma.cloud
web2-84fdf8f999-vn5l9 Running worker32.laoma.cloud

**实验结果:**pod-with-podaffinity 调度到 worker32节点。

如果此时将work32节点的 topology.kubernetes.io/zone 标签删除,重新创建pod pod-with-podaffinity,会调度到哪个节点呢?

bash 复制代码
root@master30:~# kubectl label nodes worker32.laoma.cloud \
topology.kubernetes.io/zone-
root@master30 ~# kubectl rollout restart deployment web2

# 调度到worker31节点
root@master30 ~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
web1-849b694c75-t2spf Running worker31.laoma.cloud
web2-7947994f89-pwnkv Running worker31.laoma.cloud
# 清理环境
root@master30:~# kubectl delete -f deploy-with-podaffinity.yaml 
root@master30:~# kubectl label nodes worker31.laoma.cloud \
topology.kubernetes.io/zone-

现象:worker32 不再属于 zone=v,整个 zone=v 只剩下 worker31,web2 只能调度到 worker31。

✅podAffinity 生效两个必要条件:

  1. 节点拥有topologyKey对应的标签,划分出拓扑域;
  2. 该拓扑域内部,至少已经运行一个匹配 labelSelector 的 Pod
示例 2:根据节点主机名--反亲和性调度

(topologyKey=kubernetes.io/hostname,以单个节点作为拓扑域)

经典用途:同一个应用副本不要部署到同一个节点,实现高可用。

bash 复制代码
# 准备deployment
root@master30:~# vim deploy-with-podAntiAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: store
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: web-server
        image: hub.laoma.cloud/library/nginx
# 查看pod调度情况
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-9z5ch Pending <none>
store-5596cf4c84-dswkq Running worker32.laoma.cloud
store-5596cf4c84-ww2l4 Running worker31.laoma.cloud

规则解读: topologyKey: kubernetes.io/hostname 拓扑域 = 单个节点; 硬反亲和:同一个节点上,不能存在 2 个app=store的 Pod,每个节点最多 1 个。

实验现象: 集群只有 2 个 worker 节点,replicas=3。

  • worker31:跑 1 个 store
  • worker32:跑 1 个 store
  • 第 3 个 Pod 找不到节点,状态 Pending

重点:副本数量 > 拓扑域(节点)数量,硬 podAntiAffinity 会造成 Pod Pending!

示例 3:多个应用亲和性和反亲和性调度

需求:

  1. podAntiAffinity(硬):同一个节点不能跑多个 web‑store 副本(自己和自己打散)
  2. podAffinity(硬):必须和app=store的 Pod 处在同一个节点(要跟 store 挨在一起)
bash 复制代码
# 缩容 deployment
root@master30:~# kubectl scale deployment store --replicas 1
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-dswkq Running worker32.laoma.cloud

# 准备新deployment
root@master30:~# vim deploy-with-multi-Affinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-store
spec:
  selector:
    matchLabels:
      app: web-store
  replicas: 1
  template:
    metadata:
      labels:
        app: web-store
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web-store
            topologyKey: "kubernetes.io/hostname"
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - store
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: web-app
        image: hub.laoma.cloud/library/nginx
root@master30:~# kubectl apply -f deploy-with-multi-Affinity.yaml
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-f4dfb Running worker32.laoma.cloud
web-store-7b4779c958-vpl58 Running worker32.laoma.cloud

# 扩容:验证新副本位置
root@master30:~# kubectl get pods -o wide --no-headers |awk '{print $1,$3,$7}'
store-5596cf4c84-f4dfb Running worker32.laoma.cloud
web-store-7b4779c958-bswws Pending <none>
web-store-7b4779c958-vpl58 Running worker32.laoma.cloud
# 在亲和性作用下,只有worker32节点具备满足条件的pod
# 在反亲和性作用下,worker32节点上已经运行了一个副本
# 最终结果:第二个 pod 调度 挂起。

5.4 Taint 和 Toleration

nodeAffinity:Pod 挑选节点 ;Taint:节点挑选 Pod

  • node 使用 Taint(污点),允许特定Pod在本机运行,未匹配的Pod则不能在该节点运行。
  • pod 使用 Toleration(容忍度),允许被调度到带有与之匹配的污点的节点上。

后缀IgnoredDuringExecution不适用污点;污点的NoExecute可以驱逐已经运行的 Pod。

5.4.1 给节点设置污点 Taint

使用命令 kubectl taint 给节点**增加一个污点**。

示例 :给节点 worker31.laoma.cloud 增加一个污点,键名 CPU,键值 L1,效果 NoSchedule。

shell 复制代码
# 表示拥有和这个污点相匹配的tolerations的 Pod 才能够被分配到 worker31.laoma.cloud`这个节点。
root@master30:~# kubectl taint nodes worker31.laoma.cloud \
CPU=L1:NoSchedule

若要**移除worker污点**:

shell 复制代码
# 删除污点,末尾加 `-`
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU:NoSchedule-
5.4.2 Pod 设置容忍度 tolerations

在 Pod.Spec 中定义 Pod 的 tolerations。

示例:

yaml 复制代码
tolerations:
- key: "CPU"
  operator: "Equal"
  value: "L1"
  effect: "NoSchedule"

operator 可选值:

  • Equal(默认 ):key、value、effect三者全部匹配才算命中。

  • Exists:只匹配 key,不需要写 value,只要污点 key 存在即匹配。

    bash tolerations: - key: "CPU" operator: "Exists" effect: "NoSchedule"

特殊:key:"" + operator:Exists,可以容忍任意污点。

effect 三种效果(重中之重)

  1. NoSchedule
    • 仅约束新 Pod 调度;
    • 不能容忍该污点 → 禁止调度到此节点;
    • ✅已经在节点上运行的 Pod 不会被驱逐。
  2. PreferNoSchedule(软性)
    • 尽量不要调度,只是偏好;不强制,有可能还会调度上来。
  3. NoExecute(最强,会影响正在运行的 Pod)
    • Pod 不能容忍:立刻驱逐 Pod
    • Pod 能容忍、不写tolerationSeconds:Pod 永久留在节点,不会驱逐
    • Pod 能容忍、配置tolerationSeconds: 秒数:还可以继续运行指定秒数,时间到驱逐
    • 还没调度的 Pod,禁止调度到该节点。

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

5.4.3 一个容忍度和一个污点 匹配

示例:

设置 node 污点:

bash 复制代码
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule

创建 pod :

bash 复制代码
root@master30:~# vim deploy-with-tolerations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      tolerations:
      - key: "CPU"
        operator: "Equal"
        value: "L3"
        effect: "NoSchedule"
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx
        imagePullPolicy: IfNotPresent
# 创建上述pod示例
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-5bf7b7b98f-kh7hv   0/1     Pending   0          2s
web-5bf7b7b98f-km7w5   0/1     Pending   0          2s
# pod状态为Pending

# 事件表明3个节点上的污点与pod不匹配,所以无法创建pod
root@master30:~# kubectl describe pod web-5bf7b7b98f-kh7hv
......
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  29s   default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {CPU: L1}, 1 node(s) had untolerated taint {CPU: L2}, 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.


# 删除 deployments,将容忍度改为CPU=L1
root@master30:~# kubectl delete deployments.apps web
root@master30:~# vim deploy-with-tolerations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      tolerations:
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoSchedule"
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx
        imagePullPolicy: IfNotPresent
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods -o wide
NAME                 READY   STATUS    RESTARTS   AGE   IP               NODE                   NOMINATED NODE   READINESS GATES
web-7c99d767-w9rzt   1/1     Running   0          5s    10.224.162.142   worker31.laoma.cloud   <none>           <none>
web-7c99d767-wwjhk   1/1     Running   0          5s    10.224.19.13     worker31.laoma.cloud   <none>           <none>

# Pod调度成功,而且分配到了worker31

# 清理环境
root@master30:~# kubectl taint node worker31.laoma.cloud CPU:NoSchedule-
root@master30:~# kubectl taint node worker32.laoma.cloud CPU:NoSchedule-
root@master30:~# kubectl delete deployments.apps web
5.4.4 多个容忍度和多个污点 匹配

一个节点可以打多个污点 ;一个 Pod 可以写多条容忍度。

匹配过滤逻辑:

  1. 遍历节点全部污点;Pod 容忍度匹配上哪些污点,就过滤掉这些污点。
  2. 剩下没有被容忍的污点,决定 Pod 能不能调度 / 是否驱逐
    • 剩余污点包含NoSchedule → 禁止调度
    • 没有 NoSchedule,但存在PreferNoSchedule → 尽量不调度
    • 剩余污点包含NoExecute → 禁止调度;已经运行的 Pod 直接驱逐。

例如,某个节点添加了如下污点:

shell 复制代码
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoExecute
root@master30:~# kubectl taint nodes worker31.laoma.cloud MEM=L2:NoSchedule

假定某个 Pod 有两个容忍度:

yaml 复制代码
tolerations:
- key: "CPU"
  operator: "Equal"
  value: "L1"
  effect: "NoSchedule"
- key: "CPU"
  operator: "Equal"
  value: "L1"
  effect: "NoExecute"

Pod 容忍度只写了前 2 个污点,缺少对MEM=L2:NoSchedule的容忍。

👉 结果:新 Pod 无法调度到 worker31;

👉 如果 Pod 已经提前运行在节点上:因为只有MEM=L2:NoSchedule(NoSchedule,不驱逐运行 Pod),Pod 可以继续跑,只是不能新增调度。

示例:

设置 node 多个污点

bash 复制代码
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoExecute
root@master30:~# kubectl taint nodes worker31.laoma.cloud MEM=L2:NoSchedule
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule

创建 Deployment:

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      tolerations:
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoSchedule"
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoExecute"
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx
        imagePullPolicy: IfNotPresent
# 创建上述pod示例
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pods
NAME                   READY   STATUS    RESTARTS   AGE
web-6844d698c4-b8cz7   0/1     Pending   0          4s
web-6844d698c4-wrpkj   0/1     Pending   0          4s
# pod状态为Pending

# 事件表明节点上的3个污点与pod不匹配,所以无法创建pod
root@master30:~# kubectl describe pod web-6844d698c4-b8cz7 
......
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  50s   default-scheduler  0/3 nodes are available: 1 node(s) had untolerated taint {CPU: L2}, 1 node(s) had untolerated taint {MEM: L2}, 1 node(s) had untolerated taint {node-role.kubernetes.io/control-plane: }. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling.

# 删除deployments,添加容忍度MEM=L2
root@master30:~# kubectl delete deployments.apps web
root@master30:~# vim deploy-with-tolerations.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: web
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      tolerations:
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoSchedule"
      - key: "CPU"
        operator: "Equal"
        value: "L1"
        effect: "NoExecute"
      - key: "MEM"
        operator: "Equal"
        value: "L2"
        effect: "NoSchedule"
      containers:
      - name: nginx
        image: hub.laoma.cloud/library/nginx
        imagePullPolicy: IfNotPresent
root@master30:~# kubectl apply -f deploy-with-tolerations.yaml
root@master30:~# kubectl get pod -o wide
NAME                   READY   STATUS    RESTARTS   AGE   IP             NODE                   NOMINATED NODE   READINESS GATES
web-77d5464ccf-656fd   1/1     Running   0          5s    10.224.19.18   worker31.laoma.cloud   <none>           <none>
web-77d5464ccf-wjk6m   1/1     Running   0          5s    10.224.19.27   worker31.laoma.cloud   <none>           <none>
# Pod 调度成功,而且分配到了worker31

# 清理环境
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoSchedule-
root@master30:~# kubectl taint nodes worker31.laoma.cloud CPU=L1:NoExecute-
root@master30:~# kubectl taint nodes worker31.laoma.cloud MEM=L2:NoSchedule-
root@master30:~# kubectl taint nodes worker32.laoma.cloud CPU=L2:NoSchedule-
root@master30:~# kubectl delete deployments.apps web

实验现象:Pod 状态 Pending,事件提示存在未被容忍污点。 补充完整 MEM 的容忍度,Pod 即可正常调度。

5.4.5 Taint 和 Toleration 用例
  1. 专用节点 给节点打污点dedicated=groupA:NoSchedule; 只有对应 Pod 配置容忍度,才允许调度。

⚠注意:仅容忍度只能 "允许进来",不能限制 Pod只跑在这批节点 。 如果要求 Pod只能跑在专用节点 :容忍度 + nodeAffinity 节点亲和一起配合使用。

  1. GPU 特殊硬件节点 节点打污点,普通无 GPU 业务没有容忍度就不会占用 GPU 节点;使用 GPU 的 Pod 配置容忍度。
5.4.6 系统内置自动污点(NoExecute)

节点异常时,node‑controller 自动给节点打上下面这些污点:

  • node.kubernetes.io/not-ready 节点未就绪
  • node.kubernetes.io/unreachable 节点访问不到
  • node.kubernetes.io/memory‑pressure 内存压力
  • node.kubernetes.io/disk‑pressure 磁盘压力
  • node.kubernetes.io/pid‑pressure PID 资源压力
  • node.kubernetes.io/network‑unavailable 网络不可用
  • node.kubernetes.io/unschedulable 节点不可调度

自动容忍行为(考试高频)

  1. 普通业务 Pod :K8s 自动添加容忍度,tolerationSeconds=300(5 分钟)。

节点故障,Pod 还可以继续运行 5 分钟,5 分钟之后驱逐。

  1. DaemonSet 的 Pod(calico‑node、kube‑proxy、node‑exporter)

自动容忍上面两个故障污点,没有 tolerationSeconds 。 节点失联、未就绪,永远不会驱逐 DaemonSet Pod,保障网络 / 监控组件运行。

自定义示例:Pod 希望网络断开后,多等待 10 分钟再驱逐

yaml 复制代码
tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000
5.4.7 cordon 和 uncordon
bash 复制代码
# 封锁节点,禁止新Pod调度
kubectl cordon 节点名
# 解除封锁,恢复调度
kubectl uncordon 节点名
  • 给节点==标记SchedulingDisabled ,不可调度,==将无法在node上创建新的pod。
  • ✅禁止新 Pod 调度上来
  • ❗**已经运行在节点上的 Pod 完全不受影响,不会驱逐删除**

注意:cordon 只是标记,不是打 taint 污点,机制不一样。

bash 复制代码
root@master30:~# kubectl get nodes
NAME                  STATUS   ROLES           AGE   VERSION
master30.laoma.cloud   Ready    control-plane   9d    v1.30.2
worker31.laoma.cloud   Ready    <none>          9d    v1.30.2
worker32.laoma.cloud   Ready    <none>          9d    v1.30.2

# 封锁节点,禁止新Pod调度
root@master30:~# kubectl cordon worker31.laoma.cloud 
root@master30:~# kubectl get nodes
NAME                  STATUS                     ROLES           AGE   VERSION
master30.laoma.cloud   Ready                      control-plane   9d    v1.30.2
worker31.laoma.cloud   Ready,SchedulingDisabled   <none>          9d    v1.30.2
worker32.laoma.cloud   Ready                      <none>          9d    v1.30.2


# 创建一个deployment
root@master30:~# kubectl create deployment web --image=hub.laoma.cloud/library/nginx --replicas=2
root@master30:~# kubectl get pod -o wide
NAME                   READY   STATUS    RESTARTS   AGE   IP              NODE               NOMINATED NODE   READINESS GATES
web-59b9bb7664-flhvx   1/1     Running   0          10s   10.224.225.69   worker32.laoma.cloud   <none>           <none>
web-59b9bb7664-ss586   1/1     Running   0          10s   10.224.225.68   worker32.laoma.cloud   <none>           <none>

# 解除封锁,恢复调度
root@master30:~# kubectl uncordon worker31.laoma.cloud 
root@master30:~# kubectl get nodes
NAME                STATUS   ROLES    AGE   VERSION
master.laoma.cloud   Ready    control-plane   36d   v1.30.2
worker31.laoma.cloud    Ready    <none>   36d   v1.30.2
worker32.laoma.cloud    Ready    <none>   36d   v1.30.2

root@master30:~# kubectl scale deployment web --replicas=4
root@master30:~# kubectl get pod -o wide
NAME                   READY   STATUS    RESTARTS   AGE    IP              NODE               NOMINATED NODE   READINESS GATES
web-59b9bb7664-flhvx   1/1     Running   0          2m6s   10.224.225.69   worker32.laoma.cloud   <none>           <none>
web-59b9bb7664-jkb5q   1/1     Running   0          5s     10.224.51.133   worker31.laoma.cloud   <none>           <none>
web-59b9bb7664-ss586   1/1     Running   0          2m6s   10.224.225.68   worker32.laoma.cloud   <none>           <none>
web-59b9bb7664-w6mbs   1/1     Running   0          5s     10.224.51.132   worker31.laoma.cloud   <none>           <none>
5.4.8 drain 节点排空

对节点执行维护(例如内核升级、硬件维护等 )之前, 可以使用 kubectl drain 从节点安全地逐出所有 Pod 。 安全的驱逐过程允许 Pod 的容器体面地终止, 并确保满足指定的 PodDisruptionBudgets。

drain = cordon(封锁节点) + 驱逐节点上普通业务 Pod;

被驱逐 Pod 会被控制器在其他健康节点重建。

  1. 默认会报错:不能驱逐 DaemonSet 管理 Pod(calico‑node/kube‑proxy)
  2. 必须加参数 --ignore‑daemonsets,跳过 DaemonSet Pod,它们会继续留在本节点运行。
kubectl drain 实践

kubectl drain 命令,驱逐 node 上所有 pod ,同时标记 node 为不可调度。默认情况下,该命令忽略节点上不能驱逐的 Pod

bash 复制代码
# 默认不驱逐 DaemonSet 控制的pod
root@master30:~# kubectl drain worker31.laoma.cloud 
node/worker31.laoma.cloud cordoned
error: unable to drain node "worker31.laoma.cloud", aborting command...

There are pending nodes to be drained:
 worker31.laoma.cloud
error: cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-system/calico-node-jwdzx, kube-system/kube-proxy-98qkl

# 使用--ignore-daemonsets驱逐DaemonSet控制的pod
root@master30:~# kubectl drain worker31.laoma.cloud --ignore-daemonsets 
node/worker31.laoma.cloud already cordoned
WARNING: ignoring DaemonSet-managed Pods: kube-system/calico-node-jwdzx, kube-system/kube-proxy-98qkl
evicting pod kube-system/kuboard-74c645f5df-ljk98
evicting pod laoma/web-59b9bb7664-jkb5q
evicting pod laoma/web-59b9bb7664-w6mbs
pod/web-59b9bb7664-w6mbs evicted
pod/web-59b9bb7664-jkb5q evicted
pod/kuboard-74c645f5df-ljk98 evicted
node/worker31.laoma.cloud evicted

# 还可以使用以下选项驱逐特定pod:
  --pod-selector='': Label selector to filter pods on the node
  -l, --selector='': Selector (label query) to filter on,supports '=', '==', and '!='.(e.g. -l CPU=L1,MEM=L2). Matching objects must satisfy all of the specified label constraints.

root@master30:~# kubectl get node
NAME                   STATUS                        ROLES        AGE   VERSION
master30.laoma.cloud   Ready                         control-plane   36d   v1.30.2
worker31.laoma.cloud   Ready,SchedulingDisabled   <none>       36d   v1.30.2
worker32.laoma.cloud   Ready                      <none>       36d   v1.30.2

# 删除worker31上SchedulingDisabled标记
root@master30:~# kubectl uncordon worker31.laoma.cloud 
  • drain 执行驱逐会遵守PDB (PodDisruptionBudget) 干扰预算,保证业务最小可用副本,不会一次性全部干掉业务 Pod。
  • 支持多个终端并行 drain 多个节点。
5.4.9 PriorityClass
  1. 集群级资源,非命名空间资源 ;value数值越大优先级越高。

  2. globalDefault:true:没有配置priorityClassName的 Pod 使用该优先级;集群只能有 1 个。

复制代码
   preemptionPolicy
  • PreemptLowerPriority(默认):高优先级 Pod 调度失败,可以抢占驱逐低优先级 Pod 拿资源。
  • Never:非抢占模式;只排队,不去抢占别人 Pod 资源。

yaml 示例

复制代码
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "高优先级业务"

Pod 引用优先级类:

复制代码
spec:
  priorityClassName: high-priority
5.4.10 抢占逻辑
  1. 高优先级 Pod(P)Pending,找不到合适节点;触发抢占逻辑。
  2. 寻找合适节点,驱逐节点上面低优先级 Pod;被驱逐 Pod 收到优雅终止信号。
  3. Pod P 会设置nominatedNodeName记录抢占的目标节点,不等于 nodeName,不会立刻绑定运行。
  4. 如果来了更高优先级 Pod,会把nominatedNodeName清空,抢占资格让出。

管理员可以用 ResourceQuota 限制普通用户创建高优先级 PriorityClass,防止恶意抢占。

环境清理

bash 复制代码
root@master30:~# kubectl delete ns scheduler

📝高频背诵考点汇总

  1. nodeAffinity:Pod 选节点;Taint:节点筛选 Pod。
  2. NoSchedule:只管新 Pod 调度,不驱逐正在运行 Pod;NoExecute 会驱逐运行 Pod。
  3. tolerationSeconds 只对NoExecute生效,控制故障后 Pod 滞留时间。
  4. 普通 Pod 故障污点默认容忍 300s;DaemonSet Pod 故障污点没有 tolerationSeconds,不会被驱逐。
  5. cordon 仅封锁新 Pod,不动老 Pod;drain=cordon + 驱逐普通业务 Pod。
  6. drain 必须加--ignore‑daemonsets;驱逐会遵守 PDB。
  7. PriorityClass 是集群资源,value 越大优先级越高;PreemptionPolicy=Never 关闭抢占。
  8. 多个污点:Pod 容忍匹配掉部分污点,剩下的污点决定调度结果。

排错命令

复制代码
kubectl describe pod xxx # 看事件,查看untolerated taint报错
kubectl get nodes node‑name -o yaml # 查看节点所
相关推荐
刘马想放假2 小时前
Copyparty 轻量文件共享:Docker Compose 部署、权限与多协议使用全指南
docker·容器
切糕师学AI3 小时前
Kubernetes 探针全解析:启动探针、存活探针与就绪探针(Startup/Liveness/Readiness Probe)
容器·kubernetes·探针·probe
xianyuCcCcCCCcc6 小时前
Kubernetes :健康检查、认证授权与可视化面板
运维·云原生·kubernetes·dashboard
fb_123456 小时前
K8s调度实战全景-Pod调度与资源管控
云原生·容器·kubernetes
吴声子夜歌7 小时前
Kubernetes权威指南——Kubernetes安装配置指南(二进制文件安装)
容器·kubernetes
江华森8 小时前
Kubernetes集群部署实战:使用kubeadm在4台ECS服务器上搭建K8s v1.31集群
云原生·容器·kubernetes
闲云自留地8 小时前
K8s Ingress 全解:ingress-nginx 部署、域名路径路由、HTTPS 限流灰度一篇通
nginx·https·kubernetes
SelectDB技术团队8 小时前
ClickHouse 支持存算分离吗?Doris 社区版原生存算分离怎么落地
数据库·clickhouse·云原生·开源·apache doris·技术选型·存算分层
正经教主9 小时前
【FDE系列】阶段2:Day 43(UP详细版):Docker Compose — 多容器一键编排
运维·docker·容器