023-K8S-Cordon和Drain的使用

集群环境

IP Hostname 用途
10.20.1.139 k8s-master01 Master节点
10.20.1.140 k8s-node01 Node节点
10.20.1.141 k8s-node02 Node节点
10.20.1.142 k8s-node03 Node节点

一、Cordon

kubectl cordon 命令用于将节点标记为不可调度。这意味着 Kubernetes 调度器不会将任何新的 Pod 调度到该节点上。但是,已经在该节点上运行的 Pod 不会受到影响,它们将继续运行。在 kubectl cordon 节点后删除节点上已有 Pod,Kubernetes 会自动将新的 Pod 调度到其他可用节点上,从而实现 Pod 的迁移。但是,请务必注意控制器、资源限制、节点亲和性/反亲和性以及 Taints 和 Tolerations 等因素,以确保 Pod 能够成功迁移。

1. 使用场景

  • 节点维护: 在对节点进行维护(例如,硬件更换、内核更新、资源调整)之前,可以使用 cordon 命令来防止新的 Pod 调度到该节点上。
  • 资源调整:在调整节点的计算资源时,可以先使用 cordon 命令,以避免资源竞争。
  • 工作负载重新分配:在需要将工作负载重新分配到其他节点时,可以使用 cordon 命令来阻止新的 Pod 调度到目标节点上。

2. 如何使用

  • 将节点设置成不可调度

    $ kubectl cordon <node_name> [options]
    # 具体选项参考官网:https://kubernetes.io/zh-cn/docs/reference/kubectl/generated/kubectl_cordon/

    <node-name> 替换为要 cordon 的节点的名称。

  • 检查节点状态

    使用以下命令检查节点的状态,确认节点是否已被 cordon:

    kubectl get nodes <node-name>

    在输出中,STATUS 列将显示 Ready,SchedulingDisabled,表示节点已被 cordon。

  • 解除节点的不可调度状态

    完成维护或调整后,可以使用以下命令将节点标记为可调度:

    kubectl uncordon <node-name>

    撤销状态后,新的Pod可以调度到该节点上。

3. 案例实操

  • 当前集群节点状态

    $ kubectl get nodes -o wide
    NAME           STATUS   ROLES           AGE   VERSION    INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                      KERNEL-VERSION                CONTAINER-RUNTIME
    k8s-master01   Ready    control-plane   46d   v1.29.15   10.20.1.139   <none>        Rocky Linux 9.3 (Blue Onyx)   5.14.0-362.8.1.el9_3.x86_64   docker://28.0.4
    k8s-node01     Ready    <none>          46d   v1.29.15   10.20.1.140   <none>        Rocky Linux 9.3 (Blue Onyx)   5.14.0-362.8.1.el9_3.x86_64   docker://28.0.4
    k8s-node02     Ready    <none>          46d   v1.29.15   10.20.1.141   <none>        Rocky Linux 9.3 (Blue Onyx)   5.14.0-362.8.1.el9_3.x86_64   docker://28.0.4
    k8s-node03     Ready    <none>          46d   v1.29.15   10.20.1.142   <none>        Rocky Linux 9.3 (Blue Onyx)   5.14.0-362.8.1.el9_3.x86_64   docker://28.3.1
  • 给节点设置 cordon

    # 给 node01 节点设置 cordon 
    $ kubectl cordon k8s-node01
    node/k8s-node01 cordoned
    
    # 再次查看节点状态,发现 node01 被打上了 SchedulingDisabled 标签
    $ kubectl get nodes
    NAME           STATUS                     ROLES           AGE   VERSION
    k8s-master01   Ready                      control-plane   46d   v1.29.15
    k8s-node01     Ready,SchedulingDisabled   <none>          46d   v1.29.15
    k8s-node02     Ready                      <none>          46d   v1.29.15
    k8s-node03     Ready                      <none>          46d   v1.29.15
  • 创建 Pod

    资源清单:cordon-deployment.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: myapp
      namespace: default
    spec:
      replicas: 1 # 默认副本数量为1
      selector:
        matchLabels:
          app: myapp
      template:
        metadata:
          labels:
            app: myapp
        spec:
          containers:
            - name: my-container
              image: nginx:1.29.0

    执行资源清单

    $ kubectl apply -f cordon-deployment.yaml
    deployment.apps/myapp created
    
    # 查看 Pod, 被调度到 node02 节点上
    $ kubectl get pods -o wide
    NAME                     READY   STATUS    RESTARTS   AGE   IP              NODE         NOMINATED NODE   READINESS GATES
    myapp-85497bd9fb-hdvdn   1/1     Running   0          25s   171.20.58.223   k8s-node02   <none>           <none>
  • 增加 Pod 副本数

    # Pod 设置10个副本
    $ kubectl scale deployment myapp --replicas=10
    deployment.apps/myapp scaled
    
    # 查看 Pod, 发现10个副本都被分布在 node02 和 node03 节点上,没有被调度到 node01 节点上
    $ kubectl get pods -o wide
    NAME                     READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
    myapp-85497bd9fb-8kzkf   1/1     Running   0          2m52s   171.20.58.225    k8s-node02   <none>           <none>
    myapp-85497bd9fb-9vm9s   1/1     Running   0          2m52s   171.20.58.224    k8s-node02   <none>           <none>
    myapp-85497bd9fb-gtc22   1/1     Running   0          2m52s   171.20.58.227    k8s-node02   <none>           <none>
    myapp-85497bd9fb-hdvdn   1/1     Running   0          4m4s    171.20.58.223    k8s-node02   <none>           <none>
    myapp-85497bd9fb-j2tkd   1/1     Running   0          2m52s   171.20.58.228    k8s-node02   <none>           <none>
    myapp-85497bd9fb-kg9qf   1/1     Running   0          2m52s   171.20.135.147   k8s-node03   <none>           <none>
    myapp-85497bd9fb-mh9dx   1/1     Running   0          2m53s   171.20.135.145   k8s-node03   <none>           <none>
    myapp-85497bd9fb-qvrgl   1/1     Running   0          2m52s   171.20.58.226    k8s-node02   <none>           <none>
    myapp-85497bd9fb-tbcrv   1/1     Running   0          2m52s   171.20.135.149   k8s-node03   <none>           <none>
    myapp-85497bd9fb-wgpwc   1/1     Running   0          2m52s   171.20.135.146   k8s-node03   <none>           <none>
  • 节点设置 uncordon

    # 给 node01 节点设置 uncordon 
    $ kubectl uncordon k8s-node01
    node/k8s-node01 uncordoned
    
    # 再次查看 Node 状态,发现 node01 节点的 SchedulingDisabled 已经去除了
    $ kubectl get nodes
    NAME           STATUS   ROLES           AGE   VERSION
    k8s-master01   Ready    control-plane   46d   v1.29.15
    k8s-node01     Ready    <none>          46d   v1.29.15
    k8s-node02     Ready    <none>          46d   v1.29.15
    k8s-node03     Ready    <none>          46d   v1.29.15
  • 再次增加 Pod 副本数

    # 将 Pod 的副本数从 10 增加到 20
    $ kubectl scale deployment myapp --replicas=20
    deployment.apps/myapp scaled
    
    # 再次查看 Pod, 发现可以被调度到 node01 节点了
    $ kubectl get pods -o wide
    NAME                     READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
    myapp-85497bd9fb-6wjg6   1/1     Running   0          36s     171.20.135.151   k8s-node03   <none>           <none>
    myapp-85497bd9fb-8kzkf   1/1     Running   0          6m45s   171.20.58.225    k8s-node02   <none>           <none>
    myapp-85497bd9fb-97lnr   1/1     Running   0          36s     171.20.58.229    k8s-node02   <none>           <none>
    myapp-85497bd9fb-9vm9s   1/1     Running   0          6m45s   171.20.58.224    k8s-node02   <none>           <none>
    myapp-85497bd9fb-d2s9d   1/1     Running   0          36s     171.20.85.195    k8s-node01   <none>           <none>
    myapp-85497bd9fb-f8jqn   1/1     Running   0          36s     171.20.85.254    k8s-node01   <none>           <none>
    myapp-85497bd9fb-gbg7t   1/1     Running   0          36s     171.20.85.251    k8s-node01   <none>           <none>
    myapp-85497bd9fb-gtc22   1/1     Running   0          6m45s   171.20.58.227    k8s-node02   <none>           <none>
    myapp-85497bd9fb-hdvdn   1/1     Running   0          7m57s   171.20.58.223    k8s-node02   <none>           <none>
    myapp-85497bd9fb-j2tkd   1/1     Running   0          6m45s   171.20.58.228    k8s-node02   <none>           <none>
    myapp-85497bd9fb-kg9qf   1/1     Running   0          6m45s   171.20.135.147   k8s-node03   <none>           <none>
    myapp-85497bd9fb-m7zhs   1/1     Running   0          37s     171.20.85.252    k8s-node01   <none>           <none>
    myapp-85497bd9fb-mh9dx   1/1     Running   0          6m46s   171.20.135.145   k8s-node03   <none>           <none>
    myapp-85497bd9fb-nx2nb   1/1     Running   0          36s     171.20.135.150   k8s-node03   <none>           <none>
    myapp-85497bd9fb-q4z5x   1/1     Running   0          36s     171.20.85.255    k8s-node01   <none>           <none>
    myapp-85497bd9fb-qvrgl   1/1     Running   0          6m45s   171.20.58.226    k8s-node02   <none>           <none>
    myapp-85497bd9fb-tbcrv   1/1     Running   0          6m45s   171.20.135.149   k8s-node03   <none>           <none>
    myapp-85497bd9fb-wgpwc   1/1     Running   0          6m45s   171.20.135.146   k8s-node03   <none>           <none>
    myapp-85497bd9fb-x9vdq   1/1     Running   0          36s     171.20.85.253    k8s-node01   <none>           <none>
    myapp-85497bd9fb-xc7q7   1/1     Running   0          36s     171.20.85.198    k8s-node01   <none>           <none>

二、Drain

kubectl drain 命令用于安全地将节点上的所有 Pod 驱逐到其他节点。在驱逐 Pod 之前,drain 命令会先 cordon 节点,以防止新的 Pod 调度到该节点上。

1. 使用场景

  • 节点维护:在需要关闭或删除节点之前,可以使用 drain 命令将节点上的所有 Pod 驱逐到其他节点,以确保应用程序的可用性。
  • 节点缩容:在需要缩减集群规模时,可以使用 drain 命令将要移除的节点上的 Pod 驱逐到其他节点。

2. 如何使用

  • 给节点设置 Drain

    kubectl drain <node-name> --ignore-daemonsets --force
    • --ignore-daemonsets:忽略 DaemonSet 管理的 Pod。DaemonSet 通常提供节点本地服务,即使节点被标记为不可调度,也应该在节点上运行。

    • --force:强制驱逐 Pod,即使它们没有被 ReplicationController、Job 或 DaemonSet 管理。

  • 处理 Grace Period

    如果 Pod 有较长的 grace period,drain 命令可能需要较长时间才能完成。可以使用 --grace-period 标志来覆盖 Pod 的终止宽限期,并强制立即驱逐:

    kubectl drain <node-name> --grace-period 0 --force --ignore-daemonsets

    请谨慎使用此选项,因为强制停止 Pod 可能会导致数据丢失或应用程序错误。

3. 案例实操

  • 查看当前集群 Pod 运行情况

    $ kubectl get pods -o wide
    NAME                     READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
    myapp-85497bd9fb-6wjg6   1/1     Running   0          36s     171.20.135.151   k8s-node03   <none>           <none>
    myapp-85497bd9fb-8kzkf   1/1     Running   0          6m45s   171.20.58.225    k8s-node02   <none>           <none>
    myapp-85497bd9fb-97lnr   1/1     Running   0          36s     171.20.58.229    k8s-node02   <none>           <none>
    myapp-85497bd9fb-9vm9s   1/1     Running   0          6m45s   171.20.58.224    k8s-node02   <none>           <none>
    myapp-85497bd9fb-d2s9d   1/1     Running   0          36s     171.20.85.195    k8s-node01   <none>           <none>
    myapp-85497bd9fb-f8jqn   1/1     Running   0          36s     171.20.85.254    k8s-node01   <none>           <none>
    myapp-85497bd9fb-gbg7t   1/1     Running   0          36s     171.20.85.251    k8s-node01   <none>           <none>
    myapp-85497bd9fb-gtc22   1/1     Running   0          6m45s   171.20.58.227    k8s-node02   <none>           <none>
    myapp-85497bd9fb-hdvdn   1/1     Running   0          7m57s   171.20.58.223    k8s-node02   <none>           <none>
    myapp-85497bd9fb-j2tkd   1/1     Running   0          6m45s   171.20.58.228    k8s-node02   <none>           <none>
    myapp-85497bd9fb-kg9qf   1/1     Running   0          6m45s   171.20.135.147   k8s-node03   <none>           <none>
    myapp-85497bd9fb-m7zhs   1/1     Running   0          37s     171.20.85.252    k8s-node01   <none>           <none>
    myapp-85497bd9fb-mh9dx   1/1     Running   0          6m46s   171.20.135.145   k8s-node03   <none>           <none>
    myapp-85497bd9fb-nx2nb   1/1     Running   0          36s     171.20.135.150   k8s-node03   <none>           <none>
    myapp-85497bd9fb-q4z5x   1/1     Running   0          36s     171.20.85.255    k8s-node01   <none>           <none>
    myapp-85497bd9fb-qvrgl   1/1     Running   0          6m45s   171.20.58.226    k8s-node02   <none>           <none>
    myapp-85497bd9fb-tbcrv   1/1     Running   0          6m45s   171.20.135.149   k8s-node03   <none>           <none>
    myapp-85497bd9fb-wgpwc   1/1     Running   0          6m45s   171.20.135.146   k8s-node03   <none>           <none>
    myapp-85497bd9fb-x9vdq   1/1     Running   0          36s     171.20.85.253    k8s-node01   <none>           <none>
    myapp-85497bd9fb-xc7q7   1/1     Running   0          36s     171.20.85.198    k8s-node01   <none>           <none>
  • 给 node01 节点设置 Drain

    $ kubectl drain k8s-node01 --ignore-daemonsets --force
    node/k8s-node01 cordoned
    
    # 查看节点状态, node01 被打上了 SchedulingDisabled 标签
    $ kubectl get nodes 
    NAME           STATUS                     ROLES           AGE   VERSION
    k8s-master01   Ready                      control-plane   46d   v1.29.15
    k8s-node01     Ready,SchedulingDisabled   <none>          46d   v1.29.15
    k8s-node02     Ready                      <none>          46d   v1.29.15
    k8s-node03     Ready                      <none>          46d   v1.29.15
  • 查看 node01 节点的 Pod

    # 所有pod都被调度到 node02和node03节点了
    $ kubectl get pods -o wide
    NAME                     READY   STATUS    RESTARTS   AGE     IP               NODE         NOMINATED NODE   READINESS GATES
    myapp-85497bd9fb-4cx54   1/1     Running   0          6m37s   171.20.58.233    k8s-node02   <none>           <none>
    myapp-85497bd9fb-6wjg6   1/1     Running   0          79m     171.20.135.151   k8s-node03   <none>           <none>
    myapp-85497bd9fb-8kzkf   1/1     Running   0          85m     171.20.58.225    k8s-node02   <none>           <none>
    myapp-85497bd9fb-97lnr   1/1     Running   0          79m     171.20.58.229    k8s-node02   <none>           <none>
    myapp-85497bd9fb-9vm9s   1/1     Running   0          85m     171.20.58.224    k8s-node02   <none>           <none>
    myapp-85497bd9fb-cv4xb   1/1     Running   0          6m38s   171.20.135.153   k8s-node03   <none>           <none>
    myapp-85497bd9fb-gtc22   1/1     Running   0          85m     171.20.58.227    k8s-node02   <none>           <none>
    myapp-85497bd9fb-hdvdn   1/1     Running   0          86m     171.20.58.223    k8s-node02   <none>           <none>
    myapp-85497bd9fb-j2tkd   1/1     Running   0          85m     171.20.58.228    k8s-node02   <none>           <none>
    myapp-85497bd9fb-kg9qf   1/1     Running   0          85m     171.20.135.147   k8s-node03   <none>           <none>
    myapp-85497bd9fb-mh9dx   1/1     Running   0          85m     171.20.135.145   k8s-node03   <none>           <none>
    myapp-85497bd9fb-nx2nb   1/1     Running   0          79m     171.20.135.150   k8s-node03   <none>           <none>
    myapp-85497bd9fb-qvrgl   1/1     Running   0          85m     171.20.58.226    k8s-node02   <none>           <none>
    myapp-85497bd9fb-rtcjg   1/1     Running   0          6m37s   171.20.135.154   k8s-node03   <none>           <none>
    myapp-85497bd9fb-rwvr7   1/1     Running   0          6m39s   171.20.135.152   k8s-node03   <none>           <none>
    myapp-85497bd9fb-sj22k   1/1     Running   0          6m37s   171.20.135.155   k8s-node03   <none>           <none>
    myapp-85497bd9fb-tbcrv   1/1     Running   0          85m     171.20.135.149   k8s-node03   <none>           <none>
    myapp-85497bd9fb-wgpwc   1/1     Running   0          85m     171.20.135.146   k8s-node03   <none>           <none>
    myapp-85497bd9fb-x98hg   1/1     Running   0          6m38s   171.20.58.231    k8s-node02   <none>           <none>
    myapp-85497bd9fb-z886v   1/1     Running   0          6m38s   171.20.58.232    k8s-node02   <none>           <none>
  • 取消 node01 节点的不可调度标签

    $ kubectl uncordon k8s-node01
    node/k8s-node01 uncordoned

4. drain - 特别说明

kubectl drain 操作会将相应节点上的旧 Pod 删除,并在可调度节点上面起一个对应的 Pod。当旧 Pod 没有被正常删除的情况下,新 Pod 不会起来。例如:旧 Pod 一直处于 Terminating 状态。

对应的解决方式是通过重启相应节点的 kubelet,或者强制删除该 Pod 。

# 重启发生`Terminating`节点的kubelet
systemctl restart kubelet

# 强制删除`Terminating`状态的Pod
kubectl delete pod <PodName> --namespace=<Namespace> --force --grace-period=0

5. 恢复被勿驱逐的节点

如果在执行 drain 命令时,勿将不该驱逐的 Pod 驱逐出去,例如如下情况

# 1.执行 drain 命令,驱逐 node01 节点上的Pod,由于node01部署了 metrics-server,因此发生报错,提示需要添加参数 --delete-emptydir-data
$ kubectl drain k8s-node01 --ignore-daemonsets --force
node/k8s-node01 cordoned
error: unable to drain node "k8s-node01" due to error:cannot delete Pods with local storage (use --delete-emptydir-data to override): kube-system/metrics-server-56cfc8b678-zvqpz, continuing command...
There are pending nodes to be drained:
 k8s-node01
cannot delete Pods with local storage (use --delete-emptydir-data to override): kube-system/metrics-server-56cfc8b678-zvqpz

# 查看 metrics-server
$ kubectl get pod -n kube-system  -o wide -w | grep metrics-server
metrics-server-56cfc8b678-zvqpz            1/1     Running   2 (2d1h ago)     2d1h   171.20.85.237   k8s-node01     <none>           <none>

# 2. 再次执行 drain 命令,node01 上的 Pod都被驱逐了,但也让 calico、etrics-server等组件也被驱逐了
$ kubectl drain k8s-node01 --ignore-daemonsets --force --delete-emptydir-data
node/k8s-node01 already cordoned
Warning: ignoring DaemonSet-managed Pods: ingress-nginx/ingress-nginx-controller-cv9bj, kube-system/calico-node-45x9j, kube-system/kube-proxy-9qk4c
evicting pod test-quota/test-deploy-5587d8c7c4-mptk4
evicting pod default/myapp-85497bd9fb-m7zhs
evicting pod default/myapp-85497bd9fb-gbg7t
evicting pod default/myapp-85497bd9fb-xc7q7
evicting pod default/myapp-85497bd9fb-q4z5x
evicting pod default/myapp-85497bd9fb-f8jqn
evicting pod kube-system/coredns-5f98f8d567-cv75d
evicting pod kube-system/calico-kube-controllers-558d465845-78g6d
evicting pod kube-system/metrics-server-56cfc8b678-zvqpz
evicting pod default/myapp-85497bd9fb-d2s9d
evicting pod default/myapp-85497bd9fb-x9vdq
I0912 11:08:35.913958  761187 request.go:697] Waited for 1.021455195s due to client-side throttling, not priority and fairness, request: GET:https://10.20.1.139:6443/api/v1/namespaces/kube-system/pods/metrics-server-56cfc8b678-zvqpz
I0912 11:08:46.113856  761187 request.go:697] Waited for 1.222486642s due to client-side throttling, not priority and fairness, request: GET:https://10.20.1.139:6443/api/v1/namespaces/kube-system/pods/calico-kube-controllers-558d465845-78g6d
I0912 11:08:56.114006  761187 request.go:697] Waited for 1.222272943s due to client-side throttling, not priority and fairness, request: GET:https://10.20.1.139:6443/api/v1/namespaces/default/pods/myapp-85497bd9fb-d2s9d
pod/myapp-85497bd9fb-gbg7t evicted
pod/myapp-85497bd9fb-d2s9d evicted
pod/myapp-85497bd9fb-m7zhs evicted
pod/test-deploy-5587d8c7c4-mptk4 evicted
I0912 11:09:06.914431  761187 request.go:697] Waited for 1.022353159s due to client-side throttling, not priority and fairness, request: GET:https://10.20.1.139:6443/api/v1/namespaces/kube-system/pods/coredns-5f98f8d567-cv75d
pod/myapp-85497bd9fb-f8jqn evicted
pod/myapp-85497bd9fb-xc7q7 evicted
pod/calico-kube-controllers-558d465845-78g6d evicted
pod/coredns-5f98f8d567-cv75d evicted
pod/metrics-server-56cfc8b678-zvqpz evicted
pod/myapp-85497bd9fb-x9vdq evicted
pod/myapp-85497bd9fb-q4z5x evicted
node/k8s-node01 drained

如何让 calico 、metrics-server 等组件重新调度到node01 节点

# 删除 node01 节点的 SchedulingDisabled 标签
$ kubectl uncordon k8s-node01
node/k8s-node01 uncordoned

# 滚动更新pod,重新调度
$ kubectl rollout restart deployment -n kube-system coredns
deployment.apps/coredns restarted
$ kubectl rollout restart deployment -n kube-system metrics-server
deployment.apps/metrics-server restarted
$ kubectl rollout restart deployment -n kube-system calico-kube-controllers
deployment.apps/calico-kube-controllers restarted

# 再次查看 Pod
$ kubectl get pods -n kube-system -o wide
NAME                                       READY   STATUS        RESTARTS        AGE     IP               NODE           NOMINATED NODE   READINESS GATES
calico-kube-controllers-558d465845-f2mhr   1/1     Running       0               7m46s   171.20.32.129    k8s-master01   <none>           <none>
calico-node-45x9j                          1/1     Running       0               46d     10.20.1.140      k8s-node01     <none>           <none>
calico-node-8rmt5                          1/1     Running       0               46d     10.20.1.139      k8s-master01   <none>           <none>
calico-node-fzs4f                          1/1     Running       0               46d     10.20.1.142      k8s-node03     <none>           <none>
calico-node-zhnlk                          1/1     Running       0               46d     10.20.1.141      k8s-node02     <none>           <none>
calico-typha-5b56944f9b-6gd7p              1/1     Running       0               46d     10.20.1.141      k8s-node02     <none>           <none>
coredns-5c988c55b8-ckqnf                   1/1     Running       0               30s     171.20.85.196    k8s-node01     <none>           <none>
coredns-5c988c55b8-ljzxf                   0/1     Running       0               27s     171.20.135.156   k8s-node03     <none>           <none>
coredns-5f98f8d567-72vc7                   1/1     Terminating   0               46d     171.20.58.192    k8s-node02     <none>           <none>
etcd-k8s-master01                          1/1     Running       1               46d     10.20.1.139      k8s-master01   <none>           <none>
kube-apiserver-k8s-master01                1/1     Running       4 (2d15h ago)   32d     10.20.1.139      k8s-master01   <none>           <none>
kube-controller-manager-k8s-master01       1/1     Running       39              46d     10.20.1.139      k8s-master01   <none>           <none>
kube-proxy-9qk4c                           1/1     Running       0               46d     10.20.1.140      k8s-node01     <none>           <none>
kube-proxy-bm6rt                           1/1     Running       0               46d     10.20.1.142      k8s-node03     <none>           <none>
kube-proxy-dwz4c                           1/1     Running       0               46d     10.20.1.139      k8s-master01   <none>           <none>
kube-proxy-vsl77                           1/1     Running       0               46d     10.20.1.141      k8s-node02     <none>           <none>
kube-scheduler-k8s-master01                1/1     Running       37 (24h ago)    46d     10.20.1.139      k8s-master01   <none>           <none>
metrics-server-56cfc8b678-znl7h            1/1     Running       1 (7m4s ago)    7m46s   171.20.58.230    k8s-node02     <none>           <none>

6. Taints 和 Drain 的区别

在 Kubernetes 中,Taints(污点)Drain(清空节点) 都是用于管理节点调度和 Pod 驱逐的机制,但它们的作用、实现方式和应用场景有显著区别。Taints 是一种更细粒度的调度控制工具,而 Drain 是一个高层的操作命令,用于节点维护。下面我从定义、功能、实现、场景等方面进行详细比较。

6.1 基本定义

  • Taints(污点):是一种节点属性,用于“排斥”某些 Pod 调度到该节点。只有 Pod 配置了匹配的 Tolerations(容忍) 才能调度到带有该 Taint 的节点。Taints 可以有三种效果(Effect):

    • NoSchedule:阻止新 Pod 调度到节点(不影响现有 Pod)。

    • PreferNoSchedule:优先避免调度新 Pod(但可能调度)。

    • NoExecute:阻止新 Pod 调度,并驱逐现有 Pod(如果 Pod 无匹配 Toleration)。

  • Drain(清空节点):是一个 kubectl drain 命令,用于安全地从节点上驱逐所有(或指定)Pod,并标记节点为不可调度(相当于 Cordon)。它不是一个独立机制,而是结合了 Cordon 和 Eviction 的过程。

6.2 功能比较

方面 Taints (污点) Drain (清空节点)
主要作用 控制 Pod 调度和驱逐,通过节点“污点”排斥不匹配 Pod。 安全驱逐节点上 Pod,并防止新 Pod 调度,用于节点维护。
对新 Pod 通过 NoSchedule 效果阻止调度(需 Pod 有 Toleration 才能调度)。 标记节点为 unschedulable,阻止所有新 Pod 调度(忽略 DaemonSet)。
对现有 Pod 通过 NoExecute 效果驱逐不匹配 Pod(渐进式)。 强制驱逐所有非 DaemonSet Pod(可配置忽略 PDB、local storage 等)。
自动性 手动添加/移除 Taint;节点控制器可自动添加(如 out-of-disk)。 手动执行命令;自动添加 unschedulable Taint。
影响范围 细粒度:可针对特定节点组(如专用硬件节点)隔离 Pod。 全局:针对整个节点清空,DaemonSet Pod 被忽略(会重建)。
恢复方式 kubectl taint nodes key:NoSchedule- 移除 Taint。 kubectl uncordon 移除 unschedulable 标记。
示例命令 kubectl taint nodes node1 key=value:NoSchedule kubectl drain node1 –ignore-daemonsets

6.3 实现机制的区别

  • Taints 的实现

    • Taints 存储在节点的 spec.taints 字段中,与 Pod 的 Tolerations 匹配。
    • Kubernetes 调度器(Scheduler)在调度 Pod 时检查 Taints 和 Tolerations。
    • 例如,添加 Taint 后,只有有 Toleration 的 Pod(如 DaemonSet)才能运行。节点控制器会自动添加特定 Taint(如 node.kubernetes.io/not-ready:NoExecute)来处理故障节点。
    • 它更灵活,支持自定义键值对(如 dedicated=frontend:NoSchedule),用于节点亲和性反向控制。
  • Drain 的实现

    • Drain 内部会先执行 cordon(标记节点 unschedulable),相当于添加一个隐式的 Taint node.kubernetes.io/unschedulable:NoSchedule。
    • 然后逐个驱逐 Pod(evict),尊重 PodDisruptionBudget (PDB),忽略 DaemonSet。
    • 完成后,节点被“清空”,但 DaemonSet Pod 仍运行(因为它们忽略 unschedulable Taint)。
    • Drain 不是直接使用 Taints,而是结合了 Taints 的效果(unschedulable Taint) + 手动驱逐逻辑。

    关键点:Drain 会自动使用 Taint 来实现部分功能,但 Taints 可以独立使用,而不一定涉及驱逐。

6.4 使用场景

  • Taints 适合:
    • 专用节点隔离:如将 GPU 节点 Taint 为 gpu=true:NoSchedule,只允许有 GPU Toleration 的 Pod 调度。
    • 故障处理:自动驱逐 Pod 到健康节点(NoExecute 效果)。
    • 高级调度:结合 Node Affinity,实现复杂亲和规则。
  • Drain 适合:
    • 节点维护:如升级 kubelet、重启节点前清空 Pod,确保 Pod 迁移到其他节点。
    • 临时不可用:快速标记节点维护中,防止新 Pod 调度。
  • 两者结合:在 Drain 过程中,Kubernetes 会添加 node.kubernetes.io/unschedulable Taint 来阻止调度。如果你想在 Drain 后进一步隔离,可以添加自定义 Taint。

6.5 注意事项

  • 兼容性:DaemonSet Pod 通常有内置 Toleration,能忽略 unschedulable Taint,因此 Drain 不会移除它们。
  • 潜在问题:如果 Pod 有本地存储(如 emptyDir),Drain 可能失败,需要 –delete-emptydir-data 参数。
  • 最佳实践:Drain 前备份 Pod 配置;使用 Taints 时,确保关键 Pod(如系统组件)有对应 Toleration。

参考链接

https://kubernetes.io/zh-cn/docs/reference/kubectl/generated/kubectl_cordon/

https://chegva.com/6300.html


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 george_95@126.com