news 2026/10/16 4:02:55

cert-manager 与 ACME DNS01 实现 Kubernetes 证书全自动管理

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
cert-manager 与 ACME DNS01 实现 Kubernetes 证书全自动管理

某天凌晨三点,手机告警群突然炸了——某个生产集群的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记录就搞定了。

从对比上看,两者差异很清晰:

对比项HTTP01DNS01
端口要求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去增删记录。

具体流程拆下来大致是这样:

  1. cert-manager 向 ACME 服务器发起证书订单;
  2. CA 生成一个 challenge,要求你为_acme-challenge.example.com创建 TXT 记录,记录值为一长串 token;
  3. cert-manager 拿着 DNS 服务商的 API 凭证,调用接口把这条记录写进去;
  4. CA 在公共递归DNS上查询这条 TXT 记录,发现值与 token 匹配;
  5. CA 判定验证通过,签发证书,cert-manager 将证书内容写入 Kubernetes Secret;
  6. 等到证书到期前,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 yaml

Secret里面应该有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记录失败。

优先按这个顺序排查:

  1. 登录DNS服务商控制台,看_acme-challenge记录是否真的创建出来了。
  2. 对比记录值和Challenge对象里的token是否一致。
  3. 用公共DNS视角查询,确认记录是否已经在公网递归服务器可见。
  4. 如果记录值正确但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的引用或缓存上。

排查顺序:

  1. 检查Ingress的tls.hosts与证书Secret中的域名是否匹配,特别注意泛域名和具体域名的匹配规则。
  2. 检查域名是否解析到正确的节点,负载均衡器是否把流量打到了正确的后端。
  3. 查看Ingress Controller的日志,确认它实际加载的证书是哪一份。
  4. 如果前面都正常,尝试重启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全部重建一遍,确认没问题再动生产,能避开不少升级带来的隐性坑。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 4:02:50

MFC中CListCtrl数据导出Excel:二进制写入方案与避坑指南

简介&#xff1a;这份资源面向使用 MFC 进行桌面开发的程序员&#xff0c;聚焦于将 CListCtrl 控件中的列表数据导出到 Excel 表格这一常见需求。内容围绕 COM 自动化接口展开&#xff0c;涵盖 Excel 应用程序对象创建、工作簿与工作表操作、逐行逐列读取 ListCtrl 单元格数据并…

作者头像 李华
网站建设 2026/10/16 4:02:08

用WorkBuddy把散装资料变成可直接授课的教案和PPT:一条实战工作流

上周接到一门企业内训课&#xff0c;主题是《数据分析入门——数据清洗与可视化》&#xff0c;学员是 40 多名刚入职的非技术岗员工&#xff0c;课时只有 90 分钟。最让我头疼的不是讲课本身&#xff0c;而是开课前一天晚上&#xff0c;我盯着电脑里的"课程资料"文件…

作者头像 李华
网站建设 2026/10/16 4:01:18

JSP+Session购物车案例:不登录不依赖数据库实现跨请求购物车

简介&#xff1a;这份购物车案例【简单版】面向Java Web初学者与课程设计需求者&#xff0c;用JSP、Servlet、Tomcat、Session与数据库搭建了一个可运行的电商购物车最小闭环&#xff0c;帮助理解从商品展示、加入购物车到状态保持的完整请求链路。压缩包共62个文件&#xff0c…

作者头像 李华
网站建设 2026/10/16 4:00:48

内网离线安装Docker与Docker Compose实战指南

简介&#xff1a;这份资源面向需要在无外网环境下部署容器服务的内网运维与开发人员&#xff0c;提供一套完整的离线安装方案&#xff0c;解决服务器无法访问公网、依赖缺失导致 Docker 与 Docker Compose 装不上的问题。压缩包共 22 个文件&#xff0c;约 120.91MB&#xff0c…

作者头像 李华
网站建设 2026/10/16 3:59:57

字节码指令全解析:从javap到JVM性能调优实战

调试线上问题的时候&#xff0c;我习惯先看一眼javap -c的输出。这不是强迫症&#xff0c;而是很多看起来玄乎的性能问题&#xff0c;最后都能在字节码指令这一层找到最原始的答案。class 文件里的字节码指令&#xff0c;是 Java 源码和 JVM 运行时之间的桥梁&#xff0c;也是 …

作者头像 李华
网站建设 2026/10/16 3:59:51

Agora Web SDK NG版接入实战:Web音视频通话落地关键决策指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华