Pod 调度管理

Pod 被创建出来后需要选择一个合适的节点来运行这个 Pod ,这个步骤由 Scheduler 负责完成。在没有特别设置策略的情况下系统会使用默认的调度策略,除了默认的调度策略,系统还提供了多种配置方法,供用户配置 Pod 的特定调度逻辑。下面来具体介绍常用的调度策略。

1. 基于 Node Label 的调度策略

在 Pod 的配置中,可以方便地使用 LabelSelector 来声明需要调度到具有指定 Label 的 Node上,调度器会在具有这些 Label 的 Node 中再进行选择。

给 Node 设置 Label 的语法如下:

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

查看节点标签命令:

# 查看所有节点的标签
kubectl get nodes --show-labels

# 查看某个特定节点的标签
kubectl get nodes <node-name> --show-labels

# 查看带某个标签的节点
kubectl get nodes -l gpu=true
Bash

删除节点标签命令:

kubectl label node <node-name> <label-key>-
Bash

示例:

希望 mysql 调度到一个具有 SSD 磁盘的目标节点,就可以用 NodeSelector。

  • 给带有 ssd 磁盘的物理节点打上自定义标签 disk=ssd。
 kubectl label nodes k8s-node-01 disk=ssd
Bash
  • Pod 模版中设定 NodeSelecto r的值为 disk=ssd,则会调度到指定节点。
# node-selector-demo.yaml
apiVersion: v1
kind: Pod
metadata:
  name: test-pod
  labels:
    app: mysql
spec:
  containers:
  - name: mysql
    image: mysql
    imagePullPolicy: Always
  nodeSelector:
    disk: ssd
Bash

注意:

  • 如果我们给多个 Node 都定义了相同的标签(disk=ssd),则 scheduler 会根据调度算法从这组 Node 中挑选一个可用的 Node 进行Pod 调度。
  • NodeSelector 一定会调度到包含标签的节点,没有这种节点则调度失败,Pod 无法创建。
  • 物理节点上有一些预定义标签,你也可以直接选中这些标签。

2. 亲和性与反亲和性调度策略

2.1. 核心概念

  • 亲和性:让 Pod 靠近某些其他 Pod 或 Node。
  • 反亲和性:让 Pod 远离某些其他 Pod 或 Node。

这两种策略主要在两个层面上工作:

  • 节点亲和性:基于节点标签来调度 Pod。
  • Pod 亲和性/反亲和性:基于已在节点上运行的 Pod 的标签来调度 Pod。

两个层面的亲和性与反亲和性,都对应有两种具体的策略:

  • 软策略:在 Pod 调度时可以尽量满足其规则,在无法满足规则时,可以调度到一个不匹配规则的节点上,并且可以设置权重。
  • 硬策略:是 Pod 调度时必须满足的规则,否则 Pod 对象的状态会一直是 Pending。

2.2. 节点亲和 nodeAffinity

节点亲和性类似于 nodeSelector,但功能更强大、表达力更强,硬策略与软策略可以一起用,也可以单独使用。语法结构如下:

spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution: # 硬策略
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - us-west-2a
      preferredDuringSchedulingIgnoredDuringExecution: # 软策略
      - weight: 1
        preference:
          matchExpressions:
          - key: another-node-label
            operator: In
            values:
            - another-value
YAML

关键字段解析:

  • requiredDuringScheduling...(硬策略)
    • 必须满足的条件。如果找不到匹配的节点,Pod 将一直处于 Pending 状态。
    • 可以设置多个 nodeSelectorTerms,它们之间是 逻辑或 的关系。只需要有一个条件能匹配就能完成 Pod 的调度。
    • 可以设置多个 matchExpressions,它们之间是 逻辑与 的关系。必须满足所有条件才能完成 Pod 的调度。
  • preferredDuringScheduling...(软策略)
    • 倾向于满足的条件。调度器会尝试寻找满足条件的节点,但如果找不到,也会调度到其他节点上。
    • weight权重,范围 1-100。在多个软策略同时存在时,调度器会优先选择权重更高的节点。
  • operator(操作符)
    • In:标签值在列表中
    • NotIn:标签值不在列表中
    • Exists:标签存在(不关心值是什么)
    • DoesNotExist:标签不存在,此时不需要设置 values。
    • Gt(Greater than):值大于某个值(用于数值)
    • Lt(Less than):值小于某个值(用于数值)
  • 需要注意的是软硬策略名字中后半段字符串 IgnoredDuringExecution 表示的是,在 Pod 资源基于节点亲和性规则调度到某个节点之后,如果节点的标签发生了改变,该 Pod 不再符合该节点的亲和性要求,调度器不会将 Pod 从该节点上移除,因为该规则仅对新建的Pod对象有效。

示例:

  • 配置节点亲和策略
# node-affinity-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-affinity
  labels:
    app: node-affinity
spec:
  replicas: 8 
  selector:
    matchLabels:
      app: node-affinity
  template:
    metadata:
      labels:
        app: node-affinity
    spec:
      containers:
      - name: nginx
        image: nginx:1.26.2
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略:必须满足
            nodeSelectorTerms:
            - matchExpressions:
              - key: node_type
                operator: In
                values:
                - gpu
          preferredDuringSchedulingIgnoredDuringExecution:  # 软策略:尽量满足
          - weight: 1  # 软策略可以定义权重,执行时权重值高的优先匹配
            preference:
              matchExpressions:
              - key: preference
                operator: In
                values:
                - test
YAML
  • 节点打上标签
kubectl  label nodes k8s-node-01 node_type=gpu
node/k8s-node-01 labeled

kubectl  label nodes k8s-node-02 node_type=gpu
node/k8s-node-02 labeled

kubectl  label nodes k8s-node-01 preference=test
node/k8s-node-01 labeled
Bash

说明:必须调度到 node_type=gpu 的节点上,如果这些节点里满足 preference=test 的话就尽量调度到该节点上。

# 可以看到全部调度到了 k8s-node-01 节点上
kubectl get pod -o wide 
NAME                             READY   STATUS    RESTARTS   AGE   IP               NODE          NOMINATED NODE   READINESS GATES
node-affinity-544ffcb4f6-5nbl9   1/1     Running   0          97s   10.244.154.220   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-5tlgq   1/1     Running   0          97s   10.244.154.222   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-8rsb7   1/1     Running   0          97s   10.244.154.224   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-8xlp5   1/1     Running   0          97s   10.244.154.218   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-bf6pq   1/1     Running   0          97s   10.244.154.221   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-fgcjk   1/1     Running   0          97s   10.244.154.217   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-jcpdh   1/1     Running   0          97s   10.244.154.223   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-wcgfn   1/1     Running   0          97s   10.244.154.219   k8s-node-01   <none>           <none>


# 增加副本数量,当 k8s-node-01 资源不足时就会调度到 k8s-node-02 上
kubectl scale deployment node-affinity --replicas=30
kubectl get pod -o wide 
NAME                             READY   STATUS    RESTARTS   AGE     IP               NODE          NOMINATED NODE   READINESS GATES
node-affinity-544ffcb4f6-5nbl9   1/1     Running   0          5m16s   10.244.154.220   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-5tlgq   1/1     Running   0          5m16s   10.244.154.222   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-6k72l   1/1     Running   0          43s     10.244.154.228   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-8rsb7   1/1     Running   0          5m16s   10.244.154.224   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-8shq8   1/1     Running   0          43s     10.244.154.232   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-8xlp5   1/1     Running   0          5m16s   10.244.154.218   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-bf6pq   1/1     Running   0          5m16s   10.244.154.221   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-blgcr   1/1     Running   0          43s     10.244.154.229   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-bxk7b   1/1     Running   0          6s      10.244.154.242   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-dn2hl   1/1     Running   0          43s     10.244.154.227   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-fgcjk   1/1     Running   0          5m16s   10.244.154.217   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-fpc59   1/1     Running   0          6s      10.244.154.236   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-fsttz   1/1     Running   0          43s     10.244.154.225   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-gtqwp   1/1     Running   0          6s      10.244.154.238   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-hrqcz   1/1     Running   0          43s     10.244.154.226   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-j6lgh   1/1     Running   0          6s      10.244.154.240   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-jcpdh   1/1     Running   0          5m16s   10.244.154.223   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-jgpbm   1/1     Running   0          43s     10.244.44.225    k8s-node-02   <none>           <none>
node-affinity-544ffcb4f6-lblfq   1/1     Running   0          43s     10.244.154.230   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-lq7p8   1/1     Running   0          6s      10.244.154.241   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-m8mqh   1/1     Running   0          43s     10.244.154.234   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-nxwrb   1/1     Running   0          6s      10.244.154.239   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-pxb2k   1/1     Running   0          6s      10.244.154.237   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-tpql8   1/1     Running   0          43s     10.244.154.233   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-tv4r6   1/1     Running   0          43s     10.244.44.226    k8s-node-02   <none>           <none>
node-affinity-544ffcb4f6-v64cp   1/1     Running   0          6s      10.244.154.235   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-vhmd4   1/1     Running   0          43s     10.244.154.231   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-vrf6c   1/1     Running   0          6s      10.244.44.228    k8s-node-02   <none>           <none>
node-affinity-544ffcb4f6-wcgfn   1/1     Running   0          5m16s   10.244.154.219   k8s-node-01   <none>           <none>
node-affinity-544ffcb4f6-wztbt   1/1     Running   0          6s      10.244.44.227    k8s-node-02   <none>           <none>
Bash

2.3. 节点反亲和

对于 Node 亲和性来说,虽然没有反亲和性的字段,但是可以用 NotIn 和 DoesNotExist 就能实现反亲和性的要求。

2.4. Pod 亲和性与反亲和性

这允许根据已经运行在节点上的 Pod 来调度新的 Pod,而不是根据节点本身的标签。

2.4.1. 语法结构

spec:
  affinity:
    podAffinity: # Pod 亲和性(靠近)
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: app
            operator: In
            values:
            - cache
        topologyKey: topology.kubernetes.io/zone
    podAntiAffinity: # Pod 反亲和性(远离)
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - web
          topologyKey: kubernetes.io/hostname
YAML

关键字段解析:

  • labelSelector:用于选择需要”靠近”或”远离”的 Pod 的标签。
  • topologyKey:这是最关键的概念。它定义了”域”或”拓扑”的边界。

2.4.2. 拓扑域

Pod 亲和性调度需要各个相关的 Pod 对象运行于”同一位置”, 而反亲和性调度则要求他们不能运行于”同一位置”,而这个位置就是通过 topologyKey 字段指定的一个拓扑域。

那么,什么是拓扑域,如何理解这个新概念呢?一个拓扑域由一些 Node 组成,这些 Node 通常有相同的地理空间坐标。比如:

  • 在同一个机架、机房或地区。
  • 在某些情况下,我们也可以认为一个 Node 就是一个拓扑区域。

为此,Kubernetes 为 Node 内置了一些常用的用于表示拓扑域概念的 Label。

  • kubernetes.io/hostname:这个 Label 的值就会被设置为 Node 的 hostname,表示一个节点就是一个拓扑域。

公有云厂商还会为 Node 节点打上下面两种标签,来标识拓扑域:

  • topology.kubernetes.io/zone:可用区级别。
  • topology.kubernetes.io/region:区域级别,范围更大。

定义节点归属的拓扑域:

具体如何定义 Zone 及确定 Zone 包含的 Node,只要通过给 Node 打标签即可完成。常规做法是给 Node 增加 zone=zonename 的 Label,当然也可以使用系统自动设置的 topology.kubernetes.io/zone、topology.kubernetes.io/region 等标签。

例如,通过下面的zone标签设置(如下代码所示),可以认为集群中存在两个zone,分别是zoneA(包括nodel与node2)和zoneB(node3与node4)。

NAME	STATUS LABELS	
nodel	Ready	node-nodel,zone-zoneA	
node2	Ready	node-node2,zone-zoneA	
node3	Ready	node-node3,zone-zoneB	
node4 Ready	node-node4,zone-zoneB	
Bash

2.4.3. Pod 亲和调度—硬策略

  • 准备好参照物 Pod,该 Pod 拥有标签 security=s1 和 app=nginx,后面的例子将使用该Pod 作为亲和的参照物。
# pod-flag.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    security: "s1"
    app: "nginx"
  name: pod-flag
spec:
  containers:
  - name: myapp
    image: nginx:1.26.2
YAML
  • 部署查看
$ kubectl apply -f pod-flag.yaml 
pod/pod-flag created

$ kubectl get pod -o wide 
NAME       READY   STATUS    RESTARTS   AGE   IP               NODE          NOMINATED NODE   READINESS GATES
pod-flag   1/1     Running   0          62s   10.244.154.243   k8s-node-01   <none>           <none>
Bash
  • Pod 亲和性调度—硬策略示例如下
# required-podAffinity-pod1.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-affinity
  namespace: default
  labels:
    app: pod-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pod-affinity
  template:
    metadata:
      labels:
        app: pod-affinity
    spec:
      containers:
      - name: nginx
        image: nginx:1.26.2
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: 
                - nginx
            topologyKey: kubernetes.io/hostname  # 指定拓扑域
            # namespaces: 
            # - kube-system  # 则会去该名称空间下查找带有标签app=nginx的pod,与其亲和,不设置则默认从本pod当前的名称空间下找
YAML

上述亲和性硬策略表示:新 pod 必须被调度到一个带有 app=nginx 标签的 pod 所在的拓扑域中。因为我们此时用的拓扑域为 kubernetes.io/hostname ,每台机器上打的这个标签对应的值均不同都是自己的主机名,所以用该 key 做拓扑域的话,拓扑域中只包含唯一一台机器。

$ kubectl get pod -o wide 
NAME                            READY   STATUS    RESTARTS   AGE   IP               NODE          NOMINATED NODE   READINESS GATES
pod-affinity-7d8c4bf586-4vg2v   1/1     Running   0          21s   10.244.154.244   k8s-node-01   <none>           <none>
pod-affinity-7d8c4bf586-bt8hs   1/1     Running   0          21s   10.244.154.245   k8s-node-01   <none>           <none>
pod-affinity-7d8c4bf586-pvbk2   1/1     Running   0          21s   10.244.154.246   k8s-node-01   <none>           <none>
pod-flag                        1/1     Running   0          11m   10.244.154.243   k8s-node-01   <none>           <none>
Bash
  • 你可以把参照物 pod 删掉,然后重新部署会发现一直处于 pending 状态。

2.4.4. Pod 亲和调度—软策略

  • 准备好两个参照物 pod,以便我们演示多条软策略的权重与逻辑或的关系。
# pod-flag1.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: "nginx"
  name: pod-flag1
spec:
  containers:
  - name: myapp1
    image: nginx:1.26.2

# pod-flag2.yaml 
apiVersion: v1
kind: Pod
metadata:
  labels:
    app: "db"
  name: pod-flag2
spec:
  containers:
  - name: myapp2
    image: nginx:1.26.2
YAML
  • 查看
$ kubectl get pod -o wide 
NAME        READY   STATUS    RESTARTS   AGE   IP               NODE            NOMINATED NODE   READINESS GATES
pod-flag1   1/1     Running   0          59s   10.244.154.247   k8s-node-01     <none>           <none>
pod-flag2   1/1     Running   0          55s   10.244.151.140   k8s-master-01   <none>           <none>
Bash
  • Pod 亲和性调度—软策略示例如下
# deploy-with-preferred-podAffinity.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-affinity
  labels:
    app: pod-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pod-affinity
  template:
    metadata:
      labels:
        app: pod-affinity
    spec:
      containers:
      - name: nginx
        image: nginx:1.26.2
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 80
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values: ["nginx"]
              topologyKey: kubernetes.io/hostname  # 指定拓扑域
          - weight: 20
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values: ["db"]
              topologyKey: kubernetes.io/hostname
YAML

上述的清单配置当中,pod 的软亲和调度需要将 Pod 调度到标签为 app=cache 的 pod 所在的拓扑域当中,或者调度到 app=db 标签的 pod 所在的拓扑域中,若不存在此类标签,调度器会根据软亲和调度进行随机调度到一个节点之上。

$ kubectl get pod -o wide 
NAME                            READY   STATUS    RESTARTS   AGE    IP               NODE            NOMINATED NODE   READINESS GATES
pod-affinity-55c4945fdd-22gwh   1/1     Running   0          40s    10.244.154.249   k8s-node-01     <none>           <none>
pod-affinity-55c4945fdd-6xd7q   1/1     Running   0          40s    10.244.154.250   k8s-node-01     <none>           <none>
pod-affinity-55c4945fdd-zbbkk   1/1     Running   0          40s    10.244.154.248   k8s-node-01     <none>           <none>
pod-flag1                       1/1     Running   0          7m4s   10.244.154.247   k8s-node-01     <none>           <none>
pod-flag2                       1/1     Running   0          7m     10.244.151.140   k8s-master-01   <none>           <none>
Bash
  • 调整权重,重新部署
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-affinity
  labels:
    app: pod-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pod-affinity
  template:
    metadata:
      labels:
        app: pod-affinity
    spec:
      containers:
      - name: nginx
        image: nginx:1.26.2
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 20
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values: ["nginx"]
              topologyKey: kubernetes.io/hostname  # 指定拓扑域
          - weight: 80
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values: ["db"]
              topologyKey: kubernetes.io/hostname
YAML
$ kubectl get pod -o wide 
NAME                            READY   STATUS    RESTARTS   AGE   IP               NODE            NOMINATED NODE   READINESS GATES
pod-affinity-5958f888b8-q72wm   1/1     Running   0          16s   10.244.151.143   k8s-master-01   <none>           <none>
pod-affinity-5958f888b8-sbvw2   1/1     Running   0          16s   10.244.151.141   k8s-master-01   <none>           <none>
pod-affinity-5958f888b8-xbm48   1/1     Running   0          16s   10.244.151.142   k8s-master-01   <none>           <none>
pod-flag1                       1/1     Running   0          10m   10.244.154.247   k8s-node-01     <none>           <none>
pod-flag2                       1/1     Running   0          10m   10.244.151.140   k8s-master-01   <none>           <none>
Bash
  • 删除参照物 pod,然后重新部署还是能正常启动。

2.4.5. Pod 反亲和调度

  • 准备参照物 Pod,这里直接使用上文的 pod-flag.yaml。
  • Pod反亲和示例如下
# pod-antiaffinity-demo.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pod-affinity
  labels:
    app: pod-affinity
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pod-affinity
  template:
    metadata:
      labels:
        app: pod-affinity
    spec:
      containers:
      - name: nginx
        image: nginx:1.26.2
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:  # 硬策略
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: ["nginx"]
            topologyKey: kubernetes.io/hostname
YAML
  • 查看
$ kubectl get pod -o wide 
NAME                           READY   STATUS    RESTARTS   AGE   IP               NODE            NOMINATED NODE   READINESS GATES
pod-affinity-8dd454d44-dmdvz   1/1     Running   0          12s   10.244.95.11     k8s-master-02   <none>           <none>
pod-affinity-8dd454d44-n24n8   1/1     Running   0          12s   10.244.151.145   k8s-master-01   <none>           <none>
pod-affinity-8dd454d44-vsnd2   1/1     Running   0          12s   10.244.44.229    k8s-node-02     <none>           <none>
pod-flag                       1/1     Running   0          48s   10.244.154.253   k8s-node-01     <none>           <none>

# 3个新的 pod 副本没有一个被调度到 app=nginx 标签的 pod 所在的主机域 k8s-node-01 上
Bash

3. 指定 Node 名称的定向调度策略

Kubernetes 还支持在 Pod 的配置中通过 nodeName 字段指定要求调度的目标 Node 名称,即要求这个 Pod 只能调度到指定的 Node 上。这个字段的优先级比 nodeSelector 和亲和性策略都高。设置了nodeName 字段的 Pod将不参与调度器的调度过程,相当于已经完成了调度,系统将直接通知目标 Node 的 kubelet 开始创建这个 Pod。示例如下:

apiversion: v1 
kind: Pod 
metadata:
  name:nginx 
spec:
  containers: 
  - name: nginx 
    image: nginx
  nodeName: k8s-node-01
YAML

使用nodeName 具有以下限制:

  • 如果指定的 Node 不存在或者失联,则 Pod 将无法运行。
  • 如果指定的 Node 资源不足,则 Pod 可能会运行失败。
  • 在某些云环境下,Node 名称不一定是稳定的。

4. 污点与容忍度的调度策略

4.1. 污点与容忍介绍

  • 污点(taints)
    • 对于 nodeAffinity 无论是硬策略还是软策略方式,都是调度 pod 到预期节点上,而 Taints 污点恰好与之相反,如果一个节点标记为 Taints ,除非 pod 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度 pod。
  • 容忍度(tolerations)
    • 污点(taints)是定义在节点上的一组键值型属性数据,用来让节点拒绝将 Pod 调度到该节点上,除非该 Pod 对象具有容纳节点污点的容忍度。而容忍度(tolerations)是定义在 Pod 对象上的键值型数据,用来配置让 Pod 对象可以容忍节点的污点。

污点与容忍的应用场景

比如用户希望把 Master 节点保留给 Kubernetes 系统组件使用,或者把一组具有特殊硬件设备的节点预留给某些 pod,或者让一部分节点专门给一些特定应用使用/独占,则可以为这些节点设置污点,pod 不会再被调度到 taint 标记过的节点。

4.2. 节点污点设置

污点是定义是在节点上的,而容忍度的定义是在 Pod 中的 Spec 字段下,都属于键值型数据,两种方式都支持一个 effect 标记,语法格式为 key=value:effect

# 语法
kubectl taint nodes <node_name> key=value:effect

# 示例
kubectl taint nodes node1 key=value:NoSchedule # 该污点Taint的键为key,值为value,该Taint的效果是 NoSchedule

kubectl taint nodes node1 key=:NoSchedule # value 可以省略,代表空值
Bash

其中 effect 是用来定义对 Pod 对象的排斥等级,主要包含以下3种类型:

  • NoSchedule:一定不被调度。属于强制约束,节点现存的 Pod 对象不受影响。
  • PreferNoSchedule:NoSchedule 的软策略版本,表示尽量不调度到污点节点上去。
  • NoExecute:该选项意味着一旦 Taint 生效,如该节点内正在运行的 pod 没有对应 Tolerate 设置,会直接被逐出 ,属于强制约束。

查看节点污点:

# 查看所有节点的污点信息
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

# 查看特定节点的污点
kubectl describe node <节点名称> | grep Taints

# 查看所有节点的污点(json 格式)
kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.taints}{"\n"}{end}'
Bash

去掉污点

# 语法:kubectl taint node <节点名称> <key>[:<effect>]-

# 删除特定key的所有污点
kubectl taint node node1 key1-

# 删除特定key和effect的污点
kubectl taint node node1 key1=value1:NoSchedule-

# 删除所有污点
kubectl taint node node1 --all-
Bash

4.3. Pod 设置容忍

在 Pod 对象上定义容忍度时,其支持2种 operator 操作符:Equal 和 Exists。

  • Equal:等值比较,表示容忍度和污点必须在 key、value、effect 三者之上完全匹配。
  • Exists:存在性判断,表示二者的 key 和 effect 必须完全匹配,而容忍度中的 value 字段使用空值。
  • 如果不指定 operator 属性,则默认值为 Equal。

另外,还有两个特例:

  • 空的 key 如果再配合 Exists 就能匹配所有的 key 与 value,也是是能容忍所有 node 的所有Taints。
  • 空的 effect 匹配所有的 effect。

Pod 配置容忍示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: taint
  labels:
    app: taint
spec:
  replicas: 6
  selector:
    matchLabels:
      app: taint
  template:
    metadata:
      labels:
        app: taint
    spec:
      containers:
      - name: nginx
        image: nginx:1.26.2
        ports:
        - name: http
          containerPort: 80
      tolerations:
      - key: "test"
        operator: "Exists"
        effect: "NoSchedule"
      - key: "test"
        operator: "Equal"
        value: "value1"
        effect: "NoExecute"
YAML

4.4. NoExecute Taint Pod 驱逐时间

前面提到 NoExecute 这个 Taint 的效果会对节点上正在运行的 pod 有以下影响:

  • 没有设置 Toleration 容忍的 Pod 会被立即驱逐。
  • 配置了对应 Toleration 的 Pod,并且没有为 tolerationSeconds 赋值的 Pod,则会一直保留在该节点上。
  • 配置了对应 Toleration 的 Pod,并且为 tolerationSeconds 赋了值的 Pod,则会在指定的时间后被驱逐。
apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: nginx
    image: nginx
  tolerations:
  - key: "key1"
    operator: "Equal"
    value: "value1"
    effect: "NoExecute"
    # 没有设置 tolerationSeconds,表示无限期容忍
    

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: nginx
    image: nginx
  tolerations:
  - key: "key1"
    operator: "Equal"
    value: "value1"
    effect: "NoExecute"
    tolerationSeconds: 60 # 容忍60秒后被驱逐
YAML

5. Pod 基于拓扑域均匀分布调度策略

5.1. 拓扑分布约束详解

前面介绍的 Pod 调度策略主要基于 Pod 与 Node 的关系、Pod 与 Pod 的关系进行管理。而有时候,可以将集群内的 Node 有意地划分为不同的拓扑域(Topology Area ),例如可以将某些 Node 标记为不同的 Region(地域)、Zone(区域)、Rack(机架)等逻辑概念,以使应用在多个区域的环境下实现负载均衡、高可用、容灾等要求。从 Kubernetes v1.18 版本开始,可以使用基于拓扑信息来均匀分布 Pod 的调度机制,在 Pod 的 spec.topologySpreadConstraints 字段进行设置,为 Pod 在多个拓扑域中如何调度提供更加精细化的管理策略。

topologySpreadConstraints 字段包含的配置信息如下:

apiVersion: v1
kind: Pod
metadata:
  name: pod-spec
spec:
  topologySpreadConstraints:
  - maxSkew: <integer>
    minDomains: <integer>  # 可选
    topologyKey: <string>
    whenUnsatisfiable: <string>
    labelSelector:
      matchLabels: <object>
    matchLabelKeys:  # 可选
    - <list>
    nodeAffinityPolicy: [Honor|Ignore]  # 可选
    nodeTaintsPolicy: [Honor|Ignore]  # 可选
YAML
  • maxSkew:定义不同拓扑域中 Pod 数量之间允许的最大偏差。必须大于零。
    • 作用:这是控制分布均匀性的核心参数。 skew 越小,分布越均匀。
    • 示例
      • maxSkew: 1:表示任意两个拓扑域中的 Pod 数量相差不能超过 1。这是最严格的约束。
      • maxSkew: 2:表示相差不能超过 2,允许更宽松的分布。
  • topologyKey:节点标签的键。系统根据这个键来划分拓扑域。
  • whenUnsatisfiable:当调度器无法满足约束时应该采取的行动。
    • 可选值
      • DoNotSchedule(默认):硬约束。如果找不到满足条件的节点,则不调度 Pod(保持 Pending 状态)。适用于必须满足分布要求的场景。
      • ScheduleAnyway:软约束。仍然调度 Pod,但会优先选择能降低 Skew 值的节点。适用于尽量满足但不强求的场景。
  • labelSelector:用于选择一组 Pod,计算它们在不同拓扑域中的分布情况。
    • 作用:定义“哪些 Pod 需要被均匀分布”。
  • minDomains:需要匹配的最小拓扑域数量。只有在至少有这么多个域时,约束才会被启用。
    • 应用场景:例如,希望应用至少跨 3 个可用区部署,但如果集群只有 2 个可用区,这个约束就不生效,避免 Pod 无法调度。
    • 注意:通常需要与 whenUnsatisfiable: DoNotSchedule 一起使用。
  • matchLabelKeys:一个 Pod 标签键的列表。调度器会根据这些键的值来进一步分组 Pod,并在每个组内独立计算拓扑分布。
    • 作用:实现更细粒度的“按X均匀分布”。例如,实现“同一服务的不同版本”在节点间均匀分布。
  • nodeAffinityPolicy:定义在计算拓扑分布时,是否考虑节点的亲和性选择结果。
    • Honor(默认):只考虑那些符合 Pod 的 nodeAffinity/nodeSelector 规则的节点来进行分布计算。这是安全的选择。
    • Ignore:忽略 nodeAffinity/nodeSelector,在所有节点上计算分布情况。可能更公平,但需谨慎使用。
    • 不设置 nodeAffinityPolicy 时等同于选择 Honor。
  • nodeTaintsPolicy:定义在计算拓扑分布时,是否考虑节点的污点
    • Honor(默认):只考虑那些该 Pod 能容忍其污点的节点来进行分布计算。这是安全的选择。
    • Ignore:忽略污点,在所有节点上计算分布情况。
    • 不设置 nodeTaintsPolicy 时等同于选择 Ignore。

综合示例与解释:

apiVersion: v1
kind: Pod
metadata:
  name: my-web-server
  labels:
    app: web
    version: "3.0"
spec:
  containers:
  - name: nginx
    image: nginx:1.26.2
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: web
    nodeAffinityPolicy: Honor
    nodeTaintsPolicy: Honor
YAML
  • 目标:将新创建的标签为 app: web 的这个 Pod 均匀地分布到不同的可用区
  • 均匀程度:任意两个可用区中运行的 web Pod 数量相差不能超过 1 个 (maxSkew: 1)。
  • 调度策略:如果找不到能满足这个分布要求的节点,Pod 就不调度 (DoNotSchedule)。
  • 计算范围:只会在该 Pod 能调度上去的节点(满足亲和性且容忍污点)上计算这个分布。

注意:topologySpreadConstraint.labelSelector 的搜索范围仅限于新建 Pod 所在的命名空间。

5.2. Descheduler 介绍

Descheduler 是一个均衡器,用来解决 k8s 运行过程中 Pod 分布的不均匀问题,之前学习的均匀分布机制,只负责创建时的均匀,不负责运行时。

为什么需要 Descheduler?

集群状态是动态变化的,初始的最佳调度可能随着时间推移变得不再最优:

  • 节点故障或下线:导致 Pod 集中到少数节点。
  • 新节点加入:旧 Pod 不会自动平衡到新的空节点上。
  • 拓扑分布变化:初始调度后,Pod 分布可能不再满足高可用要求。
  • 资源使用变化:某些节点可能从低负载变为高负载。
  • 亲和性/反亲和性违规:后期创建的 Pod 可能导致之前的 Pod 违反规则。

Descheduler 的核心原理:

  • Descheduler 本身并不会参与调度,它只是计算出该驱逐的 Pod 进行驱逐。
  • 驱逐后重建新 Pod 的调度的任何还是会交给默认的调度器 kube-scheduler 来完成。

部署 Descheduler:

最常见的方式是使用 Helm chart 安装到 kube-system 命名空间。

helm repo add descheduler https://kubernetes-sigs.github.io/descheduler/
helm repo update
helm install descheduler descheduler/descheduler \
  -n kube-system \
  --set schedule="*/60 * * * *" # 每60分钟运行一次
  
# Descheduler 以 CronJob 方式运行。
Bash

查看默认的驱逐策略:

kubectl get configmap -n kube-system descheduler -o yaml
Bash
apiVersion: v1
kind: ConfigMap
metadata:
  ...
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha2"
    kind: "DeschedulerPolicy"
    profiles:
    - name: default
      pluginConfig:
      - args:
          evictLocalStoragePods: true
          ignorePvcPods: true
        name: DefaultEvictor
      - name: RemoveDuplicates
      - args:
          includingInitContainers: true
          podRestartThreshold: 100
        name: RemovePodsHavingTooManyRestarts
      - args:
          nodeAffinityType:
          - requiredDuringSchedulingIgnoredDuringExecution
        name: RemovePodsViolatingNodeAffinity
      - name: RemovePodsViolatingNodeTaints
      - name: RemovePodsViolatingInterPodAntiAffinity
      - name: RemovePodsViolatingTopologySpreadConstraint
      - args:
          targetThresholds:
            cpu: 50
            memory: 50
            pods: 50
          thresholds:
            cpu: 20
            memory: 20
            pods: 20
        name: LowNodeUtilization
      plugins: # 启用的相关插件/策略
        balance:
          enabled:
          - RemoveDuplicates  # 删除调度到同一节点上的相同 Pod(通常指具有相同 Pod Template Hash 的 Pod),以避免不必要的冗余和资源浪费。
          - RemovePodsViolatingTopologySpreadConstraint # 移除违反拓扑分布约束(Topology Spread Constraints)的 Pod,以确保 Pod 在集群中的分布更加均匀。
          - LowNodeUtilization # 将工作负载从资源利用率低的节点迁移到其他节点,以释放资源或关闭低利用率节点。
        deschedule:
          enabled:
          - RemovePodsHavingTooManyRestarts # 移除因频繁重启而可能存在问题的 Pod。
          - RemovePodsViolatingNodeTaints # 移除违反节点污点(Node Taints)规则的 Pod。
          - RemovePodsViolatingNodeAffinity # 移除违反节点亲和性(Node Affinity)规则的 Pod。
          - RemovePodsViolatingInterPodAntiAffinity # :移除违反 Pod 间反亲和性(Inter-Pod Anti-Affinity)规则的 Pod。
YAML

注意事项:

  • 引入 Descheduler 之后,它会根据策略对 Pod 进行驱逐以便进行重新调度,使 k8s 的资源达到一个平衡状态。这样可能会使服务中断,强烈建议给应用创建一个对应的 PDB 对象进行保护。
  • 默认情况下,Descheduler 不会驱逐 kube-system 命名空间下的关键系统 Pod。
  • 它不会驱逐不受控制器管理的 Pod(如裸 Pod),以及有本地存储的 Pod等。

PDB 配置示例:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: pdb-demo
  namespace: default
spec:
  maxUnavailable: 1 # 设置最多不可用的副本数量,或者使用 minAvailable,可以使用整数或百分比
  selector:
    matchLabels: # 匹配Pod标签------------》针对被选中的pod,最大不可用副本为1
      app: demo
YAML

关于 PDB 的更多详细信息可以查看官方文档:https://kubernetes.io/docs/tasks/run-application/configure-pdb/

6. Pod优先级和抢占调度策略

6.1. 核心概念

什么是 Pod 优先级?

Pod 优先级表示一个 Pod 相对于其他 Pod 的重要性。它是一个整数值(priorityValue),值越大,优先级越高

什么是抢占?

抢占是调度器的一种行为。当一个高优先级 Pod(我们称其为 “抢占者”)因为资源不足而无法被调度到任何节点时,调度器会尝试驱逐一个或多个低优先级 Pod 来自目标节点,从而为高优先级 Pod 腾出空间。

6.2. 配置和使用

定义 PriorityClass

优先级通过 PriorityClass 对象定义,它是一种集群范围的、非命名空间的资源。

示例:创建一个高优先级类

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority  # PriorityClass 的名称
value: 1000000         # 32位整数值,通常<10e6,值越大优先级越高。系统类如 system-cluster-critical 的值是 2000000000。
globalDefault: false   # 可选字段。如果设置为 true,则此 PriorityClass 将成为所有未设置 priorityClassName 的 Pod 的默认优先级。整个集群中只能有一个 PriorityClass 的此字段为 true。
description: "此优先级类用于非常重要的服务Pod,可以抢占低优先级Pod。" # 可选描述
YAML

示例:创建一个低优先级类

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 100
globalDefault: false
description: "用于非关键的批处理作业Pod。"
YAML

在 Pod 中引用 PriorityClass

apiVersion: v1
kind: Pod
metadata:
  name: important-app
spec:
  containers:
  - name: nginx
    image: nginx
  priorityClassName: high-priority # 关键:指定使用之前创建的 high-priority 类
YAML
上一篇
下一篇