Appearance
在 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 3d13hdefault 是当前访问配置名称;确认 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 入口时,先确认订单是否支持此接入方式;不要用其他订单的凭据替代。

2.2 在 Cloudflare 创建 DNS API Token
登录托管目标域名的 Cloudflare 账户,进入管理账户中的 账户API令牌 页面,创建一个专用于证书域名验证的 Token。设置 Zone / DNS / Edit 和 Zone / Zone / Read 两项权限,并在 Zone Resources 中只选择目标域名所在的 Zone,不要授权所有域名区域。
创建后保存页面显示的 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-tokenbash
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 GMTsubject 显示证书主体,notBefore、notAfter 分别为生效和到期时间(此处为 GMT)。浏览器查看器可能按本地时区显示日期。
在自己的电脑上用浏览器打开网站原 HTTPS 地址,例如 https://trustasia.com/,使用第一步记录的实际域名和端口:
- 打开的是自己的网站,浏览器没有证书错误提示。
- 从地址栏的网站信息中查看证书,确认域名、有效期与 Secret 中的证书一致。接管时仍使用原证书,有效期不会变化。
- 浏览常用页面,确认图片、样式和登录等功能正常。
使用 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 / EAB | ACME 用于自动申请证书;EAB Key ID 和 HMAC Key 用于关联 CertCloud 接入资格。 |
| DNS-01 | 临时写入 TXT 记录,证明域名管理权。 |
| cert-manager | 集群中的证书管理程序,负责申请和续期。 |
| Issuer / Certificate | 前者保存签发方配置,后者声明域名和证书保存位置。 |
| Secret / Namespace | Secret 保存凭据或证书;Namespace 是资源所属分组,本例为网站所在的 web。 |
| Pod / Service | Pod 运行业务程序,Service 提供访问这些程序的内部地址。 |
| Gateway Controller / GatewayClass | Controller 实现网关,GatewayClass 指定使用哪个实现;只创建资源定义不会处理请求。 |
| Gateway / HTTPRoute | Gateway 配置 HTTPS 入口,HTTPRoute 指定后端 Service;实际请求由网关代理处理。 |
