Kubernetes 基础体系 · 第 34/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes 证书与 TLS:集群 PKI、cert-manager、轮换和故障
Kubernetes 中的“证书”并不是一种东西。控制平面组件使用的客户端证书、Ingress 对外提供的服务器证书、kubelet 的身份凭据、ServiceAccount 的 Token、etcd 的双向 TLS 证书,都属于不同的信任关系和生命周期。
如果把这些对象混为一谈,常见结果包括:
- 把 ServiceAccount Token 当成 TLS 证书;
- 只更新了 Ingress 的 Secret,却忘记客户端是否信任新的 CA;
- 证书文件已经更新,但进程仍然使用旧证书;
- 证书域名正确,却因为证书链不完整导致公网客户端失败;
- cert-manager 显示
Ready=True,但实际流量仍由另一个 Ingress Controller 终止 TLS; - 直接删除 Secret 触发了意外的重新签发、限流或服务中断。
本文先建立 TLS 和 PKI 的基础模型,再说明 Kubernetes 集群 PKI、cert-manager 的状态流转、证书轮换机制,以及如何诊断生产故障。
TLS、PKI 和证书分别解决什么问题
TLS 解决的是通信机密性和端点身份
TLS 通常同时提供三项能力:
- 机密性:通信内容经过加密,旁路观察者不能直接读取;
- 完整性:通信内容被修改后能够被检测;
- 身份认证:客户端可以验证自己连接到的服务器是否拥有某个受信任身份。
HTTPS 是 HTTP 运行在 TLS 之上。Kubernetes API Server、etcd、kubelet、Webhook、Ingress Controller 之间也可以使用 TLS,但它们的认证方式和信任范围可能不同。
TLS 服务器证书通常包含:
- 公钥;
- 证书主题和扩展;
- Subject Alternative Name,简称 SAN;
- 签发者;
- 有效期;
- 用途约束,例如
serverAuth; - CA 的数字签名。
现代 TLS 客户端主要依据 SAN 检查主机名,而不是依赖证书的 Common Name。比如客户端访问:
https://api.example.com
证书中至少应包含:
X509v3 Subject Alternative Name:
DNS:api.example.com
如果证书只包含 www.example.com,即使它由受信任 CA 签发,访问 api.example.com 仍应失败。
PKI 是管理信任关系的一套体系
PKI,Public Key Infrastructure,公钥基础设施,不只是“生成一个证书”。它至少包括:
- CA,即 Certificate Authority,证书颁发机构;
- 根 CA 或中间 CA;
- 证书签发和吊销流程;
- 私钥保护;
- 证书分发;
- 信任根分发;
- 轮换和失效处理。
证书验证可抽象为一条链:
服务器证书
↓ 被中间 CA 签发
中间 CA 证书
↓ 被根 CA 签发
根 CA
↓ 已安装在客户端信任库
客户端信任库
客户端接受服务器证书,通常需要同时满足:
其中:
SignatureValid:证书签名链验证通过;NameMatch:访问的主机名与 SAN 匹配;TimeValid:当前时间位于NotBefore和NotAfter之间;KeyUsageValid:证书用途允许服务器认证;TrustedChain:链最终连接到客户端信任的 CA。
这也解释了几个经常混淆的现象:
- CA 可信,但域名不匹配:仍然失败;
- 域名匹配,但 CA 不可信:仍然失败;
- 服务器证书正确,但没有发送中间证书:部分客户端失败;
- 证书没有过期,但节点时钟错误:可能被判断为尚未生效或已过期。
私钥和证书是两个不同对象
证书包含公钥,不包含私钥。TLS 服务器必须同时拥有:
tls.crt # 证书,通常是 PEM 编码
tls.key # 与证书公钥匹配的私钥
可以使用 OpenSSL 检查二者是否匹配:
openssl x509 -in tls.crt -pubkey -noout \
| openssl pkey -pubin -outform DER \
| sha256sum
openssl pkey -in tls.key -pubout \
| openssl pkey -pubin -outform DER \
| sha256sum
两个摘要应相同。若不同,通常表现为 Ingress Controller 加载证书失败、回退到默认自签名证书,或者启动时直接报错。
Kubernetes 集群 PKI:多个 CA,而不是一个“集群证书”
Kubernetes 本身不要求整个集群只有一个 CA。实际集群通常存在多组独立的信任域。
一个典型控制平面可以抽象为:
flowchart LR
K[kubectl / 客户端] -- 客户端证书或其他认证 --> API[kube-apiserver]
API -- TLS --> K
API -- mTLS --> E[etcd]
API -- TLS 客户端认证 --> KS[kubelet]
API -- HTTPS 调用 --> WH[Admission Webhook]
C[controller-manager] -- 客户端证书 --> API
S[scheduler] -- 客户端证书 --> API
F[front-proxy-client] -- 客户端证书 --> API
图中的每条 TLS 边都可能使用不同的 CA 和证书用途。即使某个证书由“集群 CA”签发,也不代表它可以被所有组件接受。
kube-apiserver 的主要 TLS 身份
API Server 至少需要面对两类身份:
- 服务器身份:让
kubectl、kubelet 或其他客户端验证自己连接的是正确 API Server; - 客户端身份:让 API Server 验证 kubelet、controller-manager、scheduler 等客户端。
典型证书包括:
- API Server serving certificate;
- API Server 用于访问 kubelet 的客户端证书;
- controller-manager 客户端证书;
- scheduler 客户端证书;
- kubelet 客户端证书;
- front-proxy 客户端证书。
不同部署工具的文件路径和命名不同。使用 kubeadm 创建的集群通常会在 /etc/kubernetes/pki 下看到若干证书,但这些路径属于 kubeadm 的常见实现,不是 Kubernetes API 的规范保证。
etcd 使用独立的高敏感度信任关系
etcd 保存 Kubernetes 的核心状态,包括 Secret、ConfigMap、Deployment、RBAC 对象等。生产环境通常要求:
- API Server 到 etcd 使用 TLS;
- etcd 节点之间使用 peer TLS;
- etcd 客户端证书和 peer 证书分离;
- etcd 私钥受到严格保护;
- etcd 数据盘和备份受到访问控制。
etcd 证书错误的影响通常比单个业务 Ingress 更严重,因为 API Server 可能完全无法读取或写入集群状态。
典型故障链如下:
etcd 客户端证书过期
↓
kube-apiserver 无法连接 etcd
↓
kubectl get 可能超时或返回 500
↓
控制器无法读取对象,也无法执行修复动作
因此,当 API Server 本身不可用时,依赖 Kubernetes API 的 kubectl、cert-manager Controller 和 Ingress Controller 都可能无法帮助恢复,必须转到控制平面主机、静态 Pod 日志或 etcd 级别排查。
ServiceAccount Token 不是 TLS 证书
ServiceAccount 用于 Kubernetes API 的身份认证。现代 Kubernetes 通常通过 TokenRequest API 为 Pod 获取短期、带受众和绑定信息的 Token。它是 JWT 或类似 Bearer Token 的凭据,不是用于完成 TLS 服务器认证的 X.509 证书。
两者的区别是:
| 凭据 | 主要用途 | 是否用于 TLS 握手 |
|---|---|---|
| X.509 证书和私钥 | 服务器身份或客户端证书认证 | 是 |
| ServiceAccount Token | 向 API Server 证明 Kubernetes 身份 | 通常不是 |
| OIDC Token | 用户或工作负载身份 | 不是证书 |
| CA 证书 | 验证对端证书签名链 | 不代表自身身份 |
因此,Pod 能够使用 ServiceAccount Token 调用 API,并不意味着它能够通过 TLS 访问任意内部服务。
Kubernetes Secret 如何承载 TLS 材料
Kubernetes 的 kubernetes.io/tls Secret 通常包含:
apiVersion: v1
kind: Secret
metadata:
name: web-tls
namespace: demo
type: kubernetes.io/tls
data:
tls.crt: <base64-encoded-certificate-chain>
tls.key: <base64-encoded-private-key>
这里的 data 值是 Base64,不是加密。Base64 只是一种编码,因此下面的命令可以解码私钥:
kubectl -n demo get secret web-tls \
-o jsonpath='{.data.tls.key}' | base64 -d
访问权限应由 RBAC 控制:
kubectl auth can-i get secret/web-tls \
--namespace demo \
--as system:serviceaccount:demo:default
该命令回答的是“这个身份是否有权限读取 Secret”,而不是“证书是否有效”。
etcd 加密不等于访问控制
Kubernetes Secret 默认存储在 API 对象中,API Server 写入 etcd。生产环境通常应配置静态数据加密,例如使用 EncryptionConfiguration 和 KMS provider。
但必须区分:
- RBAC:限制谁可以通过 API 读取 Secret;
- etcd 静态加密:降低直接读取 etcd 数据文件或备份时的暴露风险;
- KMS:把数据密钥相关操作交给外部密钥管理系统;
- 节点和备份保护:限制能访问磁盘、快照和备份的人。
如果攻击者已经拥有能够读取 Secret 的 Kubernetes 权限,etcd 加密不会阻止该 API 读取。相反,如果攻击者直接拿到未保护的 etcd 备份,RBAC 也不能保护其中的明文。
Secret 治理还必须考虑私钥泄漏后的恢复:删除 Secret、重新签发证书并不能让已经泄漏的私钥失效。对于支持吊销的外部 CA,需要执行吊销或等待短生命周期凭据过期;对于自建 CA,则通常需要更换密钥并更新信任链。
Ingress 中的 TLS:谁终止连接,谁必须拥有私钥
Ingress 是 Kubernetes 的路由 API。Ingress 对象只描述期望状态,真正监听端口、执行 TLS 握手和转发请求的是 Ingress Controller。
一个最小的 HTTPS Ingress 如下:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: web-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
这里的因果关系是:
- 客户端通过 DNS 找到 Ingress Controller 的入口地址;
- 客户端发送带有 SNI
app.example.com的 TLS ClientHello; - Controller 根据 SNI 选择
demo/web-tls; - Controller 使用
tls.key完成服务器证明; - TLS 建立后,Controller 根据 HTTP Host 和路径路由到 Service;
- Service 再转发到后端 Pod 的端口 80。
secretName 必须指向同一 namespace 中的 Secret。Ingress 不能直接引用其他 namespace 的 Secret。若多个 Controller 同时运行,还必须确认 ingressClassName 与实际 Controller 一致。
TLS 终止位置决定证书的管理边界
常见模式有三种:
客户端 --TLS-- Ingress Controller --HTTP-- Service --HTTP-- Pod
此时 Ingress Controller 持有服务器私钥。
客户端 --TLS-- Ingress Controller --TLS-- Service --HTTP/HTTPS-- Pod
此时外部证书在入口终止,入口到后端又建立一条独立 TLS 连接。后端证书、后端信任 CA 和客户端验证策略都需要单独配置。
客户端 --TLS Passthrough-- Pod
此时 Ingress Controller 不解密应用层 TLS,证书必须由后端服务持有,且路由能力受 Controller 实现限制。不能因为 Ingress 对象包含 tls 字段,就假定一定发生 TLS Passthrough。
Gateway API 也遵循同一个基本原则:Gateway 的 TLS 配置描述监听器如何处理 TLS,实际证书由实现 Gateway 的 Controller 加载。Gateway API 的具体版本、TLS 终止模型、跨 namespace 引用和策略机制依赖所安装的 Gateway API 版本及 Controller 实现,不能把 Ingress Controller 的行为直接推断为所有 Gateway 实现的行为。
cert-manager 的组件和状态模型
cert-manager 是 Kubernetes 上的证书生命周期控制器。它不是 CA 本身;它负责根据声明式资源向 CA 请求证书、保存结果、监测有效期并在需要时续期。
常见资源包括:
Issuer:namespace 范围内的签发器;ClusterIssuer:集群范围的签发器;Certificate:对证书内容、域名和目标 Secret 的声明;CertificateRequest:一次具体的证书申请;Order:ACME 订单;Challenge:ACME HTTP-01 或 DNS-01 验证挑战。
典型状态流转如下:
stateDiagram-v2
[*] --> CertificateCreated
CertificateCreated --> RequestCreated: cert-manager 生成 CertificateRequest
RequestCreated --> PendingCA: Issuer 接受请求
PendingCA --> Challenge: ACME 需要域名验证
Challenge --> OrderReady: HTTP-01/DNS-01 验证成功
Challenge --> Failed: 验证失败或超时
OrderReady --> SecretWritten: CA 签发证书
SecretWritten --> Ready: Secret 内容有效
Ready --> RenewalScheduled: 接近续期窗口
RenewalScheduled --> RequestCreated: 创建新的申请
Failed --> RequestCreated: 修复后重试
这里有一个重要边界:Certificate 的 Ready=True 只表示 cert-manager 认为目标证书已成功签发并写入目标 Secret。它不保证:
- Ingress Controller 已加载新 Secret;
- DNS 已经指向该入口;
- 客户端信任签发 CA;
- 后端路由正确;
- 所有副本都已经重新载入证书。
Issuer 和 ClusterIssuer
自签名测试环境可以使用 SelfSigned,但它不适合直接作为公网信任链:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: local-selfsigned
spec:
selfSigned: {}
然后声明一个证书:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: web-cert
namespace: demo
spec:
secretName: web-tls
dnsNames:
- app.example.test
issuerRef:
name: local-selfsigned
kind: ClusterIssuer
应用并检查:
kubectl apply -f issuer.yaml
kubectl apply -f certificate.yaml
kubectl -n demo get certificate web-cert
kubectl -n demo describe certificate web-cert
kubectl -n demo get secret web-tls
在这个例子中,客户端默认不会信任自签名证书。可以显式取出 CA 或证书进行测试:
kubectl -n demo get secret web-tls \
-o jsonpath='{.data.tls.crt}' | base64 -d > tls.crt
openssl x509 -in tls.crt -noout -subject -issuer -dates -ext subjectAltName
自签名签发器更适合开发、内部实验或作为更完整 PKI 的起点。生产环境需要明确谁是信任根、信任根如何分发,以及根 CA 私钥如何保护。
ACME、HTTP-01 和 DNS-01 的完整因果链
以 ACME CA 为例,cert-manager 不能凭空证明自己控制某个域名。它需要完成 CA 指定的挑战。
HTTP-01
HTTP-01 的验证路径通常是:
CA 验证器
↓ HTTP 请求
http://app.example.com/.well-known/acme-challenge/<token>
↓ DNS 和入口
Ingress Controller
↓
cert-manager 创建的临时挑战路由
↓
acmesolver Pod 返回 token
前置条件包括:
- 公网 DNS 将域名解析到正确入口;
- CA 能从公网访问 80 端口;
- 入口不会把挑战请求强制改写到错误位置;
- NetworkPolicy、防火墙和 WAF 不会阻断挑战;
- 使用正确的 IngressClass;
- 多个 Ingress Controller 不会争抢同一个挑战路由。
HTTP-01 不能验证任意端口,也不能在 CA 无法访问私网地址时工作。
DNS-01
DNS-01 要求 cert-manager 在权威 DNS 中创建 TXT 记录:
_acme-challenge.app.example.com TXT "<token>"
CA 查询该 TXT 记录并验证控制权。DNS-01 的优势是可以申请通配符证书,例如:
*.example.com
但它需要 DNS API 权限。若把拥有整个 DNS Zone 写权限的长期凭据放进一个普通 Secret,证书自动化就可能变成域名基础设施接管路径。应尽量使用最小权限、短期凭据或外部密钥系统,并区分测试和生产 DNS Zone。
通过 Ingress 注解自动创建 Certificate
cert-manager 可以通过 Ingress Shim 根据注解创建证书资源,示例:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: demo
annotations:
cert-manager.io/cluster-issuer: public-acme
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: web-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
这里 tls.hosts 和 rules.host 应保持一致。注解只是触发声明生成的便捷机制;复杂场景更适合直接管理 Certificate,因为其域名、签发器、私钥策略和续期参数更明确。
证书轮换不是一个动作,而是多个对象的协同更新
一次完整轮换通常涉及:
新私钥生成
↓
生成 CSR
↓
CA 签发新证书
↓
写入 Secret
↓
Ingress / Webhook / 应用观察到变化
↓
进程重新加载证书
↓
新连接使用新证书
↓
旧连接按原有 TLS 会话继续或关闭
其中任一步都可能产生“对象已更新但业务未更新”的中间状态。
cert-manager 的续期窗口
证书有有效期 NotBefore 到 NotAfter。cert-manager 会在接近过期时进入续期窗口。具体默认值和边界行为可能随 cert-manager 版本变化,生产配置应显式设置并查看当前版本文档;不要只根据某个旧版本的默认值推断行为。
示例:
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: web-cert
namespace: demo
spec:
secretName: web-tls
dnsNames:
- app.example.com
duration: 2160h # 90 天
renewBefore: 720h # 提前 30 天
privateKey:
rotationPolicy: Always
issuerRef:
name: public-acme
kind: ClusterIssuer
rotationPolicy: Always 表示续期时生成新的私钥,而不是只替换证书。新的私钥降低长期密钥复用风险,但也要求所有读取该 Secret 的组件能够正确处理证书和私钥同时变化。
为什么 Secret 更新后服务仍返回旧证书
Secret 更新不等于进程已经加载新证书,常见实现有三种:
- Controller 监听 Secret,动态调用 TLS 配置更新;
- Controller 监听 Secret,但需要 worker 重载;
- 进程只在启动时读取文件,必须重启。
Kubernetes Secret 以 Volume 挂载时,kubelet 通常会通过投影更新文件,但应用是否重新读取文件取决于应用实现。若使用环境变量注入 Secret,Pod 启动后环境变量不会自动变化,通常必须重建 Pod。
因此应分别验证:
# 验证 Kubernetes 中 Secret 的证书
kubectl -n demo get secret web-tls \
-o jsonpath='{.data.tls.crt}' | base64 -d \
| openssl x509 -noout -serial -dates -subject
# 验证入口实际对外提供的证书
openssl s_client \
-connect app.example.com:443 \
-servername app.example.com \
-showcerts </dev/null 2>/dev/null \
| openssl x509 -noout -serial -dates -subject -issuer
如果二者序列号不同,问题通常位于 Secret 到 Controller 的同步或加载环节,而不是 CA 签发环节。
集群内部证书轮换
kubelet 客户端证书轮换
kubelet 可以使用 TLS Bootstrap 获取初始客户端凭据,并在满足配置和权限条件时自动轮换客户端证书。轮换依赖:
- kubelet 配置启用相应的客户端证书轮换能力;
- 集群允许对应 CSR 请求;
- CSR 被自动或人工批准;
- kubelet 能够访问 API Server;
- 新证书被正确写入并被 kubelet 使用。
可以查看 CSR:
kubectl get csr
kubectl describe csr <csr-name>
不能把 kubelet 客户端证书轮换自动推断为 kubelet serving certificate 轮换。客户端证书用于 kubelet 访问 API Server;serving certificate 用于 API Server 或其他客户端访问 kubelet 的 HTTPS 端点,两者是不同方向的身份。
kubeadm 集群中的控制平面证书
kubeadm 提供检查和续期命令,例如:
kubeadm certs check-expiration
在合适的维护窗口执行续期通常类似:
kubeadm certs renew apiserver
实际可续期的证书名称、配置方式和所需重启动作取决于 kubeadm 版本和部署结构。证书文件更新后,静态 Pod 进程是否自动重启也取决于 kubelet 对 manifest 或文件变化的检测方式;不要只看到文件时间变化就认为进程已使用新证书。
应在维护前确认:
- 当前 kubeadm 版本;
- 证书由哪个组件生成和管理;
- 控制平面是否高可用;
- 是否需要在每个控制平面节点分别执行;
- 证书 SAN 是否仍覆盖实际访问地址;
- 备份和回滚路径是否可用。
CA 轮换比叶子证书轮换困难
叶子证书轮换通常只替换服务证书和私钥;CA 轮换会影响整个信任图。
安全的 CA 迁移通常需要“双信任窗口”:
旧叶子证书 ← 旧 CA
新叶子证书 ← 新 CA
客户端临时同时信任:旧 CA + 新 CA
服务逐步切换到新叶子证书
所有旧证书和旧 CA 使用者清理完成
客户端移除旧 CA
如果先移除旧 CA,再替换所有叶子证书,旧连接或未完成更新的组件会立即失去信任。如果直接替换根 CA 而没有兼容窗口,API Server、kubelet、Webhook、etcd 和控制器可能同时出现认证失败。
Kubernetes 没有一个适用于所有发行版和所有组件的“一键 CA 轮换”语义。云厂商托管控制平面的 CA 轮换还可能完全由平台控制,用户只能根据平台提供的 kubeconfig、信任包或迁移窗口操作。
故障诊断:先确定失败发生在哪一层
TLS 故障应按照连接路径分层,而不是一开始就删除 Secret。
第一层:DNS 和 TCP
dig +short app.example.com
nc -vz app.example.com 443
如果 DNS 指向错误入口,或者 443 根本不可达,证书配置尚未成为主要问题。
第二层:TLS 握手和 SNI
openssl s_client \
-connect app.example.com:443 \
-servername app.example.com \
-verify_return_error \
-showcerts </dev/null
重点查看:
subject和 SAN 是否包含目标域名;issuer是否符合预期;Verify return code;- 返回的证书链是否包括中间 CA;
- 是否因为 SNI 返回了默认服务器证书;
- TLS 版本和密码套件是否被策略拒绝。
用 IP 直接连接而不提供 -servername,经常只能拿到默认站点证书,不能据此判断基于域名访问是否正确。
第三层:Kubernetes 对象和事件
kubectl -n demo describe certificate web-cert
kubectl -n demo get certificaterequest,order,challenge
kubectl -n demo get events --sort-by=.lastTimestamp
对于 ACME:
Challenge长期Pending:通常是 DNS、入口、网络或权限问题;ChallengeFailed:查看失败原因和 CA 返回信息;Order未完成:可能是某个 Challenge 尚未通过;Certificate未 Ready:再检查目标 Secret、Issuer 和签发器日志。
检查签发器:
kubectl describe clusterissuer public-acme
kubectl -n cert-manager logs deploy/cert-manager
具体 Deployment 名称和命名空间可能因安装方式不同而变化,应先执行:
kubectl get pods -A | grep cert-manager
第四层:Ingress Controller 是否真的加载了 Secret
kubectl get ingressclass
kubectl -n demo describe ingress web
kubectl -n <controller-namespace> logs deploy/<controller-deployment>
重点排查:
ingressClassName是否匹配;- Secret 是否在 Ingress 同一 namespace;
- Secret 类型和字段名是否正确;
- 证书与私钥是否匹配;
- Controller 是否报告 reload 错误;
- 是否有多个 Controller 对同一 Ingress 产生竞争;
- 云厂商 Load Balancer 是否在 Controller 之外终止了 TLS。
一个特别常见的错误是:cert-manager 成功更新了 demo/web-tls,但公网 TLS 实际由云负载均衡器终止,而云负载均衡器使用的是另一份托管证书。此时检查 Kubernetes Secret 永远不会解释公网看到的证书。
第五层:客户端信任
内部 CA 或自签名 CA 的故障,经常表现为:
x509: certificate signed by unknown authority
这不是服务端证书一定错误,而是客户端信任库中没有对应 CA,或者服务端没有发送完整中间链。应明确:
- 谁是 TLS 客户端;
- 客户端使用哪个 CA Bundle;
- CA Bundle 是否已通过 ConfigMap、Webhook 配置、应用参数或操作系统信任库分发;
- 访问的是哪个主机名;
- 服务端发送了哪些证书。
Admission Webhook 还存在额外信任关系:API Server 调用 Webhook 时,需要使用 Webhook 配置中的 caBundle 或实现规定的信任来源验证 Webhook 服务证书。Webhook 证书轮换而 caBundle 未同步时,业务 Pod 可能无法创建或更新,表现为 API 请求被拒绝,而不是普通 HTTPS 页面错误。
常见反例和错误修复
只更新 tls.crt,不更新 tls.key
这会产生公私钥不匹配。正确的轮换单位是证书和私钥的对应集合,不能把二者分别当作独立配置更新。
证书包含 Common Name,但没有正确 SAN
现代客户端按 SAN 校验主机名。修复方式是重新签发包含准确 DNS SAN 的证书,而不是修改客户端跳过验证。
把证书链完整性误认为 CA 信任问题
服务端通常应发送叶子证书和必要的中间 CA,但不一定发送根 CA。客户端需要预置根 CA。缺少中间 CA 时,某些客户端能通过缓存或 AIA 补齐,另一些客户端不能,因此会出现环境相关故障。
删除 Secret 作为第一步修复
删除 Secret 可能触发重新签发,但也可能导致:
- Ingress 暂时没有可用证书;
- ACME CA 触发请求频率限制;
- 旧的有效证书被提前移除;
- 依赖该 Secret 的应用启动失败;
- cert-manager 需要重新创建多个中间对象。
更安全的顺序是先保留现场:
kubectl -n demo get secret web-tls -o yaml > web-tls.backup.yaml
kubectl -n demo describe certificate web-cert > certificate.describe.txt
kubectl -n demo get events --sort-by=.lastTimestamp > events.txt
然后判断是签发失败、Secret 内容错误、Controller 未加载,还是公网入口使用了另一份证书。
使用 curl -k 判断 TLS 正常
-k 会跳过证书验证,只能说明 TCP 和部分 TLS 握手可能成功,不能证明身份认证正确。诊断时可以临时使用它区分“握手失败”和“信任失败”,但不能把成功结果作为生产正确性的证据。
把集群内部 Service DNS 写进公网证书
例如证书只包含:
web.demo.svc.cluster.local
它只能覆盖使用该名称访问的场景,不能覆盖 app.example.com。证书 SAN 必须与实际客户端连接时使用的主机名一致;Ingress 外部名称和 Pod 内部 Service 名称通常是两套不同身份。
生产设计中的边界和取舍
公网入口通常使用公共 ACME CA,因为公网客户端已经信任其根证书。内部服务则可能使用企业 CA、SPIFFE 等工作负载身份系统或服务网格自己的证书体系。选择哪种方式取决于客户端范围、域名控制权、网络可达性和信任根分发能力。
证书有效期越短,泄漏后的暴露窗口通常越小,但轮换系统必须更可靠。短期证书需要同时具备:
- 可观测的续期状态;
- CA 和 DNS API 限流处理;
- Controller 可用性;
- 应用热加载或可控重启;
- 旧证书和新证书的兼容窗口;
- 轮换失败告警。
告警不应只监控 NotAfter。至少还应监控:
Certificate是否Ready=True;- 距离过期剩余时间;
CertificateRequest、Order、Challenge是否长期 Pending;- Secret 的资源版本是否持续变化;
- 入口实际返回证书的序列号和过期时间;
- Webhook、kubelet、API Server、etcd 的证书错误日志。
最后必须区分三个问题:
证书是否已签发?
Secret 是否包含正确材料?
实际服务是否正在使用并被客户端信任?
这三个答案可以分别是“是、是、否”。例如 cert-manager 已经签发新证书,Secret 也已更新,但 Ingress Controller 没有 reload;或者入口已经使用新证书,但客户端没有新的内部 CA。只有沿着“签发—存储—加载—握手—验证”完整链路检查,才能准确定位 Kubernetes 证书与 TLS 故障。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:Kubernetes Service Mesh:Sidecar、mTLS、流量治理、遥测和边界
- 下一篇:Kubernetes 网络排障:DNS、Service、CNI、MTU、Conntrack 和抓包
- 延伸:Ingress 与 Gateway API:路由、TLS、Controller、策略和迁移
- 延伸:Kubernetes Secret 治理:etcd 加密、KMS、External Secrets、轮换和泄漏
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论