证书过期是 Kubernetes 集群最经典的「定时炸弹」:一个运行了 366 天的集群,可能在某天早上突然 kubelet 全部失联、kubectl 报 x509: certificate has expired,而根因只是一张一年期证书到期了。Kubernetes 的证书体系横跨控制面(apiserver、etcd、controller-manager)、节点(kubelet 客户端/服务端证书)与工作负载(ServiceAccount Token、Ingress TLS、mTLS)三层,每一层的轮换机制都不同。本文把这些证书理清,给出可执行的检查、轮换与自动化方案。
1. Kubernetes 里的证书版图
1.1 三层证书
| 层 | 证书 | 签发者 | 默认有效期 |
|---|---|---|---|
| 控制面 | apiserver 服务端证书 | 集群 CA | 1 年 |
| 控制面 | apiserver → kubelet 客户端证书 | 集群 CA | 1 年 |
| 控制面 | apiserver → etcd 客户端证书 | etcd CA | 1 年 |
| 控制面 | etcd 服务端/peer 证书 | etcd CA | 1 年 |
| 控制面 | front-proxy 客户端证书 | front-proxy CA | 1 年 |
| 节点 | kubelet 客户端证书(bootstrap) | 集群 CA | 1 年(可轮换) |
| 节点 | kubelet 服务端证书 | 自签或集群 CA | 1 年 |
| 工作负载 | ServiceAccount Token | 集群 SA key | 可配置(默认 1h 起) |
| 工作负载 | Ingress TLS / mTLS | cert-manager / 外部 CA | 90 天(ACME) |
# 一览 kubeadm 管理的所有证书与到期时间
kubeadm certs check-expiration
CERTIFICATE EXPIRES RESIDUAL TIME ...
admin.conf Oct 07, 2027 08:00 UTC 364d ...
apiserver Oct 07, 2027 08:00 UTC 364d ...
apiserver-kubelet-client Oct 07, 2027 08:00 UTC 364d ...
controller-manager.conf Oct 07, 2027 08:00 UTC 364d ...
etcd-healthcheck-client Oct 07, 2027 08:00 UTC 364d ...
etcd-peer Oct 07, 2027 08:00 UTC 364d ...
front-proxy-client Oct 07, 2027 08:00 UTC 364d ...
关键认知:kubeadm certs check-expiration 只覆盖 kubeadm 管理的证书,kubelet 客户端证书与 ServiceAccount Token 不在其中——它们由各自机制轮换。
1.2 证书链与信任关系
集群 CA(ca.crt)
├─ apiserver 服务端证书(SAN 含所有 API 地址与 LB VIP)
├─ apiserver-kubelet-client(apiserver 冒充客户端访问 kubelet)
├─ front-proxy-client(聚合层身份透传)
└─ kubelet 客户端证书(system:node:<name>)
etcd CA(独立)
├─ etcd server / peer 证书
└─ etcd-healthcheck-client
ServiceAccount key pair(RSA/ECDSA)
└─ 签发/验证 ServiceAccount Token(JWT),不是 X.509
front-proxy 是聚合层的关键:apiserver 用它作为客户端证书访问聚合 API(如 metrics-server),并依赖 --requestheader-client-ca-file 验证回来时的 X-Remote-User 头。
2. kubeadm 证书体系与轮换
2.1 检查与续期
# 检查所有证书到期时间
kubeadm certs check-expiration
# 续期全部证书(就地覆盖 /etc/kubernetes/pki 下的文件)
kubeadm certs renew all
# 续期单个证书
kubeadm certs renew apiserver
kubeadm certs renew apiserver-kubelet-client
# 续期 kubeconfig(admin.conf / kubelet.conf 里的客户端证书)
kubeadm certs renew admin.conf
kubeadm certs renew all --config=kubeadm-config.yaml # 从配置读 SAN
续期后必须重启使用该证书的组件,否则新证书不会被加载:
# 静态 Pod 部署的控制面:重启容器即可
systemctl restart kubelet # 让静态 Pod 重建
# 或直接删容器让 kubelet 重建
crictl ps | grep -E "kube-apiserver|kube-controller|kube-scheduler"
crictl rm -f <container-id>
2.2 kubeadm 的自动续期
kubeadm 在 kubeadm upgrade 时会自动续期所有证书(每次升级顺带续一年)。除此之外没有内置的自动轮换——一年后如果没做任何操作,证书就会过期。
# 升级时顺带续期(kubeadm 默认行为)
kubeadm upgrade apply v1.30.0
# 若不希望续期(罕见),可显式关闭
kubeadm upgrade apply v1.30.0 --certificate-renewal=false
运维结论:要么保证至少每年升级一次集群,要么设置定时任务主动 kubeadm certs renew all。更稳妥的做法是写一个定时脚本,在证书剩余有效期 < 90 天时自动续期并重启控制面组件:
#!/bin/bash
set -euo pipefail
DAYS_LEFT=$(kubeadm certs check-expiration | awk '/admin.conf/ {print $4}' | tr -d 'd')
[ "${DAYS_LEFT:-0}" -lt 90 ] && kubeadm certs renew all && systemctl restart kubelet
2.3 集群升级与证书的联动
证书续期与集群升级是同一件事的两面:kubeadm upgrade 会续期,而续期后的证书 SAN 必须匹配新的 API 地址。升级前请确认 ClusterConfiguration 里的 apiServer.certSANs 包含所有实际访问地址(LB VIP、域名),否则续期后 apiserver 会因为「证书不匹配」而无法被访问。升级流程详见 Kubernetes 集群升级
。
一句话:kubeadm 的证书「随升级续期」但不「自动轮换」——不升级的集群必须自己设定时续期任务,否则一年后必然集体过期。
3. kubelet 证书轮换
3.1 TLS Bootstrap 流程
kubelet 首次启动时没有客户端证书,它通过 TLS Bootstrap 向 apiserver 申请:
kubelet 启动
1. 用 bootstrap token(bootstrap-kubelet.conf 里的 token)认证
2. 提交 CSR(CertificateSigningRequest),CN=system:node:<node-name>
3. apiserver 的 CSR 控制器(或人工)批准(Approve)
4. 签发证书,kubelet 写入 kubelet.conf
5. 之后用该证书与 apiserver 通信
# 查看待批准的 CSR
kubectl get csr
kubectl certificate approve <csr-name>
# 节点加入集群时若卡在 NotReady,先查这里
kubectl get csr | grep Pending
注意:system:node 用户的 CSR 默认需要人工批准(system:certificates.k8s.io:certificatesigningrequests:nodeclient)。若要自动批准,需要给相应的 ClusterRoleBinding 授权——这是安全与便利的权衡点。
3.2 自动轮换机制
kubelet 支持客户端证书自动轮换,通过两个参数控制:
# KubeletConfiguration
rotateCertificates: true # 客户端证书轮换(默认 true)
serverTLSBootstrap: true # 服务端证书也走 CSR(默认 false)
# 检查 kubelet 配置是否开启
cat /var/lib/kubelet/config.yaml | grep -E "rotateCertificates|serverTLSBootstrap"
轮换行为:
kubelet 证书剩余有效期 < 20%(约 73 天)时:
→ 自动生成新私钥 + 提交 CSR(同名 CN)
→ 等待批准 → 拿到新证书 → 原子替换 kubelet.conf
整个过程不需要重启 kubelet
| 参数 | 默认 | 含义 |
|---|---|---|
rotateCertificates | true | 客户端证书自动轮换 |
serverTLSBootstrap | false | 服务端证书走 CSR(默认自签,1 年后过期不轮换) |
serverTLSBootstrap: false 是一个隐藏的过期风险:kubelet 的服务端证书默认自签且不轮换。apiserver 访问 kubelet(kubectl logs、kubectl exec)时会校验该证书,证书过期后这些操作会失败。生产建议开启 serverTLSBootstrap: true,并配套自动批准 selfnodeclient/selfnodeserver 的 CSR。
3.3 kubelet 证书排障
# kubelet 客户端/服务端证书信息
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -dates -subject
openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -noout -dates -subject
# 看轮换相关日志
journalctl -u kubelet | grep -iE "certificate|csr|rotat"
# 手动触发一次轮换:删除当前证书后重启 kubelet
rm /var/lib/kubelet/pki/kubelet-client-current.pem && systemctl restart kubelet
典型故障:x509: certificate has expired or is not yet valid 出现在 kubelet 日志里,说明客户端证书过期且未轮换(通常因为 rotateCertificates: false 或 CSR 长期未被批准)。恢复方式是用 bootstrap token 重新引导,或手动签发新证书。
一句话:kubelet 的客户端证书会自动轮换,但服务端证书默认不轮换——
serverTLSBootstrap: true+ CSR 自动批准,才是完整的节点证书方案。
4. ServiceAccount Token
4.1 从 Secret 到 TokenRequest
ServiceAccount Token 的机制经历过一次重要演进:
| 时期 | 机制 | 特点 |
|---|---|---|
| 旧(≤ v1.20) | 静态 Secret 里的 token 字段 | 永不过期,长期有效,泄露即长期风险 |
| 新(v1.21+) | TokenRequest API + 投影卷(projected volume) | 有过期时间,自动轮换,绑定 Pod 与 audience |
# 现代写法:Pod 自动挂载投影 Token(无需手动创建 Secret)
spec:
serviceAccountName: myapp
containers:
- name: app
volumeMounts:
- name: kube-api-access
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
readOnly: true
volumes:
- name: kube-api-access
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600 # 1 小时
audience: https://kubernetes.default.svc
- configMap: { name: kube-root-ca.crt }
4.2 自动轮换
投影 Token 的轮换是由 kubelet 自动完成的:
kubelet 监控投影 Token 的过期时间
→ 剩余 80% 生命周期(或 < 24h)时,调用 TokenRequest 申请新 Token
→ 原子替换挂载路径下的 token 文件
→ 应用需要重新读取文件才能用上新 Token
应用侧的关键要求:如果应用把 Token 读一次就缓存,轮换后会用到过期 Token。正确做法是每次使用前重新读文件,或使用支持自动重读的客户端库(如 client-go 的 NewForConfig + in-cluster 配置会周期性重读)。
安全基线:v1.24+ 集群不应再自动创建长期 Token Secret(kubectl get secrets -A --field-selector type=kubernetes.io/service-account-token 应无输出);若历史遗留了此类 Secret,应清理并改用投影卷。这与 Kubernetes 集群安全加固
的基线要求一致。
5. cert-manager
5.1 定位与安装
cert-manager 是集群内的证书控制器,把「申请、签发、续期、注入」证书变成声明式资源。它支持 ACME(Let’s Encrypt)、Vault、自建 CA、私有 PKI 等签发者。
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.15.0/cert-manager.yaml
kubectl get pods -n cert-manager
5.2 Issuer 与 ClusterIssuer
# ClusterIssuer:ACME(Let's Encrypt),DNS-01 校验
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ops@example.com
privateKeySecretRef: { name: letsencrypt-prod-account-key }
solvers:
- dns01:
route53:
region: us-east-1
accessKeyIDSecretRef: { name: route53-credentials, key: access-key-id }
# Issuer:自建 CA(用于内部服务 mTLS)
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: internal-ca
namespace: prod
spec:
ca:
secretName: internal-ca-keypair # 内含 tls.crt / tls.key
| 类型 | 用途 | 校验方式 |
|---|---|---|
| ACME(Let’s Encrypt) | 公网域名证书 | HTTP-01 / DNS-01 |
| CA | 内部 PKI | 用给定 CA 直接签 |
| Vault | 企业 PKI | Vault PKI 引擎 |
| SelfSigned | 自签(引导用) | 无 |
5.3 Certificate 资源
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: web-tls
namespace: prod
spec:
secretName: web-tls-secret # 签好的证书写进这个 Secret
duration: 2160h # 90 天
renewBefore: 720h # 到期前 30 天开始续期
subject:
organizations: ["Example Inc"]
dnsNames:
- app.example.com
- www.app.example.com
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
续期逻辑:cert-manager 在 renewBefore(本例 30 天)到达时自动重新签发并更新 Secret。Ingress Controller 会自动感知 Secret 变化并热加载新证书,无需人工介入。
5.4 Ingress 集成
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod # 自动创建 Certificate
spec:
tls:
- hosts: [app.example.com]
secretName: web-tls-secret
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port: { number: 80 }
加上那一条注解,cert-manager 就会自动生成 Certificate 并完成 ACME 校验。Ingress 与 Gateway API 的证书注入方式不同,后者用 Gateway 的 tls.certificateRefs,可参考 Ingress 与 Gateway API
。
5.5 cert-manager 排障
# 资源状态
kubectl get certificate -A
kubectl get certificaterequest -A
kubectl get order,challenge -A # ACME 专用
# 详情(Ready=False 时会说明原因)
kubectl describe certificate web-tls -n prod
kubectl describe challenge -n prod
# cert-manager 控制器日志
kubectl logs -n cert-manager deploy/cert-manager --tail=100
| 症状 | 常见原因 |
|---|---|
Ready: False,Waiting for DNS-01 challenge | DNS 传播慢 / DNS 服务商凭证错误 |
HTTP-01 challenge failed | Ingress 未就绪 / 域名未解析到 Ingress |
Order failed: rate limited | Let’s Encrypt 频率限制(同一域名每周 5 次) |
| Secret 未生成 | RBAC 不足 / issuerRef 指向不存在的 Issuer |
生产注意:先用 staging 环境(acme-staging-v02)验证,避免触发 Let’s Encrypt 生产环境的频率限制;DNS-01 比 HTTP-01 更适合通配符证书与内网场景。
6. 内部 PKI 与 mTLS
6.1 用 cert-manager 做服务间 mTLS
每个服务一份 Certificate,用短有效期降低泄露风险,usages 同时声明 server 与 client auth:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: payment-svc
spec:
secretName: payment-svc-tls
duration: 24h # 短有效期
renewBefore: 8h
commonName: payment.prod.svc.cluster.local
usages: [server auth, client auth]
issuerRef: { name: internal-ca, kind: Issuer }
6.2 与 Service Mesh 的关系
Service Mesh(Istio/Linkerd)自带独立的证书体系(Istiod 作为 CA,为每个工作负载签发短期 SVID),与集群 CA 和 cert-manager 是三套并行的 PKI:
| 场景 | 建议 |
|---|---|
| Mesh 内部 mTLS | 交给 Mesh 的 CA,不掺 cert-manager |
| Mesh 边界(Ingress/Gateway) | cert-manager 签公网证书,Gateway 挂载 |
| 非 Mesh 的服务间 mTLS | cert-manager + 内部 CA |
| 证书信任根 | 明确各套 PKI 的信任边界,避免互信混乱 |
Mesh 的证书与 mTLS 机制见 Kubernetes 服务网格 。
一句话:cert-manager 管「工作负载证书」,kubeadm 管「控制面证书」,Mesh 管「数据面 mTLS」——三套体系各司其职,混用时要先划清信任边界。
7. 监控与告警
7.1 证书过期监控
# 用 blackbox-exporter 或 ssl_exporter 探测端点证书
- alert: CertificateExpiringSoon
expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 30 # 30 天
for: 1h
labels: { severity: warning }
- alert: CertificateExpired
expr: probe_ssl_earliest_cert_expiry - time() < 0
labels: { severity: critical }
7.2 cert-manager 指标
certmanager_certificate_expiration_timestamp_seconds # 证书到期时间戳
certmanager_certificate_ready_status # Ready 状态(0/1)
certmanager_certificate_ready_status{condition="False"} == 1 持续 30 分钟即应告警——它意味着某个 Certificate 的签发或续期已经失败。
7.3 关键节点证书的检查清单
# 控制面证书(kubeadm 集群)
kubeadm certs check-expiration
# kubelet 客户端/服务端证书
openssl x509 -in /var/lib/kubelet/pki/kubelet-client-current.pem -noout -enddate
openssl x509 -in /var/lib/kubelet/pki/kubelet.crt -noout -enddate
# etcd 证书
openssl x509 -in /etc/kubernetes/pki/etcd/server.crt -noout -enddate
# Ingress 证书
kubectl get secrets -A --field-selector type=kubernetes.io/tls
| 检查项 | 频率 | 告警线 |
|---|---|---|
| 控制面证书剩余有效期 | 每日 | < 90 天 |
| kubelet 证书剩余有效期 | 每日 | < 30 天 |
| cert-manager Certificate Ready | 实时 | False 持续 30 分钟 |
| APIService caBundle | 每月 | 与对应 Secret 一致 |
| 上游 DNS/网关证书 | 实时 | < 30 天 |
8. 小结
| 层 | 证书 | 轮换机制 | 关键动作 |
|---|---|---|---|
| 控制面 | apiserver/etcd/kubeconfig | kubeadm 随升级续期 | 每年升级或定时 certs renew all |
| 节点 | kubelet 客户端 | rotateCertificates: true 自动 | 确保 CSR 能被批准 |
| 节点 | kubelet 服务端 | 默认自签不轮换 | 开 serverTLSBootstrap: true |
| 工作负载 | ServiceAccount Token | 投影卷 + kubelet 自动 | 应用每次使用前重读文件 |
| 工作负载 | Ingress / mTLS | cert-manager renewBefore | 装 exporter 做到期告警 |
| 数据面 | Mesh SVID | Mesh CA 自动 | 与集群 PKI 划清信任边界 |
一句话记住:Kubernetes 的证书管理是「三套体系 + 一个告警」——kubeadm 管控制面(随升级续期)、kubelet 管节点(客户端自动、服务端要显式开启)、cert-manager 管工作负载(声明式 + 自动续期),而贯穿三者的唯一保险是证书到期监控。把所有证书的剩余有效期变成 Prometheus 指标并设置 90/30/7 天三级告警,就能把「某天早上集群全挂」这类事故彻底消除在发生之前。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。