系统学习 故障排查学习路径:从方法论到系统、网络、数据库与 K8s

incident-cert-expiry

内部证书集中到期导致节点批量 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 通,看起来“链路没问题”。

真正推翻这个判断的证据有四条:

  1. TCP 能连通不等于 TLS 能完成。 nc -vz <apiserver>:6443 成功只说明三次握手完成,证书过期发生在握手之后的认证阶段。用 openssl s_client 一看,notAfter 已经是过去时间。
  2. 故障几乎同时发生,且跨越不同机架和不同 CNI 实例。 网络故障通常沿链路逐步扩散,而证书到期是共享的时间条件,所有使用同一批证书的节点会在同一时刻失败。
  3. kubelet 日志直接给出 x509 错误。 这条证据被 CNI 重启的噪声掩盖了很久;排障时应优先在节点本地看 kubelet 日志,而不是只看应用侧超时。
  4. CNI Pod 重启是结果,不是原因。 CNI 组件同样需要访问 apiserver,证书过期后它无法完成认证,重启只会反复失败。

另一个容易忽略的点是:管理员 kubeconfig 也失效,说明问题不局限于节点侧,而是集群级的证书或时间问题。此时应立刻检查时间同步,排除时钟漂移,再检查证书有效期。

根因

直接原因是 kubelet 客户端证书与 apiserver 服务端证书都已过期:前者没有完成自动轮换,后者没有提前续期。集群内部 CA 签发的叶子证书集中在一批初始化操作中签发,有效期相近;当这批证书同期到期后,kubelet 无法向 apiserver 认证,节点状态停止上报,管理员 kubectl 也因为服务端证书过期而无法连接,于是出现节点集体 NotReady。

促成条件有三个:

需要强调:本次是叶子证书过期,内部 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 和业务健康后再继续。

预防

相关命令

参考链接

读完自测

合上页面,先回答,再展开对照。

  1. 节点集体 NotReady 时,哪两条证据能最快把方向从网络故障切到证书过期?
  2. kubeadm certs renew all 之后为什么还必须重启控制面组件?kubelet 证书会自动续期吗?
  3. 为什么“6443 端口能 telnet 通”不能证明 apiserver 可用?
答案
  1. kubelet 日志里的 x509: certificate has expired or is not yet valid,以及 openssl s_client 看到的 apiserver/客户端证书 notAfter 已经早于当前时间。两者都把问题锁定在 TLS 认证层,而不是链路层。
  2. 续期命令只更新磁盘上的证书文件,运行中的 apiserver、controller-manager、scheduler 仍持有旧证书,必须重启静态 Pod 才会重新加载。kubelet 证书不在 kubeadm certs renew all 的覆盖范围内,需要依赖自身轮换或手工重签。
  3. TCP 握手只证明端口可达;证书过期、证书链不受信任、SNI 不匹配都发生在 TLS 握手阶段。端口通但 TLS 失败时,apiserver 仍然不可用。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令