某天凌晨三点,手机告警群突然炸了——某个生产集群的Kubernetes Ingress证书过期,所有HTTPS访问全部报错,浏览器里那个刺眼的“不安全”提示持续了整整二十分钟。也是从那次之后,我彻底把 cert-manager 配合 ACME DNS01 挑战这套自动化方案排上了优先级。这篇文章想把这段时间踩过的坑、理清的思路和最终落地的配置一次性说清楚,给正在计划做证书全自动管理的小伙伴一个可以直接参考的路径。无论你是刚开始接触K8s运维,还是已经在生产环境跑了一段时间,这其中的细节应该都能用得上。
1. 项目背景与整体思路拆解
1.1 cert-manager 到底解决了什么痛点
先说痛点。没有自动化之前,我维护的那套集群里证书从申请到续期基本靠人肉:找一个能签发证书的服务器,执行生成CSR的命令,然后把得到的证书和私钥手动传到服务器上,再打包成Secret,改一圈Ingress配置。听起来不难,但一旦域名多起来就完全失控了。十几个域名、两三个集群、错开的到期时间,每个都要盯着日历去续,稍微漏掉一个就是线上事故。
cert-manager 做的事情很简单:它把“证书申请、校验、签发、存储、续期”这整条链路变成Kubernetes原生资源。你只需要声明想要的域名和签发方式,剩下的它自己去和ACME服务器沟通。等证书签好了,它会把证书与私钥写进你指定的Secret里,到期前还会自动续期。整个过程就像给服务器配了一个自动续停车费的服务,到了时间自动扣款,不用你半夜爬起来处理。
这套方案的价值不只是省事,而是让证书状态变成一种可以审计、可以版本化、可以监控的资源。后续无论是做GitOps、做多集群统一管理,还是想接入告警,都变得很自然。
1.2 ACME 协议与 DNS01 挑战的背景
ACME 是自动化证书管理环境协议,现在主流的公共CA基本都支持它。它的核心思路是:客户端向CA发起证书申请,CA通过某种“挑战”来验证你是否真的拥有对应域名的控制权,验证通过就签发证书。
挑战方式常见有三种:HTTP01、TLS-ALPN01和DNS01。前两种都需要CA能够直接访问到你运行服务的公网地址,比如HTTP01要求特定路径在80端口可访问,TLS-ALPN01要求443端口能响应特定握手。DNS01则不同,它不要求CA访问你的服务,只要求你在域名的DNS TXT记录里放一个CA指定的随机值,CA去公共递归DNS上查询,查到且匹配就算通过。
这个特点让DNS01在很多场景里成了唯一解:比如你的服务前面还有一层负载均衡或网关,80和443端口已经被业务占满;比如你需要给内网服务签发证书,但不想把服务直接暴露到公网;又比如你想签泛域名证书,用DNS01只验证一次根域名就够了。
1.3 为什么选 DNS01 而不是 HTTP01
我最终选择DNS01,不是因为它比HTTP01更高级,而是它更适合我当时的环境。我这边集群前面还有一层反代和WAF,80和443端口业务流量一直在跑,HTTP01验证插进去很容易冲突。而且我同时需要管理两个泛域名,HTTP01对泛域名的支持很勉强,DNS01一行TXT记录就搞定了。
从对比上看,两者差异很清晰:
| 对比项 | HTTP01 | DNS01 |
|---|---|---|
| 端口要求 | 80端口可公网访问 | 无端口要求 |
| 网络拓扑 | CA必须直达服务 | CA只需查询公网DNS |
| 泛域名支持 | 不支持 | 天然支持 |
| 自动化方式 | 修改Ingress/网关配置 | 调用DNS服务商API |
| 适用场景 | 公网直连、端口空闲 | 端口受限、内网、多域名 |
当然,DNS01也有它的麻烦:你必须给cert-manager提供DNS服务商的API凭证,而且DNS记录写入后有传播延迟,如果传播太慢或TXT值没对上,签发就会失败。但这属于配置层面的问题,架构上它反而更干净、更可控。
2. 核心原理与关键细节解析
2.1 DNS01 挑战的原理拆解
ACME 的 DNS01 验证方式,本质是让CA去查询你域名下的一条特殊TXT记录,记录名固定以_acme-challenge开头,值是一个由CA生成的随机数。你只要能在该记录里正确返回这个随机数,就证明你对该域名有管理权,因为正常情况下只有域名所有者才能通过DNS服务商的控制台或API去增删记录。
具体流程拆下来大致是这样:
- cert-manager 向 ACME 服务器发起证书订单;
- CA 生成一个 challenge,要求你为
_acme-challenge.example.com创建 TXT 记录,记录值为一长串 token; - cert-manager 拿着 DNS 服务商的 API 凭证,调用接口把这条记录写进去;
- CA 在公共递归DNS上查询这条 TXT 记录,发现值与 token 匹配;
- CA 判定验证通过,签发证书,cert-manager 将证书内容写入 Kubernetes Secret;
- 等到证书到期前,cert-manager 再次走同样的流程完成续期,并会把上一轮用于验证的 TXT 记录清理掉。
这里有个容易被忽略的细节:TXT记录的TTL尽量设置得短一些。因为DNS01验证本来就依赖记录能被CA的递归服务器查到,TTL太长会导致旧记录缓存迟迟不失效,验证直接超时。通常60秒就够了,有些服务商甚至支持更短的TTL。如果取默认的一小时,在传播速度慢的环境下很容易成为失败的导火索。
2.2 cert-manager 的架构组件与工作流程
cert-manager 的核心由几个CRD驱动:Certificate、Issuer、ClusterIssuer、Order、Challenge。你平时只需要关注前三个,Order和Challenge是中间状态,用来反映签发进度和验证结果。
整个工作流大概是:你创建Certificate对象,指定域名和要用的Issuer;cert-manager的控制器发现这个对象后,生成对应的ACME账户和订单;订单被拆成Challenge,控制器根据Issuer里配置的DNS凭证去完成任务;CA验证通过后证书签发,最后写入spec.secretName指定的Secret里。这个流程每次续期都会重新走一遍,所以只要DNS凭证没有失效,证书基本就是永续状态。
在集群里,cert-manager通常以Deployment方式部署,同时带有Webhook和CA Injector两个辅助组件。Webhook负责对CRD做字段校验,CA Injector负责给相关组件自动注入CA根证书。它们三个任何一个不健康,表面上就会表现为你创建Certificate后一直不进入正常流程,所以排查问题第一步永远是看这三个组件是否都在正常Running。
2.3 DNS 凭证的存储与安全设计
DNS01 绕不开的是权限问题。要让 cert-manager 去创建和删除TXT记录,就必须给它一套DNS服务商的API凭证。这个凭证的权限一定要收得足够窄,理想情况下只需要授权它修改指定域名的解析记录,不要给整账号的管理员权限。有些服务商支持子账号或API Token级别的授权,建议单独为 cert-manager 创建一个专用凭证,后续吊销和审计都方便。
凭证落地到集群时,常规做法是放进Secret,然后在Issuer里通过字段引用。Secret可以放在 cert-manager 所在命名空间,也可以单独指定。生产环境建议配合加密存储方案,比如用密钥管理工具渲染后再提交到集群,不要把明文Token直接写进Git仓库。我见过不少直接把Token明文写进YAML然后推到代码仓库的案例,一旦仓库泄露,整个域名的解析权限都跟着完蛋。
2.4 证书与Secret的管理逻辑
Certificate对象声明了“我要什么证书”,Issuer负责“怎么去签”,Secret是最终产物的存放地。这个分层清晰之后,你会发现一切都很符合K8s的习惯:底层存储是Secret,上层声明是CRD,中间控制器自动完成转换。
续期逻辑由Certificate控制器自动计算。你可以通过spec.renewBefore调整续期提前量,比如提前30天还是提前15天。如果你有监控告警系统,可以考虑对证书到期时间做阈值告警,双保险。
另一个值得提醒的点是:泛域名证书在DNS01模式下相当容易做,因为*.example.com的验证只需要_acme-challenge.example.com一条记录,不需要每个子域名各自验证。这也是很多团队选择DNS01的核心动机之一。
3. 实操过程与核心配置实现
3.1 环境准备与工具选型
开始之前,建议先确认环境版本和权限:
- 集群版本不用太新,但也不要停留在已经停止支持的版本上,否则可能遇到CRD兼容问题。
- 准备好 kubectl 和 Helm,如果你只是临时测试,Helm不是必须的,但生产环境强烈建议用Helm管理安装和升级。
- 去DNS服务商后台创建一个专用Token,权限只给到需要自动管理的域名解析即可。不要拿主账号的密钥去配置,否则泄露后的影响面不可控。
- 准备一个不重要的测试域名,先把整套流程跑通,再上生产域名。DNS01的失败很多时候是服务商API细节导致的,先拿测试域名摸清自己服务商的特性和延迟,能省很多事。
另外,如果你用的是托管集群,通常集群本身自带一些插件市场,也可以直接搜索 cert-manager 一键安装。但自带插件版本可能滞后,建议还是手动安装,方便后续控制版本和参数。
3.2 安装 cert-manager
安装方式我推荐使用Helm。先添加Chart仓库,然后执行安装命令。安装时要注意启用CRD的自动安装,否则后面创建的Certificate等资源会一直提示NotFound。我这里用占位符代替实际的仓库地址,具体地址以你所用环境对应的Chart仓库说明为准。
helm repo add cert-manager <chart仓库地址> helm repo update helm install cert-manager cert-manager/cert-manager \ --namespace cert-manager \ --create-namespace \ --set crds.enabled=true安装完成后,检查命名空间下的Pod是否全部Running:
kubectl -n cert-manager get pods正常情况下会有controller、webhook、cainjector三个组件。这里有个坑:webhook如果启动失败,很多CRD操作会卡住,因为API Server要调用webhook做字段校验。遇到创建Certificate没反应的情况,第一件事先看webhook日志。
3.3 配置 ClusterIssuer 与 DNS 凭证
先创建一个Secret存放DNS服务商凭证。这里我用stringData方便演示,实际生产环境不要这样直接写明文。
apiVersion: v1 kind: Secret metadata: name: dns-credentials namespace: cert-manager type: Opaque stringData: api-token: <你的DNS服务商Token>然后创建ClusterIssuer。ClusterIssuer是集群级别的签发配置,可以被任意命名空间引用。这里用webhook方式接入DNS服务商,因为不同服务商的API差异很大,通过webhook可以灵活适配。如果你的服务商在cert-manager里有内置支持,也可以直接使用内置Solver,配置会简化不少。
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: acme-dns01 spec: acme: server: <你的ACME服务器目录地址> email: <你的联系邮箱> privateKeySecretRef: name: acme-account-key solvers: - dns01: webhook: groupName: acme.example.com solverName: example-dns-provider config: apiTokenSecretRef: name: dns-credentials key: api-token这里几个字段解释一下:
server是ACME目录地址。调试阶段可以先用测试环境,因为测试环境配额更宽,不会污染正式证书。email用于接收CA的证书到期通知。privateKeySecretRef是用来存放ACME账户私钥的Secret,cert-manager会自动创建,不需要手动准备。solverName是webhook插件的名称,不同的Webhook插件会有不同的配置参数,比如区域ID、按域名匹配规则等。
配置完成后,可以先查看一下ClusterIssuer的状态:
kubectl get clusterissuer acme-dns01 -o wide如果状态不是Ready,通常是ACME账户注册失败或ACME服务器地址不可达,去查cert-manager的controller日志定位。
3.4 创建证书并验证签发结果
证书的创建很简单,关键是指定好secretName和dnsNames。如果你想签泛域名,直接写在dnsNames里即可,但要注意某些CA对于泛域名和根域名在同一张证书中的策略,提前确认。
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: example-tls namespace: default spec: secretName: example-tls issuerRef: name: acme-dns01 kind: ClusterIssuer dnsNames: - example.com - "*.example.com"创建后,观察签发进度:
kubectl get certificate example-tls kubectl describe certificate example-tls kubectl get order.acme.cert-manager.io kubectl get challenge一开始Certificate可能是False,Order会经历valid→ready之类的状态,Challenge则会经历pending→valid。全部走完后,查看Secret:
kubectl get secret example-tls -o yamlSecret里面应该有tls.crt和tls.key,说明签发成功。如果卡在Challenge阶段,优先去DNS服务商控制台确认TXT记录是否真的出现了。
3.5 证书自动续期观测
签发完成只是第一步,续期是否稳定才是生产环境真正关心的。先看证书的到期时间:
kubectl get certificate example-tls -o jsonpath='{.status.notAfter}'如果想手动触发续期测试,可以使用 cmctl 工具。cmctl不是必须安装的,但它能直观地看到证书和Issuer的状态。手动续期前记得先了解你所在CA的限流策略,频繁手动触发有风险。
cmctl renew example-tls然后观察controller日志,留意新的Order和Challenge对象有没有正常出现:
kubectl logs -n cert-manager deploy/cert-manager -f | grep -i challenge如果看到新的Order创建、TXT记录写入、证书更新,整条链路就算闭环了。我通常还会做一个自愈验证:把已经签好的Secret删掉,看cert-manager会不会自动重建。它会重新走一遍签发流程,但这个动作在生产环境不建议乱做,因为你不知道当前域名还剩下多少签发配额。
4. 常见问题与排查技巧实录
4.1 TXT 记录未生效或传播超时
现象:Challenge一直Pending,CA侧查询TXT记录失败。
优先按这个顺序排查:
- 登录DNS服务商控制台,看
_acme-challenge记录是否真的创建出来了。 - 对比记录值和Challenge对象里的token是否一致。
- 用公共DNS视角查询,确认记录是否已经在公网递归服务器可见。
- 如果记录值正确但CA还是查不到,基本就是传播延迟问题。
解决办法有几个:把ClusterIssuer的propagationTimeout调大,给DNS传播更多时间;同时把TXT记录的TTL尽量调小。有的服务商API写入后刷新很慢,调大超时参数是最直接的缓解手段。这里尤其注意,不同服务商的反映速度差异很大,不要拿一个服务商的超时经验套用到另一家。
4.2 限流与超时错误
现象:日志里出现rate limited、429或类似配额超限的提示。
这通常是同一域名在短时间内反复申请证书触发了CA的配额限制。调试阶段最容易遇到,因为你可能反复删除Certificate、调节配置、重新签发。
解决办法是:正式调试切到ACME测试环境,测试环境的配额比生产环境宽裕很多。等流程完全跑通,再把Issuer切换到正式CA。另一个经验是,证书签好之后不要频繁删除重建,除非真的有必要。给失败重试留出间隔,不要连续触发,也能有效避开限流。
4.3 私有 DNS 环境与自动同步
现象:集群在内网,业务域名是内网域名,CA在公网解析不到。
这个属于架构层面的问题。公共CA做DNS01验证时,走的是公网递归DNS路径,它不可能解析到你的私有DNS区域。如果你的域名没有在公网权威DNS上登记解析,DNS01验证必然失败,这不是调参能解决的。
解决方案要看你的实际需求:如果只是内部服务需要证书访问,建议部署一套内建CA,再通过cert-manager对接它的API;如果域名本身是公网注册的,但你坚持只在私有DNS解析,那就让私有DNS的权威服务器对公网可见,或者干脆把验证域名切到公网可解析的路径上。想清楚这些边界条件再动手,比在集群配置里折腾半天有效得多。
4.4 证书签发成功但 Ingress 未生效
现象:Secret里已经有证书内容,但浏览器访问还是提示证书错误。
这种事最容易让人误判。证书本身没有问题,问题往往出在Ingress的引用或缓存上。
排查顺序:
- 检查Ingress的
tls.hosts与证书Secret中的域名是否匹配,特别注意泛域名和具体域名的匹配规则。 - 检查域名是否解析到正确的节点,负载均衡器是否把流量打到了正确的后端。
- 查看Ingress Controller的日志,确认它实际加载的证书是哪一份。
- 如果前面都正常,尝试重启Ingress Controller的Pod,部分Controller对TLS证书变更的感知有延迟。
这类问题通常和cert-manager无关,更多是你自己的Ingress配置或负载层缓存没跟上。
4.5 避坑经验与运维建议
最后整理几条实际运维中沉淀下来的经验:
- 不要把默认参数想得太完美。
propagationTimeout、TTL这些要结合你的DNS服务商实测来调整。 - 不要把生产所有证书都堆在同一个Issuer上。如果那个DNS服务商的API出现故障,影响面会被放大。
- 定期巡检到期时间,写个脚本扫一遍所有Certificate,低于阈值就告警,比等告警群炸了再处理体面很多。
- cert-manager升级前一定要看Release Notes,它调整CRD或默认行为的情况不少,直接升级生产常有惊喜。
- DNS凭证的权限要最小化,单独创建,泄露后能一键吊销。别怕多花两分钟建子账号,这是值得的。
我个人在实际操作中的体会是:DNS01这套方案本身不复杂,真正考验人的是DNS服务商的API行为差异和传播延迟。你越是把超时、TTL这些细节调到贴合自己的环境,后续就越省心。如果你也在维护多集群环境,建议先把证书生命周期监控加上,再考虑大批量迁移到DNS01。最后再分享一个小技巧:每次升级cert-manager前,先在测试环境里把现有的Issuer和Certificate全部重建一遍,确认没问题再动生产,能避开不少升级带来的隐性坑。