Skip to content

在 Kubernetes 中自动申请证书并启用 HTTPS ​

本文适用于已有 Kubernetes 网站和 HTTPS 入口、需要自动更新证书的场景。使用 cert-manager 接入 CertCloud ACME,将证书写入 TLS Secret,再由原 Gateway 或 Ingress Controller 加载。示例使用 Cloudflare 完成 DNS-01 验证;网站 Service 和入口保持原配置,不为续期重建网站。

尚未启用 HTTPS 时,先阅读首次接入专题。本教程使用 Kubernetes 开源组件 cert-manager,不是 TrustAsia 的 CertManager 产品,也不需要安装 CertiMan CLI 或注册 CLI 计划任务。

操作流程: 找到原证书 Secret → 获取并保存凭据 → 配置 Issuer → 接管证书 → 检查入口和网站。

下文输出摘自测试集群实际执行结果,域名替换为 trustasia.com,资源名称与本文示例对齐,省略无关记录和字段。IP、运行时长和证书日期以实际输出为准。

方案与基本概念 ​

网站访客与证书申请走的是两条不同的路径:

cert-manager 向 CertCloud 申请证书,保存到 web-tls Secret,现有网站入口读取它提供 HTTPS。使用 Ingress 的网站沿用原入口,不必改成 Gateway。

操作前记下网站的 Namespace、Service 和证书 Secret 名称,后文分别以 web、web-service:80、web-tls 为例;trustasia.com 和 ops@trustasia.com 换成实际域名和邮箱。陌生名词可随时查阅文末术语速查,不必先读完才能操作。

开始前准备什么? ​

  • 网站已有正常 HTTPS,入口为 Gateway 或 Ingress。
  • 有一台能用 kubectl 管理集群的机器;本文所有命令和 YAML 都在这台机器上执行,浏览器验收在自己的电脑上进行。
  • 集群已安装 cert-manager;尚未安装时先按文末安装说明准备。
  • 可以访问对应 CertCloud 订单,并管理网站域名的 Cloudflare DNS。

网络放行 ​

cert-manager 所在网络需能访问订单的 ACME 服务地址和 Cloudflare API(HTTPS,通常 TCP 443),并能进行 DNS 查询和访问 Kubernetes API。自己的电脑需能访问网站 HTTPS。安装组件时还需能下载安装清单和容器镜像。

第一步:找到网站当前使用的 TLS Secret ​

在有 kubectl 管理权限的终端确认集群、网站和 cert-manager。下面的 Namespace、Service 名称及端口须替换为实际值:

bash
kubectl config current-context
kubectl get namespace web
kubectl get service web-service -n web
kubectl get pods -n cert-manager

确认是预期集群,网站 Namespace 和 Service 存在,cert-manager 组件为 Running 且已就绪。

前面三条命令的实测输出依次为:

text
default
NAME   STATUS   AGE
web    Active   3d13h
NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
web-service   ClusterIP   10.43.38.255    <none>        80/TCP    3d13h

default 是当前访问配置名称;确认 web 为网站 Namespace,web-service 的服务端口为 80。cert-manager 的 Pod 输出如下:

text
NAME                                       READY   STATUS    RESTARTS       AGE
cert-manager-74b9767444-vlkzc                1/1     Running   2 (87m ago)    3d13h
cert-manager-cainjector-7745d85c9c-g2bq7      1/1     Running   2 (2d6h ago)   3d13h
cert-manager-webhook-67fd9d5484-pcjt8         1/1     Running   2 (87m ago)    3d13h

看 READY=1/1 和 STATUS=Running,表示三个组件当前已就绪。Pod 名称后缀和重启次数无需照抄。

使用网站所在的现有 Namespace,不为套用示例另建 web。web-service 或端口不同时,统一替换示例并确认网站 Pod 正常;本文不部署网站本身。

按网站现有入口选择一种查询,名称及 Namespace 换成实际值:

bash
# 使用 Gateway 的网站
kubectl get gateway web-gateway -n web -o yaml

# 使用 Ingress 的网站
kubectl get ingress web-ingress -n web -o yaml

找到网站域名对应的证书引用,按下表记录:

Gateway 查询结果中,目标 HTTPS listener 的关键字段如下(其余字段省略):

yaml
metadata:
  name: web-gateway
  namespace: web
spec:
  listeners:
    - hostname: trustasia.com
      name: https
      port: 443
      protocol: HTTPS
      tls:
        certificateRefs:
          - group: ""
            kind: Secret
            name: web-tls
        mode: Terminate

找到 hostname: trustasia.com,再看该监听的 certificateRefs 内 name: web-tls;不要把监听名称 https 当成 Secret 名称。

Ingress 查询结果的关键字段如下:

yaml
metadata:
  name: web-ingress
  namespace: web
spec:
  tls:
    - hosts:
        - trustasia.com
      secretName: web-tls

找到 hosts 中的网站域名,记下同一项的 secretName: web-tls。以上为已有配置的查询节选,不需要重新应用。

从原配置取得后面填写到哪里
Gateway 的 HTTPS listener 中 tls.certificateRefs[].name,或 Ingress 的 spec.tls[].secretName第四步 Certificate 的 spec.secretName
入口的 metadata.namespace凭据 Secret、Issuer、Certificate 的 metadata.namespace
网站实际域名第四步 Certificate 的 dnsNames,以及浏览器访问地址

本文按入口和证书 Secret 同在 web Namespace、Secret 名为 web-tls 操作,替换为刚查到的值。沿用原入口和网站路由。示例的 renewBeforePercentage 需要 cert-manager v1.16.3 或更新版本。

第二步:取得并保存接入凭据 ​

2.1 在 CertCloud 订单中获取 ACME 凭据 ​

已有订单先确认该订单支持 ACME 自动化接入,无需为了使用本教程重复购买。打开 CertCloud 中的目标证书订单,在订单下方找到自动化配置,进入 ACME,按页面提示开启或查看该订单的 ACME 接入信息。记录页面提供的完整 ACME Directory URL、EAB Key ID 和 EAB HMAC Key,分别用于第三步的 server、keyID 和第二步的 hmac-key。页面没有 ACME 入口时,先确认订单是否支持此接入方式;不要用其他订单的凭据替代。

CertCloud 订单下方的自动化配置与 ACME 入口

2.2 在 Cloudflare 创建 DNS API Token ​

登录托管目标域名的 Cloudflare 账户,进入管理账户中的 账户API令牌 页面,创建一个专用于证书域名验证的 Token。设置 Zone / DNS / Edit 和 Zone / Zone / Read 两项权限,并在 Zone Resources 中只选择目标域名所在的 Zone,不要授权所有域名区域。 Cloudflare DNS API Token 权限配置 创建后保存页面显示的 Token,第二步写入 cloudflare-dns-token Secret。它用于添加和清理 _acme-challenge TXT 记录,与 CertCloud EAB 凭据不能互换;不要公开或截图实际 Token。

2.3 保存两种凭据 ​

这两种凭据不能互换:**EAB 用于向 CertCloud 注册账户,Cloudflare Token 用于临时添加 DNS TXT 记录。**将下面的内容保存为 credentials.yaml,在 web 命名空间中创建两个 Secret。不要把填入真实密钥的文件提交到 Git。

yaml
apiVersion: v1
kind: Secret
metadata:
  name: certcloud-eab
  namespace: web
type: Opaque
stringData:
  hmac-key: "REPLACE_WITH_EAB_HMAC_KEY"
---
apiVersion: v1
kind: Secret
metadata:
  name: cloudflare-dns-token
  namespace: web
type: Opaque
stringData:
  api-token: "REPLACE_WITH_CLOUDFLARE_API_TOKEN"

替换占位值后执行。若同名 Secret 已存在,先确认其归属;不要覆盖其他订单或工具的凭据:

bash
kubectl apply -f credentials.yaml
kubectl get secret certcloud-eab cloudflare-dns-token -n web

查询实测输出(名称已对齐):

text
NAME                   TYPE     DATA   AGE
certcloud-eab          Opaque   1      28m
cloudflare-dns-token   Opaque   1      28m

确认两条记录都存在、TYPE=Opaque、各有一个数据项;这里无需显示密钥内容。

stringData 直接接收原始字符串。EAB HMAC Key 按平台提供的值填写,不解码或再次编码。这里只检查 Secret 是否存在,不读取内容;真实凭据文件和 Secret 应按现有凭据管理方式限制访问,不提交到 Git。

第三步:配置证书签发方 ​

创建命名空间级 Issuer。它不是网站证书,而是 cert-manager 申请证书的配置:CertCloud 的接入地址、EAB 凭据,以及通过 Cloudflare 完成 DNS-01 的方法。

将下面内容保存为 issuer.yaml;把 server 替换为 CertCloud 提供的完整 URL(包含 https://),并填写实际 Key ID 与联系邮箱:

yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: certcloud-acme
  namespace: web
spec:
  acme:
    email: ops@trustasia.com
    server: "REPLACE_WITH_COMPLETE_ACME_DIRECTORY_URL"
    externalAccountBinding:
      keyID: "REPLACE_WITH_EAB_KEY_ID"
      keySecretRef:
        name: certcloud-eab
        key: hmac-key
    privateKeySecretRef:
      name: certcloud-acme-account
    solvers:
      - dns01:
          cloudflare:
            apiTokenSecretRef:
              name: cloudflare-dns-token
              key: api-token
bash
kubectl apply -f issuer.yaml
kubectl wait -n web --for=condition=Ready issuer/certcloud-acme --timeout=120s

等待命令的实测输出:

text
issuer.cert-manager.io/certcloud-acme condition met

命令显示 condition met 表示 Issuer 已就绪,即 ACME 账户已注册;此时尚未申请网站证书。账户私钥 Secret certcloud-acme-account 由 cert-manager 自动创建,不要预先创建空 Secret。若等待超时,运行 kubectl describe issuer certcloud-acme -n web,检查 URL、EAB 和网络。

第四步:让 cert-manager 管理原证书 Secret ​

Certificate 声明域名、签发方和目标 TLS Secret。将 secretName 填为现有入口使用的 Secret 名称,域名填写当前网站的实际域名;示例为 web-tls 和 trustasia.com。需要签发时,cert-manager 生成私钥、通过 ACME 申请证书并完成 DNS-01 验证。已有 Secret 中的证书仍有效且符合配置时,接管后可能直接显示 Ready,按计划等待续期,不一定立即申请新证书。

先保存现有证书和入口配置。已有 Certificate 时编辑原清单,不让两套任务同时更新同一个 Secret。实际签发或续期时会生成新私钥,请按目标订单的 ACME 接入说明确认签发影响,不将其当作原私钥导入。

将下面内容保存为 certificate.yaml,替换 Namespace、域名、目标 Secret 和 Issuer 名称:

yaml
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: web-tls
  namespace: web
spec:
  secretName: web-tls
  dnsNames:
    - trustasia.com
  renewBeforePercentage: 33
  privateKey:
    algorithm: RSA
    size: 2048
    rotationPolicy: Always
  usages:
    - digital signature
    - key encipherment
    - server auth
  issuerRef:
    name: certcloud-acme
    kind: Issuer
    group: cert-manager.io

域名和密钥参数须符合实际产品要求,usages 不保证签发方一定按请求保留所有用途。rotationPolicy: Always 表示每次签发使用新的私钥;renewBeforePercentage: 33 表示剩余约 33% 有效期时尝试续期,不表示每 33 天执行一次。

bash
kubectl apply -f certificate.yaml
kubectl get certificate,certificaterequest -n web
kubectl get orders.acme.cert-manager.io,challenges.acme.cert-manager.io -n web
kubectl wait -n web --for=condition=Ready certificate/web-tls --timeout=600s
kubectl get secret web-tls -n web

成功时 Certificate 的 READY 为 True,web-tls Secret 的 TYPE 为 kubernetes.io/tls。其中 tls.crt 是证书,tls.key 是私钥;不要打印私钥,也不要让其他工具同时管理这个 Secret。若等待超时,检查 kubectl describe certificate web-tls -n web 和域名验证对象 Challenge 的事件;DNS 传播或订单审核可能需要更久,超时不表示申请已取消。

接管已有有效证书的输出示例(摘录目标资源):

text
NAME                                   READY   SECRET    AGE
certificate.cert-manager.io/web-tls     True    web-tls   0s
certificate.cert-manager.io/web-tls condition met
NAME      TYPE                DATA   AGE
web-tls   kubernetes.io/tls   2      6m34s

重点看 READY=True、SECRET=web-tls:当前证书可用,管理的是第一步记录的 Secret。该示例中原证书仍有效,cert-manager 接管后等待续期,尚未创建新的 CertificateRequest 或 ACME Order。

第五步:检查入口和网站访问 ​

5.1 检查入口状态 ​

确认入口仍引用第一步记录的 Secret。按现有入口选择下面一种检查,名称和 Namespace 换成实际值。

Gateway:

bash
kubectl get gateway,httproute -n web
kubectl describe gateway web-gateway -n web
kubectl describe httproute web -n web

在 describe gateway 的 Status > Conditions 中检查 Accepted=True 和 Programmed=True;在 Listeners > Conditions 中检查 ResolvedRefs=True,表示引用的证书可用。在 describe httproute 的 Status > Parents > Conditions 中检查 Accepted=True 和 ResolvedRefs=True,表示路由已被接收且能找到目标 Service。证书已 Ready 不代表网站入口已就绪。

实际输出中,条件按下面的形式排列(节选):

text
Status: True
Type:   Accepted
Status: True
Type:   Programmed

每个 Type 与它上面的 Status 配对;同样查看目标 listener 和 HTTPRoute 的 ResolvedRefs 是否为 True。

Ingress:

bash
kubectl describe ingress web-ingress -n web

确认 TLS 部分的域名、Secret 与第一步一致,后端仍为原 Service。入口不可达时,可参考首次部署的入口说明。

实测输出中对应的 TLS 行为:

text
TLS:
  web-tls terminates trustasia.com

这里的 web-tls 应与第四步 spec.secretName 相同,trustasia.com 应为网站域名。

5.2 检查网站访问 ​

在集群管理终端查看 Secret 中公开证书的域名和有效期,供浏览器验收时对照:

bash
kubectl get secret web-tls -n web -o jsonpath='{.data.tls\.crt}' \
  | openssl base64 -d -A \
  | openssl x509 -noout -subject -dates

上面命令的实测输出(域名已替换):

text
subject=CN=trustasia.com
notBefore=Oct  9 23:00:00 2026 GMT
notAfter=Apr 26 22:59:59 2027 GMT

subject 显示证书主体,notBefore、notAfter 分别为生效和到期时间(此处为 GMT)。浏览器查看器可能按本地时区显示日期。

在自己的电脑上用浏览器打开网站原 HTTPS 地址,例如 https://trustasia.com/,使用第一步记录的实际域名和端口:

  1. 打开的是自己的网站,浏览器没有证书错误提示。
  2. 从地址栏的网站信息中查看证书,确认域名、有效期与 Secret 中的证书一致。接管时仍使用原证书,有效期不会变化。
  3. 浏览常用页面,确认图片、样式和登录等功能正常。

使用 CDN 等代理或怀疑显示旧证书时,按证书检查说明核对集群入口的证书。

完成条件: Certificate Ready、入口引用及状态正常、客户端域名和信任链校验通过、业务正常。Controller 通常会检测 Secret 变化并加载,实际行为按所用实现验证,不要求每次换证都重启应用 Pod。

后续维护与异常恢复 ​

cert-manager 负责在证书到期前尝试重新签发。查看计划续期时间和到期时间:

bash
kubectl get certificate web-tls -n web \
  -o jsonpath='{.status.renewalTime}{"\n"}{.status.notAfter}{"\n"}'

输出示例:

text
2027-02-20T06:55:11Z
2027-04-26T22:59:59Z

第一行是计划续期时间,第二行是证书到期时间,Z 表示 UTC。你的日期取决于实际证书;正常接管后无需另外注册 Cron。

定期检查 Certificate 的 READY 状态、实际入口证书、Cloudflare Token 权限,以及 CertCloud 订单服务期与审核状态。**证书自动续期不等于订单自动续费。**配置变更触发的重新签发可以验证证书替换,但不能证明未来进入续期窗口后一定成功。

遇到问题时,先查看对象状态与事件,保留正在服务的旧 TLS Secret,不要靠删除 Secret 排障:

bash
kubectl describe issuer certcloud-acme -n web
kubectl describe certificate web-tls -n web
kubectl get events -n web --sort-by=.metadata.creationTimestamp
  • Issuer 不就绪:核对 ACME 地址、EAB 凭据和出站网络。
  • Challenge 一直 pending:核对 Cloudflare Token 权限、域名的权威 DNS 与 TXT 传播。
  • 证书已 Ready,网站却超时或返回旧证书:核对 Gateway 状态、入口地址、客户端网络和实际终止 TLS 的位置。

异常恢复 ​

签发失败而原 Secret 尚未更新时,先修正凭据、验证或网络,保留原入口和有效证书,不删除 Secret 重新申请。ACME 等待超时不代表订单已取消,也不应连续修改 Certificate 强行触发重签。

若换证后入口异常,先核对 Secret 引用和 Controller 状态。需要恢复旧材料时,必须先停止该 Certificate 的自动管理,避免恢复后又被控制器覆盖。由原 Helm/GitOps/发布流程暂停该资源的同步,按下一节解除管理,再用受限备份更新原 Secret:

bash
NAMESPACE='web'
TLS_SECRET='web-tls'
BACKUP_CERT='/root/cert-backup/fullchain.pem'
BACKUP_KEY='/root/cert-backup/privkey.pem'

set -o pipefail
kubectl create secret tls "$TLS_SECRET" -n "$NAMESPACE" \
  --cert="$BACKUP_CERT" --key="$BACKUP_KEY" --dry-run=client -o yaml \
  | kubectl apply -f -

NAMESPACE、TLS_SECRET 填写原入口使用的位置;两个备份路径填写更新前仍有效、域名正确且配对的全链和私钥。整段执行,避免将包含私钥的 YAML 输出到终端或日志。确认原 Secret 为此网站专用且没有其他管理方,再恢复,不用过期或重颁发后已失效的证书。

入口配置有误时,通过原发布流程恢复入口清单,不重建 Namespace 或 Controller。恢复后按第五步验收;重新接入前修复根因,恢复原 Certificate 清单及同步流程并观察签发与加载结果。重新启用可能触发签发,不保证继续使用备份证书。

停用自动更新或卸载 ​

只停用一个网站的证书自动更新时,解除对应 Certificate 的管理,保留有效 TLS Secret 和入口。不要卸载全局 cert-manager,以免影响其他网站。

先记录并受限备份原 Certificate 清单、TLS Secret 和入口配置,确认当前没有进行中的签发任务,暂停会重新创建该 Certificate 的 GitOps/Helm 管理。按实际名称在管理终端执行:

bash
NAMESPACE='web'
CERTIFICATE_NAME='web-tls'

kubectl delete certificate "$CERTIFICATE_NAME" -n "$NAMESPACE" --cascade=orphan
kubectl get secret web-tls -n "$NAMESPACE"

CERTIFICATE_NAME 是 Certificate 资源名称,不一定等于 Secret 名称;最后一条的 web-tls 也需替换为原 spec.secretName。--cascade=orphan 用于保留从属资源,避免启用 Certificate→Secret owner reference 的环境连带删除证书。操作前仍须确认实际资源归属、备份和其他自动化管理方;保留下来的请求等资源后续按需处理,不批量删除共享对象。

解除管理不会撤销平台订单,也不关闭已有 HTTPS,但此后需手动换证或交由其他工具接管。Issuer、EAB 和 DNS 凭据可能被其他证书使用,不随此操作删除。确实需要卸载整个 cert-manager 时,先安排所有受管证书的接管,再按原 Helm/GitOps 安装方式及官方卸载说明处理,不直接删除 Namespace 或 CRD。

补充配置与适用情况 ​

以下只补充证书自动化相关内容:cert-manager 安装、已有 Ingress 的更新检查、其他 DNS 服务商。Gateway 安装和裸机入口配置统一在首次部署篇,已有正常入口不重复配置。

安装 cert-manager ​

kubectl get pods -n cert-manager 没有相关 Pod 或未就绪时,先确认原安装是否存在及健康。下面仅适用于尚未安装 cert-manager、且集群版本与所选版本兼容的环境;已有安装的维护和升级沿用原 Helm/GitOps 流程,不重复应用。

集群管理员可安装固定版本的 cert-manager v1.21.2,并等待三个组件就绪:

bash
kubectl apply --server-side -f \
  https://github.com/cert-manager/cert-manager/releases/download/v1.21.2/cert-manager.yaml
kubectl wait -n cert-manager --timeout=10m --for=condition=Available \
  deployment/cert-manager deployment/cert-manager-cainjector deployment/cert-manager-webhook

已有 Ingress 如何复用证书 ​

已有 Ingress 不必新建 Gateway 与 HTTPRoute。核对原 spec.tls[].secretName 与 Certificate 的 spec.secretName 一致,Secret 与 Ingress 在同一 Namespace,hosts 覆盖实际域名。查询实际 Ingress,检查原配置,不输出私钥:

bash
kubectl get ingress -n web
kubectl describe ingress REPLACE_WITH_INGRESS_NAME -n web

证书引用尚未配置时,按首次部署的 Ingress 配置合并。已有正常引用只核对,不重新应用入口模板;按第五步验收 Controller 是否加载更新后的 Secret。DNS-01 不需要额外 HTTP 验证路由。

本节已使用 Traefik v3.7.13 验证:Ingress 的 spec.ingressClassName 指向专用 IngressClass,spec.rules 将同一域名转发到 web-service:80,spec.tls 引用同命名空间的 web-tls。控制器配置了 HTTPS 监听端口和 TLS;仅添加 spec.tls 不能代替控制器安装或入口端口配置。

Secret 更新后,请检查所用 Controller 是否加载新证书,并验证实际入口的域名、信任链及业务访问。日常验收沿用正常签发与续期流程。

其他 DNS 服务商 ​

第二、三步采用了 Cloudflare 专用的 Token 与 solvers[].dns01.cloudflare 配置。若域名的权威 DNS 由其他服务商管理,先确认 cert-manager 是否有对应 DNS-01 solver,再按该服务商要求准备凭据,并将 Issuer 中的 cloudflare 段替换成对应 solver。仅修改 Secret 名称或 DNS Token 字符串不能让 Cloudflare solver 控制其他 DNS 服务商。

更换 solver 后,第四步的 Certificate 和第五步的 Gateway 逻辑不变;签发前确认 cert-manager 能访问该 DNS 服务商 API,并能从公网查询临时 TXT 记录。如果使用与标准 DNS-01 不同的持久验证方案,应先确认签发方与客户端是否同时支持,再决定是否改变 solver,不应直接删除 DNS-01 配置。

术语速查 ​

名称在本文中的作用
ACME / EABACME 用于自动申请证书;EAB Key ID 和 HMAC Key 用于关联 CertCloud 接入资格。
DNS-01临时写入 TXT 记录,证明域名管理权。
cert-manager集群中的证书管理程序,负责申请和续期。
Issuer / Certificate前者保存签发方配置,后者声明域名和证书保存位置。
Secret / NamespaceSecret 保存凭据或证书;Namespace 是资源所属分组,本例为网站所在的 web。
Pod / ServicePod 运行业务程序,Service 提供访问这些程序的内部地址。
Gateway Controller / GatewayClassController 实现网关,GatewayClass 指定使用哪个实现;只创建资源定义不会处理请求。
Gateway / HTTPRouteGateway 配置 HTTPS 入口,HTTPRoute 指定后端 Service;实际请求由网关代理处理。