Cert-Manager + Traefik + 腾讯云 DNS 实现域名自动续期

AI 总结

本文介绍基于cert-manager、Traefik和腾讯云DNSPod实现Let's Encrypt证书自动申请与续期的方案。首先概述架构,包含Kubernetes集群中Traefik、Secret和cert-manager的协作,以及DNS-01验证流程。详细说明证书申请和续期的完整步骤,包括安装cert-manager、配置腾讯云CAM账号并创建Secret、部署webhook、创建ClusterIssuer和Certificate资源。强调自动续期机制(证书到期前30天触发)和故障排查方法。最终实现证书全自动管理,无需人工干预。

背景: 因证书手动续期繁琐、易过期的问题,基于 cert-manager腾讯云 DNSPod API 实现 Let's Encrypt 证书的全自动申请与续期。
因为我将前端也放到K8S集群里了,所有本次演示K8S集群的自动续期方式。

架构概述

┌─────────────────────────────────────────────────────────┐
│                    Kubernetes Cluster                    │
│  ┌──────────────┐      ┌──────────────┐                 │
│  │   Traefik    │◄─────│    Secret    │                 │
│  │   Ingress    │      │  (TLS Cert)  │                 │
│  └──────────────┘      └──────┬───────┘                 │
│                               │                          │
│                         ┌─────▼──────┐                  │
│                         │ cert-manager│                  │
│                         └─────┬──────┘                  │
│                               │                          │
└───────────────────────────────┼──────────────────────────┘
                                │ DNS-01 Challenge
                                ▼
                        ┌──────────────┐
                        │  腾讯云 DNS   │
                        │   (DNSPod)   │
                        └──────────────┘

工作流程

  1. cert-manager 监控 Certificate 资源
  2. 自动向 Let's Encrypt 发起证书申请
  3. 通过腾讯云 DNS API 自动添加 TXT 记录完成 DNS-01 验证
  4. 证书签发后自动存入 Kubernetes Secret
  5. Traefik 自动感知 Secret 更新,无需重启 Pod

完整工作流程

证书申请流程(首次部署)

┌─────────────────────────────────────────────────────────────────┐
│  1. cert-manager 监控到 Certificate 资源(包含域名列表)           │
│     例如:xwutx.cn, www.xwutx.cn                                 │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  2. cert-manager 向 Let's Encrypt 发起证书申请请求               │
│     请求签发包含上述域名的证书                                    │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  3. Let's Encrypt 返回挑战(Challenge):                         │
│     "请证明你拥有这些域名"                                        │
│     验证方式:DNS-01(在 DNS 添加 TXT 记录)                     │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  4. cert-manager 调用 cert-manager-webhook-dnspod               │
│     传入域名和需要创建的 TXT 记录内容                             │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  5. webhook 使用 CAM 账号(SecretId/SecretKey)                 │
│     调用腾讯云 DNSPod API                                       │
│     【CAM 账号作用:认证身份,允许操作 DNS 记录】                  │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  6. DNSPod 创建 TXT 记录:                                       │
│     _acme-challenge.xwutx.cn    TXT    "随机字符串"             │
│     _acme-challenge.www.xwutx.cn    TXT    "随机字符串"         │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  7. 等待 DNS 传播(propagationTimeout: 300 秒)                  │
│     每 10 秒检查一次(pollingInterval: 10)                      │
│     确保全球 DNS 服务器都能查到该记录                             │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  8. Let's Encrypt 查询 DNS 记录                                  │
│     确认 TXT 记录存在且内容正确                                   │
│     → 验证通过,域名所有权确认                                    │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  9. Let's Encrypt 签发证书                                       │
│     返回证书文件(.crt)和私钥(.key)                           │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  10. cert-manager 将证书存入 Kubernetes Secret                   │
│      Secret 名称:xwutx-tls                                      │
│      类型:kubernetes.io/tls                                     │
│      包含:tls.crt(证书)+ tls.key(私钥)                      │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  11. webhook 清理:删除临时 TXT 记录                             │
│      保持 DNS 记录整洁                                           │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  12. Traefik 感知 Secret 更新(Watch 机制)                      │
│      自动加载新证书                                              │
│      【无需重启 Pod,热更新】                                     │
└────────────────────────────┬────────────────────────────────────┘
                             ▼
┌─────────────────────────────────────────────────────────────────┐
│  13. 用户访问 https://xwutx.cn                                   │
│      Traefik 使用新证书建立 HTTPS 连接                           │
│      证书有效期:90 天                                           │
└─────────────────────────────────────────────────────────────────┘

自动续期流程(每 60 天)

证书到期前 30 天(Renewal Time)
    ↓
cert-manager 检测到需要续期
    ↓
重复上述步骤 2-13
    ↓
新证书自动替换旧证书
    ↓
Traefik 自动加载新证书
    ↓
全程无感知,无需人工干预

CAM 账号在流程中的作用

┌─────────────────────────────────────────────────────────┐
│  CAM 账号 = 操作腾讯云 DNS 的"钥匙"                       │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  为什么需要?                                            │
│  ├─ 域名托管在腾讯云 DNSPod                              │
│  ├─ Let's Encrypt 要求 DNS-01 验证                       │
│  │   (添加 TXT 记录证明域名所有权)                     │
│  └─ cert-manager 需要权限调用 DNSPod API                 │
│      来创建/删除 TXT 记录                                │
│                                                         │
│  CAM 账号的具体操作:                                    │
│  ├─ dnspod:CreateRecord      → 创建 TXT 记录           │
│  ├─ dnspod:DescribeRecordList → 查询记录是否存在        │
│  └─ dnspod:DeleteRecord      → 删除 TXT 记录           │
│                                                         │
│  权限最小化原则:                                        │
│  └─ 只授予 DNS 记录操作权限,不包含其他 DNSPod 权限      │
│                                                         │
└─────────────────────────────────────────────────────────┘

环境信息

当前环境

项目版本/信息
Kubernetes1.32
Traefik2.11.18
域名xwutx.cn
DNS 服务商腾讯云 DNSPod
证书类型Let's Encrypt (免费)

域名清单

域名用途所在 IngressSecret 名称
xwutx.cn不带www.访问ingress-mainxwutx-tls
www.xwutx.cn带www.访问ingress-wwwxwutx-tls

前置准备

1.安装 Helm(如未安装)

# 下载 Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# 验证
helm version

2. 检查 Traefik 版本

kubectl get deployment -n kube-system traefik -o jsonpath='{.spec.template.spec.containers[0].image}'

预期输出

rancher/mirrored-library-traefik:2.11.18

✅ Traefik 2.11.18 支持证书热更新,无需重启 Pod

安装 cert-manager

1. 添加 Helm 仓库

helm repo add jetstack https://charts.jetstack.io
helm repo update

2. 安装 cert-manager

helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set installCRDs=true

3. 验证安装

kubectl get pods -n cert-manager

预期输出:

NAME                                       READY   STATUS    RESTARTS   AGE
cert-manager-7d8f4b5b8c-xxxxx              1/1     Running   0          1m
cert-manager-cainjector-6f4d8b5b8c-xxxxx   1/1     Running   0          1m
cert-manager-webhook-5c7f9b5b8c-xxxxx      1/1     Running   0          1m

配置腾讯云 DNS API

1. 创建自定义权限策略

  1. 登录 腾讯云 CAM 控制台
  2. 进入 策略 → 新建自定义策略
  3. 选择 按策略语法创建
  4. 选择 空白模板
  5. 填写策略名称(如 cert-manager-dns
  6. 粘贴以下策略内容:
{
    "version": "2.0",
    "statement": [
        {
            "action": [
                "dnspod:DescribeRecordList",
                "dnspod:CreateRecord",
                "dnspod:DeleteRecord",
                "dnspod:ModifyRecord"
            ],
            "resource": "*",
            "effect": "allow"
        }
    ]
}
  • 7.点击 创建策略

2. 创建 CAM 子账号

  1. 进入 用户 → 用户列表 → 新建用户 → 自定义创建
  2. 选择 编程访问,设置用户名(如 cert-manager-dns
  3. 在设置用户权限步骤,搜索并勾选刚创建的自定义策略(如 cert-manager-dns
  4. 点击 下一步 完成创建
  5. 保存 SecretIdSecretKey

⚠️ SecretKey 只显示一次,务必保存!

3. 创建 Kubernetes Secret

apiVersion: v1
kind: Secret
metadata:
  name: tencent-cloud-secret
  namespace: cert-manager
type: Opaque
stringData:
  secret-id: "你的SecretId"
  secret-key: "你的SecretKey"

⚠️ 注意:SecretKey 不要有空格!

4. 验证 Secret

kubectl get secret tencent-cloud-secret -n cert-manager

安装 cert-manager-webhook-dnspod

1. 安装 webhook

kubectl apply -f https://raw.githubusercontent.com/imroc/cert-manager-webhook-dnspod/master/bundle.yaml

这会创建 Deployment、Service、RBAC 权限和 APIService

2. 创建 RBAC 权限(重要!)

bundle.yaml 不包含以下 RBAC 配置,需要手动创建,否则 cert-manager 无法调用 webhook。
创建文件 cert-manager-webhook-rbac.yaml

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cert-manager-webhook-dnspod:flowcontrol
rules:
- apiGroups: ["acme.dnspod.com"]
  resources: ["dnspod"]
  verbs: ["create", "get", "list", "watch", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cert-manager-webhook-dnspod:flowcontrol
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cert-manager-webhook-dnspod:flowcontrol
subjects:
- kind: ServiceAccount
  name: cert-manager
  namespace: cert-manager

应用:

kubectl apply -f cert-manager-webhook-rbac.yaml

作用说明:

  • 赋予 cert-manager ServiceAccount 操作 dnspod 资源的权限
  • 没有此配置,cert-manager 调用 webhook 会报错:dnspod.acme.dnspod.com is forbidden

3. 验证安装

# 查看 webhook Pod
kubectl get pods -n cert-manager | grep dnspod

# 查看 APIService
kubectl get apiservice | grep dnspod

# 测试权限(应输出 yes)
kubectl auth can-i create dnspod.acme.dnspod.com \
  --as=system:serviceaccount:cert-manager:cert-manager

预期输出:

cert-manager-webhook-dnspod-xxxxx   1/1     Running   0          1m
v1alpha1.acme.dnspod.com    cert-manager/cert-manager-webhook-dnspod   True   1m
yes

创建 ClusterIssuer

创建文件 cluster-issuer.yaml

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # 生产环境地址(测试环境用 https://acme-staging-v02.api.letsencrypt.org/directory)
    server: https://acme-v02.api.letsencrypt.org/directory
    email: your-email@example.com  # 替换为你的邮箱
    privateKeySecretRef:
      name: letsencrypt-prod-account-key
    solvers:
    - dns01:
        webhook:
          groupName: acme.dnspod.com
          solverName: dnspod
          config:
            secretIdRef:
              name: tencent-cloud-secret
              key: secret-id
            secretKeyRef:
              name: tencent-cloud-secret
              key: secret-key
            # DNS 传播超时配置(重要!)
            propagationTimeout: 300  # 5 分钟
            pollingInterval: 10      # 每 10 秒检查一次

应用:

kubectl apply -f cluster-issuer.yaml

验证 ClusterIssuer

kubectl get clusterissuer

预期输出

NAME               READY   AGE
letsencrypt-prod   True    1m

创建 Certificate 资源

1. 备份现有证书(可选但推荐)

# 备份现有证书
kubectl get secret xwutx-tls -n default -o yaml > xwutx-tls-backup.yaml

2. 创建 Certificate

创建文件 certificates.yaml

# certificates.yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: xwutx-cn-tls
  namespace: default
spec:
  secretName: xwutx-tls  # 与现有 Secret 名称一致,会自动覆盖
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
  - xwutx.cn
  - www.xwutx.cn

应用:

kubectl apply -f certificates.yaml

验证部署

1. 查看 Certificate 状态

kubectl get certificate -n default

预期输出:

NAME               READY   SECRET            AGE
xwutx-cn-tls       True    xwutx-tls         2m

2. 查看证书详情

kubectl describe certificate xwutx-cn-tls -n default

3. 检查 Secret 已更新

kubectl describe secret xwutx-tls -n default

查看证书信息

echo | openssl s_client -connect xwutx.cn:443 2>/dev/null | openssl x509 -noout -dates -issuer

输出

notBefore=Sep 15 08:57:59 2026 GMT
notAfter=Dec 14 08:57:58 2026 GMT
issuer=C = US, O = Let's Encrypt, CN = YR1

自动续期机制

工作原理

  • 监控:cert-manager 持续监控 Certificate 资源
  • 续期时机:证书剩余有效期 < 30 天时自动续期
  • 自动更新:新证书自动更新到 Secret,Traefik 自动感知

续期流程

证书到期前 30 天
    ↓
cert-manager 自动检测
    ↓
重新执行 DNS-01 验证流程
    ↓
签发新证书 → 更新 Secret
    ↓
Traefik 自动加载新证书
    ↓
全程无感知,无需人工干预

查看证书有效期

kubectl get certificate xwutx-cn-tls -n default -o jsonpath='{.status.notAfter}'

查看续期日志

kubectl logs -n cert-manager deployment/cert-manager --tail=100

故障排查

1. 证书一直处于 Pending 状态

# 查看证书事件
kubectl describe certificate xwutx-cn-tls -n default

# 查看证书请求
kubectl get certificaterequest -n default

# 查看订单
kubectl get order -n default

# 查看挑战
kubectl get challenge -n default

常见原因:

  • DNS 传播延迟(等待 5-10 分钟)
  • SecretId/SecretKey 错误
  • 域名不在当前腾讯云账号下
  • 权限不足(检查自定义策略是否正确)

2. 查看 cert-manager 日志

kubectl logs -n cert-manager deployment/cert-manager --tail=100

3. 查看 webhook 日志

kubectl logs -n cert-manager deployment/cert-manager-webhook-dnspod --tail=100

4. 测试 DNS 验证

# 手动检查 TXT 记录
dig TXT _acme-challenge.xwutx.cn
dig TXT _acme-challenge.www.xwutx.cn

5. 强制重新签发证书

# 删除现有证书,cert-manager 会自动重新申请
kubectl delete secret xwutx-tls -n default
kubectl delete secret con-xwutx-tls -n default

6. 清理卡住的 Challenge

# 强制删除所有 Challenge
for challenge in $(kubectl get challenge -n default -o name 2>/dev/null); do
  kubectl patch $challenge -n default --type=merge -p '{"metadata":{"finalizers":[]}}' 2>/dev/null
done
kubectl delete challenge -n default --all --force --grace-period=0 2>/dev/null

评论 (0)