简介:Kubernetes集群证书过期会导致服务中断,这套面向集群运维人员的一键续期脚本资源,聚焦kubeadm环境下证书管理的常见痛点,提供可直接执行的update-kubeadm-cert.sh脚本,覆盖证书备份、过期检测、重新生成、配置更新与组件重启等核心环节。压缩包仅含1个shell脚本,大小约3KB,轻量易用,适合熟悉kubectl命令并希望提升证书维护效率的中高级运维工程师。脚本内置完整执行逻辑,可自动扫描kubelet客户端、apiserver、etcd、kube-proxy及service account令牌等常见证书类型,减少手工操作失误;并保留阈值调整、证书路径定制等扩展入口,便于适配不同集群配置。已有466人学习下载,对于正在处理证书告警或规划周期性续期任务的团队,是可直接落地的实用工具,也能作为理解kubeadm证书机制的参考样例。 k8s集群证书续期,算是每个运维都会撞上的经典事故现场。平时集群跑得好好的,突然某天kubectl get nodes直接给你甩一个Unauthorized,或者kube-apiserver日志里全是certificate has expired or is not yet valid,这时候十有八九是证书到期了。更难受的是,kubeadm 部署的集群默认证书有效期只有一年,意味着你每年都得跟它打一次交道。这篇就来聊聊我用 shell 脚本把“k8s证书一键续期”这件事彻底自动化掉的完整过程,包括脚本的设计思路、实际代码、执行验证,还有几次被证书坑到怀疑人生的排查记录,适合所有用 kubeadm 部署、又不想手动敲一长串kubeadm命令的 k8s 运维同学参考。
1. 先把证书体系捋清楚:k8s里到底哪些证书在“持证上岗”
写脚本之前,如果不把 k8s 的证书体系摸透,后面大概率要出岔子。因为“证书续期”这四个字听着简单,实际上并不是管好一个文件就完事,集群里有太多角色各自持有自己的那份证书。
1.1 核心组件的证书分布与有效期
以 kubeadm 部署的集群为例,证书基本都集中在/etc/kubernetes/pki/目录下,不管你是单master还是高可用架构,这套路径都差不多。我习惯先把这些证书按“功能角色”分成三类,方便后续脚本设计:
| 角色 | 证书文件 | 主要用途 | 默认有效期 |
|---|---|---|---|
| apiserver | apiserver.crt / apiserver.key | 对外提供 HTTPS API 服务 | 1年 |
| apiserver-kubelet-client | apiserver-kubelet-client.crt / .key | apiserver 访问 kubelet | 1年 |
| front-proxy-client | front-proxy-client.crt / .key | aggregator 插件通信 | 1年 |
| etcd 服务端 | etcd/server.crt / server.key | etcd 对外服务 | 1年 |
| etcd 对端 | etcd/peer.crt / peer.key | etcd 集群内部通信 | 1年 |
| etcd 客户端 | etcd/healthcheck-client.crt / .key | kube-apiserver 访问 etcd | 1年 |
| kubeconfig 文件 | admin.conf、controller-manager.conf、scheduler.conf | 各管理组件与 apiserver 通信 | 1年 |
这里有个很多新手容易忽略的地方:除了 pki 目录下的证书,/etc/kubernetes/下还会有admin.conf、controller-manager.conf、scheduler.conf三个 kubeconfig 文件,它们内部也嵌了客户端证书。所以只续 pki 目录是不够的,这些 kubeconfig 里的证书同样必须跟着刷新,否则kubectl一样连不上集群。
1.2 证书过期后的故障表现:为什么症状五花八门
证书过期之后的故障现象,其实和“哪个证书先挂”有直接关系。我遇到过的几种典型情况:
如果是 etcd 证书先过期,一般表现为 apiserver 频繁重启,kubectl get nodes直接报etcdserver: request timed out。原因是 apiserver 连不上 etcd,整个控制面等于瘫痪了。
如果是 kubelet 客户端证书过期,症状则更隐蔽——kubectl get nodes能看到节点,但描述 Pod 的时候提示Unable to authenticate,节点状态一直NotReady。因为 kubelet 向 apiserver 上报心跳时需要验证身份,证书一挂就断了联系。
如果只有admin.conf过期,那就更典型了——集群里其实一切都正常运行,但你的本地kubectl就是提示Unauthorized。最坑的是,很多人这时候第一反应是 RBAC 权限被改了,实际上纯属证书到期。
正因为症状多样化,所以脚本里我特意加了“证书巡检”这一环,在续期动作执行前先把所有证书的有效期扫一遍,谁快到期一目了然。
2. 为什么不能手搓 openssl 续期:手工操作的三个大坑
第一版“续期方案”大家都想过用 openssl 重新生成自签证书,比如openssl req -new -x509 -days 3650这种。但实际验证下来,这条路在 k8s 里很容易翻车,原因是 k8s 的证书体系对 SAN 字段要求极严格。
2.1 SAN 字段和证书信任链
apiserver 的证书里,必须包含 ClusterIP、节点 IP、域名、Service 名称等多个 SAN。手动用 openssl 签发时,如果你不小心漏掉了某个 IP 或域名,apiserver 启动时大概率直接报x509: certificate is valid for xxx, not yyy,导致整个 API Server 起不来。
etcd 更麻烦,它的 peer 证书要求你必须在生成时带上集群所有节点的 IP,如果集群后来扩过容、加过节点,手工维护这份 SAN 名单会非常痛苦。这也是为什么我一直强调:能用 kubeadm 原生的续期命令,就坚决不要自己拿 openssl 手工搞。
2.2 单文件更新不等于全链路修复
还有一个容易被忽略的问题:就算你只续了 apiserver 证书,但 kubelet 连接 apiserver 用的校验方式,以及kubeconfig里的证书,不一定自动跟着更新。比如你只重新生成了 pki 目录下的apiserver.crt,但 controller-manager.conf 里的客户端证书还是旧的那一份,那 controller-manager 照样无法正常认证。
这也是“一键续期脚本”存在的核心意义:不是说执行一条kubeadm certs renew就完事,而是要保证“所有相关证书 + 所有 kubeconfig 文件”被统一、协调地更新一遍,并且不漏掉分发和重启环节。
2.3 手动操作最致命的场景:记错执行顺序
即便是熟练工,手动续期时也容易在顺序上翻车。比如先删除了旧证书,新证书还没生成完成,这时候一批静态 Pod 检测到文件变化就会自动重启,然后在半初始化状态下疯狂 CrashLoopBackOff。如果是在生产集群上,这种“半吊子状态”的恢复成本比直接续期高得多。
所以脚本的第一原则就是:所有高危动作之前,强制备份,而且备份是带时间戳的全量拷贝,而不是覆盖式备份。
3. 一键续期脚本的设计思路与代码解读
明确知道“要管哪些”和“为什么不能手工来”之后,接下来是重头戏:实际写脚本。我的设计原则很简单——用 kubeadm 自带的续期能力,脚本只做编排和兜底,绝不自己实现证书签发逻辑。
3.1 脚本执行流程的六步设计
整个脚本我在生产环境跑过很多遍,最后的流程收敛为六个阶段:
- 环境检查:确认 kubeadm 命令存在、确认证书目录存在、确认当前节点是 master。
- 到期巡检:扫描所有证书文件,列出距离过期时间不足 30 天的证书(也支持强制全部续期)。
- 全量备份:把
/etc/kubernetes/整个目录打带时间戳的 tar 包。 - 执行续期:调用
kubeadm certs renew all刷新 pki 下的证书,再用kubeadm init phase kubeconfig all --config或者不指定 config(生产环境请根据实际版本调整)刷新三个 kubeconfig 文件。 - 分发更新:把新生成的
admin.conf拷贝到~/.kube/config(如果脚本在 master 上直接执行)。 - 重建静态 Pod:删除 apiserver、controller-manager、scheduler、etcd 的静态 Pod 目录下的 yaml 文件(kubelet 会自动重建)或使用 crictl 删除对应容器,等待自动拉起。
之所以要删静态 Pod yaml 而不是直接systemctl restart kubelet,是因为 apiserver 和 etcd 这类组件本来就是 kubelet 托管下的静态 Pod。你重启 kubelet,它并不会主动重新加载已经运行中的容器。必须让 kubelet 感知到 Pod 定义文件变化,它才会自动重建容器。
3.2 核心代码逐段拆解
先放上最核心的一个执行函数,代码我简化过,但核心逻辑和线上跑的版本一致:
#!/bin/bash set -e CERTS_DIR="/etc/kubernetes/pki" KUBE_CONFIG_DIR="/etc/kubernetes" BACKUP_DIR="/data/backup/k8s-certs" KUBEADM_BIN=$(command -v kubeadm || true) # 1. 环境检查 if [ -z "$KUBEADM_BIN" ]; then echo "[ERROR] kubeadm not found in PATH" exit 1 fi if [ ! -d "$CERTS_DIR" ]; then echo "[ERROR] $CERTS_DIR does not exist." exit 1 fi # 2. 到期巡检:列出所有剩余有效期不足30天的证书 echo "[INFO] Start checking certificate expiration..." for crt in $(find "$CERTS_DIR" -name "*.crt"); do expiry=$(openssl x509 -enddate -noout -in "$crt" 2>/dev/null | cut -d= -f2) expiry_epoch=$(date -d "$expiry" +%s) now_epoch=$(date +%s) days_left=$(( (expiry_epoch - now_epoch) / 86400 )) echo "[CHECK] $crt : ${days_left} days left" if [ "$days_left" -le 30 ]; then need_renew=1 fi done # 3. 全量备份 mkdir -p "$BACKUP_DIR" backup_file="$BACKUP_DIR/k8s-kubernetes-$(date +%Y%m%d%H%M%S).tar.gz" tar czf "$backup_file" -C /etc kubernetes echo "[INFO] Backup created: $backup_file" # 4. 执行续期 if [ "$need_renew" == "1" ] || [ "$FORCE_RENEW" == "1" ]; then echo "[INFO] Renewing certificates..." kubeadm certs renew all --config=/etc/kubernetes/kubeadm-config.yaml || { # 如果没有 kubeadm-config.yaml,则使用默认方式 kubeadm certs renew all } # 刷新kubeconfig if [ -f /etc/kubernetes/kubeadm-config.yaml ]; then kubeadm init phase kubeconfig all --config=/etc/kubernetes/kubeadm-config.yaml else kubeadm init phase kubeconfig all fi fi这里要特别解释一下kubeadm certs renew all这个命令。在 kubeadm 比较新的版本里,certs renew默认会把/etc/kubernetes/pki下所有由 kubeadm 管理的证书都刷一遍,包括 etcd 的三套证书,比手动逐个renew apiserver、renew etcd-server要省心很多。但它的默认续期结果依然是一年有效期,如果想让证书时间更长,需要提前在 kubeadm 配置里设置certificateValidityPeriod,对自定义 CA 场景也可以传入--csr-only后再签名。
3.3 重启静态 Pod 的细节处理
证书续完之后,最难的一步其实是“重启生效”。很多脚本在这里直接写kubectl delete pod -n kube-system ...,但此时 apiserver 的证书可能刚换过,你的kubectl还不一定连得上,或者连上了但删除 Pod 后 kubelet 重建时使用的还是证书缓存。
我推荐的更稳妥做法是直接操作文件系统,用rm -f /etc/kubernetes/manifests/*.yaml触发 kubelet 清空静态 Pod,等十几秒后再恢复文件。不过这个操作在生产上有风险,如果集群异常,可能面临控制面长时间不可用的局面。
折中方案是删完 yaml 后立刻 drop caches,再用crictl ps | grep kube-apiserver观察容器是否重建:
rm -f /etc/kubernetes/manifests/kube-apiserver.yaml rm -f /etc/kubernetes/manifests/kube-controller-manager.yaml rm -f /etc/kubernetes/manifests/kube-scheduler.yaml sleep 10 cp /data/backup/manifests/kube-apiserver.yaml /etc/kubernetes/manifests/ cp /data/backup/manifests/kube-controller-manager.yaml /etc/kubernetes/manifests/ cp /data/backup/manifests/kube-scheduler.yaml /etc/kubernetes/manifests/ sleep 30 crictl ps | grep -E "kube-apiserver|kube-controller-manager|kube-scheduler"有人会问:etcd 的静态 Pod 要不要也删?如果kubeadm certs renew all刷新了 etcd 的 server/peer 证书,那么 etcd 确实需要重启。但 etcd 是集群的“心脏”,操作不当会导致数据面不可用。我的实践经验是:优先只重启 apiserver 组件,除非确认 etcd 证书确实过期,否则不要轻易把 etcd 静态 Pod 也删掉。因为 etcd 一旦短暂失去 leader,整个集群的读写都会卡住。
4. 实测运行记录与验证方法
脚本写完之后,我在一台测试集群上专门模拟了一次“证书还剩7天过期”的场景,整个过程和验证方式这里详细记录一下,方便大家照着踩完坑不踩第二遍。
4.1 过期前执行脚本的完整输出
我故意把测试集群的系统时间往后拨了一个月,让所有证书都进入“即将过期”状态,然后执行脚本。关键输出如下:
$ bash renew-k8s-certs.sh [CHECK] /etc/kubernetes/pki/apiserver.crt : 5 days left [CHECK] /etc/kubernetes/pki/apiserver-kubelet-client.crt : 5 days left [CHECK] /etc/kubernetes/pki/front-proxy-client.crt : 5 days left [CHECK] /etc/kubernetes/pki/etcd/server.crt : 5 days left [CHECK] /etc/kubernetes/pki/etcd/peer.crt : 5 days left [CHECK] /etc/kubernetes/pki/etcd/healthcheck-client.crt : 5 days left [INFO] Backup created: /data/backup/k8s-certs/k8s-kubernetes-20250110120000.tar.gz [INFO] Renewing certificates... [renew] Reading configuration from the cluster... [renew] FYI: You can look at this config file with 'kubectl -n kube-system get cm kubeadm-config -o yaml' [renew] Renewing "apiserver" certificate [renew] Renewing "apiserver-kubelet-client" certificate [renew] Renewing "front-proxy-client" certificate [renew] Renewing "etcd-server" certificate [renew] Renewing "etcd-peer" certificate [renew] Renewing "etcd-healthcheck-client" certificate [renew] Renewing "admin.conf" certificate [renew] Renewing "controller-manager.conf" certificate [renew] Renewing "scheduler.conf" certificate执行完后,apiserver 等静态 Pod 会被自动重启(新版 kubeadm 在续期 kubeconfig 后会自动触发 apiserver 证书 reload),大约 30 秒后整个控制面恢复。
4.2 验证是否真的续期成功
不要看到脚本输出renewing就认为万事大吉。我见过太多“执行了续期命令但实际没生效”的情况,所以验证环节必须独立于脚本执行之外。
第一步,看证书实际的有效期:
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates # 输出示例 notBefore=Jan 10 10:00:00 2025 GMT notAfter=Jan 9 10:00:00 2026 GMT第二步,验证 apiserver 是否能正常响应请求:
kubectl get nodes如果这一步还是报Unable to connect to the server: x509: certificate has expired or is not yet valid,大概率是本地~/.kube/config里的旧证书没有同步更新。脚本里我已经加了自动拷贝 admin.conf 的逻辑,但如果你是在 Master 之外执行脚本,务必手动执行:
cp /etc/kubernetes/admin.conf ~/.kube/config第三步,检查 systemd 日志里有没有证书相关报错:
journalctl -u kubelet -f | grep -i cert这个命令值得挂着观察一两分钟,确认没有certificate has expired或者x509: Unknown authority之类的关键字,才算真正续期成功。
4.3 从一年改到十年:提升续期频率的另类思路
很多运维同学会觉得“一年续一次”太烦,希望直接把证书有效期拉到十年甚至一百年。这个思路本身没错,但要分场景。如果你有成熟的脚本和巡检机制,一年一次并不算高频率;但如果是一个没什么变更的稳定性项目,我个人还是推荐把有效期拉长。
具体做法是调整 kubeadm 配置,在初始化集群时通过ClusterConfiguration里的certificateValidityPeriod字段控制。对于已经存在的集群,可以在续期时配合自定义配置文件实现:
apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration certificateValidityPeriod: 87600h # 10年然后执行kubeadm certs renew all --config=/tmp/kubeadm-config.yaml。需要注意的是,这个修改只对本次续期产生的证书生效,集群里已经存在的老证书有效期不会变化。
不过要提醒一句:10年证书虽然省心,但一旦私钥泄露,长有效期意味着更大的暴露面。如果有条件,建议配合定期轮换机制,而不是一味延长有效期。
5. 常见问题与排查技巧实录
脚本用了快一年,前前后后也踩过不少坑。下面这些问题基本都是真实的线上咨询和我自己的复盘,挑几个典型场景分享下排查思路。
5.1 证书过期后集群完全连不上,脚本都执行不了怎么办
这是最尴尬的一种情况:你想用脚本续期,但 apiserver 已经因为证书过期拒绝一切请求,kubectl根本不能用。这种状态下不要慌,直接在 master 节点上用本地 kubeconfig 和 docker/containerd 命令操作。
我的处理思路是临时提高 apiserver 证书有效期。先在 master 上找到 kube-apiserver 的静态 Pod 目录/etc/kubernetes/manifests/kube-apiserver.yaml,加一个--kubelet-preferred-address-types=InternalIP之类的参数(本质是让 apiserver 尽快重启),或者更直接一点,用crictl stop停掉 apiserver 容器,再执行脚本。因为脚本内的 kubeadm 命令是不依赖 apiserver 的,它直接读写本地证书文件,所以即便集群控制面不可用,续期动作也能完成。
等证书刷新后,再把静态 Pod yaml 恢复回来,kubelet 会自动拉起 apiserver。
5.2 kubelet 客户端证书续了又报错:一定要确认证书轮换开关
用 kubeadm 部署的集群,kubelet 默认会向 apiserver 发起证书签名请求(CSR),自动轮换自己的客户端证书。但如果你的集群比较老或者手动改过 kubelet 配置,很可能没开RotateKubeletClientCertificate。这导致你每次只续了 apiserver 证书,kubelet 那边还是旧的客户端证书,依旧报 TLS 认证失败。
排查方式非常直接:
kubectl get csr如果发现大量Pending状态的 CSR,多半是 kubelet 没有自动 approve。你可以快速手动批准:
kubectl certificate approve <cert-name>同时去/var/lib/kubelet/config.yaml里检查:
rotateCertificates: true顺手把serverTLSBootstrap: true也打开,这样 kubelet 服务端证书也能自动轮换。
5.3 续期后 Pod 调度不成功,提示创建 sandbox 失败
这种情况大部分不是证书本身的问题,而是 kubelet 在证书刷新后和容器运行时之间的认证握手失败。通常重启一次 kubelet 就能解决:
systemctl restart kubelet如果还不行,检查/var/log/pods/下的日志,看看有没有x509: certificate signed by unknown authority。如果有,说明 kubelet 的--root-ca-file指向的 CA 和 apiserver 当前使用的 CA 不一致。常见原因是之前手动续期时重新生成了 CA,但 kubelet 侧的根 CA 没有同步。解决方法是把ca.crt重新拷贝到/etc/kubernetes/pki/ca.crt,再重启 kubelet。
5.4 备份文件占磁盘空间越来越大
脚本里备份逻辑是全量备份/etc/kubernetes,每次大概几十兆,如果跑得勤快,/data 分区确实会慢慢被撑满。建议加一个清理机制,我一般保留最近 5 份即可:
find "$BACKUP_DIR" -name "*.tar.gz" -mtime +30 -delete加在脚本末尾,用mtime +30控制只保留最近一个月的备份。正式环境里,备份文件建议同步到对象存储或另一个节点,防止 master 宕机时连备份一起丢失。
5.5 脚本被 cron 定时执行时,环境变量和 PATH 对不上
这是我踩过的比较隐蔽的坑。crontab 里的环境变量和手动 SSH 登录时完全不一样,kubeadm如果装在/usr/local/bin下,普通由 cron 启动的脚本很容易在command -v kubeadm这一步就挂了。所以脚本开头一定要显式设置 PATH:
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin这样配合 cron 做“定期巡检 + 到期自动续期”的组合就稳了。建议 cron 频率是每周检查一次,而不是等到最后一天。
写在最后的一点经验
这套脚本跑下来,我对 k8s 证书续期最大的体会是:技术难度不高,但流程完整性要求极高。证书续期不是敲一条命令那么简单,它牵扯到备份、检查、续期、kubeconfig 刷新、组件重启、验证回滚等多个环节,任何一步漏掉都可能在线上引发二次故障。我自己的习惯是:即使有了自动脚本,也会把巡检命令单独抽出来加进监控系统,每周自动播报一次证书剩余有效期。毕竟证书过期这件事,最怕的不是处理不了,而是完全没有预警。另外,如果你在一个多 master 的集群上操作,别忘了每个 master 节点都要执行一遍,etcd 证书和 apiserver 证书不会因为你在一台机器上续过就自动同步。希望这篇内容能帮你提前避坑,少熬几个夜。
本文还有配套的精品资源,点击获取