凌晨两点半被叫起来处理集群故障,kubectl 敲下去,屏幕上弹出一行刺眼的报错:
Unable to connect to the server: x509: certificate has expired or is not yet valid看到x509和expired这两个关键词,基本可以锁定是证书问题。但麻烦的地方在于,这个报错背后到底是谁的证书过期,并不像表面上那么简单——可能是本地 kubeconfig 里的客户端证书到期,也可能是 API Server 的服务端证书失效,甚至在新节点加入、时钟漂移、集群恢复备份的场景里,报错原因会完全不同。这篇文章把我处理这类故障的完整思路、定位方法和根治步骤整理出来,希望能帮你少走几个弯路。
1. 报错现场:一场让所有 kubectl 命令瞬间失效的故障
先还原一下这个报错出现的典型场景。
你在一台机器上执行kubectl get nodes,甚至只是kubectl version,客户端工具本身能启动,但一连接到 API Server,TLS 握手就被掐断了,然后抛出这样一段:
Unable to connect to the server: x509: certificate has expired or is not yet valid这跟常见的 "connection refused" 或 "timeout" 完全不一样。TCP 层其实是通的,说明 API Server 还在监听端口、还在响应,问题出在 TLS 证书校验环节。客户端(kubectl)校验服务端证书,服务端也可能反过来校验客户端证书,任一方带着一张已经过期或还没生效的证书,握手就过不去。
1.1 影响范围比你想的更广
很多人以为这只是 kubectl 连不上,换个工具不就行了。实际上,只要走 TLS 访问 API Server 的客户端都会一起炸掉:
- 本地开发者的
~/.kube/config - CI/CD 流水线里配置的 kubeconfig
- Dashboard、监控采集组件、日志采集 agent
- 集群里依赖 API Server 的内部组件(例如 Controller Manager 访问 API Server)
- 节点上的 kubelet 与 API Server 的心跳上报
所以这个报错一出,往往不是“一个人不便”,而是整个集群的运维通道都断了。如果是生产环境,紧接着就是告警刷屏、工单堆积。
1.2 报错里藏着的信息
把报错拆开看:
Unable to connect to the server表示连接动作已经发起,但 TLS 握手阶段失败。x509是 Go 语言标准库中处理证书的那个包,Kubernetes 和 kubectl 都是 Go 写的,所以报错措辞来自 Go 的证书校验逻辑。certificate has expired or is not yet valid是 Go 在检查证书有效期时的统一提示,它同时覆盖了“证书过期”和“证书尚未生效”两种情况。
后面这种“尚未生效”往往最容易被忽略。很多人看到 not yet valid 就懵了,明明证书还没过期,为什么说无效?这颗雷我在后面专门分析。
同样的错误,也可能是certificate signed by unknown authority(证书不受信任的 CA 签发)或x509: certificate is valid for xxx, not yyy(域名不匹配)。这几个报错经常被混为一谈,但定位路径完全不同,先分清楚再动手。
2. 根因拆解:Kubernetes 的证书体系与有效期机制
要根治问题,得先看懂 kubeadm 搭出来的集群里,证书到底放在哪里、谁签发了谁、为什么一年之后就爆。
2.1 控制面上的证书分布
kubeadm 初始化集群后,所有核心证书默认放在/etc/kubernetes/pki目录下,结构大致是:
/etc/kubernetes/pki/ ├── apiserver.crt ├── apiserver.key ├── apiserver-kubelet-client.crt ├── apiserver-kubelet-client.key ├── ca.crt ├── ca.key ├── front-proxy-ca.crt ├── front-proxy-client.crt ├── sa.pub ├── sa.key └── etcd/ ├── ca.crt ├── healthcheck-client.crt ├── peer.crt ├── server.crt └── ...其中角色大致是这样:
| 证书文件 | 用途 | 谁签发 |
|---|---|---|
| ca.crt / ca.key | 集群根 CA,签发其他所有组件证书 | 自己(自签名) |
| apiserver.crt | API Server 对外提供 HTTPS 服务的服务端证书 | 集群 CA |
| apiserver-kubelet-client.crt | API Server 作为客户端访问 kubelet 时使用 | 集群 CA |
| front-proxy-client.crt | 聚合 API 代理(如 metrics-server)使用 | front-proxy CA |
| etcd/server.crt | etcd 对外提供服务的证书 | etcd CA |
| etcd/peer.crt | etcd 节点之间互相通信的证书 | etcd CA |
| sa.pub / sa.key | ServiceAccount token 签名密钥,不是证书 | 无 |
kubectl客户端本身不直接读 pki 目录,它用的是 kubeconfig。kubeadm 初始化或 join 时,会在/etc/kubernetes/admin.conf、kubelet.conf这些文件里嵌入客户端证书和 CA 数据。我们日常使用的~/.kube/config,通常就是从 admin.conf 复制过来的。
2.2 为什么过期的总是客户端证书
这是很多人没想明白的关键点。
kubeadm 在创建证书时,对不同角色给的默认有效期不一样:
- 集群CA 证书:默认 10 年
- API Server、Controller Manager、Scheduler、etcd、admin.conf 等组件证书:默认1 年
- kubelet 客户端证书:默认 1 年,但在开启
RotateKubeletClientCertificate后会自动轮换
也就是说,集群跑了一年多之后,最容易过期的是 pki 目录下一批"非 CA"证书,以及 kubeconfig 里嵌入的 client 证书。而根 CA 还没到期,所以从报错措辞上你只会看到“证书过期”,而不会立刻想到是哪一张。
我处理过的生产集群故障里,绝大多数情况是admin.conf里的客户端证书过期了。原因是:这套证书在 kubeadm 初始化时生成,kubeadm 本身并不会像 kubelet 那样自动给 admin.conf 续期,集群管理员如果没做监控,一年到期后自然全断。
另一个常见来源是:如果用户是从某平台或工具直接签发的 client certificate(比如自己用 CA 签的),这类外部签发证书 kubeadm 不认识,也不会自动续,过期风险更高。
2.3 “is not yet valid” 到底在说什么
这个报错短语很坑,它把两种情况合并成一句话。
第一种:证书的notAfter时间已经过了,也就是真的过期了。
第二种:证书的notBefore时间还没到,也就是证书在“未来”才生效。这种情况在正常集群里很少见,但只要出现,九成是节点时间漂了。比如某台机器的系统时间比别人快了半小时,从这台机器上执行命令、或者在这台机器上签发的证书,自然会被其他机器判定为“not yet valid”。
还有一个容易踩的场景:从服务器上把 admin.conf 拷到本地,但本地机器时间不对(比服务器慢或者快),或者服务器时间本身已经漂了。时间基准不一致,证书校验就全乱套。所以排查证书问题时,第一件事要看所有相关节点的时间是否一致。
3. 三分钟定位:到底是哪张证书过期了
遇到这个报错,别急着去搜“如何续期证书”,先搞清楚是哪张证书出了问题。定位路径不对,越修越乱。
3.1 第一步:检查本地 kubeconfig 的客户端证书
在本机执行:
grep client-certificate-data ~/.kube/config | awk '{print $2}' | base64 -d | openssl x509 -noout -dates -subject -issuer这段命令把 kubeconfig 里的证书内容 base64 解码后,用 openssl 查看签发时间、过期时间、Subject 和 Issuer。
看输出的notAfter字段,如果已经早于当前时间,那问题就在本地 kubeconfig 上。此时你可以再用kubectl config view --minify --raw看当前 context 用的到底是不是这个证书,防止有多个 context 切换错乱。
3.2 第二步:检查 API Server 的服务端证书
如果本地 kubeconfig 证书明明在有效期内,但依然报错,那就需要 SSH 登录到 master 节点上检查服务端证书。
sudo openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -dates顺便检查 etcd 和 front-proxy 等证书:
sudo find /etc/kubernetes/pki -name "*.crt" -exec openssl x509 -in {} -noout -dates \; 2>/dev/null这个命令会把 pki 目录下所有证书的有效期一次性列出来。注意输出比较长,可以配合grep过滤,比如只关心 already expired 的关键词。
3.3 第三步:确认节点时间基线
所有节点上执行:
date timedatectl status理想情况下,各个节点时间误差应该在几秒以内。如果某台机器时间明显偏了,先同步时间再判断证书问题。节点时间漂移造成“not yet valid”的情况,我遇到不下三次,每次都是先把系统时间校回来,证书问题自动消失。
如果三步走完,证书都不过在 apiserver 侧,还可以用带调试输出的方式确认握手细节:
kubectl get nodes -v=6这个命令会打印 kubectl 访问 API Server 过程中的日志,里面会带出更多 TLS 相关的错误上下文。
4. 完整恢复流程:更新证书、重配 kubeconfig、重启控制面
定位清楚之后,真正动手修。下面这套流程适用于 kubeadm 部署的集群,命令在 Kubernetes 1.15+ 版本均可使用。
4.1 备份永远放在第一步
动证书之前,先备份。别嫌麻烦,续期过程一旦中断或者执行错误,集群可能直接从“证书过期”变成“证书损坏”。
sudo cp -a /etc/kubernetes /etc/kubernetes.bak.$(date +%Y%m%d%H%M%S)注意这里备份的是整个/etc/kubernetes目录,而不仅仅是 pki。因为里面还有 manifest 文件、kubeconfig、etcd 配置等,恢复时一个目录全回来。备份完成后再继续。
4.2 给全部证书续期
kubeadm 提供了一条命令,可以一次续掉所有非 CA 证书:
sudo kubeadm certs renew all新版 kubeadm 命令是kubeadm certs renew,老版本(1.15 之前)可能是kubeadm alpha certs renew。执行成功后会看到类似输出,提示哪些证书被更新了,并且会提醒你要更新 kubeconfig 文件。
这条命令本质上是用 pki 下的 CA 密钥重新签发了 apiserver、apiserver-kubelet-client、front-proxy-client、etcd 等组件的证书。注意:它默认不会续约 CA 证书本身,因为 CA 10 年才到期,一般不需要动。如果你的 CA 也已经过期,那情况要复杂得多,后面单独说。
如果你只想续某一个,也可以指定名字:
sudo kubeadm certs renew apiserver sudo kubeadm certs renew etcd-server手动指定适合在只想修某个组件的场景下用,生产环境建议还是一把梭全续。
4.3 重新生成 admin.conf,并覆盖到本地 kubeconfig
很多人走到renew all就以为完事了,结果 kubectl 还是连不上。原因在于:kubeconfig 里嵌入的 client 证书虽然也在 renew 范围内,但kubeadm certs renew all更新的是文件系统中的 kubeconfig,而你本地的~/.kube/config是旧拷贝。
正确做法是在 master 上重新生成 kubeconfig:
sudo kubeadm kubeconfig admin --out-dir /etc/kubernetes这会覆盖/etc/kubernetes/admin.conf,然后把新文件同步到本地:
sudo cp /etc/kubernetes/admin.conf ~/.kube/config sudo chown $(id -u):$(id -g) ~/.kube/config第六个坑提醒:从 root 用户拷出来的 admin.conf 如果不改属主,kubectl 会直接报权限错误;如果本地有其他 context,用之前先kubectl config get-contexts确认当前 context。
4.4 重启控制面组件,让新证书真正生效
证书文件更新只是第一步,运行中的进程还在用旧证书。kube-apiserver 等核心组件是静态 Pod,由 kubelet 拉起,不会因为证书文件变更而自动重启。这一步一定要做。
最稳妥的方式是用crictl找到容器并删除,kubelet 检测到容器缺失会重新创建:
sudo crictl ps | grep -E 'kube-apiserver|kube-controller-manager|kube-scheduler' sudo crictl rm <容器ID>逐个删完对应容器,等待新 Pod 起来。也可以用:
sudo systemctl restart kubelet但 restart kubelet 会把所有静态 Pod 一并重建,包括 etcd,影响范围更大。如果是生产环境、多 master 节点,建议逐台操作,避免控制面同时抖动。
4.5 验证集群恢复
执行:
kubectl get nodes kubectl -n kube-system get pods重点看 API Server、Controller Manager、Scheduler、etcd 是否都恢复到 Running/Ready。如果某个组件 CrashLoopBackOff,第一时间看日志:
kubectl -n kube-system logs <pod-name> -f大部分情况下,到这里集群就恢复正常了。
5. 别等一年后再爆:证书有效期监控与自动续期
证书过期不是“突如其来的故障”,而是“早就计划好的事故”。集群跑一年左右,它必然会来。唯一的问题是:你是主动发现,还是等它爆了再被动处理。
5.1 kubeadm 自带的检查命令
新版本 kubeadm 提供了很方便的检查命令:
sudo kubeadm certs check-expiration输出大致如下:
CERTIFICATE EXPIRES RESIDUAL TIME CERTIFICATE AUTHORITY EXTERNALLY MANAGED admin.conf Feb 01, 2025 14:32 UTC 364d ca no apiserver Feb 01, 2025 14:32 UTC 364d ca no apiserver-kubelet-client Feb 01, 2025 14:32 UTC 364d ca no etcd/healthcheck-client Feb 01, 2025 14:32 UTC 364d etcd-ca no ...这一条命令就能把所有证书的到期时间、剩余时间都看一遍。把它写进巡检脚本里,比什么都放心。
5.2 写一个简单的有效期巡检脚本
可以在 master 上放一个脚本,扫描 pki 目录下所有.crt文件,找出即将过期的证书:
#!/bin/bash CERT_DIR="/etc/kubernetes/pki" WARN_DAYS=30 find "$CERT_DIR" -name "*.crt" | while read -r cert; do enddate=$(openssl x509 -enddate -noout -in "$cert" | cut -d= -f2) end_ts=$(date -d "$enddate" +%s) now_ts=$(date +%s) remain=$(( (end_ts - now_ts) / 86400 )) if [ "$remain" -lt "$WARN_DAYS" ]; then echo "[WARN] $cert 剩余 ${remain} 天到期: $enddate" else echo "[OK] $cert 剩余 ${remain} 天" fi done脚本放到 cron 里每周跑一次,配合告警通知,基本能保证到期前提前发现。实际使用中,还可以加上-checkend参数简化:
sudo find /etc/kubernetes/pki -name "*.crt" -exec openssl x509 -checkend 2592000 -noout -in {} \;这里的2592000是 30 天的秒数。如果某张证书剩余时间少于 30 天,openssl 会输出类似Certificate will expire的提示;如果输出为空,说明安全。
5.3 自动化续期方案要慎重
有人会想到直接跑 cron 定期执行kubeadm certs renew all。从技术上可行,但有两个问题:
- renew 之后必须重启控制面组件,期间 kubectl 和 API 请求可能短暂中断;
- 如果
renew命令本身执行失败(比如机器负载异常、磁盘满),没人处理反而更麻烦。
我的建议是:监控要自动化,续期操作保留人工确认,或者采用半自动脚本,先检查剩余时间,小于阈值时发出告警,由运维在变更窗口内执行renew all + 重启。别让一个坏掉的 cron 把集群搞得更乱。
5.4 顺带说一句:调大证书有效期
kubeadm 初始化时是可以通过配置扩长证书有效期的。在 kubeadm 配置文件中设置CertificateValidityPeriod和CACertificateValidityPeriod字段,比如:
apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration certificateValidityPeriod: 730h caCertificateValidityPeriod: 8760h这个字段支持在集群初始化时生效,但对已经初始化的集群不直接生效,以后要重建或升级时可以考虑。延长有效期可以减少续期频率,但并不能根治问题——轮换和监控永远需要。
6. 高频故障速查与避坑心得
处理同类故障久了,我把容易踩的坑和快速定位方法整理成一张表,遇到问题直接对照查。
| 症状 | 常见原因 | 快速定位 | 标准解法 |
|---|---|---|---|
| kubectl 报 expired | 本地 kubeconfig client 证书过期 | grep client-certificate-data 看有效期 | kubeadm certs renew all + 重新生成 admin.conf |
| apiserver 报 expired | apiserver.crt 过期 | SSH 检查 pki 目录 | renew + 重启静态 Pod |
| 集群节点 NotReady | kubelet client 证书过期 | 查看 /var/lib/kubelet/pki/kubelet-client-current.pem | 重启 kubelet,触发自动 CSR 轮换 |
| 报 unknown authority | kubeconfig 的 CA 数据与集群 CA 不匹配 | kubectl config view 对比 ca data | 重新拷贝 admin.conf |
| 报 not yet valid | 节点时间超前或漂移 | date 对比多节点时间 | 同步时间(ntp/chrony) |
| etcd 日志报过期 | etcd 证书过期 | 检查 etcd 容器日志 | kubeadm certs renew etcd-server 等 + 重启 etcd |
维护 kubeadm 集群时间久了,我有几个很深的体会:
第一,证书故障几乎没有“临时绕过”的方案。不要想着在 kubeconfig 里加insecure-skip-tls-verify: true来绕过,那是把整车安全系统关了,生产环境绝对不能碰。它只是临时掩盖问题,后面带来的隐患远大于收益。
第二,时间同步是所有证书问题的地基。任何一个节点时间不对,证书校验都可能冤枉好人。所以我建议在每台节点上都配好 chrony 或 systemd-timesyncd,并纳入监控。
第三,自动续期和人工续期的边界要想清楚。我见过有团队把 renew 写进 cron,结果证书续上了,组件没重启,问题比之前更诡异。最稳妥的做法是:自动化发现,半自动执行,人工确认变更窗口。
最后再分享一个小技巧:每次处理完证书问题,把当时的本地 kubeconfig 时间戳记下来,并记录集群所有节点的证书到期情况。等到下一次到期前,你手上有完整的备份和成熟的流程,十分钟就能完成一次平稳的证书轮换,而不是在半夜被一个 x509 报错从床上拉起来。