在双节点 kind 集群停掉 worker 的 kubelet,观察 NotReady、自动污点与 Pod 驱逐

演练目标

环境准备

隔离边界:只操作本机 kind 集群的 worker 容器,不要在任何真实节点上执行 systemctl stop kubelet,也不要修改宿主机 systemd。准备 Docker、kind、kubectl,预留约 2 GB 内存。

cat > /tmp/kind-notready.yaml <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
  - role: worker
EOF
kind create cluster --name lab-notready --config /tmp/kind-notready.yaml
kubectl get nodes -o wide

等两个节点都为 Ready 再继续。若使用 minikube,可执行 minikube start -p lab-notready --nodes 2,后续把 worker 容器名替换为 lab-notready-m02。

注入故障

先部署一个带短驱逐容忍时间的 Deployment,并固定到 worker:

kubectl create deployment notready-lab --image=nginx:1.27-alpine --replicas=2
kubectl patch deployment notready-lab --type=strategic -p '
{"spec":{"template":{"spec":{
  "tolerations":[{"key":"node.kubernetes.io/not-ready","operator":"Exists","effect":"NoExecute","tolerationSeconds":10}],
  "nodeSelector":{"kubernetes.io/hostname":"lab-notready-worker"}
}}}}'
kubectl rollout status deployment/notready-lab --timeout=120s
kubectl get pods -l app=notready-lab -o wide

确认两个 Pod 都在 lab-notready-worker 上 Running。然后停止该节点的 kubelet,模拟节点失联:

docker exec lab-notready-worker systemctl stop kubelet
sleep 45
kubectl get nodes

kubelet 停止后,节点状态需要经过多个心跳周期才会变为 NotReady,通常 40 秒左右。

观察现象

kubectl get nodes -o wide
kubectl describe node lab-notready-worker | sed -n '/Conditions:/,/Addresses:/p'
kubectl describe node lab-notready-worker | grep -A3 Taints
kubectl get pods -l app=notready-lab -o wide
kubectl get events --sort-by=.lastTimestamp | tail -20

预期 worker 的 Ready Condition 变成 False,Reason 为 KubeletNotReady 或 NodeStatusUnknown;节点自动加上 node.kubernetes.io/not-ready:NoSchedule 和 NoExecute 污点。Pod 在 tolerationSeconds=10 到期后进入 Terminating 或被删除,事件中出现 TaintManagerEviction / Deleting pod for node condition。由于只有 worker 能满足 nodeSelector,Pod 会重建为 Pending,不会自动跑到 control-plane。

判断与定位

  1. 先写假设:kubelet 进程退出、节点资源压力(MemoryPressure/DiskPressure/PIDPressure)、节点网络中断,还是 apiserver 到 kubelet 的链路问题。不同假设对应不同 Condition 和日志。
  2. 看 kubectl get nodes 与 describe node 的 Conditions。Ready=False 且 KubeletNotReady 多半是 kubelet 自身;Ready=Unknown 更像心跳中断或网络分区。
  3. 看是否有 MemoryPressure、DiskPressure、PIDPressure=True。资源压力触发的 NotReady 通常伴随驱逐和 kubelet 日志中的 imageGC/eviction 记录。
  4. 到节点侧验证。kind 节点用 docker exec <node> systemctl status kubelet、journalctl -u kubelet -n 50;真实节点同样先看 kubelet 日志,再检查容器运行时和磁盘。
  5. 容易误判:kubectl get nodes 显示 NotReady 不代表节点上所有容器都停了。kubelet 停止后静态 Pod 和已运行容器可能继续工作,业务甚至还能短暂提供服务;但调度、探针和驱逐会失效。另一个误判是把 NodeStatusUnknown 当成 kubelet 一定挂了,网络分区也会产生同样状态。
  6. 如果只是单个 Pod 异常而节点 Ready,不要从节点下手,应先看 Pod 事件和探针。

修复

重新启动 worker 的 kubelet,等待节点恢复 Ready,再解除 nodeSelector:

docker exec lab-notready-worker systemctl start kubelet
kubectl wait --for=condition=Ready node/lab-notready-worker --timeout=180s
kubectl get nodes
kubectl get pods -l app=notready-lab -o wide

节点 Ready 后自动污点会被移除。把 Deployment 的 nodeSelector 去掉,让副本回到可调度状态:

kubectl patch deployment notready-lab --type=json \
  -p '[{"op":"remove","path":"/spec/template/spec/nodeSelector"}]'
kubectl rollout status deployment/notready-lab --timeout=180s
kubectl get pods -l app=notready-lab -o wide

两个副本都 Running 且分布合理即恢复。生产中还要确认 kubelet 停止的根因(OOM、证书过期、配置错误、磁盘满),否则重启后可能再次 NotReady。

清理与回滚

docker exec lab-notready-worker systemctl start kubelet 2>/dev/null || true
kubectl delete deployment notready-lab --ignore-not-found
kind delete cluster --name lab-notready
rm -f /tmp/kind-notready.yaml

不要对真实节点执行上述 systemctl 命令;本页所有节点操作都只针对 kind 创建的 Docker 容器。

复盘

延伸阅读

读完自测

  1. kubelet 停止后,节点上的业务容器会立刻全部停止吗?
  2. Ready=Unknown 与 Ready=False 分别更可能指向什么问题?
  3. 为什么本实验中 Pod 被驱逐后没有自动调度到 control-plane?
答案
  1. 不会;已运行容器可能继续运行,但调度、探针执行和驱逐控制会失效。
  2. Unknown 更常见于心跳中断或网络分区,False 更常见于 kubelet 主动上报自身不健康或资源压力。
  3. control-plane 带有 NoSchedule 污点,且 Pod 还带着只匹配 worker 的 nodeSelector。