早上八点半,监控群里弹出一条告警:kube-apiserver 证书剩余有效期不足 30 天。这个场景对任何自建过 Kubernetes 集群的人来说都不陌生,尤其在用 kubeadm 维护的集群里,证书过期几乎是每年都要面对一次的事故。很多人第一反应是:能不能直接把证书可用年限改成十年,一劳永逸?答案并没有想象中那么简单,因为一个集群里同时存在好几类证书,它们各自的签发机制、续期命令、控制参数完全不一样。
这篇文章就围绕“Kubernetes 证书可用年限修改”这件事,把控制平面证书的默认策略、过期后的急救流程、kubelet 证书的独立规则、从源头改年限的可行路线,以及改完之后的验证和长期巡检,一条线讲清楚。无论是刚接手集群的新手,还是已经踩过几次证书坑的运维,都能在这篇里找到可以直接照做的步骤。
1. 一年一签是默认策略,不是安全法则
先别急着改年限,第一步是把集群里到底有哪些证书搞清楚。kubeadm 初始化和节点加入时,会在控制平面节点上生成一套完整的 PKI 体系,大部分证书都集中在/etc/kubernetes/pki目录下。
1.1 控制平面证书的类型与信任关系
我把常见证书的用途列成了一张表,实际排查时对照看会清晰很多:
| 路径/文件 | 用途 |
|---|---|
/etc/kubernetes/pki/ca.crt/ca.key | 集群根 CA,签发控制平面组件证书、kubelet 客户端证书 |
/etc/kubernetes/pki/apiserver.crt/apiserver.key | kube-apiserver 对外提供服务的证书 |
/etc/kubernetes/pki/apiserver-kubelet-client.crt | apiserver 访问 kubelet 时使用的客户端证书 |
/etc/kubernetes/pki/front-proxy-ca.crt/front-proxy-ca.key | 聚合层代理 CA,给扩展 API Server 用 |
/etc/kubernetes/pki/front-proxy-client.crt | 聚合层客户端证书 |
/etc/kubernetes/pki/etcd/ca.crt/ca.key | etcd 集群的根 CA,独立于集群根 CA |
/etc/kubernetes/pki/etcd/server.crt | etcd 对外服务端证书 |
/etc/kubernetes/pki/etcd/peer.crt | etcd 节点间通信证书 |
/etc/kubernetes/pki/etcd/healthcheck-client.crt | 健康检查用客户端证书 |
/etc/kubernetes/admin.conf等 kubeconfig | 内嵌客户端证书,用于 kubectl、controller-manager、scheduler 访问 apiserver |
这里最容易被忽略的是“信任链”问题。apiserver 的证书必须由/etc/kubernetes/pki/ca.crt对应的私钥签发,etcd 的证书必须由etcd/ca.crt签发,聚合层证书必须由front-proxy-ca.crt签发。手动改证书时如果破坏了这套对应关系,组件启动会直接报 x509 错误,根本起不来。
1.2 为什么默认只签一年
kubeadm 生成证书的时候,控制平面组件证书有效期默认是 365 天,CA 根证书默认是 10 年。看到十年和一年的落差,很多人会以为 CA 是“永久”的,其实不是,CA 的有效期同样受源码里的常量控制,只不过 kubeadm 官方认为根 CA 私钥保存得更严格,长期风险可控。
组件证书只签一年的理由很直白:证书轮换机制已经足够成熟,短期证书能有效压缩私钥泄露后的影响窗口。如果某个组件的私钥意外泄露,最长也就影响一年,到期后证书自动失效,攻击者手里的私钥就变成废品。从这个角度看,一年一签不是设计缺陷,而是官方在“安全”和“运维成本”之间做的取舍。真正该做的不是取消轮换,而是把轮换流程自动化、可控化。
1.3 先分清哪些证书会“卡住”业务
集群证书给业务带来的故障通常分两类:一类是 apiserver、controller-manager、scheduler、etcd 这些静态证书过期,导致组件间通信失败,最典型的现象是 kubectl 执行命令报certificate has expired;另一类是 kubelet 证书过期,表现是节点状态变成 NotReady,Pod 调度不上去,但 apiserver 本身看起来又是正常的。
这两类问题的处理路径完全不同。静态证书主要靠kubeadm certs renew续期,kubelet 证书则依赖 controller-manager 的签名参数和 kubelet 自动轮换机制。如果混在一起处理,很容易出现“改了 apiserver 证书,过了几天节点又 NotReady”的尴尬局面。后面的章节我会把两条线分开讲。
2. 证书告警来了:先看状态再动手
遇到证书过期,最忌讳的是直接跑去删掉旧证书、重新生成。所有操作应该从“查看状态”开始,先弄清楚哪些证书快到期、哪些证书还健康,再做定向处理。
2.1 用一条命令看清全部证书状态
kubeadm 提供了现成的检查命令,在控制平面节点上执行即可:
kubeadm certs check-expiration输出大致长这样,每行对应一个证书文件或 kubeconfig 里内嵌的证书:
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED admin.conf May 05, 2025 15:20 UTC 28d ca.crt no apiserver May 05, 2025 15:20 UTC 28d ca.crt no apiserver-kubelet-client May 05, 2025 15:20 UTC 28d ca.crt no controller-manager.conf May 05, 2025 15:20 UTC 28d ca.crt no etcd/healthcheck-client May 05, 2025 15:20 UTC 28d etcd/ca.crt no etcd/peer May 05, 2025 15:20 UTC 28d etcd/ca.crt no etcd/server May 05, 2025 15:20 UTC 28d etcd/ca.crt no front-proxy-client May 05, 2025 15:20 UTC 28d front-proxy-ca.crt no scheduler.conf May 05, 2025 15:20 UTC 28d ca.crt no重点看RESIDUAL TIME和EXTERNALLY MANAGED两列。EXTERNALLY MANAGED为 yes 的时候,说明该证书由外部证书管理器接管,kubeadm 不会也不应该动它,这时候不能依赖kubeadm certs renew,要去找对应的管理平台处理。
2.2kubeadm certs renew all的正确操作姿势
确认证书快到期之后,标准操作是kubeadm certs renew all,它会根据当前 kubeadm 内置的默认有效期,重新签发所有证书和 kubeconfig。我建议命令执行前先备份,这条经验在多次续期事故里都被验证过:
cp -r /etc/kubernetes/pki /etc/kubernetes/pki.bak.$(date +%F) cp /etc/kubernetes/admin.conf /etc/kubernetes/admin.conf.bak.$(date +%F) kubeadm certs renew all需要注意,renew all只负责把新证书写到磁盘,并不会热加载到正在运行的组件进程里。很多人执行完这条命令后立刻看集群状态,发现 apiserver 日志还是报证书过期,就以为续期没生效,其实只是进程还在用旧证书。此时需要触发组件重新加载。
最简单的办法是先重启 kubelet:
systemctl restart kubelet如果重启后 apiserver、controller-manager、scheduler 等静态 Pod 仍然没有重新读取证书,那就用更强制的手段,把对应的 manifest 文件移走再移回,让 kubelet 感知到变化并重建容器:
cd /etc/kubernetes/manifests mv kube-apiserver.yaml /tmp/ sleep 5 mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/controller-manager、scheduler、etcd 的 manifest 也用同样的方式处理。这么做是因为静态 Pod 的 spec 没有变化时,kubelet 未必会重建容器,而证书又是以文件挂载进去的,进程不重启就永远读不到新证书。
2.3 续期后的三个隐藏动作,漏一个都会留隐患
第一,更新你本地 kubectl 使用的 kubeconfig。renew all会重新签发/etc/kubernetes/admin.conf,但如果你本机的 kubectl 是从旧 admin.conf 拷贝出来的,旧证书已经过期,那 kubeconfig 里的客户端证书也过期了。续期之后要把新 admin.conf 拉回本地重新配置。
第二,高可用集群要逐节点执行,不是只跑一台。多个控制平面节点的证书各自独立存储,共享同一个 CA,但每台机器上的证书文件是自己那份。只在一台节点上 renew,其他节点的证书依然是旧的,apiserver 最怕这种“集群内部证书不一致”的状态。
第三,etcd 证书续期后要保证 etcd 进程重新加载。etcd 是静态 Pod,处理方式同上,mv 掉 manifest 再放回即可。多 etcd 成员的集群不要同时重启所有成员,最好一个节点处理完确认 etcd 集群健康,再处理下一个,避免短时间内 etcd 成员大面积重启造成选举抖动。
3. kubelet 证书的年限是另一套规则
很多人在做证书续期时把 kubelet 的证书给漏了,因为它在kubeadm certs renew all的输出里根本不会出现。原因很简单:kubelet 的证书不是 kubeadm 签发的,而是 kubelet 自己发起 CSR 请求,由 controller-manager 里的 signer 负责签字。
3.1 kubelet 证书的存放位置与轮换机制
kubelet 的客户端证书和服务端证书放在/var/lib/kubelet/pki目录下。你会看到kubelet-client-current.pem这类名字,它其实是一个符号链接,真正指向带有时间戳的文件。kubelet 在自动轮换时,会生成新文件并更新软链接,而不是原地覆盖。
这个机制决定了:不要手动去修改kubelet-client-current.pem的内容,手动覆盖软链接指向的旧文件,很容易把轮换状态搞坏。kubelet 也不是等到证书过期才动手,而是会在证书剩余时间低于某个阈值时,自动发起新的 CSR 申请。只要 controller-manager 正常批准签名,kubelet 证书实际上是“自我维持”的,不需要管理员每年手工换一次。
3.2 通过 cluster-signing-duration 控制 kubelet 证书年限
如果确实希望 kubelet 证书的有效期更长,控制参数在 kube-controller-manager 上。默认情况下,controller-manager 给 CSR 签发的证书有效期是 8760 小时,也就是 365 天。
修改方式是在/etc/kubernetes/manifests/kube-controller-manager.yaml的启动参数里加一行:
spec: containers: - name: kube-controller-manager command: - kube-controller-manager - --cluster-signing-duration=87600h87600 小时等于十年。改完后重启 kube-controller-manager,之后新签发的 kubelet 客户端证书和服务端证书都会变成十年有效。
这里有一个容易踩的坑:修改参数只对“之后新签发”的证书生效,已经签发的旧证书不会因此自动延长。如果你希望立刻生效,可以手动删除/var/lib/kubelet/pki下的旧证书文件并重启 kubelet,让 kubelet 重新发起 CSR。不过正常情况下没必要这么做,kubelet 会按自己的轮换节奏自然过渡到十年期证书。
3.3 轮换链路上真正的风险点
把 kubelet 证书改成十年之后,最容易忽视的是“轮换请求被拒绝”。kubelet 发起 CSR 后,默认由 controller-manager 的kubelet-serving和kube-apiserver-client这两个 signer 自动批准。如果集群里有人配置了额外的审批策略,或者 signer 被异常关闭,kubelet 的 CSR 就会一直卡在 Pending 状态,直到旧证书过期,节点直接 NotReady。
排查的时候,用这条命令看 CSR 列表最直接:
kubectl get csr看到大量 Pending 状态且为kubernetes.io/kube-apiserver-client-kubelet的请求,基本就是自动批准链路出了问题。另外,如果你把cluster-signing-duration改得非常大,还要确认 kubelet 的自动轮换开关是开着的,否则证书到期后依然不会自动续期。kubeadm 部署的集群默认开了 rotation,但手动搭建的集群不一定。
4. 想改十年?先搞清两条路线的取舍
讲完了应急处理和 kubelet 参数,回到最初的问题:到底能不能让证书十年一换。答案是可以,但必须选对路线。这里我整理了两条主流做法,一条是改 kubeadm 源码,一条是用外部 CA 体系,两者各有代价。
4.1 路线一:修改 kubeadm 源码,重新编译
kubeadm 生成证书时,有效期写在源码常量里。以常见版本为例,文件位置在kubeadm/app/constants/constants.go:
const ( CertificateValidityPeriod = time.Hour * 24 * 365 CACertificateValidityPeriod = time.Hour * 24 * 365 * 10 )第一行控制普通组件证书,第二行控制 CA 根证书。把它们改成十年或二十年:
const ( CertificateValidityPeriod = time.Hour * 24 * 365 * 10 CACertificateValidityPeriod = time.Hour * 24 * 365 * 10 )然后在 Kubernetes 源码根目录执行:
make kubeadm编译出的kubeadm二进制替换到所有控制平面节点的/usr/bin/kubeadm。之后不管是kubeadm init新建集群,还是对已有集群执行kubeadm certs renew all,生成的证书都会按新常量确定有效期。
这条路线看起来直接,但代价也直接:每次 Kubernetes 小版本升级都要重新编译,否则新旧 kubeadm 混用会带来证书有效期不一致的问题。如果你所在环境是离线内网、版本升级节奏很慢、并且完全由自己掌控 kubeadm 分发,这条路可行。如果集群升级频繁、用了第三方集群安装工具,就别碰源码改动了,维护成本会超出收益。
4.2 路线二:用外部 CA 签发长期证书,更接近标准做法
第二条路线是把证书签发权从 kubeadm 内部 CA 转移到自己的 PKI 体系。具体做法是用 openssl 或 cfssl 生成独立根 CA,然后手动签发十年期的组件证书,替换掉/etc/kubernetes/pki下的对应文件。
以 apiserver 证书为例,关键是 SAN 配置不能出错。kubeadm 签发的证书里天生带着 apiserver 需要的 IP 和 DNS 条目,手动签发时漏掉任何一个,apiserver 启动后客户端访问就会报证书校验失败。一个最小可用的 OpenSSL 配置文件如下:
[req] distinguished_name = dn req_extensions = v3_req prompt = no [dn] CN = kube-apiserver [v3_req] keyUsage = keyEncipherment, digitalSignature extendedKeyUsage = serverAuth, clientAuth subjectAltName = @alt_names [alt_names] DNS.1 = kubernetes DNS.2 = kubernetes.default DNS.3 = kubernetes.default.svc DNS.4 = kubernetes.default.svc.cluster.local DNS.5 = lb.example.internal IP.1 = 10.96.0.1 IP.2 = 192.168.100.10IP.1是集群 Service 网段里的第一个地址,也就是 apiserver 的 ClusterIP;IP.2是控制平面节点的 IP 或负载均衡 VIP。签发命令参考:
openssl genrsa -out apiserver.key 2048 openssl req -new -key apiserver.key -out apiserver.csr -config apiserver-openssl.cnf openssl x509 -req -in apiserver.csr \ -CA /etc/kubernetes/pki/ca.crt \ -CAkey /etc/kubernetes/pki/ca.key \ -CAcreateserial \ -out apiserver.crt \ -days 3650 \ -extensions v3_req \ -extfile apiserver-openssl.cnf替换好证书后,用前面说过的“mv manifest 触发静态 Pod 重建”方式让组件重新加载。
4.3 两条路线怎么选,我的建议
| 维度 | 改 kubeadm 源码 | 外部 CA |
|---|---|---|
| 新建集群生效 | 每次都要用自编译二进制 | 需要提前配置 PKI 流程 |
| 已有集群续期 | 重新执行 renew all 即生效 | 要逐证书替换并重启组件 |
| 升级成本 | 每次版本升级重新编译 | 升级不影响证书体系 |
| 最大风险 | 二进制不受官方管理 | 私钥泄露影响面大、回退难 |
| 适合场景 | 离线内网、长期不升级 | 有规范化 PKI 体系的团队 |
我的建议是:除非集群数量很少且团队对 kubeadm 源码很熟,否则优先考虑外部 CA 路线,同时保留 kubelet 证书的自动轮换机制。组件证书改成十年,kubelet 证书继续走 CSR 自动轮换,这样既减少了人工操作点,又不破坏 Kubernetes 原生的证书管理逻辑。
5. 证书改完怎么验证,以及后续怎么少踩坑
证书操作结束后,最怕的是静默出错。apiserver 证书明明换了,但某个组件因为种种原因还在用旧证书,集群表面看起来正常,等下一次重启才暴露问题。所以验证这一步不能省。
5.1 验证命令:从证书文件到组件行为
先看文件层,确认证书的到期时间和 SAN 是否正确:
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A2 Validity openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A10 "Subject Alternative Name"再用 kubeadm 全局审视一遍:
kubeadm certs check-expiration如果还有证书显示剩余时间不对,说明续期没有完全生效。接着看组件行为,apiserver 日志里出现x509: certificate signed by unknown authority或certificate has expired都说明证书链路有问题:
journalctl -u kubelet -n 200 | grep -i x509 kubectl get nodes kubectl get --raw /healthz最后做一个最直观的业务验证:正常执行一次 Pod 的创建和删除流程,确认 apiserver、scheduler、kubelet 之间的认证链路是通的。
5.2 把证书检查做成巡检任务,而不是年底事故
见过太多集群是“证书过期前一天才被运维发现”,本质上是因为没有人把检查变成例行任务。写一个简单的检查脚本,放到控制平面节点的 crontab 里,每天跑一次,是投入产出比最高的动作。
#!/bin/bash # /usr/local/bin/check-cert-expiration.sh for cert in /etc/kubernetes/pki/*.crt /etc/kubernetes/pki/etcd/*.crt; do enddate=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2) end_ts=$(date -d "$enddate" +%s) now_ts=$(date +%s) remain_days=$(( (end_ts - now_ts) / 86400 )) if [ "$remain_days" -lt 30 ]; then echo "即将到期: $cert 剩余 ${remain_days} 天" fi donecron 里加上执行权限,然后定时任务可以这样写:
0 9 * * * /usr/local/bin/check-cert-expiration.sh > /var/log/cert-check.log 2>&15.3 几年踩坑之后我总结的几个原则
先说升级窗口:kubeadm upgrade默认会顺带执行证书续期,如果集群版本要升级,尽量把证书续期绑定到升级窗口里一起做,省一次割接时间。但千万不要在证书剩余时间已经不足两周时直接升级,先kubeadm certs renew all把地基打稳,再动版本。
再说多控制平面集群:每个控制平面节点的证书文件都是独立的,不能认为一台机器 renew 成功等于全员成功。操作时一台一台来,每台机器做完都要kubeadm certs check-expiration确认,再切到下一台。
最后是备份习惯。每次证书操作之前,把/etc/kubernetes/pki和所有 kubeconfig 打包备份,是我个人强烈推荐的动作。证书操作本身不难,难的是万一中途改坏了 apiserver 的一个 CN 或 SAN,想要凭记忆恢复原状几乎不可能。有一份半小时前的备份,至少能把集群拉回可用状态再继续排查。这些习惯看上去琐碎,但在真实事故里,每一条都帮我少熬过好几个夜。