
简介面向Kubernetes集群运维人员与系统管理员的一款Shell脚本工具主要解决k8s证书临近过期时手动更新流程繁琐、易出错的问题。脚本会将证书备份、过期检测、重新生成、配置更新、组件重启与通信验证等步骤自动串联一条命令即可完成续期操作大幅降低证书管理风险。它内置证书清单扫描涵盖kubelet客户端证书、apiserver证书、etcd证书、kube-proxy证书及service account令牌等多种类型适配常见k8s版本。资源包为zip格式仅含1个sh脚本文件包体大小仅3KB轻量易部署已有470人学习/下载。脚本基于kubeadm机制设计适用于生产或测试环境执行前需具备sudo权限并根据实际集群自定义证书路径或过期阈值借助它可快速完成集群内各类证书轮换避免因证书过期导致服务中断保障集群长期稳定运行。1. 证书过期不是玄学先救集群再想脚本某天凌晨两点我接到一套自建集群的告警节点批量 NotReadyPod 被驱逐kubectl 连上去直接报certificate has expired or is not yet valid。第一反应是节点负载有问题排查半天最后发现真相特别朴素——kubeadm 签发的证书默认一年有效期而这个集群已经跑了一年多。那晚唯一的收获就是意识到证书过期这件事远比想象中普遍而且一旦发生整个集群会像断电一样瞬间哑火。“k8s 证书一键续期脚本”解决的就是这件事把证书体检、备份、续期、重启组件、结果校验串成一条可重复执行的命令过期前主动跑一次几分钟收工而不是等集群瘫了再去抢救。适合所有用 kubeadm 自建集群的运维、交付同学尤其是节点不多、没有专门证书管理平台的中小团队。这篇会从“证书为什么会惹祸”讲起再给出我日常在用的脚本结构和参数最后把续期路上的血泪坑挨个点名。2. 自查过期时间一条命令看懂全集群证书2.1 哪些证书在计时过期后哪些服务先遭殃一个 kubeadm 部署的控制平面节点证书零散分布在两个目录/etc/kubernetes/pki/下的 apiserver、apiserver-kubelet-client、front-proxy-client 等证书以及/etc/kubernetes/pki/etcd/下的 server、peer、healthcheck-client 证书。另外还有四份 kubeconfigadmin.conf、kubelet.conf、controller-manager.conf、scheduler.conf它们里面嵌着客户端证书同样有有效期。过期之后的表现各不相同但都很难受。我把它整理成下面这张表排障时直接对着看证书对象谁在消费它过期后最先看到的症状apiserver.crtkube-apiserver 的 HTTPS 端口kubectl 连不上报证书过期apiserver-kubelet-client.crtkube-apiserver 访问 kubeletkubelet 日志里出现证书校验失败front-proxy-client.crt聚合 API、metrics-server 等扩展 API 调用返回 401/403etcd server / peer 证书etcd 节点间通信与客户端通信apiserver 连不上 etcd集群写不进去kubelet.conf 里的 client 证书kubelet 上报节点状态node 变成 NotReady调度停摆admin.conf / controller-manager.conf管理端与控制器访问 apiserverkubectl 报错controller-manager 无法工作这张表不用背只要记住一个结论任何一份过期最迟到第二天一定会有一批组件罢工而且症状互相遮蔽。所以我后续所有操作都建立在“先把全量证书的剩余时间扫出来”这个前提下。2.2 用 kubeadm 一条命令体检输出怎么读如果是 kubeadm 部署的集群最省事的体检方式是直接执行kubeadm certs check-expiration输出是一张表格按证书文件分组列出过期时间和剩余时间最后还会标注“CA certificate still valid”之类的提示。看的时候我先只看两列剩余不足 90 天的要排期处理不足 30 天的按高危对待。注意kubeadm certs check-expiration只检查 kubeadm 自己管理的证书如果集群里塞了第三方组件签发的证书它管不到要单独用 openssl 扫。kubeadm certs check-expiration这个子命令从 kubeadm 1.15 左右开始提供不同小版本列出的证书集合略有差异但基本覆盖上述两个 pki 目录。如果输出里有“Certificate is invalid”或“已经过期”的条目先别急着续期把第 2.3 节的 openssl 扫描也跑一遍确认是不是 kubeadm 漏管的证书。2.3 不用 kubeadm 时用 openssl 手动扫一遍有些集群是用二进制或其他部署工具搭的kubeadm 命令存在但不好使或者 pki 目录结构不完全一致。这时候我有两条路一是直接改部署工具自己的证书管理命令二是回到最原始的方法用 openssl 把每个证书的 enddate 读出来。后者通用性最强只需要知道证书放哪了。CERT_DIRS/etc/kubernetes/pki /etc/kubernetes/pki/etcd for dir in $CERT_DIRS; do if [ -d $dir ]; then for cert in $dir/*.crt; do echo $cert openssl x509 -in $cert -noout -enddate 2/dev/null done fi done逻辑说明外层循环遍历两个证书目录内层循环匹配所有.crt文件用openssl x509 -noout -enddate只输出有效期截止时间不输出证书全部内容。2/dev/null把不相关报错丢掉避免坏文件干扰判断。参数说明CERT_DIRS可自行改为实际证书目录如果证书存到了其他自定义路径把路径加进这个变量就行。-noout表示不打印证书主体-enddate是读取有效期截止日期。这套循环在只读状态下运行对运行中的集群没有任何副作用适合先挂到 cron 里做日常巡检。扫出来的日期一般比kubeadm certs check-expiration更原始也更可靠。如果两种方式结果不一致以 openssl 读出来的时间为准因为后者是直接从文件读取的客观事实。3. kubeadm renew all 的边界能续什么续完还差什么3.1 renew all 能干的和不能干的一条条对齐常见的方案里最核心的一条命令是kubeadm certs renew all。它会把 kubeadm 管理范围内的所有证书重新签发一遍同时更新 kubeconfig 文件里嵌入的客户端证书。这一步确实能解决“证书文件本身过期”的问题但它有个致命盲区不会重启任何进程。kube-apiserver、etcd、kubelet 这些组件都是在启动时把证书读进内存的。你续期了磁盘上的证书文件正在运行的进程依然拿着旧证书工作有些组件甚至不会重新读取文件表现就是“续期跑完了集群还是报错”。所以脚本里必须补上重启动作而且要按顺序重启。另一个盲区是kubeadm certs renew all只认 kubeadm 自己维护的证书。用其他工具或完全手工签发出来的证书它既扫不到也不会动。因此脚本第一件事是“体检 判断体系”而不是上来就 renew。还有如果 CA 证书本身过期了renew all也没用——子证书可以重签但签发它们的那把根钥匙已经失效这种情况已经不是续期脚本的职责范围得走 CA 迁移或集群重建。3.2 续期先备份、再重启这个顺序为什么省事我在自己的方案里固定成三步备份、续期、重启。顺序不能反原因很现实。第一步备份相当于给集群留后悔药。证书和私钥是配对文件续期动作会把磁盘上的旧文件覆盖掉。万一续出来的证书有问题或者某个组件对新证书的兼容性不佳想要回到续期前的状态就得靠备份。备份不需要多复杂把/etc/kubernetes/pki整个目录打包走就行3MB 左右放哪都不占地方。第二步续期只改变文件内容不碰运行中的进程。这一步做完集群其实还是旧的但“可回滚窗口”已经打开。第三步重启让进程加载新证书。重启顺序我固定为先 etcd再 kube-apiserver再 controller-manager 和 scheduler最后 kubelet。因为 apiserver 要连 etcd如果 apiserver 先重启而 etcd 还没加载新证书它可能拿到旧证书去握手继续报错反过来先重启 etcd再让 apiserver 重新建连路径就顺了。很多人在这一步翻车是图省事直接systemctl restart kubelet完事觉得 kubelet 是管控一切的。其实控制平面的静态 Pod 由 kubelet 拉起但 kubelet 不会因为你重启了某个 Pod 的进程就去重新读证书它会继续按现有 manifest 文件里的命令参数拉起一个同样的容器。所以对控制平面组件正确操作是让 kubelet 感知到 manifest 文件变化强制重建静态 Pod具体写法在第 4 章。3.3 动手前先判断这套集群到底是不是 kubeadm 体系脚本里必须加一道前置校验。我踩过最冤的一次坑是在一套人工整理的目录结构非常像 kubeadm 的集群上直接跑kubeadm certs renew all。结果 kubeadm 确实执行了但没生成任何新文件因为它的答案文件不是那套集群用的。之后我学乖了先做三连查if [ ! -x $(command -v kubeadm) ]; then echo kubeadm 不存在疑似非 kubeadm 体系拒绝执行 exit 1 fi if [ ! -d /etc/kubernetes/pki ]; then echo 缺少 /etc/kubernetes/pki 目录拒绝执行 exit 1 fi if [ ! -f /var/lib/kubelet/kubeadm-flags.env ]; then echo 缺少 kubelet 的 kubeadm 标志文件大概率不是 kubeadm 部署 exit 1 fi逻辑说明三个条件同时满足才认为集群足够接近 kubeadm 体系。command -v kubeadm判断命令是否在 PATH 里/etc/kubernetes/pki是证书目录存在性检查/var/lib/kubelet/kubeadm-flags.env是 kubeadm 初始化节点时写入的标记文件用它比看版本号更可靠。参数说明这三个条件是我自己实践的默认值如果你的集群把 kubelet 数据目录改过第二、三条的路径要跟着换。这套预检在续期脚本里必须排在第一位不然一键脚本很容易在错误集群上硬跑制造新的烂摊子。4. 把一键续期脚本写出来体检、备份、续期、重启四件事4.1 脚本骨架参数、目录、预检避免一键按到坑里下面这份脚本草图是我在处理自建集群时常用的结构去掉了一些特定环境的硬编码保留了最核心的骨架。先看整体入口和参数处理#!/usr/bin/env bash set -euo pipefail CERTS_DIR/etc/kubernetes/pki MANIFEST_DIR/etc/kubernetes/manifests BACKUP_ROOT/etc/kubernetes/cert-backup EXPIRE_WARN_DAYS${EXPIRE_WARN_DAYS:-90} KUBEADM_CONF/etc/kubernetes/kubeadm-config.yaml DRY_RUN${DRY_RUN:-false} usage() { echo 用法: $0 [--check|--renew|--backup|--restart] echo --check 仅检查证书过期时间 echo --renew 备份续期重启校验 echo --backup 仅备份证书目录 echo --restart 仅重启控制平面与 kubelet exit 1 } main() { preflight case ${1:-} in --check) check_expiration ;; --renew) backup; renew_all; restart_control_plane; restart_kubelet; check_expiration ;; --backup) backup ;; --restart) restart_control_plane; restart_kubelet ;; *) usage ;; esac } main $逻辑说明set -euo pipefail让脚本在任何一步失败时立即退出避免带着坏状态继续往下跑对续期这种高危操作尤其重要。主流程把动作拆成独立函数--renew按“备份→续期→重启控制平面→重启 kubelet→复查”的顺序串联。--check和--backup是低风险只读/压缩操作可以随时单独执行。参数说明BACKUP_ROOT指定备份目录的根路径每次备份按时间戳建子目录EXPIRE_WARN_DAYS是判定“需要续期”的阈值天数我默认 90 天你可以按团队节奏改DRY_RUN置为 true 时只打印要执行的命令不实际执行新环境第一次跑建议先开着。4.2 备份与续期代码先给集群留后悔药先看备份函数这部分是整个脚本的安全网backup() { local ts ts$(date %Y%m%d%H%M%S) local backup_dir${BACKUP_ROOT}/cert-backup-${ts} mkdir -p ${backup_dir} cp -a ${CERTS_DIR} ${backup_dir}/pki if [ -f ${KUBEADM_CONF} ]; then cp -a ${KUBEADM_CONF} ${backup_dir}/kubeadm-config.yaml fi echo 证书备份完成: ${backup_dir} # 只保留最近 7 份备份避免磁盘被撑爆 ls -1dt ${BACKUP_ROOT}/cert-backup-* 2/dev/null | tail -n 8 | xargs -r rm -rf echo 清理超过 7 份的旧备份 }逻辑说明cp -a保留文件属性和权限证书目录里的私钥文件权限通常是 600普通 cp 可能把它改掉导致续期后组件读不了密钥。ls -1dt ... | tail -n 8 | xargs -r rm -rf是保留最近 7 份备份的常规写法tail -n 8跳过前 7 行剩下的都是旧备份直接删掉。参数说明如果你想保留更多历史备份把8改成N1即可。备份目录建议放在独立磁盘分区或至少不在同一条 IO 瓶颈路径上防止备份本身拖垮节点。接着是续期和对续期环境的校验renew_all() { if [ $DRY_RUN true ]; then echo [DRY_RUN] kubeadm certs renew all return 0 fi if ! kubeadm certs renew all; then echo kubeadm renew all 执行失败尝试从备份恢复 local latest latest$(ls -1dt ${BACKUP_ROOT}/cert-backup-* | head -n 1) if [ -n $latest ]; then rm -rf ${CERTS_DIR} cp -a ${latest}/pki ${CERTS_DIR} echo 已从 ${latest} 恢复 pki 目录 fi exit 1 fi }逻辑说明kubeadm certs renew all失败时立刻回滚避免出现“旧证书被覆盖、新证书又没生成”的半坏状态。回滚用的是最近一次备份。这个回滚逻辑同样受set -e保护任何一步失败都会让脚本退出如果回滚也失败说明备份本身有问题需要人工介入。参数说明回滚里rm -rf ${CERTS_DIR}之后再用备份目录cp -a恢复是为了避免残留新旧文件混在一起。如果你担心rm -rf的风险可以改成先把当前目录改名再拷贝回去效果一样但更稳。4.3 重启控制平面通过挪动 manifest 让 kubelet 重建静态 Pod控制平面的 kube-apiserver、controller-manager、scheduler 以及单机上的 etcd都是以静态 Pod 形式跑在 kubelet 下的它们的声明文件都在/etc/kubernetes/manifests。让 kubelet 重建这些 Pod 的最通用办法是短暂挪走 manifest 文件再挪回来kubelet 检测到文件变化后会停掉旧 Pod再用新文件拉起新 Pod。整个过程的代码可以这么写restart_control_plane() { local delay3 for f in ${MANIFEST_DIR}/*.yaml; do [ -f $f ] || continue echo 重建静态 Pod: $(basename $f) if [ $DRY_RUN true ]; then echo [DRY_RUN] mv $f ${f}.tmp mv ${f}.tmp $f continue fi mv $f ${f}.tmp sleep $delay mv ${f}.tmp $f done echo 控制平面 manifest 全部触发重建等待 kubelet 拉起新 Pod sleep 10 }逻辑说明sleep $delay给 kubelet 一个发现文件消失、停止旧容器的时间窗口。如果节点负载高delay可以调大比如 5 秒。所有 manifest 依次挪动不会出现多个控制平面组件同时失去声明文件的情况相比“全部 mv 出去再一起 mv 回来”的做法更稳。参数说明delay是单个文件被挪开后的等待秒数。为什么不是一起挪走一起挪回因为 controller-manager 和 scheduler 短暂下线可接受但 apiserver 和 etcd 同时消失会让整个控制面瞬间失联。逐个重建可以让每个组件尽量保持可用虽然仍然有秒级抖动但对业务影响小得多。4.4 重启 kubelet 与 kube-proxy并接好一键入口控制平面重建之后还有一个重要角色没动kubelet 自己。kubelet 有独立的配置文件/var/lib/kubelet/kubeconfig续期命令会更新其中的客户端证书但 kubelet 进程不知道必须重启一次。同时 kube-proxy 是 DaemonSet它承载的 Pod 也需要滚动重建以加载新证书。restart_kubelet() { echo 重启 kubelet if [ $DRY_RUN true ]; then echo [DRY_RUN] systemctl restart kubelet return 0 fi systemctl restart kubelet sleep 5 } restart_kube_proxy() { echo 滚动重启 kube-proxy DaemonSet kubectl -n kube-system rollout restart daemonset/kube-proxy 2/dev/null || \ echo 未发现 kube-proxy DaemonSet跳过 }逻辑说明systemctl restart kubelet是被 systemd 管理节点上的常规做法如果你的 kubelet 没走 systemd改用service kubelet restart。kube-proxy 部分用rollout restart让 DaemonSet 按滚动方式重建 Pod不会一次性把所有节点上的 kube-proxy 全部干掉。参数说明rollout restart daemonset/kube-proxy后面加的||容错是针对集群未安装 kube-proxy 或已切换到其他数据面组件的场景避免一键脚本在这类环境里直接跑挂。如果你也希望脚本兼容非标准数据面这段容错可以保留。把几个函数串起来之后--renew就是真正的一键操作执行顺序固定为备份 → 续期 → 重建控制平面静态 Pod → 重启 kubelet → 滚动重启 kube-proxy → 输出校验。加上预检和体检整个脚本在 kubeadm 体系的集群上基本能覆盖所有主流程。5. 续期避坑实录五个翻车现场与处理顺序5.1 坑 1续期后 kubectl 照样报证书过期现象脚本跑完没有任何报错kubeadm certs check-expiration显示剩余时间变成 365 天但 kubectl 依然返回certificate has expired or is not yet valid。原因磁盘上的证书确实续期成功了可 kube-apiserver 进程还在用旧证书。控制平面组件在启动时把证书读进内存后续不会再看磁盘文件所以必须重建相关静态 Pod。很多脚本漏掉重启动作就会看到这个诡异现象。解决按照第 4.3 节挪动 manifest 文件的方式重建控制平面。如果只重启 kubelet 还不够再检查 apiserver 容器是否真的被 kubelet 重新拉起——用crictl ps | grep kube-apiserver看容器启动时间确认是最近几分钟拉起的。5.2 坑 2etcd 证书过期apiserver 跟着起不来现象集群整体写不进去数据apiserver 日志持续输出failed to connect to etcd: x509: certificate has expired或context deadline exceeded。此时 etcd 容器看着在跑但 apiserver 就是连不上。原因apiserver 访问 etcd 时用的 client 证书存于/etc/kubernetes/pki/etcd和 apiserver 自己的证书是两套。kubeadm certs renew all会重签这一套但如果 etcd 静态 Pod 没重建etcd 进程依然持旧证书回应握手apiserver 校验不过去。解决必须按序处理。先续期再触发 etcd manifest 重建最后重建 apiserver。顺序错了比如先重启 apiserver 而 etcd 还没加载新证书apiserver 依然连不上。所以脚本里“先 etcd 后 apiserver”的顺序不能随意调整。5.3 坑 3node 一直是 NotReadykubelet 报 Unauthorized现象控制平面看起来全好了但kubectl get node里有一批节点 NotReadykubelet 日志刷Unauthorized节点上的 Pod 被陆续驱逐。原因kubelet 有自己独立的客户端证书存在/var/lib/kubelet/pki或 kubeconfig 里。它向 apiserver 上报状态时带着这份证书如果集群没有开启 kubelet 证书自动轮换--rotate-certificatestrue证书到期后 kubelet 上报全部被拒。解决续期命令会处理 kubelet 的证书文件但你必须重启 kubelet 让它重新加载重启后节点会重新上线。如果重启后仍报 Unauthorized检查 apiserver 侧有没有启用--kubelet-certificate-authority以及 node 对象的 UID 是否与证书里的 UID 一致不一致时需要删除节点对象让 kubelet 重新注册。5.4 坑 4多节点控制平面只续了一个VIP 一切就断现象高可用集群在两个控制平面节点上搭的脚本只跑在其中一台。续期后第一台正常但负载均衡器把流量切到第二台时kubectl 立刻报x509: certificate is valid for xxx, not for yyy或直接握手失败。原因每个控制平面节点的 apiserver 证书是节点启动时各自生成的SAN 列表可能只包含本节点 IP 和集群 VIP。只续一个节点另一个节点上的证书还是旧文件加上负载均衡会把请求分布到多个节点任何一个节点证书出问题都会导致一部分请求失败。解决所有控制平面节点都要跑同一个脚本至少保证每个节点的证书都续期并重启。注意 apiserver 证书的 SAN 里必须包含 VIP 地址或负载均衡地址否则即使续期成功正常访问也过不了域名校验这是脚本之外更需要关心的问题。5.5 坑 5非 kubeadm 集群被硬续越续越乱现象脚本在非 kubeadm 体系的集群上执行预检没做或者跳过了。kubeadm certs renew all跑完没有任何效果甚至把某些自建目录结构误当 pki 目录覆盖了不相关的文件集群状态变得更糟。原因kubeadm certs renew all严格依赖 kubeadm 初始化时写入的配置和目录布局。其他部署工具或手工二进制部署的集群虽然也常见/etc/kubernetes但证书签发体系和 kubeadm 不一定兼容。解决脚本的 preflight 必须保留第 3.3 节三个条件缺一不可。如果预检不通过直接退出并提示用户去使用对应部署工具的续期方案比如二进制部署的集群就按手工重签流程操作不要硬套 kubeadm。6. 续完怎么确认没白跑验证顺序与日常留后手6.1 三轮验证把续期结果钉在明面上续期脚本跑完不要只看“没有任何报错”就收工。我做三轮验证顺序固定第一轮看文件时间。执行openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -enddate确认到期时间已经变成一年后同时看/etc/kubernetes/pki/etcd/server.crt也同步更新。第二轮看集群状态。kubectl get nodes所有节点必须 Readykubectl get componentstatus不一定准确更靠谱的是kubectl get pods -n kube-system看 apiserver、etcd、controller-manager、scheduler 四个 pod 都处于 Running 且重启次数没有持续上涨。第三轮看日志尾巴。用journalctl -u kubelet --since 5 minutes ago | grep -i x509和crictl logs找 apiserver 最近的错误日志没有x509、certificate expired、Unauthorized关键字才算过关。三轮都过了我才会把这次的续期时间记下来。6.2 把检查做成例行任务别等下一年再想起它我现在习惯不依赖“记住每年要续期”这件事而是把检查交给定时任务。最简单的做法是把脚本的--check模式加到 cron 里每天凌晨跑一次只做只读体检把输出追加到日志文件。当剩余时间低于阈值时日志里会出现明确的 “EXPIRING” 标记监控系统抓到这个关键词再告警。无论何时看到告警再登录节点执行一次--renew整套流程就是可预期的了。另一个一直保留的习惯是续期完成后我会顺手把备份目录里最新的那份残留下来不急着清理。备份策略里保留 7 份但对刚续完的这次我会让它多躺一个月再被轮掉。一来给回滚留更长窗口二来一旦一个月后发现某些跨组件调用有隐藏的证书校验问题还能回去对比旧证书。这个习惯帮我免掉不止一次“续期两周后才发现某监控组件握手失败”的尴尬。证书续期这事看着是运维里的小活但每年不声不响咬你一口时代价往往就是整个集群的可用性。一套能预检、能备份、能重启、能校验的一键脚本就是给自己留的后悔药。希望帮到你。本文还有配套的精品资源点击获取