问题背景
生产 K8s 集群(v1.28)运行约 18 个月,节点总数 42 台。近期因安全合规要求,计划将 kubelet 客户端证书有效期从默认 1 年缩短至 90 天,并开启 rotateCertificates: true 自动轮换。变更后第 3 天凌晨,监控突然告警:12 台节点同时进入 NotReady 状态,持续约 47 分钟后才陆续恢复。业务 Pod 因节点 NotReady 被驱逐,部分有状态服务短暂中断。
故障现象
kubectl get nodes显示 12 台节点 ConditionReady为False,ReasonKubeletNotReady,Message 包含x509: certificate has expired or is not yet valid。- 受影响节点全部为「老节点」(kubelet 启动时间 > 400 天),新节点(近 3 个月上线的)全部正常。
- apiserver 日志出现大量
authentication.go: Failed to verify client certificate错误,对应节点 kubelet 连接被拒绝。 - 节点上
journalctl -u kubelet可见:1 2certificate rotation failed: x509: certificate signed by unknown authority server returned error: x509: certificate has expired or is not yet valid - 集群层面
kubelet_server_expiration_renew_errors指标在故障窗口内突增。
排查过程
1. 确认证书状态
在故障节点执行:
|
|
发现:
kubelet-client-current.pem指向的实际文件kubelet-client-2025-06-12-10.pem过期时间为 2026-06-12(已过期 91 天)。- Issuer 为集群自签 CA
kubelet-bootstrap-ca@162xxxx。 - 目录下还存在 3 个历史证书文件,最新一个也是 2025-06-12 生成。
2. 检查 kubelet 配置
关键配置 /var/lib/kubelet/config.yaml:
|
|
rotateCertificates: true 已开启,但 serverTLSBootstrap 为 false(默认)。
进一步检查 kubelet 启动参数:
|
|
发现节点使用的是静态 kubeconfig(/etc/kubernetes/kubelet.conf),而非 TLS Bootstrapping 流程生成的 bootstrap-kubeconfig。
3. 对比正常节点与故障节点
正常节点(近 3 个月上线):
- 使用
kubeadm init时自动生成的 bootstrap token +bootstrap-kubeconfig。 - kubelet 启动参数包含
--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubeconfig。 - 证书轮换成功,当前证书有效期至 2026-12-01。
故障节点(老节点):
- 早期使用
kubeadm join --token xxx一次性加入,kubeconfig 写死在/etc/kubernetes/kubelet.conf。 - 没有
bootstrap-kubeconfig,kubelet.conf中的 client-certificate 直接指向过期文件。 - 即使
rotateCertificates: true,kubelet 也无法触发「续期请求」,因为它不知道去哪里申请新证书。
4. 根因定位
核心矛盾:rotateCertificates 依赖 TLS Bootstrapping 流程,而老节点从未配置过 bootstrap token + bootstrap-kubeconfig。
当原 client 证书过期后:
- kubelet 尝试用过期证书连接 apiserver → 认证失败。
- 即使配置了
rotateCertificates: true,kubelet 也无法构造合法的 CSR 请求(因为连 apiserver 都连不上)。 - 节点进入 NotReady → 触发 Pod 驱逐 → 雪崩。
解决方案
紧急恢复(已执行)
对 12 台故障节点逐一执行「手动续期 + 重启」:
|
|
注意:生产环境严禁直接删除 /var/lib/kubelet/pki,必须先备份。
根治方案(计划本周上线)
-
全量切换到 TLS Bootstrapping:
- 为所有节点创建长期有效的 bootstrap token(或使用
kubeadm token create --ttl=0)。 - 在所有节点部署
/etc/kubernetes/bootstrap-kubeconfig。 - 修改 kubelet 启动参数,移除静态
--kubeconfig,改用--bootstrap-kubeconfig。
- 为所有节点创建长期有效的 bootstrap token(或使用
-
配置证书轮换参数:
1 2 3# /var/lib/kubelet/config.yaml rotateCertificates: true serverTLSBootstrap: true # 同时开启 server 证书轮换 -
监控与告警:
- 新增 Prometheus 规则:
1 2- alert: KubeletClientCertExpiringSoon expr: kubelet_certificate_manager_client_expiration_seconds < 86400 * 14 - 设置 14 天提前告警,避免再次出现批量过期。
- 新增 Prometheus 规则:
-
变更验收 Checklist(已写入运维手册):
- 证书有效期检查脚本每日巡检。
- 节点 NotReady 数量 > 3 触发 PagerDuty 立即响应。
- 证书轮换操作必须在「维护窗口 + 灰度 10% 节点」后全量。
根因分析
| 层面 | 具体原因 | 为什么没被发现 |
|---|---|---|
| 架构 | 老节点使用静态 kubeconfig,缺少 bootstrap 流程 | 早期集群规模小,证书 1 年有效期,运维人员认为「够用」 |
| 配置 | rotateCertificates: true 但缺少配套 bootstrap 配置 |
文档中只写了「开启即可」,未强调「老节点需先迁移」 |
| 监控 | 仅监控证书剩余天数,未区分「是否可自动续期」 | 指标 kubelet_certificate_manager_client_expiration_seconds 在过期后才报警 |
| 变更管理 | 安全合规要求「缩短有效期」时,未做节点分层评估 | 变更 checklist 缺少「证书轮换依赖项检查」 |
预防措施
-
节点生命周期管理:
- 新节点必须通过「标准化镜像 + cloud-init + kubeadm join」流程上线,禁止手动
kubeadm join。 - 老节点每年至少做一次「证书与 bootstrap 流程一致性检查」。
- 新节点必须通过「标准化镜像 + cloud-init + kubeadm join」流程上线,禁止手动
-
证书治理平台:
- 引入
cert-manager或自研「K8s 证书中心」,统一管理 client/server 证书生命周期。 - 提供「一键续期」与「过期前 30 天自动续期」能力。
- 引入
-
变更前置检查脚本:
1 2 3 4 5 6#!/bin/bash # check_kubelet_bootstrap.sh if [ ! -f /etc/kubernetes/bootstrap-kubeconfig ]; then echo "WARNING: node $(hostname) is using static kubeconfig, certificate rotation may fail" exit 1 fi -
定期演练:
- 每季度执行一次「模拟证书过期」演练,验证自动轮换与应急流程。
总结
本次故障的本质是**「配置项与节点历史状态不匹配」**。rotateCertificates: true 看似简单,实则依赖完整的 TLS Bootstrapping 链路。老节点因历史遗留问题,在证书过期后无法自愈,导致批量 NotReady 风暴。
教训:
- 任何「看似自动」的功能,都必须在全量节点上验证依赖条件。
- 安全合规变更必须结合「节点分层」与「历史技术债」评估。
- 监控不仅要看「结果」(证书是否过期),更要看「过程」(是否具备自愈能力)。
后续将把「kubelet 证书轮换一致性检查」纳入每月运维 Checklist,并计划在 Q4 引入 cert-manager 做全生命周期治理。