1. 基础信息介绍

核心组件

  • cert-manager (Controller)
    • 职责:大脑。它负责监控你在 K8s 中创建的 CertificateIssuer 等资源,并驱动整个申请流程(创建 Order、Challenge 等)
  • cert-manager-cainjector
    • 职责:辅助工具。它负责将证书注入到 Webhook 或 APIService 中。在申请普通业务证书时,它主要在后台保障 cert-manager 自身的通信安全。
  • cert-manager-webhook
    • 职责:守门员。当你提交一个 Ingress 或 Certificate YAML 时,它会验证你的 YAML 语法是否合法。如果你填错了字段,报错通常就是它弹出来的。

然后为了申请证书,我们还需要配置另外一个重要的组件 Issuer,主要定义如何与证书颁发机构(如 Let’s Encrypt)对接(比如用 HTTP Challenge 还是 DNS Challenge,访问网址,身份信息等等)。
Issuer 分两种, Issuer vs ClusterIssuer

  • Issuer命名空间级别。只能为同一个 Namespace 下的 Certificate 提供服务。
  • ClusterIssuer集群级别(推荐)。全集群通用,不管你的业务在哪个 Namespace,都可以引用它。

ClusterIssuer 样例

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@laily.net
    privateKeySecretRef:
      name: letsencrypt-account-key # 存储你 LE 账号私钥的 Secret 名字
    solvers:
    - dns01:
        cloudflare:
          email: your-email@laily.net
          apiTokenSecretRef: # 这里是一个 secret 用来存储 dns 服务商的信息
            name: cloudflare-api-token-secret
            key: api-token

对应的 dns api 信息

apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-api-token
  namespace: cert-manager
type: Opaque
stringData:
  api-token: "xxxx"
graph TD
    subgraph "管理层 (Global)"
        CI[ClusterIssuer: letsencrypt]
    end

    subgraph "业务层 (Namespace: seafile)"
        Cert[Certificate: laily-wildcard-cert]
        Sec[Secret: laily-net-tls-secret]
        Ing[Ingress: seafile-ingress]
    end

    subgraph "执行层 (Cert-Manager Pods)"
        Webhook[Webhook: 校验语法]
        Controller[Controller: 流程控制]
        CAJ[CA-Injector: 注入证书]
    end

    subgraph "外部验证 (Internet)"
        LE[Let's Encrypt API]
        CF[Cloudflare DNS API]
    end

    %% 流程连接
    Cert -- 引用 --> CI
    Cert -- 触发 --> Controller
    Controller -- 1.创建 DNS 记录 --> CF
    Controller -- 2.请求签发 --> LE
    LE -- 3.校验记录 --> CF
    LE -- 4.颁发证书 --> Controller
    Controller -- 5.写入数据 --> Sec
    Ing -- 6.使用证书 --> Sec
    Ing -- 7.对外提供服务 --> User((用户浏览器))

2. 证书申请

手动申请

当你需要为多个子域名(如 files.*, git.*)提供证书时,手动申请泛域名证书比让 Ingress 自动生成更专业且更易管理。
不要依赖 Ingress 的自动注入,手动定义 Certificate 对象。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: laily-wildcard-cert
  namespace: seafile  # 注意:Secret 无法跨 Namespace,建议在每个业务空间定义一个
spec:
  secretName: laily-net-tls-secret # 存储证书的盒子的名字
  commonName: "*.laily.net"
  dnsNames:
  - "laily.net"
  - "*.laily.net"
  issuerRef:
    name: letsencrypt # 指向你的 ClusterIssuer
    kind: ClusterIssuer

自动申请

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: seafile-auto-ingress
  namespace: seafile
  annotations:
    # 核心:告诉 cert-manager 自动为此 Ingress 申请证书
    kubernetes.io/tls-acme: "true"
    # 指定使用哪个 Issuer(必须是集群里已有的)
    cert-manager.io/cluster-issuer: "letsencrypt"
spec:
  ingressClassName: traefik
  rules:
  - host: files.hanxiai.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: seafile-service
            port:
              number: 80
  tls:
  - hosts:
    # 指定这个这个域名去哪个 secret 里获取证书
    - "files.laily.net"
    # cert-manager 会自动创建这个名字的 Secret 并填入证书
    secretName: seafile-auto-tls-secret

通过上面的配置,Cert-Manager 就会自己去创建一个 Certificate 然后申请证书放到对应的 Secret 里。

避坑指南

  • Namespace 隔离:由于 K8s 的限制,Ingress 只能引用同命名空间下的 Secret。如果多个 Namespace 需要证书,要么在每个空间建一个 Certificate,要么使用 Reflector 插件同步。
  • Cloudflare 权限:使用 DNS-01 校验时,API Token 必须包含 Zone:Read 权限。否则清理 TXT 记录时会报 7003: Could not route to /zones//... 错误(Zone ID 缺失)。
  • 限频问题:Let’s Encrypt 对相同域名的重复签发限制为每周 5 次。只要证书入库了,cert-manager 只会在到期前 30 天续期,不会触发限频。

3. 排查方式

当你在 Certificate 提交后发现证书没 Ready,请按以下顺序由外向内排查。

第一层:Certificate(总开关)

首先看证书对象的总体状态。

kubectl get certificate -n seafile
  • 指标READY 应为 True
  • 如果为 False:运行 kubectl describe certificate <name> -n seafile
  • 看什么:看底部的 Events。如果是 Issuing 表示正在处理;如果有报错,它会指引你去查看 CertificateRequest

第二层:CertificateRequest(签发请求)

这是 Certificate 为了获取证书而自动创建的中间对象。

kubectl get certificaterequest -n seafile
  • 看什么kubectl describe certificaterequest <name> -n seafile
  • 重点:看 Status 里的 Approved 是否为 True。如果卡在 Waiting for Order,说明问题在 ACME 订单上。

第三层:Order(订单状态)

cert-manager 会向 Let’s Encrypt 提交一个订单。

kubectl get order -n seafile
  • 看什么kubectl describe order <name> -n seafile
  • 关键点:看 State 是否为 pending。它会列出为了完成这个订单,需要通过哪些 Challenge(挑战)。

第四层:Challenge(核心:DNS 校验)

这是最容易报错的地方。它负责去 Cloudflare 修改 DNS 记录

kubectl get challenges -n seafile
  • 排查命令kubectl describe challenge <name> -n seafile
  • 常见报错及含义
    • 7003: Could not route...:Cloudflare API 权限不足(缺 Zone:Read)。
    • Waiting for DNS-01 challenge propagationcert-manager 自检没过,它在等 DNS 记录生效。
    • Presented: true:表示已经在 Cloudflare 成功创建了 TXT 记录。

强制“重连”

如果你修改了配置(如改了 Cloudflare Token),但发现状态一直不更新,可以执行这套重置逻辑:

  1. 重启控制器(让它重新读取 Secret 里的新 Token): kubectl rollout restart deployment cert-manager -n cert-manager
  2. 强制重新触发申请: 删除当前的证书对象(这不会影响已有的 Secret,除非你手动删 Secret): kubectl delete cert <name> -n seafile 然后重新 kubectl apply 你的 YAML。
  3. 手动验证: 在 describe challenge 提示已 Present 时,手动在本地查一下: nslookup -q=txt _acme-challenge.files.hanxiai.io 如果查到了那一串随机字符,说明 API 没问题,只需静候 1-2 分钟即可。