在 Kubernetes 集群里对外暴露 HTTPS 服务时,最容易被忽视的环节就是证书管理。很多团队在《K8s Ingress 实战》里手动贴上一张 Let’s Encrypt 证书,三个月后因为忘记续期导致全站 502。cert-manager 正是为解决这个痛点而生:它作为 K8s 的原生控制器,自动完成证书的签发、部署与续期,让你彻底告别手动 renew。本文从核心概念讲到生产级部署,带你把 TLS 证书变成”会自己续命”的资源。
一、为什么需要 cert-manager
在没有 cert-manager 的集群里,HTTPS 证书往往是一条”一次性流水线”:用 certbot 申请,把 fullchain.pem 复制进 Secret,再在 Ingress 里引用。问题很快暴露——证书 90 天就过期,谁来续?多域名、多命名空间时,Secret 散落各处,更新一次要改 N 个地方。一旦漏掉一个,业务就因为证书过期而中断。
我们曾遇到过真实案例:一个边缘站点只在上线时手工贴了一张证书,运维同学离职后没人接手续期。三个月后的某个深夜,证书悄然过期,网关返回 ERR_CERT_DATE_INVALID,移动端 App 直接拒绝连接,告警炸了半小时才定位到是证书而非代码问题。这类”静默过期的定时炸弹”,正是 cert-manager 要根除的。
cert-manager 把这件事变成了声明式资源:你只描述”我需要一张 example.com 的证书”,控制器负责向 CA 申请、写入 Secret、在到期前自动续期,并热更新到 Ingress。证书从运维负担变成了和 Deployment 一样的”基础设施即代码”。
二、cert-manager 核心概念
理解三个 CRD 就够了:Issuer / ClusterIssuer 定义”证书从哪里签发”,Certificate 定义”我要哪张证书”,最终产物是 K8s 的 Secret(tls.crt + tls.key),供 Ingress 引用。
底层依赖的是 ACME 协议:cert-manager 的 controller 通过 Informer 监听 Certificate 等资源的变化,进入调和(reconcile)循环——对比期望的 dnsNames 与实际 Secret 内容,不一致就向 CA 发起订单(order),完成挑战(challenge),下载证书并写回 Secret,再触发 Ingress 热加载。整个过程幂等,反复执行不会产生副作用,所以你可以放心地把它交给 GitOps 持续调和。
2.1 Issuer 与 ClusterIssuer 的区别
| 对比项 | Issuer | ClusterIssuer |
| 作用范围 | 仅当前命名空间 | 全集群可见 |
| 适用场景 | 多租户各自管理 CA 账号 | 统一公共 CA(如 Let’s Encrypt) |
| 引用方式 | 同命名空间直接写名称 | 任意命名空间写名称 |
实战中,公共 CA 几乎总是用 ClusterIssuer,避免每个命名空间重复定义。私有内部 CA 才用 Issuer 隔离租户。
2.2 Certificate 资源如何工作
当你创建一个 Certificate,cert-manager 会:① 比对 Secret 中的证书是否匹配 dnsNames;② 不匹配或即将过期时,通过引用的 Issuer 向 CA 发起签发;③ 把返回的证书写回 SecretName 指定的 Secret。后续续期完全自动化,无需人工干预。
三、安装 cert-manager
3.1 用 Helm 安装(推荐)
helm repo add jetstack https://charts.jetstack.io
helm repo update
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--version v1.16.1 \
--set crds.enabled=true
3.2 用 kubectl 安装
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.16.1/cert-manager.yaml
kubectl get pods -n cert-manager
安装完成后应看到 cainjector、controller、webhook 三个组件 Running。webhook 负责校验资源,缺它会导致 Certificate 创建失败。
版本选择上有两点提醒:一是 CRD 必须与 controller 版本匹配,升级时先 apply 新版 CRD 再升级 helm release,否则会出现 “no matches for kind Certificate” 这类报错;二是 webhook 在大规模集群里偶尔会因 API 延迟触发超时,可通过 --kube-api-qps 调高 QPS。保持每年至少一次小版本升级,避免欠账过多。
四、配置 Let’s Encrypt 签发器
4.1 先区分 staging 与 production
Let’s Encrypt 对生产环境有严格的签发频率限制(同一域名每周最多 5 张重复证书)。因此务必先用 staging 环境跑通流程,确认无误再切 production,否则容易触发限流被封好几天。
4.2 HTTP01 挑战的 ClusterIssuer
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
五、为 Ingress 自动签发证书
这是最常用的场景。相比在《K8s Ingress 实战:NGINX 暴露服务与 TLS》里手动创建 Secret 再挂载,cert-manager 只需在 Ingress 上加一行注解,证书就会自动建好并续期。
5.1 在 Ingress 上声明证书
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
apply 之后,cert-manager 会创建名为 app-tls 的 Secret,并把证书注入 Ingress。HTTP01 挑战期间它会临时生成一个 cm-acme-http-solver 的 Pod 和 Ingress 来完成校验,完成后自动清理。
拿到 Secret 后,建议用 openssl 验证证书链是否完整、域名是否匹配,避免”证书能访问但中间证书缺失”导致部分客户端不信任:
kubectl get secret app-tls -o jsonpath='{.data.tls\.crt}' | base64 -d > fullchain.pem
openssl x509 -in fullchain.pem -noout -text | grep -E "Subject|DNS|Not After"
openssl verify -CAfile fullchain.pem fullchain.pem
六、自动续期原理与监控
cert-manager 默认在证书到期前 30 天进入续期窗口,重新向 CA 申请并替换 Secret,Ingress 滚动加载新证书,业务无感知。你可以用以下命令观察状态:
若要接入 Prometheus,cert-manager 自带指标如 certmanager_certificate_expiration_timestamp_seconds,配合一条告警规则就能在证书剩余不足 21 天时提前通知,而不是等它真的过期。Grafana 官方社区也有现成的证书看板可一键导入。
kubectl get certificate -A
kubectl describe certificate app-tls
kubectl get secret app-tls -o yaml
6.1 常见问题排查
| 现象 | 根因 | 解法 |
| Certificate 长期 Ready=False | 看 describe 的 Events | HTTP01 需 Ingress controller 公网可达 |
| 签发报 rate limit | 重复申请触发限流 | 改用 staging 验证,等窗口恢复 |
| DNS01 卡在 Presenting | DNS API token 权限不对 | 检查 Secret 中的 key 与权限 |
七、进阶:DNS01 挑战与泛域名
当域名无法在 80 端口暴露(如内网、CDN 回源),或你需要通配符 *.example.com 时,HTTP01 就不够用了,改用 DNS01:cert-manager 通过 DNS provider 的 API 自动添加 TXT 校验记录。
| 维度 | HTTP01 | DNS01 |
| 适用 | 公网可访问的域名 | 泛域名、内网、CDN |
| 挑战方式 | 在 80 端口放校验文件 | 在 DNS 加 TXT 记录 |
| 优点 | 简单、无需 DNS 权限 | 支持通配符 *.example.com |
| 缺点 | 需 80 端口开放 | 需 DNS provider API |
7.1 以 Cloudflare 为例
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-dns01
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-dns01
solvers:
- dns01:
cloudflare:
email: you@example.com
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
先把 Cloudflare API Token 存进 Secret,再声明通配符证书:
kubectl create secret generic cloudflare-api-token \
--from-literal=api-token=<TOKEN> -n cert-manager
# Certificate 中引用
spec:
secretName: wildcard-tls
dnsNames:
- example.com
- "*.example.com"
issuerRef:
name: letsencrypt-dns01
kind: ClusterIssuer
八、总结与避坑清单
把证书交给 cert-manager 后,TLS 不再是定时炸弹。上生产前请把这份清单过一遍:① 先用 staging 跑通整个链路;② 公共 CA 统一用 ClusterIssuer;③ 给证书过期配置监控告警,别等 502 才发现;④ 通配符或内网域名果断上 DNS01。如果你还没上 Kubernetes,裸机 Nginx 仍可用 certbot 自动续期,参考《Let’s Encrypt 免费 SSL 证书自动续期:certbot 与宝塔实战》。当集群规模上来后,再用本文的方案把续期彻底自动化,才是长治久安之道。
最后的工程化建议:把 ClusterIssuer 和 Certificate 的 YAML 全部纳入 Git 仓库,用 Argo CD 或 Flux 做 GitOps 同步。这样证书配置也有版本历史、可审计、可回滚,新集群拉起时证书能力一并就绪,真正做到”基础设施即代码”的闭环。




