K8s证书过期怎么办?kubeadm集群证书一键续期脚本实战

📅 发布时间:2026/10/18 23:05:38
K8s证书过期怎么办?kubeadm集群证书一键续期脚本实战
简介这是面向Kubernetes集群运维人员的一键证书续期Shell脚本。证书在k8s中承担身份验证、授权和加密通信等关键职责一旦接近过期就可能影响集群稳定性该脚本正是为解决kubeadm环境下apiserver、etcd、kubelet、kube-proxy等证书更新难题而设计。包内仅含1个update-kubeadm-cert.sh脚本文件整个压缩包约3KB非常轻量下载后可按实际集群调整证书路径和过期阈值后使用。脚本执行流程包含备份现有证书、检测过期证书、重新生成证书、更新组件配置、重启必要组件及验证更新结果形成完整运维闭环可帮助运维人员减少手动操作中的遗漏与出错避免因证书过期导致的服务中断。目前已有470人学习下载适合熟悉kubeadm部署、希望简化证书生命周期管理的初中级Kubernetes运维与架构人员参考。1. 证书过期是每个 K8s 运维都躲不掉的“定时炸弹”某天早上某公司的值班同学慌慌张张找到我说集群登不上去了kubectl直接报Unable to connect to the server: x509: certificate has expired or is not yet valid。我第一反应是 apiserver 挂了结果上去一看进程还在日志里全是证书校验失败。那一刻我就知道集群的 CA 证书过期了。用过kubeadm部署集群的都知道默认证书有效期只有一年有些版本甚至更短。而证书过期这事儿不是重启就能解决的它会让整个控制面陷入“假死”状态——你连 kubectl 都发不出去更别提什么优雅操作了。于是我自己写了套 k8s 证书一键续期脚本手动流程压缩成了一行命令从发现问题到集群恢复十分钟内搞定。这篇笔记就是把当时踩过的坑、试过的方案、最终稳定的做法完整复现一遍让同样被证书逼疯的运维少走弯路。先说结论kubeadm 管理的集群证书续期并不需要重装集群也不需要手动逐个生成 CSR核心就两件事——重新生成证书和重新分发 kubeconfig。难点不在于生成在于你把证书续上之后集群里一堆组件还在用旧证书它们不认账这才是最容易翻车的地方。所以这套方案的价值就在于把一个牵一发动全身的敏感操作变成一个你有把握、可回滚、可验证的固定流程。2. 证书体系拆解K8s 里到底有哪些证书在“倒计时”2.1 控制面证书全家桶哪些过期会致命哪些没那么急K8s 集群里的证书分布是分散的不是一张证书管所有。我用kubeadm初始化集群后证书是按角色区分开的绝大多数躺在/etc/kubernetes/pki/下证书/密钥文件用途过期影响ca.crt/ca.key集群根 CA签发下级证书根 CA 过期全线崩apiserver.crt/apiserver.keyapiserver 对外服务的 TLS 证书kubeconfig 连接失败apiserver-kubelet-client.crtapiserver 访问 kubelet节点状态收集异常front-proxy-ca.crtfront proxy 客户端 CA聚合 API 异常etcd/ca.crtetcd 独立 CAetcd 客户端/peer 全部不可用etcd/server.crtetcd server SSLetcd 不可用控制面雪崩这里最容易麻痹大意的是 kubeconfig大多数人只盯着pki/目录忽略了/etc/kubernetes/下几个.conf文件里的 client 证书。kubelet.conf和admin.conf里嵌的证书有效期同样是跟着 CA 走的CA 一过期这几个 conf 再有权限也白搭。实际操作中经常是 pki 里的证书显示还有效但 kubectl 就是用不了原因就是admin.conf里的客户端证书先失效了。2.2 证书有效期到底谁在管kubeadm 的自动续期为什么失灵kubeadm设计了一套证书自动续期机制理论上控制面组件通过kubeadm certs renew可以无感更新证书但被无数人忽略的关键点是该命令只针对静态 Pod 的证书且必须满足“非外部托管 CA”的前提。如果你的集群当初是用自签 CA、ca.key还在节点上那么kubeadm certs renew all是管用的。但公司内部很多集群是外部 CA 签发的入口证书此时renew会直接报x509: certificate signed by unknown authority之类的错自动续期就成了摆设。另一个反直觉的情况是即便kubeadm certs renew all成功执行了kubelet 的证书也不会跟着更新。kubelet 的证书轮换是独立的由kubelet.conf里的rotate-certificates机制控制。所以你在控制面上把证书全续了一遍但 node 节点上的 kubelet 仍在用旧 CA 签出的证书一旦旧 CA 过期kubelet 与 apiserver 的通信照样断掉。续期脚本如果不覆盖这一层时间长了必然二次翻车。2.3 全量刷新 vs 按需刷新哪种续期策略更安全面对已经过期的集群行业内有两个路子全量刷新所有证书和 kubeconfig这是最稳的无论根 CA 是否过期、是否外部托管都能用按需刷新则是只处理报错的证书快但隐患大。我自己的习惯是能用全量刷新绝不局部续期。原因很实在局部续期你得排查到底哪张证书过期、哪些组件引用了它、client 证书是否匹配排查成本可能比全新签发还高而且容易漏。全量刷新有没有风险当然有最大的坑在于etcd 证书不能乱续。etcd 集群内部节点互信靠的是 peer 证书如果你只续了 server 证书没同步 peer或者续出来的新 CA 与旧 CA 不一致整个 etcd 集群会脑裂。所以我处理 etcd 的策略是优先只续等值证书保证 CA 不变更。也就是说ca.crt、ca.key保持不变用同一 CA 重新签发新的server.crt这样新旧其实是一个信任体系etcd 不会恐慌。3. 续期前必须搞懂的 3 个核心判断这决定你的脚本会不会翻车3.1 根 CA 是否过期过期与未过期处理路径完全不同判断根 CA 是否过期只需在 master 节点上执行openssl x509 -in /etc/kubernetes/pki/ca.crt -noout -dates输出里会有notBefore和notAfter两行。如果notAfter还在未来说明根 CA 还活着那么你就可以走“轻量续期”路线只用同一把ca.key重新签发所有叶子证书再把新的 kubeconfig 分发下去。这时候旧证书理论上还可以继续用但实践中我建议统一刷新避免新旧证书混用导致的权限排查困难。但如果是根 CA 本身过期了——年份足够久的老集群比较常见——那以上所有基于同一个 CA 的签发全部失效唯一出路是“重建 PKI”。这类情况团队内部通常的解决方案是重新生成整套 ca、apiserver、etcd 证书再用新 CA 签发所有 client kubeconfig节点上的 kubelet 也需要逐一更新。我一般建议读者直接备份 etcd然后重建证书纯手动续期只在 CA 有效的前提下才有意义。我翻车过一次的案例就是根 CA 过期后仍旧执行了 renew结果生成了一堆新证书但 kubelet 那边还在用旧根 CA节点全部 NotReady。所以根 CA 状态是整个续期脚本的“第一道岔路口”必须在脚本开头判断不能省。3.2 etcd 证书能否复用旧 CApeer 证书的信任边界etcd 证书和 apiserver 证书有个本质区别apiserver 的 client 是外部来连证书过期换新的就行但 etcd 的peer 证书参与节点间心跳和选主如果新旧证书的 CA 不同哪怕新 CA 再权威另一端也不认识。这就是为什么日志里会出现etcdserver: request timed out因为集群内部分裂了。我的处理原则是etcd 目录下的证书必须“原 CA 续签”也就是读旧etcd/ca.crt和etcd/ca.key去签发同名的新server.crt和peer.crt保证新旧证书根一致。具体操作时需要注意etcd/ca.key权限通常是0600用 root 执行才读得动脚本里如果没有sudo包一层会碰到permission denied。3.3 apiserver 证书的 SAN 列表续完连不上八成是 SAN 丢了apiserver 证书是集群的“门面”kubeconfig 里的server地址指向它kubelet、controller-manager、scheduler 也都连接它。所以这张证书必须包含所有访问入口的地址不只是kubernetes.default.svc。现实中被问得最多的问题是续期后 kubectl 从外部访问不通但节点上访问是好的本质就是证书 SAN 不全。节点上的 kubeconfig 用的是https://node-ip:6443SANS 里有节点 IP 所以通而外部 kubectl 用的是负载均衡域名SAN 没写这个域名证书校验直接失败。续期脚本里SAN 列表一定要显式列出openssl req -new -key apiserver.key -out apiserver.csr -subj /CNkube-apiserver -config (cat EOF [req] distinguished_name dn req_extensions v3_req [dn] [v3_req] subjectAltName alt_names [alt_names] DNS.1 kubernetes DNS.2 kubernetes.default DNS.3 kubernetes.default.svc DNS.4 kubernetes.default.svc.cluster.local IP.1 10.96.0.1 IP.2 apiserver节点内网IP DNS.5 负载均衡域名 EOF )这段就是典型的 “续完连不上” 的后悔药。如果当初初始化集群时用了额外的 SAN续期时需要逐一补齐遗漏任何一个访问入口都会造成连接超时或证书错误。更省事的方式是续期前先把旧证书的 SAN 导出来脚本里动态追加不要手写死。4. 写一个真正“一键”的续期脚本从备份到分发含完整参数与避坑说明4.1 备份先行续期前 5 分钟是全流程唯一后悔药任何证书操作都有风险我见过太多人跳过硬备份续完证书发现 apiserver 起不来最后只能凭记忆去网上找恢复教程。所以第一原则是不改则已改则先备份。备份范围包含/etc/kubernetes/整个目录、etcd 的数据目录快照以及/var/lib/kubelet/pki/。不要只cp到本地临时目录本地存储挂了就全没了。我习惯在同目录下压成一个带时间戳的 tar.gz同时再异地拷贝一份双保险。加上后面的 rollback 逻辑基本能应对绝大多数续期事故。4.2 主体脚本kubeadm certs renew 与手动 openssl 二选一的落点脚本编写前期先判断系统能不能直接用kubeadm certs renew。支持的话我倾向优先用它因为这个命令会连带更新/etc/kubernetes/*.conf省去手动拼 kubeconfig 的麻烦。核心代码块如下#!/bin/bash set -euo pipefail KUBE_DIR/etc/kubernetes PKI_DIR${KUBE_DIR}/pki # 判断根 CA 是否已过期 CA_EXPIRE$(openssl x509 -in ${PKI_DIR}/ca.crt -noout -enddate | cut -d -f2) CA_EXPIRE_EPOCH$(date -d ${CA_EXPIRE} %s) NOW_EPOCH$(date %s) if [ ${NOW_EPOCH} -gt ${CA_EXPIRE_EPOCH} ]; then echo 根 CA 已过期, 不能直接 renew, 需要重建 PKI exit 1 fi # 方案 A : 支持 kubeadm renew 的分支 TIMESTAMP$(date %Y%m%d%H%M%S) cd ${KUBE_DIR} tar czf ./backup-k8s-${TIMESTAMP}.tar.gz pki *.conf kubeadm certs renew all --config /etc/kubernetes/kubeadmcfg.yaml kubeadm init phase kubeconfig all --config /etc/kubernetes/kubeadmcfg.yaml # 覆盖 root 用户的 kubeconfig cp ${KUBE_DIR}/admin.conf ${HOME}/.kube/config echo 证书续期完成, 准备重启相关静态 Pod 使新证书生效这段脚本有两个被我验证过多次的注意点。第一kubeadm certs renew all虽然能生成新证书但不会自动重启 apiserver、controller-manager、scheduler 这几个静态 Pod你需要手动重启或等待 kubelet 检测到文件变化后滚动重启。而这个“检测”逻辑有时并不灵最保险的是docker或containerd层面把静态 Pod 的容器删掉让它被 kubelet 重新拉起。第二命令里的kubeadmcfg.yaml必须存在它保存着集群初始化时的配置文件包括apiServer.certSANs和etcd的通信端点。如果当初初始化时没保存renew会丢失 SAN 信息所以我建议团队在集群交付第一天就把这个文件归档进/etc/kubernetes/。再补一个常见的坑脚本里的set -e会让kubeadm certs renew all碰到某张证书签发失败就整体退出看似安全实则麻烦。比如某张证书的外部签名者不可用时后面的 kubeconfig 更新也被拦住了。这种情况下我的处理是单独对kubeadm certs renew做一次|| true然后通过后续的证书检查步骤来兜底而不是让脚本死在中间。4.3 不支持 kubeadm renew 的分支openssl 硬签与 kubeconfig 手工缝合部分严格安全要求的环境下kubeadm命令可能被安全策略屏蔽或者用外部 CA 托管导致 renew 失败。这种情况下只能退回纯 openssl。这一分支的手工流程是把旧证书作为 template从旧证书读取 SAN用同一个 CA key 重新签发。这里面最容易踩的坑是模板的copy_extensions选项不生效openssl x509 -req -copy_extensions copy在新版本 OpenSSL 里对subjectAltName的复制是可靠的但在老版本上经常被静默忽略。所以我不依赖复制而是先提取旧 SAN再显式拼到新 CSR 里。# 提取旧证书 SAN 并生成新证书 SAN_TEXT$(openssl x509 -in ${PKI_DIR}/apiserver.crt -noout -text | grep -A1 Subject Alternative Name | tail -1 | sed s/DNS:/DNS:/g; s/IP Address:/IP:/g) openssl x509 -req -in apiserver.csr -CA ${PKI_DIR}/ca.crt -CAkey ${PKI_DIR}/ca.key -CAcreateserial -out apiserver.crt -days 3650 -extfile (printf subjectAltName%s ${SAN_TEXT})这个命令的关键在于SAN_TEXT变量不能为空如果旧证书里压根没有 SAN 扩展那grep -A1会取出空行生成的证书就是无 SAN 的。所以脚本里还得加一道判断if [ -z ${SAN_TEXT} ]; then echo 旧证书无 SAN, 请手动补充; exit 1; fi。手工签发了 apiserver 证书后/etc/kubernetes/*.conf还是旧客户端证书。此时需要重新生成 kubeconfig常见做法是执行kubeadm init phase kubeconfig admin --apiserver-advertise-address ip再手动替换。如果没有kubeadm可用需要自己用openssl生成 client 证书并 base64 编码填充到 kubeconfig 的client-certificate-data字段。这一步比较繁琐我的经验是尽量保住kubeadm这个基础工具它确实比手工拼要省事得多。4.4 分发控制面续完不算事kubelet.conf 没更新一样白搭证书续完之后不能就在 master 上验证一下就收工。集群里每个 worker 节点都有两个核心文件/var/lib/kubelet/pki/kubelet-client-current.pem和/etc/kubernetes/kubelet.conf。如果 CA 证书本身没变kubelet 侧的证书通常可以自然轮换但 kubelet.conf 里的certificate-authority-data是旧的如果根 CA 已变那必须分发新 CA 到每个节点。这里我给个安全分发顺序先把新 CA 推到所有节点的/etc/kubernetes/pki/ca.crt不动 kubelet 的 client 证书然后重启 kubelet 让它基于新 CA 重新发起 CSR自动获得新签证书。如果集群关闭了 CSR 自动批准那还得手动 approve 所有 kubelet 的证书签名请求这可是一堆Pending状态很容易被遗漏。有个能很快验证分发是否成功的方法不必逐个节点跑 kubectlkubectl get csr --sort-by.metadata.creationTimestamp | tail -20如果看到大量Pending的 csr多半是新 CA 已分发但自动批准没开。此时手动批kubectl certificate approve csr-name5. 续期后的验证与 4 个高频踩坑复盘从“续完就好”到“续完即崩”5.1 验证清单三条命令判断集群是否“真的好了”续期完成后不能只看 apiserver 进程在跑就说成功。我的固定验证顺序是先验证 apiserver 证书有效性再验证 kubeconfig 能否通过认证最后验证节点状态。# 本地直接校验 apiserver 证书 openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates # 用全新 admin.conf 访问集群 kubectl --kubeconfig /etc/kubernetes/admin.conf get nodes # 检查所有节点 Ready 状态 kubectl get nodes -o wide如果kubectl get nodes能正常输出但节点状态全是NotReady那问题大概率出在 kubelet 的 client 证书或它与 apiserver 的通信上。此时需要登到 worker 节点看 kubelet 日志寻找x509、certificate关键字快速定位。5.2 避坑一静态 Pod 不重启旧证书还在内存里跑这是续期后最典型的“假成功”。证书文件已经换成新的了但 apiserver 进程没变。因为静态 Pod 由 kubelet 监管kubelet 通过文件 hash 判断是否需要重建容器但对证书这类被进程读取且缓存的资源它不一定感知得到。现象就是 kubectl 仍能用旧证书访问、但 apiserver 日志里持续刷 tls 错误或者直接看不到新证书内容。原因就是静态 Pod 没有自动滚动更新。解决方式有两个最简单的删容器让它重建crictl rm $(crictl ps | grep kube-apiserver | awk {print $1})另一个更优雅但也更慢的方式是手动编辑/etc/kubernetes/manifests/kube-apiserver.yaml触发 hash 变化比如添加一个无害的 annotation 或者变更--tls-cert-file的值再改回来。等待 kubelet 感知并重建容器。注意不要直接 kill 容器进程kubelet 会拉起它但可能拉起时读到的是旧文件缓存。5.3 避坑二普通用户 kubeconfig 没更新集群像坏了一样很多时候 master 上一切正常但开发反馈连不上集群。原因在于普通用户使用的$HOME/.kube/config是从 admin.conf 拷贝的旧版本里面的 client-certificate-data 已经过期。续期脚本如果只更新了/etc/kubernetes/admin.conf没有同步拷贝到用户目录那用户的 kubectl 必然继续报证书过期。解决方法是脚本末尾统一覆盖install -o $(id -un) -g $(id -gn) -m 0600 /etc/kubernetes/admin.conf ~/.kube/config这里我用install而不是cp是为了在覆盖时强制重置属主和权限避免原文件被 root 占用后普通用户无法读取的情况。公司内部团队多人协作时最好维护一个统一的 kubeconfig 分发通道而不是各拷各的否则版本漂移问题会反复出现。5.4 避坑三etcd 证书更新后 peer 之间互不信任etcd 证书续期比 apiserver 更敏感。现象是续期后 etcd 集群反复选举etcd容器日志里大量出现failed to connect to https://...:2380。原因是 peer 证书更新后 CA 不一致或节点间证书地址不匹配互相认为对方是非法成员。此时不要慌乱地逐节点重启 etcd——顺序不对会加速脑裂。我的处理方式是先把所有节点的 etcd 容器停止确认无主后检查新旧证书的 CA 是否一致再确定重启顺序。但更省心的方案是从头就不去碰 etcd peer 证书只续 server 证书。我们团队经历过一次事故后加了个硬性规定etcd peer 证书有效期必须与根 CA 保持一致续期时 peer 证书只做轮换不做重签。5.5 避坑四证书续期与 kubelet 自动轮换的“竞争条件”集群运行中 kubelet 会自动轮换自身 client 证书这个机制是好的但续期脚本如果用得不是时候会与它产生冲突同时有两套证书在竞争。现象是同一个节点的 CSR 反复提交、被批准但 kubelet 状态一直在Ready与NotReady之间抖动。原因多半是 kubelet 还在用旧证书发起请求而 apiserver 端已经换了新 CA导致 CSR 多次失败重试。解决方式是在续期前先修改 kubelet 配置强制轮换kubectl drain node --ignore-daemonsets systemctl restart kubelet kubectl uncordon node顺序不能反先排空再重启确保 kubelet 的请求在新 CA 环境下成功。用kubectl certificate approve批量批准后再执行续期脚本而不是同一时间点一起操作。6. 把手动续期固化成团队脚本自动检测、批量分发与失败回滚手动跑通一次后我把它固化成了团队内部的巡检脚本实现真正的“一键”。目标很简单每周定时检查证书剩余天数剩余小于 30 天时自动续期续期完成后把新 kubeconfig 分发到指定用户目录。这里给出核心的检测函数check_cert_days() { cert_path$1 end_date$(openssl x509 -in ${cert_path} -noout -enddate | cut -d -f2) end_epoch$(date -d ${end_date} %s) now_epoch$(date %s) remain_days$(( (end_epoch - now_epoch) / 86400 )) echo ${remain_days} } for cert in $(find /etc/kubernetes/pki -name *.crt); do days$(check_cert_days ${cert}) if [ ${days} -lt 30 ]; then echo 证书 ${cert} 剩余 ${days} 天, 触发续期 fi done对于剩余天数阈值我建议不要等 0 天再操作30 天是一个比较舒服的提前量。如果剩余不足 7 天留给处理意外的时间太短需要人工介入。脚本设计上阈值可以做成参数但团队实际运行中我是写死 30 天因为太频繁续期也会让证书文件频繁变动凭空增加 K8s 组件的重启次数反而产生不必要的抖动。回滚动作单独封装成一个函数原理是备份 tar 包恢复rollback() { cd /etc/kubernetes tar xzf backup-k8s-*.tar.gz --overwrite systemctl restart kubelet }回滚我不自动触发因为风险高需要人判断。但脚本会打印出回滚命令由值班同学决定是否执行。这算是我个人习惯上的一个安全冗余。整个方案实际运行半年后集群的证书问题基本做到了“先于用户发现而处理”再没有出现过半夜被叫醒去处理证书过期的事故。就我自己的经验来说把证书续期这类低频、高危、步骤明确的操作脚本化是最值得投入的运维自动化方向——因为它不常发生所以容易忘但又因为一旦发生就是 P0 事故所以必须有可靠的流程兜底。如果你也在维护 kubeadm 集群建议按上面这套思路把流程跑一遍提前把备份和 SAN 提取验证好别等到证书真过期了再慌乱操作。希望帮到你。本文还有配套的精品资源点击获取