尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

K8s证书一键续期脚本:从TLS原理到集群实践全解析

K8s证书一键续期脚本:从TLS原理到集群实践全解析 简介面向Kubernetes集群运维人员一份专治证书过期问题的一键续期脚本。集群证书接近有效期时手动更新步骤繁琐且易出错该工具将备份、过期检测、证书重新生成、配置文件更新、组件重启及结果验证整合为自动化流程适配kubeadm及cfssl等常见环境减少服务中断风险。压缩包为zip格式整体仅3KB内含1个sh脚本文件轻量易部署可直接上传集群节点执行。脚本默认包含安全防护逻辑操作前自动备份原证书支持定制过期阈值与证书路径兼顾灵活性与安全性。目前已有466人学习下载适合负责k8s集群日常维护、希望提升证书管理效率的运维工程师参考使用。通过该脚本可快速掌握证书续期的标准处理方式有效规避因证书过期导致的集群不可用问题。 凌晨两点被监控告警叫醒kubectl get nodes卡住不动kube-apiserver日志刷出一片TLS handshake error打开/etc/kubernetes/pki/apiserver.crt一看刚好过期——这种场景凡是自己用 kubeadm 搭过集群的人迟早都会遇到一次。k8s 的控制面组件之间全部走 TLS 加密通信证书一旦过期Apiserver、kubelet、etcd 之间互相不信任整个集群就像一群拿着过期身份证的人谁都不认谁。网上关于证书续期的教程很多但要么只给单条命令要么停留在原理层面真正能拿来直接跑、还考虑容错和回滚方案的脚本少之又少。这篇文章我就把 k8s 证书一键续期脚本的完整设计逻辑、实现细节和实测过程拆开讲清楚给正在维护 kubeadm 集群、或者刚接手一个服役一年以上集群的朋友做个参考。1. 证书过期之前集群到底会先出现哪些奇怪症状很多朋友对证书过期的理解是某一天所有命令全部报错集群彻底不可用。实际上生产环境里的表现往往更狡猾是一步步病下去的。最典型的是 kubelet 和 Apiserver 之间的通信故障。kubelet 默认使用/var/lib/kubelet/pki/kubelet-client-current.pem连接 Apiserver证书过期前会有段时间频繁断连又重连节点状态在NotReady和Ready之间反复横跳。你去看 kubelet 日志会翻到大量的Unauthorized、x509: certificate has expired or is not yet valid但节点并没有宕机系统负载也正常很容易误判成网络抖动或者负载过高。另一种常见表现是 Apiserver 自身的客户端证书过期。此时kubectl命令直接失效错误信息一般是Unable to connect to the server: x509: certificate has expired or is not yet valid这类错误网上搜到的解法大多是重新拷一份 kubeconfig但如果在 Apiserver 证书已经过期的情况下仅仅拷贝 kubeconfig 是没用的因为服务端根本无法完成 TLS 握手。我见过有同事在这条路上折腾了两个小时最后才发现是/etc/kubernetes/pki/apiserver.crt的有效期问题。还有一种容易被忽略的情况etcd 证书过期。etcd 与 Apiserver 之间使用 2379 端口做 mTLS 双向认证证书失效后 Apiserver 连不上 etcd表现是kubectl get能返回部分数据但kubectl apply报etcdserver: request timed out集群的写入能力基本瘫痪。如果你只重启 Apiserver反而会让情况更糟因为重启后 Apiserver 彻底无法从 etcd 恢复数据整个控制面起不来。为什么 kubeadm 部署的集群这么容易踩中这个坑原因在于 kubeadm 默认签发的证书有效期相对有限控制面组件的证书大多只有一年kubelet 的客户端证书在启用自动轮换之前也是类似。很多集群搭好之后运行稳定大家忙着部署应用根本没人记得一年后要续期。等到第二年的某天深夜监控突然大面积告警才开始手忙脚乱地查文档。所以我在这篇博文里定了一个基调证书续期不能靠记忆必须脚本化、流程化最好连备份、续期、重启、验证一次性完成顺手还能在 crontab 里挂一个提醒任务。2. 动手写脚本前先摸清 kubeadm 集群里到底有哪几类证书一个典型的 kubeadm 部署集群证书分布在/etc/kubernetes/pki目录以及各组件的工作目录下不同证书服务的对象完全不同续期脚本必须按类别处理不能一把梭。证书/文件存放位置主要用途过期影响ca.crt/ca.key/etc/kubernetes/pki集群根 CA签发其他证书根 CA 过期意味所有子证书失效影响面最大apiserver.crt/apiserver.key/etc/kubernetes/pkikube-apiserver 对外提供 HTTPS 服务的服务端证书kubectl 无法访问 Apiserverapiserver-kubelet-client.crt/etc/kubernetes/pkiApiserver 访问 kubelet 时使用的客户端证书Apiserver 无法收集节点状态、执行 exec/logsetcd/server.crt,etcd/peer.crt/etc/kubernetes/pki/etcdetcd 的服务端与节点间通信证书etcd 集群内通信异常Apiserver 读写超时front-proxy-ca.crt等/etc/kubernetes/pkiaggregation layer 扩展 API 通信自定义 API 扩展不可用admin.conf/etc/kuberneteskubectl 管理员的 kubeconfigkubectl 无法认证kubelet.conf/etc/kuberneteskubelet 连接 Apiserver 使用的 kubeconfigkubelet 注册失败节点 NotReadykubelet-client-current.pem/var/lib/kubelet/pkikubelet 与 Apiserver 双向 TLS 客户端证书节点与 Apiserver 连接失败先说根 CA。很多人有个误区kubeadm 签发的 CA 证书有效期是 10 年所以只要 CA 不出问题子证书过期无所谓。这个说法对了一半。CA 有效期确实长但 kubeadm 默认签发的服务证书和客户端证书都只有一年而且子证书是由 CA 签发的子证书过期后必须用同一把 CA key 重新签发否则新证书和旧 CA 对不上其他组件校验时一样会报信任失败。也就是说续期动作本身不复杂但要保证同源必须用集群现有的 CA 来签而不能用一个新的 CA 去签。再看 service account 相关的问题。kubeadm 在kube-system命名空间下管理着一个名为kubernetes的 service account secret里面保存着签名 service account token 的密钥对。这个 secret 不会过期但如果你不小心执行了某些清理操作或者从别的集群恢复时没带上它所有 service account token 会立即失效结果就是kube-system里跑着的一堆控制器全部报 401。我的脚本里没有动这个 secret但要提醒一句备份时一定把/etc/kubernetes/pki整个目录拷走别只拷*.crt不拷*.key少了sa.key后续续期脚本能把组件证书都续上但调度器、控制器管理器根本起不来。最后是 kubelet 的证书轮换机制。高版本 kubeadm 默认开启了RotateKubeletClientCertificatekubelet 会在客户端证书过期前自动申请新证书不需要人工介入。但服务端证书RotateKubeletServerCertificate并不总是开启而且自动轮换依赖 Apiserver 和 kubelet 之间的 connectivity如果 Apiserver 已因证书过期而不可用自动轮换也转不起来。所以脚本里仍需考虑 kubelet 相关证书的兜底处理。3. 一键续期脚本的核心思路与完整实现我的脚本目标很明确在 kubeadm 部署的控制面节点上执行一次完成备份 - 检查 - 续期 - 重建 kubeconfig - 重启组件 - 验证全流程。下面先把脚本主体贴出来再逐步解释每个模块为什么这么写。#!/usr/bin/env bash set -euo pipefail BACKUP_TIMESTAMP$(date %Y%m%d%H%M%S) BACKUP_DIR/etc/kubernetes/backup/certs-${BACKUP_TIMESTAMP} LOG_FILE/var/log/k8s-cert-renew-${BACKUP_TIMESTAMP}.log log() { echo [$(date %Y-%m-%d %H:%M:%S)] $* | tee -a ${LOG_FILE} } # 1. 前置检查 check_prerequisites() { log 开始前置检查... if [ $(id -u) -ne 0 ]; then log 错误: 脚本需要以 root 用户运行 exit 1 fi local components(kubeadm kubelet openssl crictl) for cmd in ${components[]}; do if ! command -v ${cmd} /dev/null 21; then log 错误: 未找到命令 ${cmd}请确认 kubeadm 集群组件完整 exit 1 fi done if ! systemctl is-active --quiet kubelet; then log 警告: kubelet 当前未运行脚本会继续但请确认集群状态 fi # 确认集群配置存在续期时使用与初始部署一致的配置 if [ ! -f /etc/kubernetes/kubeadm-config.yaml ] ! kubeadm config view /dev/null 21; then log 错误: 找不到 kubeadm 配置无法可靠续期 exit 1 fi log 前置检查完成 } # 2. 备份证书与 kubeconfig backup_certs() { log 开始备份证书与 kubeconfig 到 ${BACKUP_DIR} mkdir -p ${BACKUP_DIR} cp -a /etc/kubernetes/pki ${BACKUP_DIR}/ cp -a /etc/kubernetes/*.conf ${BACKUP_DIR}/ 2/dev/null || true cp -a /var/lib/kubelet/pki ${BACKUP_DIR}/kubelet-pki 2/dev/null || true log 备份完成: $(du -sh ${BACKUP_DIR} | awk {print $1}) } # 3. 续期前证书有效期快照 snapshot_expiration() { log 续期前证书有效期 for cert in /etc/kubernetes/pki/*.crt; do local enddate enddate$(openssl x509 -in ${cert} -noout -enddate 2/dev/null | cut -d -f2) log $(basename ${cert}): ${enddate} done log } # 4. 执行证书续期 renew_certs() { log 开始执行证书续期... local kubeadm_args() if [ -f /etc/kubernetes/kubeadm-config.yaml ]; then kubeadm_args(--config/etc/kubernetes/kubeadm-config.yaml) fi kubeadm certs renew all ${kubeadm_args[]} 21 | tee -a ${LOG_FILE} if [ ${PIPESTATUS[0]} -ne 0 ]; then log 错误: kubeadm certs renew all 执行失败正在尝试恢复备份 restore_backup exit 1 fi log 证书续期完成 } # 5. 重建 admin.conf 和 kubelet.conf renew_kubeconfig() { log 开始重建 kubeconfig 文件... if [ -f /etc/kubernetes/kubeadm-config.yaml ]; then kubeadm init phase kubeconfig admin --config/etc/kubernetes/kubeadm-config.yaml 21 | tee -a ${LOG_FILE} kubeadm init phase kubeconfig kubelet --config/etc/kubernetes/kubeadm-config.yaml 21 | tee -a ${LOG_FILE} else kubeadm init phase kubeconfig admin 21 | tee -a ${LOG_FILE} kubeadm init phase kubeconfig kubelet 21 | tee -a ${LOG_FILE} fi # 让 kubelet 继承新的 kubelet.conf cp -p /etc/kubernetes/kubelet.conf /var/lib/kubelet/kubeconfig log kubeconfig 重建完成 } # 6. 重启控制面静态 Pod restart_control_plane() { log 开始重启控制面组件... # 通过删除静态 Pod 让 kubelet 重新拉起从而加载新证书 for pod in $(crictl pods -q --namespace kube-system 2/dev/null \ | xargs -I{} crictl inspectp {} --output json 2/dev/null \ | python3 -c import json,sys for line in sys.stdin: try: data json.loads(line) name data.get(status, {}).get(labels, {}).get(io.kubernetes.pod.name, ) ns data.get(status, {}).get(labels, {}).get(io.kubernetes.pod.namespace, ) if ns kube-system and name in (kube-apiserver, kube-controller-manager, kube-scheduler): print(data.get(status, {}).get(id, )) except Exception: pass 2/dev/null); do [ -n ${pod} ] crictl rmp -f ${pod} 2/dev/null || true done # 对于不用 crictl 的环境改用 docker 方式 if command -v docker /dev/null 21; then for container in kube-apiserver kube-controller-manager kube-scheduler; do local cid cid$(docker ps -q --filter name${container} | head -n1) [ -n ${cid} ] docker rm -f ${cid} 2/dev/null || true done fi log 控制面组件已触发重启等待 kubelet 重新拉起... sleep 30 } # 7. 验证 verify_results() { log 续期后证书有效期 for cert in /etc/kubernetes/pki/*.crt; do local enddate enddate$(openssl x509 -in ${cert} -noout -enddate 2/dev/null | cut -d -f2) log $(basename ${cert}): ${enddate} done log export KUBECONFIG/etc/kubernetes/admin.conf log 节点状态: kubectl get nodes -o wide 21 | tee -a ${LOG_FILE} log 控制面 Pod 状态: kubectl get pods -n kube-system -o wide 21 | tee -a ${LOG_FILE} } restore_backup() { log 从 ${BACKUP_DIR} 恢复证书... cp -a ${BACKUP_DIR}/pki /etc/kubernetes/ cp -a ${BACKUP_DIR}/*.conf /etc/kubernetes/ 2/dev/null || true log 恢复完成建议立即手动排查失败原因 } check_prerequisites snapshot_expiration backup_certs if renew_certs; then renew_kubeconfig restart_control_plane verify_results fi这个脚本里最值得展开讲的有三点。第一备份不是可选项而是保命项。证书续期出的问题十有八九不是续期动作本身出错而是后续操作互相干扰比如旧 kubeconfig 没有更新或者某些证书被重复签发导致链路不一致。有了BACKUP_DIR哪怕续期后集群彻底起不来也能用三行命令恢复到几分钟前的状态。我见过太多人直接执行kubeadm certs renew all莽一波结果整个控制面瘫了又不知道怎么回滚。第二为什么续期完还要单独执行kubeadm init phase kubeconfig admin。很多人不知道kubeadm certs renew all只负责重新签发/etc/kubernetes/pki下的证书文件/etc/kubernetes/admin.conf、kubelet.conf里的证书并不会被自动更新。如果这一步漏掉你的根 CA 没变但 admin.conf 里的客户端证书过期了kubectl 照样连不上。这也是很多续期后依然报错问题的根源。第三重启控制面组件的姿势。kubeadm 部署的 Apiserver、Controller-Manager、Scheduler 都是静态 Pod由 kubelet 直接管理。证书文件更新后进程不会自动加载新证书必须让容器退出重建。最简单的做法是用crictl rmp -f删掉对应 Podkubelet 检测到管控文件还在会自动重新创建容器这个过程中 Apiserver 会有短暂的不可用时间大概几秒到十几秒业务侧表现为 API 请求抖动但存量业务长连接一般不受影响。脚本里我优先用 crictl检测到没有 crictl 时退回 docker两种运行时环境都能覆盖。4. 脚本实测从执行到验证中间发生了什么我在一台 kubeadm v1.26 部署的三节点集群上完整跑了一次脚本控制面节点为单节点部署etcd 也跑在控制面节点上这样能模拟大多数中小团队的最小高可用方案。执行前我特意记录了两个关键状态集群已连续运行 11 个月apiserver.crt剩余有效期约 30 天kubelet-client-current.pem剩余约 20 天实际上已经进入比较危险的倒计时阶段。脚本执行过程的时间分布大致如下阶段耗时说明前置检查约 1 秒检查 root、kubeadm、openssl、crictl 等依赖备份约 5 秒/etc/kubernetes/pki下证书文件不大拷贝很快kubeadm certs renew all约 10 秒逐个签发 apiserver、etcd、front-proxy 系列证书重建 kubeconfig约 3 秒重新生成 admin.conf、kubelet.conf重启控制面静态 Pod约 30 秒删除容器后等待 kubelet 重建并回归 Ready验证约 10 秒检查证书有效期与节点状态脚本输出的续期后证书有效期我挑几个关键的记录apiserver.crt: notAfter2026-06-17 12:34:56 0000 UTC apiserver-kubelet-client.crt: notAfter2026-06-17 12:34:56 0000 UTC etcd/server.crt: notAfter2026-06-17 12:34:56 0000 UTC也就是说续期后所有组件证书的有效期都延长到了距离当前时间约一年后的日期符合 kubeadm 默认行为。验证阶段我除了看kubectl get nodes全部 Ready 之外还会做三件额外检查这里也分享给读者检查 Apiserver 日志里有没有持续刷TLS handshake error。命令是journalctl -u kubelet --since 5 minutes ago | grep -i TLS handshake | tail -n 20如果没有新的握手错误说明 kubelet 已经用新证书重新注册到 Apiserver。随机挑选一个工作节点手工 exec 进一个业务 Pod执行kubectl exec -it 任意pod -- date这一步验证的是 Apiserver 到 kubelet 的kubeletclient证书链正常。很多人只检查kubectl get nodes却没意识到 exec 走的是另一条链路如果apiserver-kubelet-client.crt续期失败节点状态可能正常但 exec 全部报错。确认 etcd 集群健康kubectl exec -n kube-system etcd-control-plane-node -- etcdctl endpoint health --cluster如果这一步能正常输出healthy说明 etcd 的新证书和 Apiserver 侧握手完全正常。实测结果里有一个细节值得说明在重启控制面静态 Pod 后的前几秒kubectl get nodes会短暂超时因为 Apiserver 容器正在重建这是正常现象。我第一次写这个脚本时在restart_control_plane里只 sleep 了 10 秒结果验证阶段偶尔会因为 Apiserver 还没完全就绪而误报失败后来统一改成 30 秒等kube-system下三个核心 Pod 全部回到 Running 再做验证就稳定多了。5. 这些坑我基本都踩过希望你不要再踩5.1 续期后忘了同步其他节点的 kubeconfig脚本只在控制面节点上执行理论上只更新该节点上的 admin.conf 和 kubelet.conf。但如果你平时喜欢在跳板机上拷一份 admin.conf 来管理集群续期完成后跳板机上那份 admin.conf 里的客户端证书仍然是旧的一旦旧证书过期你的 kubectl 照样失效。最好的做法是续期成功后把新生成的/etc/kubernetes/admin.conf重新分发到所有需要管理集群的机器上。5.2 二三十个证书不是全部都要续期执行kubeadm certs renew all时脚本并不会续期/var/lib/kubelet/pki/kubelet-client-current.pem。这个文件是 kubelet 自己管理的开启自动轮换时它会在有效期内自动申请新证书正常情况下根本不用管。我早期不懂手动删掉过这个文件结果 kubelet 直接起不来后来才知道是操作方式不对——等它自动轮换就好或者重启 kubelet 让它重新注册时自动签发新证书千万不要手动乱删。5.3 时间不同步是证书问题的隐形帮凶证书有效性验证依赖系统时间。节点时钟如果慢了几分钟在一张剩余有效期只有十几分钟的证书上表现出来就是立即失效。有次我们排查一个节点反复 NotReady检查证书完全没有过期最后发现是chronyd异常退出导致系统时间慢了 8 分钟。所以在执行续期脚本前建议先看一眼所有节点的时钟偏差timedatectl status chronyc tracking | grep -E System time|Last offset5.4 别忽略集群配置文件的完整性kubeadm certs renew all --config/etc/kubernetes/kubeadm-config.yaml要求配置文件里包含完整的 ClusterConfiguration比如 certSANs。如果你的 Apiserver 原来配过certSANs配置里没写续期后新的证书就不会带上这些 SAN结果是某些通过其他域名或 IP 访问 Apiserver 的客户端全部握手失败。续期前务必检查配置里的certSANs是否和实际访问方式一致。5.5 用 systemd timer 而不是 crontab这是个人偏好但我觉得更值得推荐。crontab 里写计划任务日志要重定向到文件失败告警还要自己包装systemd timer 天然有 journal 日志配合OnCalendar可以做更灵活的触发策略比如每月 1 号 03:00 执行一次。更重要的是可以把续期脚本做成一个幂等脚本先检查所有证书剩余有效期是否小于某个阈值不满足就退出满足才执行续期。这样定时任务挂上去不会像无头苍蝇一样每个月把所有证书都重新签一遍。判断逻辑可以放在脚本最前面类似NEED_RENEW0 for cert in /etc/kubernetes/pki/*.crt; do EXPIRY$(openssl x509 -in $cert -noout -enddate | cut -d -f2) EXPIRY_TS$(date -d $EXPIRY %s) NOW_TS$(date %s) REMAIN_DAYS$(( (EXPIRY_TS - NOW_TS) / 86400 )) if [ $REMAIN_DAYS -lt 30 ]; then NEED_RENEW1 fi done这套思路我用在几个客户集群上后续基本没再因为证书问题被半夜叫起来过。5.6 大版本升级前后不要急着续期如果你计划近期做 kubeadm 集群大版本升级比如从 v1.24 升到 v1.26建议先升完级再执行续期。因为kubeadm upgrade本身也会处理一部分证书迁移和重新签发逻辑升级前续期等于白做还可能让升级过程产生某些证书相关的不确定因素。把续期放到升级完成、集群稳定之后再执行是最省心的节奏。总结一下我个人的体会k8s 证书续期没有一个一劳永逸的魔法命令核心在于流程化管理——前置检查、全量备份、同源续期、kubeconfig 同步、组件重启、链路验证每一步都不能省。脚本我给你了也告诉你每个步骤为什么要这么做实际部署时你完全可以按自己的集群规模去改。最后再分享一个小技巧把脚本执行后生成的日志文件路径钉到团队群里每次续期完大家都能看到证书有效期延长了多少下次到期前 60 天再跑一次证书相关的告警基本能归零。本文还有配套的精品资源点击获取
返回列表