第12章 Pod调度
官方文档:
https://kubernetes.io/zh/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity
1.nodeSelector
nodeSelector 是节点选择约束的最简单推荐形式。
nodeSelector 是 PodSpec 的一个字段。 它包含键值对的映射。
为了使 pod 可以在某个节点上运行,该节点的标签中 必须包含这里的每个键值对(它也可以具有其他标签)。
最常见的用法的是一对键值对
2.亲和性和反亲和性
亲和性和反亲和性调度介绍
Pod调度有几种特性,分别为:
节点亲和性:nodeAffinity
Pod亲和性:podAffinity
Pod反亲和性:podAntiAffinity
亲和调度可以分为软需求和硬需求两种:
硬需求:必须满足指定的规则才可以调度Pod到Node上(功能和nodeSelector很像,但是使用的是不同的语法),相当于硬设置。
软需求:强调优先满足指定规则,调度器会尝试调度Pod到Node上,但并不强求,相当于软限制。多个优先级规则还可以设置权重(weight)用来定义执行的先后顺序.
配置参数:
软策略:preferredDuringSchedulingIgnoredDuringExecution
硬策略:requiredDuringSchedulingIgnoredDuringExecution
节点亲和性 nodeAffinity
节点亲和性 nodeAffinity主要用来控制Pod可以被调度到哪些节点已经不能被调度到哪些节点,可以进行一些逻辑判断,不单单只是简单的相等匹配。
举例:
调度条件是硬策略不调度到Master节点,软策略是优先调度到拥有disktype=SSD标签的节点。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-affinity
  labels:
    app: node-affinity
spec:
  replicas: 4
  selector:
    matchLabels:
      app: node-affinity
  template:
    metadata:
      labels:
        app: node-affinity
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
          name: nginxweb
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:      #硬策略
            nodeSelectorTerms:
            - matchExpressions:
              - key: kubernetes.io/hostname
                operator: NotIn
                values:
                - master
          preferredDuringSchedulingIgnoredDuringExecution:     #软策略
          - weight: 1
            preference:
              matchExpressions:
              - key: disktype
                operator: In
                values:
                - SSD
应用后查看结果:可以发现都调度到了node2节点,因为我们在node2节点配置了标签
[root@node1 affinity]# kubectl get pod  -o wide
NAME                             READY   STATUS    RESTARTS   AGE     IP           NODE    NOMINATED NODE   READINESS GATES
node-affinity-7d8cf96487-c2jzc   1/1     Running   0          4m12s   10.2.2.139   node2   <none>           <none>
node-affinity-7d8cf96487-drz9h   1/1     Running   0          4m11s   10.2.2.140   node2   <none>           <none>
node-affinity-7d8cf96487-nhwqv   1/1     Running   0          4m13s   10.2.2.138   node2   <none>           <none>
node-affinity-7d8cf96487-wr2mq   1/1     Running   0          4m11s   10.2.2.141   node2   <none>           <none>
[root@node1 affinity]#
匹配操作符解释:
In: label的值在列表中
NotIn: label的值不在列表中
Gt: label的值大于值
Lt: label的值小于值
Exists: 存在这个label
DoesNotExist: 不存在这个label

注意事项:
nodeSelectorTerms下面可以配置多个选项,满足任何一个条件就可以了
matchExpressions下面也可以配置多个选项,但是必须同时满足这些条件才能正常被调度
Pod亲和性 podAffinity
Pod亲和性主要是解决Pod可以和哪些Pod部署在同一拓扑域中的问题,所谓的拓扑域可以理解为Pod运行的区域,对于单集群来说,那么可以认为每台主机都是一个区域,可以使用主机名标签实现。
简单来说,Pod亲和性就是指如果一个Pod运行在某个节点上,那么我也得运行在这个节点上。
Pod反亲和则相反,如果某个Pod运行在某个节点上,那么我就不和他运行在同一个节点上。
首先创建一个mysql-dp:
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql-dp
spec:
  selector:
    matchLabels:
      app: mysql-dp
  replicas: 1
  template:
    metadata:
      name: mysql-dp
      labels:
        app: mysql-dp
    spec:
      volumes:
      - name: mysql-volume
        hostPath:
          path: /data/mysql
          type: DirectoryOrCreate
      containers:
      - name: mysql-dp
        imagePullPolicy: IfNotPresent
        image: mysql:5.7
        ports:
        - name: mysql-port
          containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: "123456"
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: mysql-volume
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            preference:
              matchExpressions:
              - key: disktype
                operator: In
                values:
                - SSD
查看运行结果:可以发现运行在node2节点
[root@node1 pod]# kubectl get pod -o wide
NAME                       READY   STATUS    RESTARTS   AGE   IP           NODE    NOMINATED NODE   READINESS GATES
mysql-dp-6568b7498-7cjhg   1/1     Running   0          39s   10.2.2.200   node2
创建一个亲和Pod
apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: nginx-dp
   labels:
     app: nginx-dp
 spec:
   replicas: 2
   selector:
     matchLabels:
       app: nginx-dp
   template:
     metadata:
       labels:
         app: nginx-dp
     spec:
       containers:
       - name: nginx-dp
         imagePullPolicy: IfNotPresent
         image: nginx
         ports:
         - name: http
           containerPort: 80
       affinity:
         podAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
           - labelSelector:
               matchExpressions:
               - key: app
                 operator: In
                 values:
                 - mysql-dp
             topologyKey: kubernetes.io/hostname
查看运行结果:
[root@node1 pod]# kubectl get pod -o wide
NAME                        READY   STATUS    RESTARTS   AGE    IP           NODE
TES
mysql-dp-6568b7498-7cjhg    1/1     Running   0          4m2s   10.2.2.200   node2
nginx-dp-778856f464-b9nnc   1/1     Running   0          4s     10.2.2.201   node2
nginx-dp-778856f464-gsdbq   1/1     Running   0          4s     10.2.2.202   node2
Pod反亲和性 podAntiAffinity
修改刚才的配置,这次为反亲和:
apiVersion: apps/v1
 kind: Deployment
 metadata:
   name: nginx-dp
   labels:
     app: nginx-dp
 spec:
   replicas: 2
   selector:
     matchLabels:
       app: nginx-dp
   template:
     metadata:
       labels:
         app: nginx-dp
     spec:
       containers:
       - name: nginx-dp
         imagePullPolicy: IfNotPresent
         image: nginx
         ports:
         - name: http
           containerPort: 80
       affinity:
         podAntiAffinity:
           requiredDuringSchedulingIgnoredDuringExecution:
           - labelSelector:
               matchExpressions:
               - key: app
                 operator: In
                 values:
                 - mysql-dp
             topologyKey: kubernetes.io/hostname
查看运行结果:
[root@node1 pod]# kubectl get pod -o wide
 NAME                        READY   STATUS    RESTARTS   AGE     IP           NODE
 ATES
 mysql-dp-6568b7498-7cjhg    1/1     Running   0          2m45s   10.2.2.200   node2
 nginx-dp-6554ffbc84-v4rdf   1/1     Running   0          4s      10.2.1.223   node3
 nginx-dp-6554ffbc84-zxngq   1/1     Running   0          4s      10.2.1.222   node3
总结
第一种分类:

第二种分类:

3.污点与容忍
污点
对于节点调度来说,无论是软策略还是硬策略,都是调度Pod到预期的节点上,而污点(Taints)则与之相反。
简单来说污点的作用就是说不希望Pod被调度过来,如果你硬要调度过来,那么你就要容忍污点。
我们使用kubeadm安装的集群里的master节点默认是添加了污点标记的,所以我们正常运行的pod都不会被调度到master上去。
[root@node1 ~]# kubectl describe nodes node1
Name:               node1
Roles:              master
Labels:             app=traefik-ingress
                    beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/os=linux
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=node1
                    kubernetes.io/os=linux
                    node-role.kubernetes.io/master=
..........
Taints:             node-role.kubernetes.io/master:NoSchedule
Unschedulable:      false
其中Taints就是污点的标记,这里写的参数是NoSchedule,表示Pod不会被调度到这个节点。
除了NoSchedule以外,还有另外两个参数选项:
- PreferNoSchedule:NoSchedule的软策略,表示尽量不被调度到有污点的节点
- NoExecute:该选项意味着一旦Taints生效,如果该节点内正在运行的Pod没有对应的容忍设置,则会直接被驱逐。
污点添加命令:
kubectl taint nodes node2 key=value:NoSchedule
举例:
kubectl taint nodes node2 test=node2:NoSchedule
查看效果:
[root@node1 ~]# kubectl describe nodes node2 |grep Taints
Taints:             test=node2:NoSchedule
容忍
如果需要Pod可以被调度到设置了污点的节点,需要在Pod的资源配置清单里添加容忍污点的相关配置,下面的配置意思为可以容忍(tolerations)具有该污点(Taint)的Node.
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"
参数解释:
Pod的Toleration生命的key和effect需要和Taint的设置保持一致,并且满足以下条件之一。
- 如果operator的值是Exists,则value的属性可以省略。
- 如果operator的值是Equal,则表示其key和value之间的关系是等于。
- 如果不指定operator的属性,则默认为Equal。
另外,还有两个特例:
- 空的key如果再配合Exists就能匹配所有的key与value,也就是能容忍所有节点的所有Taints。
- 空的effect匹配所有的effect
举例:这个Pod可以容忍具有污点为test的node节点。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: taint
  labels:
    app: taint
spec:
  replicas: 3
  selector:
    matchLabels:
      app: taint
  template:
    metadata:
      labels:
        app: taint
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - name: http
          containerPort: 80
      tolerations:
      - key: "test"                     #这里和node节点的污点的名字一样
        operator: "Exists"      #匹配test的任何属性
        effect: "NoSchedule"  #匹配污点的效果
执行后可以发现可以被调度到设置了污点的node2上
[root@node1 ~]# kubectl get pod -o wide
taint-ddd44544-jgdlk             1/1     Running   0          31m     10.2.2.156   node2
taint-ddd44544-ktd5x             1/1     Running   0          31m     10.2.1.168   node3
taint-ddd44544-wgn57             1/1     Running   0          30m     10.2.2.157   node2
取消污点
如果我们想取消污点,执行以下命令
[root@node1 ~]# kubectl taint node node2 test-
node/node2 untainted
4.Node节点下线维护流程
下线流程
如果我们的Node节点需要关机维护,那么这时候正确的处理流程应该是如何呢?我们首先要确保新的Pod不会被调度到需要下线的节点,其次再将需要下线的节点上的Pod驱逐到其他节点上,等维护好之后再重新配置可以调度。流程梳理如下:
1.配置该Node不可被调度
2.驱逐Node上正在运行的Pod
3.下线节点进行维护
4.维护好后开机启动服务
5.恢复该Node可以被正常调度
设置节点不可被调度
kubectl cordon node2
驱逐节点的Pod
前面提到的NoExecute这个Taint效果对节点上正在运行的Pod有以下影响:
- 没有设置NoExecute的Pod会立刻被驱逐
- 配置了对应Toleration的Pod,如果没有为tolerationSeconds赋值,则会一直留在这一节点中。
kubectl taint node node2 test=node2:NoExecute
节点重新被调度
kubectl uncordon node2
5.安全驱逐节点
通过上节的实验我们已经掌握了如何限制Pod的调度,其实还有更简单的命令可以安全的驱逐Pod并下线Node,这条命令就是kubectl drain
官方地址:
https://kubernetes.io/zh/docs/tasks/administer-cluster/safely-drain-node/
说明:
在对节点执行维护(例如内核升级、硬件维护等)之前, 可以使用 kubectl drain 从节点安全地逐出所有 Pods。 安全的驱逐过程允许 Pod 的容器 体面地终止, 并确保满足指定的 PodDisruptionBudgets。
操作命令:
kubectl drain <nodename>
执行效果:
[root@node1 ~]# kubectl drain node3
node/node3 already cordoned
error: unable to drain node "node3", aborting command...
There are pending nodes to be drained:
 node3
cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): kube-system/kube-flannel-ds-gzmxp, kube-system/kube-proxy-cfhwj, monitoring/node-exporter-4f9hz
cannot delete Pods with local storage (use --delete-local-data to override): kube-system/metrics-server-5dbcdf9f85-vx5k2, kube-system/traefik-6488cd4b75-n7dfr, monitoring/alertmanager-main-1, monitoring/grafana-6ccd8d89f8-n2wqz, monitoring/prometheus-adapter-5df846c94f-5hlhn, monitoring/prometheus-adapter-5df846c94f-5mwvx, monitoring/prometheus-k8s-0, monitoring/prometheus-k8s-1
这里报错了,提示我们不能执行驱逐命令,原因有2:
1.这个节点上运行DaemonSet类型的Pod,这里为kube-flannel和kube-proxy
2.这个节点上有local存储类型的Pod,这里为prometheus的组件
如果我们想继续驱逐,需要通过以下命令设置忽略DaemonSet和local存储
 kubectl drain node3 --ignore-daemonsets --delete-local-data
检查效果:
坑:这里驱逐之后提示报错,原因是因为prometheus节点必须要保持有1个在运行,但是另一个Pod还没有准备好,所以不允许被删除。解决方法就是扩容或缩容,使其满足运行的条件。
evicting pod monitoring/prometheus-k8s-1
error when evicting pod "prometheus-k8s-1" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod monitoring/prometheus-k8s-1
error when evicting pod "prometheus-k8s-1" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod monitoring/prometheus-k8s-1
error when evicting pod "prometheus-k8s-1" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod monitoring/prometheus-k8s-1
error when evicting pod "prometheus-k8s-1" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod monitoring/prometheus-k8s-1
error when evicting pod "prometheus-k8s-1" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
6.驱逐后不平衡
更新: 2024-09-06 14:12:55