容器与平台

Kubernetes 用 cert-manager 自动签发 Let's Encrypt 证书

作者: · 最后核实

在 Kubernetes 里用 cert-manager 签发 Let's Encrypt 证书分三步:用 Helm 安装 cert-manager,创建 ClusterIssuer,再给 Ingress(或 Gateway)加注解。证书保存在 Secret 里,cert-manager 会自动续期。

适用于 cert-manager 1.18 及以上(截至 2026 年 11 月为 1.21.x)、任意受支持的 Kubernetes 版本和 Helm 3。

前提条件

HTTP-01 验证需要公网能访问到你的 Ingress 控制器或网关。

  • Kubernetes 集群和有 cluster-admin 权限的 kubectl,本地装好 Helm 3。
  • 有公网 IP 的 Ingress 控制器或 Gateway API 实现,例如 203.0.113.10。还在用社区版 ingress-nginx 的,需要规划迁移,它已于 2026 年 3 月退役。
  • example.com 的 A/AAAA 记录指向这个公网 IP;集群在中国大陆的,域名需已备案。
  • 公网能通过 80 端口访问控制器(HTTP-01 需要)。80 端口不通时用第四步的 DNS-01。

第一步:用 Helm 安装 cert-manager

cert-manager 用官方 Helm chart 安装,crds.enabled=true 会同时安装 CRD:

helm install cert-manager oci://quay.io/jetstack/charts/cert-manager \
  --version v1.21.3 \
  --namespace cert-manager --create-namespace \
  --set crds.enabled=true
kubectl get pods -n cert-manager

v1.21.3 换成 cert-manager 安装文档里的当前版本。controller、webhook、cainjector 三个 Pod 都应变为 Ready。国内集群拉取 quay.io 镜像慢时,可以先同步到自己的镜像仓库,再通过 Helm 参数覆盖镜像地址。

第二步:创建测试和生产 ClusterIssuer

ClusterIssuer 告诉 cert-manager 向哪个 ACME 服务器申请证书。先建测试和生产两个,用测试环境调通后再切到生产:

# issuers.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-staging
spec:
  acme:
    server: https://acme-staging-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-staging-account
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik
---
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-prod-account
    solvers:
      - http01:
          ingress:
            ingressClassName: traefik
kubectl apply -f issuers.yaml
kubectl get clusterissuer

ingressClassName 改成你的控制器的类名。想申请 45 天证书,在 acme: 下加 profile: tlsserver,见 Let's Encrypt 证书配置文件。

第三步:通过 Ingress 申请证书

给 Ingress 加注解并声明 TLS 域名和 Secret 名称,cert-manager 会自动创建 Certificate:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: web
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-staging
spec:
  ingressClassName: traefik
  tls:
    - hosts:
        - example.com
      secretName: example-com-tls
  rules:
    - host: example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 80
kubectl apply -f ingress.yaml
kubectl get certificate example-com-tls -w

测试环境下 READY 变为 True 后,把注解改成 letsencrypt-prod 并删除 Secret,重新签发受信任的证书:

kubectl annotate ingress web cert-manager.io/cluster-issuer=letsencrypt-prod --overwrite
kubectl delete secret example-com-tls

用 Gateway API 的,先开启 cert-manager 的 Gateway 支持,再在 Gateway 上加同样的 cert-manager.io/cluster-issuer 注解,带 tls.certificateRefs 的监听器会自动获得证书(详见 cert-manager 的 Gateway 文档)。

第四步(可选):用 DNS-01 申请通配符证书

通配符证书只能用 DNS-01 验证,在 ClusterIssuer 的 solvers 里加一个 dns01 求解器,以 Cloudflare 为例:

solvers:
  - dns01:
      cloudflare:
        apiTokenSecretRef:
          name: cloudflare-api-token
          key: api-token
kubectl create secret generic cloudflare-api-token -n cert-manager \
  --from-literal=api-token=your-cloudflare-api-token

ClusterIssuer 引用的 Secret 必须放在 cert-manager 命名空间。阿里云 DNS、DNSPod 需要社区 webhook,见常见问题。

验证

签发成功后,Certificate 的 READY 为 True,公网访问返回 Let's Encrypt 证书。用 SSL 证书检查 检查公网访问,或在终端运行:

curl ssl.sh/example.com

在集群里:

kubectl describe certificate example-com-tls
kubectl get secret example-com-tls -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -issuer -dates

自动续期

cert-manager 在证书有效期过去三分之二时自动续期(可用 renewBefore 或 renewBeforePercentage 调整),更新 Secret 后 Ingress 控制器和网关会自动加载。续期时间按证书实际有效期计算,所以 2027 年 2 月 10 日 Let's Encrypt 改为 64 天、2028 年 2 月 16 日改为 45 天时都不用改配置。保持 cert-manager 升级,留意 Ready=False 的 Certificate,并把公网域名加到 ssl.sh 监控(开发中):

kubectl get certificates -A

常见问题排查

按 Certificate → CertificateRequest → Order → Challenge 的顺序找卡在哪一步:

kubectl get certificaterequest,order,challenge -A
kubectl describe challenge -A
  • Waiting for HTTP-01 challenge propagation: wrong status code 404:求解器的 ingressClassName 和控制器不一致,或 80 端口到不了控制器(安全组、未备案拦截),原因和 certbot 连接超时 相同。
  • 客户端报 x509: certificate signed by unknown authority:还在用测试环境证书,见 x509: certificate signed by unknown authority。
  • too many certificates already issued:在生产环境反复删除重签,见 Let's Encrypt 频率限制。
  • CAA record prevents issuance:见 CAA 记录阻止签发。
  • Secret 已存在,控制器却返回默认证书:Ingress 的 tls.secretName 或命名空间和 Secret 对不上。

常见问题

为什么先用测试环境的 Issuer?

测试环境的频率限制宽松得多,DNS、Ingress 类名或验证方式配错时不会把生产环境的额度用光。测试证书不受公网信任,客户端会报未知颁发者,确认流程跑通后再把注解改成生产 Issuer。

域名在阿里云 DNS 或 DNSPod,怎么用 DNS-01?

cert-manager 内置的 DNS-01 支持 Cloudflare、Route 53 等少数服务商,阿里云 DNS、DNSPod 需要社区维护的 webhook 求解器,安装和配置以对应项目文档为准。不想引入 webhook 的,也可以在集群外用 acme.sh 签发后写入 Secret,但那样续期要自己负责。

cert-manager 什么时候续期?

默认在证书有效期过去三分之二时续期,90 天证书约在到期前 30 天续期。可以在 Certificate 上用 spec.renewBefore 或 spec.renewBeforePercentage 调整。Secret 更新后 Ingress 控制器或网关会自动加载。

资料来源

  1. cert-manager: Helm installation
  2. cert-manager: ACME
  3. cert-manager: HTTP01
  4. cert-manager: DNS01
  5. cert-manager: Annotated Gateway resource
  6. Kubernetes blog: Ingress NGINX Retirement