内部证书集中到期导致节点批量 NotReady,以及如何从“网络故障”的误判中拉回。
某业务集群在凌晨出现大面积节点 NotReady。多个节点几乎在同一分钟被标记为不可用,kubelet 进程仍在运行,容器也没有被批量杀掉,但节点状态无法上报,新建 Pod 无法调度,滚动发布全部卡住。入口错误率快速上升,部分服务因为 Endpoints 被摘除而出现 502/504。
更奇怪的是,值班同学用管理员 kubeconfig 执行 kubectl get nodes 时,客户端直接报 TLS 错误;换一台机器、换一个网络出口仍然如此。集群内与 apiserver 通信的组件同时异常,但节点之间 ping 正常,SSH 也能登录,第一眼看上去很像“网络抖动”或“控制面过载”。
节点上的 kubelet 日志给出了第一条硬线索:
x509: certificate has expired or is not yet valid: current time ... is after ...
与此同时,监控里的 Node Ready 条件变成 Unknown,原因是 Kubelet stopped posting node status。这不是容器运行时崩溃,而是 kubelet 无法完成与 apiserver 的 TLS 认证,节点状态因此停止更新。
| 时间 | 事件 |
|---|---|
| T+0 | 监控告警:NotReady 节点数在 2 分钟内从 0 升到 20 以上,入口 5xx 同步上升 |
| T+3m | 值班接管,按“节点网络故障”排查 CNI Pod、节点互通与 DNS |
| T+8m | 重启 CNI DaemonSet、检查路由,NotReady 数量没有变化;ping 与 nc -vz 均通,进一步强化了“网络没问题”的判断 |
| T+13m | kubectl logs、kubectl exec 失败,管理员 kubectl 也报 x509,开始怀疑控制面证书 |
| T+18m | 登录节点查看 kubelet 日志,确认 certificate has expired |
| T+22m | 用 openssl s_client 检查 apiserver 服务端证书,用 openssl x509 检查 kubelet 客户端证书,确认两者都已过期,其他控制面证书也只剩很短有效期 |
| T+28m | 备份 /etc/kubernetes 与 /var/lib/kubelet/pki,执行控制面证书续期并重启静态 Pod |
| T+36m | 逐批恢复/重签 kubelet 客户端证书,先恢复一个节点验证,再灰度到其余节点 |
| T+44m | 节点陆续 Ready,调度与控制器恢复 |
| T+55m | 入口错误率回到基线,积压任务开始消化 |
| T+2h | 完成全量证书盘点,补上到期监控与轮换检查 |
约三成节点在四十多分钟内处于 NotReady,受影响的主要是依赖跨节点调用的在线服务。已运行的容器没有全部退出,但服务发现、健康检查和滚动发布都失效,部分流量被摘除后打到仍有副本的节点,造成局部过载。入口 5xx 峰值超过两成,消息消费出现积压。
控制面在证书过期期间基本不可写:发布、扩缩容、故障转移和证书审批都无法执行,故障恢复动作被严重拖慢。由于这是认证层的硬失败,重启 kubelet 或重启 CNI 都不会恢复,反而会扩大重启风暴。事故没有造成持久化数据丢失,但有少量请求需要重试或人工对账。
最初的判断是网络或 CNI 故障,理由有三点:第一,告警同时包含节点 NotReady 和跨节点请求超时;第二,告警前后有 CNI Pod 重启;第三,节点之间 ping 通、SSH 通,看起来“链路没问题”。
真正推翻这个判断的证据有四条:
nc -vz <apiserver>:6443 成功只说明三次握手完成,证书过期发生在握手之后的认证阶段。用 openssl s_client 一看,notAfter 已经是过去时间。另一个容易忽略的点是:管理员 kubeconfig 也失效,说明问题不局限于节点侧,而是集群级的证书或时间问题。此时应立刻检查时间同步,排除时钟漂移,再检查证书有效期。
直接原因是 kubelet 客户端证书与 apiserver 服务端证书都已过期:前者没有完成自动轮换,后者没有提前续期。集群内部 CA 签发的叶子证书集中在一批初始化操作中签发,有效期相近;当这批证书同期到期后,kubelet 无法向 apiserver 认证,节点状态停止上报,管理员 kubectl 也因为服务端证书过期而无法连接,于是出现节点集体 NotReady。
促成条件有三个:
rotateCertificates 被关闭,要么 CSR 自动批准链路不可用。证书已经过期时,kubelet 也无法再用旧证书认证并提交新的 CSR,形成“过期后无法自愈”的死结。kubeadm certs check-expiration 只覆盖 kubeadm 管理的控制面证书,不覆盖每个节点上的 kubelet 客户端证书,导致监控盲区。需要强调:本次是叶子证书过期,内部 CA 根证书仍然有效,所以不需要做完整的 CA 轮换。如果根证书也过期,处理范围会大得多,必须先恢复信任链再谈节点恢复。
先确认是证书过期而不是时钟漂移:
timedatectl
sudo openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem \
-noout -subject -issuer -dates
# 若未启用轮换,客户端证书可能内嵌在 kubelet.conf
sudo kubectl config view --raw --kubeconfig=/etc/kubernetes/kubelet.conf \
-o jsonpath='{.users[0].user.client-certificate-data}' | base64 -d \
| openssl x509 -noout -subject -dates
openssl s_client -connect <apiserver-vip>:6443 -servername kubernetes </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
sudo kubeadm certs check-expiration
备份后再续期,避免把唯一的 CA 材料改坏:
sudo cp -a /etc/kubernetes /etc/kubernetes.bak.$(date +%Y%m%d%H%M)
sudo cp -a /var/lib/kubelet/pki /var/lib/kubelet/pki.bak.$(date +%Y%m%d%H%M)
sudo kubeadm certs renew all
sudo kubeadm certs check-expiration
备份目录应限制权限(例如先执行 umask 077),并尽快转移到受控位置,避免把 CA 私钥留在临时目录。
kubeadm certs renew 只写文件,不会让运行中的组件自动加载新证书,必须重启控制面静态 Pod。多控制平面节点要对每个节点分别执行并逐台重启,避免同时失去 quorum:
sudo crictl ps --name kube-apiserver -q | xargs -r sudo crictl stop
sudo crictl ps --name kube-controller-manager -q | xargs -r sudo crictl stop
sudo crictl ps --name kube-scheduler -q | xargs -r sudo crictl stop
kubectl get --raw='/readyz?verbose'
控制面使用 stacked etcd 时,kubeadm certs renew all 也会续期 etcd 证书;需要逐台重启 etcd 静态 Pod,并确认 quorum 与 leader 稳定后再继续下一个节点。
kubelet 侧要分两种情况。若轮换仍可用,重启 kubelet 后会产生 CSR,需要及时批准:
sudo systemctl restart kubelet
kubectl get csr
kubectl certificate approve <csr-name>
若证书已经过期,kubelet 无法认证,也就无法自己提交 CSR,此时必须先从安全备份恢复一份未过期的 kubelet.conf,或用集群 CA 重新签发,再逐节点替换并重启 kubelet。重签时使用 system:node:<node> 作为客户端名、system:nodes 作为组,替换前先用 kubectl --kubeconfig 验证身份和权限。恢复顺序应遵循“先控制面、再一个节点、再分批”,每批确认 Ready 和业务健康后再继续。
kubeadm certs check-expiration 导出剩余天数,kubelet 用 openssl x509 -enddate 采集每个节点的客户端证书,入口和内部服务证书也纳入同一看板。合上页面,先回答,再展开对照。
kubeadm certs renew all 之后为什么还必须重启控制面组件?kubelet 证书会自动续期吗?x509: certificate has expired or is not yet valid,以及 openssl s_client 看到的 apiserver/客户端证书 notAfter 已经早于当前时间。两者都把问题锁定在 TLS 认证层,而不是链路层。kubeadm certs renew all 的覆盖范围内,需要依赖自身轮换或手工重签。
小程序版
微信扫码,随时查命令