记一次 K8s 节点 kubelet 证书轮换失败导致节点 NotReady 风暴的排查

K8s 集群中 kubelet 证书自动轮换失败后,节点批量 NotReady,排查证书链路、kubelet 配置与 apiserver 认证的根因。

问题背景

生产 K8s 集群(v1.28)运行约 18 个月,节点总数 42 台。近期因安全合规要求,计划将 kubelet 客户端证书有效期从默认 1 年缩短至 90 天,并开启 rotateCertificates: true 自动轮换。变更后第 3 天凌晨,监控突然告警:12 台节点同时进入 NotReady 状态,持续约 47 分钟后才陆续恢复。业务 Pod 因节点 NotReady 被驱逐,部分有状态服务短暂中断。

故障现象

  • kubectl get nodes 显示 12 台节点 Condition ReadyFalse,Reason KubeletNotReady,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
    2
    
    certificate 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. 确认证书状态

在故障节点执行:

1
2
3
kubelet --version
ls -l /var/lib/kubelet/pki/
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates -issuer

发现:

  • 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

1
2
rotateCertificates: true
serverTLSBootstrap: false

rotateCertificates: true 已开启,但 serverTLSBootstrap 为 false(默认)。

进一步检查 kubelet 启动参数:

1
ps aux | grep kubelet | grep -E 'bootstrap-kubeconfig|kubeconfig'

发现节点使用的是静态 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-kubeconfigkubelet.conf 中的 client-certificate 直接指向过期文件。
  • 即使 rotateCertificates: true,kubelet 也无法触发「续期请求」,因为它不知道去哪里申请新证书。

4. 根因定位

核心矛盾rotateCertificates 依赖 TLS Bootstrapping 流程,而老节点从未配置过 bootstrap token + bootstrap-kubeconfig

当原 client 证书过期后:

  1. kubelet 尝试用过期证书连接 apiserver → 认证失败。
  2. 即使配置了 rotateCertificates: true,kubelet 也无法构造合法的 CSR 请求(因为连 apiserver 都连不上)。
  3. 节点进入 NotReady → 触发 Pod 驱逐 → 雪崩。

解决方案

紧急恢复(已执行)

对 12 台故障节点逐一执行「手动续期 + 重启」:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 1. 备份旧证书
mv /var/lib/kubelet/pki/kubelet-client-current.pem /var/lib/kubelet/pki/backup/

# 2. 用管理员 kubeconfig 手动生成新证书(通过 kubectl certificate approve 模拟)
kubectl create csr node-manual-renewal-$(hostname) \
  --from-file=/var/lib/kubelet/pki/kubelet-client.key \
  --signer-name=kubernetes.io/kubelet-serving

# 3. 实际操作中直接用 kubeadm 重新 join(最稳妥)
kubeadm token create --print-join-command
kubeadm join ... --ignore-preflight-errors=DirAvailable--var-lib-etcd

注意:生产环境严禁直接删除 /var/lib/kubelet/pki,必须先备份。

根治方案(计划本周上线)

  1. 全量切换到 TLS Bootstrapping

    • 为所有节点创建长期有效的 bootstrap token(或使用 kubeadm token create --ttl=0)。
    • 在所有节点部署 /etc/kubernetes/bootstrap-kubeconfig
    • 修改 kubelet 启动参数,移除静态 --kubeconfig,改用 --bootstrap-kubeconfig
  2. 配置证书轮换参数

    1
    2
    3
    
    # /var/lib/kubelet/config.yaml
    rotateCertificates: true
    serverTLSBootstrap: true   # 同时开启 server 证书轮换
    
  3. 监控与告警

    • 新增 Prometheus 规则:
      1
      2
      
      - alert: KubeletClientCertExpiringSoon
        expr: kubelet_certificate_manager_client_expiration_seconds < 86400 * 14
      
    • 设置 14 天提前告警,避免再次出现批量过期。
  4. 变更验收 Checklist(已写入运维手册):

    • 证书有效期检查脚本每日巡检。
    • 节点 NotReady 数量 > 3 触发 PagerDuty 立即响应。
    • 证书轮换操作必须在「维护窗口 + 灰度 10% 节点」后全量。

根因分析

层面 具体原因 为什么没被发现
架构 老节点使用静态 kubeconfig,缺少 bootstrap 流程 早期集群规模小,证书 1 年有效期,运维人员认为「够用」
配置 rotateCertificates: true 但缺少配套 bootstrap 配置 文档中只写了「开启即可」,未强调「老节点需先迁移」
监控 仅监控证书剩余天数,未区分「是否可自动续期」 指标 kubelet_certificate_manager_client_expiration_seconds 在过期后才报警
变更管理 安全合规要求「缩短有效期」时,未做节点分层评估 变更 checklist 缺少「证书轮换依赖项检查」

预防措施

  1. 节点生命周期管理

    • 新节点必须通过「标准化镜像 + cloud-init + kubeadm join」流程上线,禁止手动 kubeadm join
    • 老节点每年至少做一次「证书与 bootstrap 流程一致性检查」。
  2. 证书治理平台

    • 引入 cert-manager 或自研「K8s 证书中心」,统一管理 client/server 证书生命周期。
    • 提供「一键续期」与「过期前 30 天自动续期」能力。
  3. 变更前置检查脚本

    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
    
  4. 定期演练

    • 每季度执行一次「模拟证书过期」演练,验证自动轮换与应急流程。

总结

本次故障的本质是**「配置项与节点历史状态不匹配」**。rotateCertificates: true 看似简单,实则依赖完整的 TLS Bootstrapping 链路。老节点因历史遗留问题,在证书过期后无法自愈,导致批量 NotReady 风暴。

教训:

  • 任何「看似自动」的功能,都必须在全量节点上验证依赖条件。
  • 安全合规变更必须结合「节点分层」与「历史技术债」评估。
  • 监控不仅要看「结果」(证书是否过期),更要看「过程」(是否具备自愈能力)。

后续将把「kubelet 证书轮换一致性检查」纳入每月运维 Checklist,并计划在 Q4 引入 cert-manager 做全生命周期治理。

使用 Hugo 构建
主题 StackJimmy 设计