cert-manager 实战:K8s 证书自动签发续期

在 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 的区别

对比项IssuerClusterIssuer
作用范围仅当前命名空间全集群可见
适用场景多租户各自管理 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 的 EventsHTTP01 需 Ingress controller 公网可达
签发报 rate limit重复申请触发限流改用 staging 验证,等窗口恢复
DNS01 卡在 PresentingDNS API token 权限不对检查 Secret 中的 key 与权限

七、进阶:DNS01 挑战与泛域名

当域名无法在 80 端口暴露(如内网、CDN 回源),或你需要通配符 *.example.com 时,HTTP01 就不够用了,改用 DNS01:cert-manager 通过 DNS provider 的 API 自动添加 TXT 校验记录。

维度HTTP01DNS01
适用公网可访问的域名泛域名、内网、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 同步。这样证书配置也有版本历史、可审计、可回滚,新集群拉起时证书能力一并就绪,真正做到”基础设施即代码”的闭环。

上一篇 服务器时钟漂移导致线上故障:NTP/chrony 排查与根治
下一篇 限流误杀线上故障:Nginx 与网关限流陷阱根治