云原生探索系列(八):Pod的调度

前言

上一章内容主要讲解了Pod的资源管理,这篇文章主要讲解Pod的调度策略。

nodeSelector

将pod调度到指定节点上。看个案例: nginx.yaml

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
      nodeSelector:
        disk: ssd

这意味着,该pod只想运行在带有disk=ssd标签的Node上。此时,我部署的节点都没有这个标签,试着创建pod看看发生了什么

lua 复制代码
kubectl create -f nginx.yaml

此时我们监控pod变化, kubectl get po -n nginx -w ,会发现pod一直处于Pending状态,我们使用describe查看详细信息,在Events事件中会看到如下内容

rust 复制代码
Events:
  Type     Reason            Age    From               Message
  ----     ------            ----   ----               -------
  Warning  FailedScheduling  2m10s  default-scheduler  0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.
  Warning  FailedScheduling  2m9s   default-scheduler  0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.

可以看到Pod调度失败,两个节点没有匹配Pod的节点亲和性(node affinity)或选择器(selector)的要求。

好,现在我们给k8s-node01节点增加标签disk=ssd,

ini 复制代码
kubectl label no k8s-node01 disk=ssd

然后,我们可以如下命令,查看标签设置成功

sql 复制代码
kubectl get no k8s-node01 --show-labels

此时,我们再次看pod的Events事件

scss 复制代码
Warning  FailedScheduling  10m   default-scheduler  0/3 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 2 node(s) didn't match Pod's node affinity/selector.
  Normal   Scheduled         2m    default-scheduler  Successfully assigned nginx/nginx-deployment-68cb47c4b-lrdbw to k8s-node01
  Normal   Pulling           117s  kubelet            Pulling image "nginx"

发现调度成功了,并且调度到了拥有标签disk=ssd的k8s-node01节点上。

ps: 如果想要给节点取消标签,可以这样执行kubectl label no k8s-node01 disk- ,意思就是删除disk标签

nodeAffinity

nodeAffinity目前支持两种:preferredDuringSchedulingIgnoredDuringExecution和requiredDuringSchedulingIgnoredDuringExecution两种约束。

requiredDuringSchedulingIgnoredDuringExecution:硬需求,类似nodeSelector

preferredDuringSchedulingIgnoredDuringExecution:软需求,将Pod优先调度到满足标签节点上,但如果集群中不存在这样的节点,或者由于其他原因无法调度到这样的节点上,那么调度到其他节点上也是可以接受的。

requiredDuringSchedulingIgnoredDuringExecution使用案例:

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: disk
                    operator: In
                    values:
                      - ssd
      containers:
        - name: nginx
          image: nginx

表面pod要被调度到包含标签disk且值为ssd的node上,此时我的节点都没有此标签,是调度不成功的,不多说了,和上面的nodeSelector一样的效果。

那此时,我们把requiredDuringSchedulingIgnoredDuringExecution改为preferredDuringSchedulingIgnoredDuringExecution,看下面案例:

yaml 复制代码
# ......
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 1
              preference:
                matchExpressions:
                  - key: disk
                    operator: In
                    values:
                      - ssd
# ......

上面的yaml文件修改nodeAffinity部分变为软亲和,然后我们再次执行该yaml文件,看看pod是否可以创建成功,这样执行后,会发现调度成功了。 笔者试验中,此pod被调度到了k8s-node02节点,虽然这个节点并没有disk=ssd的标签。

从上面的配置中可以看到In操作符,nodeAffinity语法支持的操作符包括In、NotIn、Exists、DoesNotExist、Gt、Lt。虽然没有节点排斥功能,但是用NotIn和DoesNotExist就可以实现排斥的功能了

以上就是节点亲和与反亲和

pod亲和性与反亲和性

podAffinity

Pod亲和与互斥的调度具体做法,就是通过在Pod的定义上增加topologyKey属性,来声明对应的目标拓扑区域内几种相关联的Pod 要"在一起或不在一起"。与节点亲和相同,Pod亲和与互斥的条件设置也是requiredDuringSchedulingIgnoredDuringExecution和 preferredDuringSchedulingIgnoredDuringExecution。Pod的亲和性被定义于PodSpec的affinity字段的podAffinity子字段中;Pod间的互斥性则被定义于同一层次的podAntiAffinity子字段中。

podAffinity是一个用来定义Pod亲和性规则的概念。亲和性规则用于指定希望将一个 Pod 调度到另一个 Pod 的附近的偏好设置。

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - frontend
              topologyKey: kubernetes.io/hostname
      containers:
        - name: nginx
          image: nginx

这个示例指定了一个必须满足的亲和性规则,要求将新的 Pod 调度到标签为 app=frontend 的其他 Pod 所在的节点上。

topologyKey 指定了用来匹配节点的拓扑约束键。

除了 requiredDuringSchedulingIgnoredDuringExecution 外,还可以使用 preferredDuringSchedulingIgnoredDuringExecution 类型的亲和性规则。这种类型的规则描述了偏好而非硬性要求,如果无法满足,Pod 仍然可以被调度到不符合条件的节点。例如:

yaml 复制代码
# ......
    spec:
      affinity:
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
              labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - frontend
              topologyKey: kubernetes.io/hostname

      containers:
        - name: nginx
          image: nginx

这个示例设置了一个优先级为 100 的偏好规则,希望将新的 Pod 调度到标签为 app=frontend 的其他 Pod 所在的节点上。

podAntiAffinity

podAntiAffinity 是一种用来定义 Pod 反亲和性规则的概念。 反亲和性规则用于指定不希望将一个 Pod 调度到另一个 Pod 的附近的偏好设置。这种设置可以帮助提高应用程序的高可用性和容错能力,避免将同一应用程序的多个实例或相关的服务部署到同一节点上。

yaml 复制代码
# ......
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - frontend
              topologyKey: kubernetes.io/hostname

      containers:
        - name: nginx
          image: nginx

这个示例指定了一个必须满足的反亲和性规则,要求新的 Pod 不应该调度到已经有标签为 app=frontend 的其他 Pod 所在的节点上。

反亲和性规则也可以使用 preferredDuringSchedulingIgnoredDuringExecution 类型。这种类型描述了偏好而非硬性要求,如果无法满足,Pod 仍然可以被调度到不符合条件的节点。例如:

yaml 复制代码
# ......
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
              labelSelector:
                matchExpressions:
                  - key: app
                    operator: In
                    values:
                      - frontend
              topologyKey: kubernetes.io/hostname

      containers:
        - name: nginx
          image: nginx

这个示例设置了一个优先级为 100 的反亲和性偏好规则,希望新的 Pod 尽量不要调度到标签为 app=backend 的其他 Pod 所在的节点上。

topologyKey

topologyKey可以理解为节点的拓扑级别,其后需要填写的是节点的某个标签的键。在Pod反亲和性中,topologyKey的作用是在拥有相同标签值的节点上,保证只有一个相关Pod存在。

Kubernetes内置了如下一些常用的默认拓扑域: 比如:kubernetes.io/hostname;

可以执行如下命令查看:

sql 复制代码
kubectl get no k8s-node01 --show-labels

需要注意的是,以上拓扑域是由Kubernetes自己维护的,在Node节点初始化时,controller-manager会为Node打上许多标签,比如 kubernetes.io/hostname这个标签的值就会被设置为Node节点的 hostname。

另外,公有云厂商提供的Kubernetes服务(比如阿里云ack),还会给Node打上 topology.kubernetes.io/region和topology.kubernetes.io/zone标签,以确定 各个节点所属的拓扑域。

Taints和Tolerations

Taints和Tolerations用于保证Pod不被调度到不合适的Node上,其中Taints用于Node上,Tolerations用于Pod上。

支持的Taints类型:

  • NoSchedule:新的Pod不调度到该Node上,不影响正在运行的Pod;
  • PreferNoSchedule:尽量不调度到该Node上
  • NoExecute:新的Pod不调度到该Node上,并且删除(evict)已在运行的Pod。Pod可以增加一个时间(tolerationSeconds)

好,接下来,我们进行实验:

在k8s-node02节点上,先跑一个pod,我跑了tomcat的pod,如下:

sql 复制代码
NAME                                 READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
tomcat-deployment-5d64468cf4-5ht2f   1/1     Running   0          23s   10.244.2.83   k8s-node02   <none>           <none>

好了,前置条件准备完了,接下来,给k8s-node02节点添加污点(Taints),执行如下命令

NoSchedule

ini 复制代码
kubectl taint no k8s-node02 for-special-user=fengxiao:NoSchedule

执行成功后,查询该节点详细信息kubectl describe no k8s-node02 ,会看到如下内容

ini 复制代码
Taints:             for-special-user=fengxiao:NoSchedule

这就表明污点添加成功了。污点的键为 for-special-user,值为 fengxiao,效果是 NoSchedule,即不允许普通的 Pod 被调度到这个节点上。

接下来,我们创建pod,但是未容忍node上的污点,看看能否调度到k8s-node02节点(注意我这里给该节点增加的标签disk=ssd),
nginx-deployment.yaml

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  namespace: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
              - matchExpressions:
                  - key: disk
                    operator: In
                    values:
                      - ssd
      containers:
        - name: nginx
          image: nginx

执行命令kubectl apply -f nginx-deployment.yaml ,创建之后,会发现pod一直处于Pending状态,查看该pod详细信息,会看到如下输出内容

scss 复制代码
1 node(s) had taint {for-special-user: fengxiao}, that the pod didn't tolerate, 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate.

再看看,k8s-node02节点上已经运行的pod, kubectl get po -n nginx -owide ,输出如下内容

sql 复制代码
NAME                                 READY   STATUS    RESTARTS   AGE     IP            NODE         NOMINATED NODE   READINESS GATES
nginx-deployment-6c4cf4b58b-fbnnb    0/1     Pending   0          4m22s   <none>        <none>       <none>           <none>
tomcat-deployment-5d64468cf4-5ht2f   1/1     Running   0          23m     10.244.2.83   k8s-node02   <none>           <none>

可以看到,NoSchedule确实是新的Pod不调度到该Node上,不影响正在运行的Pod。

PreferNoSchedule

上面案例我们实验了NoSchedule效果,现在实验PreferNoSchedule,增加PreferNoSchedule类型污点

ini 复制代码
kubectl taint no k8s-node02 for-special-user=fengxiao:PreferNoSchedule

此时,查看pod状态,还是Pending状态,这是因为剩余的Taint中存在effect=NoSchedule,则调度器不会把该Pod调度到这一节点上。

我们取消NoSchedule污点

ini 复制代码
kubectl taint no k8s-node02 for-special-user=fengxiao:NoSchedule-

取消后,发现pod调度到该节点了。

NoExecute

增加PreferNoSchedule类型污点

ini 复制代码
kubectl taint no k8s-node02 for-special-user=fengxiao:NoExecute

添加之后,会发现运行在该节点上的pod都被删除了.

以上都是pod未容忍污点的情况,下面我们尝试让pod容忍污点。

还是上面的nginx-deployment.yaml ,我们在spec部分稍加变更:

yaml 复制代码
# ......
    spec:
      tolerations:
        - key: "for-special-user"
          operator: "Equal"
          value: "fengxiao"
          effect: "NoSchedule"
# ......

针对k8s-node02节点添加NoSchedule类型的污点,添加方法可以看上面文章有提到,添加之后,运行pod,发现此时 pod可以被调度到有污点的k8s-node02节点上了。

那如果增加NoExecute类型的污点呢,pod还会被驱逐吗?,我们继续实验,增加NoExecute类型的节点,观察pod变化

下面是我未增加NoExecute类型的污点时,该节点pod运行情况

sql 复制代码
NAME                                READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
nginx-deployment-747797bbc7-k9mb5   1/1     Running   0          14m   10.244.2.87   k8s-node02   <none>           <none>
tomcat-deployment-ddd48b668-wd49h   1/1     Running   0          24m   10.244.2.86   k8s-node02   <none>           <none>

这两个pod中nginx-deployment添加了容忍(tolerations),tomcat-deployment未添加。

vbnet 复制代码
spec:
      tolerations:
        - key: "for-special-user"
          operator: "Equal"
          value: "fengxiao"
          effect: "NoExecute"

增加NoExecute类型污点后,pod发生如下变化

css 复制代码
NAME                                READY   STATUS    RESTARTS   AGE   IP            NODE         NOMINATED NODE   READINESS GATES
nginx-deployment-747797bbc7-k9mb5   1/1     Running   0          14m   10.244.2.87   k8s-node02   <none>           <none>
tomcat-deployment-ddd48b668-wd49h   1/1     Running   0          24m   10.244.2.86   k8s-node02   <none>           <none>
tomcat-deployment-ddd48b668-wd49h   1/1     Terminating   0          27m   10.244.2.86   k8s-node02   <none>           <none>
tomcat-deployment-ddd48b668-bw4h9   0/1     Pending       0          0s    <none>        <none>       <none>           <none>
tomcat-deployment-ddd48b668-bw4h9   0/1     Pending       0          0s    <none>        <none>       <none>           <none>
tomcat-deployment-ddd48b668-wd49h   0/1     Terminating   0          27m   10.244.2.86   k8s-node02   <none>           <none>
tomcat-deployment-ddd48b668-wd49h   0/1     Terminating   0          27m   10.244.2.86   k8s-node02   <none>           <none>
tomcat-deployment-ddd48b668-wd49h   0/1     Terminating   0          27m   10.244.2.86   k8s-node02   <none>           <none>

可以看到,未添加容忍的pod:tomcat-deployment-ddd48b668-wd49h被删除了,而添加容忍的pod:nginx-deployment-747797bbc7-k9mb5 未受影响

那我如果给给具有NoExecute 效果的Toleration加入一个可选的tolerationSeconds字段,情况会发生如何变化呢:

vbnet 复制代码
spec:
      tolerations:
        - key: "for-special-user"
          operator: "Equal"
          value: "fengxiao"
          effect: "NoExecute"
          tolerationSeconds: 60

会发现pod在这个节点上存活60秒后被驱逐。如果60秒内,将污点去除,就不会将pod驱逐。

tolerations配置技巧

Pod的Toleration声明中的key和effect需要与Taint的设置保持一致

  • operator的值是Exists时,无需设置value
  • operator的值是Equal时,需要value相等
  • operator未设置时,则默认值为Equal
  • 空的key配合Exists操作符能够匹配所有键和值
  • 空的effect匹配所有effect

案例总结

通过上面的例子,我们总结一下:

添加NoExecute类型的污点,会对该节点上运行的pod产生影响:

  • 没有设置Toleration的Pod会被立刻驱逐
  • 配置了对应Toleration的Pod,如果没有为tolerationSeconds赋 值,则会一直留在这一节点中
  • 置了对应Toleration的Pod且指定了tolerationSeconds值,则会 在指定的时间后驱逐

我们通过describe命令查看pod的详细信息,会发现有一些默认Toleration

ini 复制代码
Tolerations:                 for-special-user=fengxiao:NoSchedule
                             for-special-user=fengxiao:NoExecute for 60s
                             node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s

Kubernetes 调度器会在发现节点不就绪或不可达时自动向节点添加相应的污点。这是Kubernetes控制节点健康状况的一种机制:

  • 当节点无法定期向控制平面发送心跳时,控制平面会认为节点不可达,自动添加 node.kubernetes.io/unreachable 污点。
  • 当节点无法定期向控制平面发送节点状态更新时,控制平面会认为节点不就绪,自动添加 node.kubernetes.io/not-ready 污点。

添加的这种自动机制保证了在某些节点发生一些临时性问题时,Pod默认能够继续停留在当前节点运行5min等待节点恢复,而不是立即被驱逐,从而避免系统的异常波动。

最后

好了,终于写完了,Pod的调度是一个很重要的知识点,关于Pod调度原理,可能还需要一篇文章来阐述。

相关推荐
Linux运维老纪3 小时前
K8s之Service详解(Detailed Explanation of K8s Service)
服务器·网络·云原生·容器·kubernetes·云计算·运维开发
Charlie__ZS5 小时前
微服务-配置管理
微服务·云原生·架构
A ?Charis7 小时前
ExternalName Service 针对的是k8s集群外部有api服务的场景?
kubernetes
Dusk_橙子7 小时前
在K8S中,pending状态一般由什么原因导致的?
云原生·容器·kubernetes
喝醉酒的小白10 小时前
几种K8s运维管理平台对比说明
运维·容器·kubernetes
Linux运维老纪1 天前
DNS缓存详解(DNS Cache Detailed Explanation)
计算机网络·缓存·云原生·容器·kubernetes·云计算·运维开发
元气满满的热码式1 天前
K8S部署DevOps自动化运维平台
运维·kubernetes·devops
IT艺术家-rookie1 天前
k8s--部署k8s集群--控制平面节点
容器·kubernetes
Elastic 中国社区官方博客1 天前
使用 Ollama 和 Kibana 在本地为 RAG 测试 DeepSeek R1
大数据·数据库·人工智能·elasticsearch·ai·云原生·全文检索
Linux运维老纪2 天前
windows部署deepseek之方法(The Method of Deploying DeepSeek on Windows)
linux·人工智能·分布式·云原生·运维开发·devops