Repository Reading Site
第十七课:ACME、Let's Encrypt、HTTP-01 / DNS-01、Orders / Challenges 与生产限制原理
上一课你已经把 cert-manager 的“内部签发模型”打通了: 但上一课解决的是: 这当然很重要,但它还不是公网生产 HTTPS 的完整答案。 真实生产里,前台站点通常面对的是: 这些客户端不会默认信任你实验室自建的 root CA。 所以如果你想让浏览器默认信任,一般要么: 最典型的就是: 而公共 CA 不会像你自己的 CA 一样,直接听你说“这是我
第十七课:ACME、Let's Encrypt、HTTP-01 / DNS-01、Orders / Challenges 与生产限制原理
为什么这一课必须接在 cert-manager 之后
上一课你已经把 cert-manager 的“内部签发模型”打通了:
Certificate是期望状态Secret是结果状态ClusterIssuer决定“由谁签”ingress-shim会根据 Ingress 自动补出CertificateCertificateRequest是签发过程中的关键中间态
但上一课解决的是:
我自己在集群里当 CA。
这当然很重要,但它还不是公网生产 HTTPS 的完整答案。
真实生产里,前台站点通常面对的是:
- 浏览器
- 手机 App
- 第三方系统
- 外部客户
这些客户端不会默认信任你实验室自建的 root CA。
所以如果你想让浏览器默认信任,一般要么:
- 用系统信任链中的公共 CA
最典型的就是:
- Let's Encrypt
而公共 CA 不会像你自己的 CA 一样,直接听你说“这是我的域名”就给你证书。
它必须验证:
你是否真的控制这个域名。
这就是 ACME 体系存在的核心原因。
这一课的目标不是“会抄一个 ClusterIssuer YAML”,而是把下面这条链真正讲透:
Ingress / Certificate
-> CertificateRequest
-> Order
-> Authorization
-> Challenge
-> solver Pod / Service / Ingress
-> 域名控制权验证
-> Secret
-> Ingress 对外提供受公共 CA 信任的证书
你学完这一课,必须不只是会说:
HTTP-01DNS-01
而是要能准确解释:
- 它们在协议上到底验证什么
- 为什么一个要依赖公网
80 - 为什么另一个要依赖 DNS API
- 为什么 wildcard 常常必须用 DNS-01
- 为什么 cert-manager 会先做 self-check
- 为什么
NodePort 30080并不自动等于 HTTP-01 可用
先把 ACME 这个词彻底拆开
ACME 全称是:
- Automated Certificate Management Environment
它本质上是一套协议。
这套协议解决的问题不是“加密算法”本身,而是:
客户端怎样自动向 CA 申请证书,并自动证明自己控制了目标域名。
这里有四个角色:
第一:Subscriber / Client
也就是你这一边。
在这套实验里,这个角色主要由:
- cert-manager
来扮演。
第二:ACME Account
很多初学者第一次学 ACME,最容易忽略这层。
在 ACME 世界里,你不是“匿名申请证书”的。
你先要有一个:
- ACME account
这个 account 用一把私钥标识自己。
这把私钥就是 issuer 里的:
privateKeySecretRef
但注意:
这把 key 是“ACME 账户身份密钥”,不是你网站 TLS 证书的私钥。
这是整节课最关键、最容易混淆的点之一。
第三:ACME Server / Public CA
比如:
- Let's Encrypt staging
- Let's Encrypt production
它负责:
- 接受订单
- 下发挑战
- 检查挑战是否完成
- 完成授权
- 签发证书
第四:Identifier Owner Proof
也就是:
- 你拿什么证明域名是你的
这就是 challenge 的意义。
这节课最重要的第一个概念:ACME 账户私钥,不等于业务证书私钥
很多人刚接触 ACME 时,会把这两个 Secret 混成一锅粥。
你必须强行分开。
ACME 账户私钥
用于:
- 标识“申请证书的人是谁”
- 与 ACME server 建立账户关系
在这次实验里,这对应:
letsencrypt-staging-http01-account-key
它属于:
ClusterIssuer letsencrypt-staging-http01
业务证书私钥
用于:
- 你的站点实际 TLS 握手
- 最终写入
tls.key
在这次实验里,这对应的是 cert-manager 为这张业务证书临时生成的:
acme-web-tls-dpx4c
以及未来成功后要写进的:
acme-web-tls
为什么必须分开理解
因为它们承担的是完全不同的职责:
ACME account key:
我是谁,我是否有资格和 ACME CA 交互
certificate private key:
这张网站证书以后由谁在 TLS 握手里使用
如果你把这两者混了,后面排障会非常痛苦。
公共 CA 和内部 CA 的根本区别是什么
上一课我们自己造了 root CA。
那时信任关系是:
你自己定义信任根
-> 你自己签叶子证书
-> 谁要信,就把这张根证书装进 trust store
而公共 CA 的模型是:
客户端系统 / 浏览器
-> 已经内置一批公共信任根
-> 公共 CA 必须按照公开规则签发
-> 客户端默认信任这条链
所以公共 CA 不是“更神秘”,而是:
- 它已经在公众客户端的 trust store 里
- 但它对签发前验证更加严格
换句话说:
内部 CA 的难点是“如何分发信任根”,公共 CA 的难点是“如何证明域名控制权”。
为什么公共 CA 不会给 *.local 这种名字签证书
这也是很多初学者会踩的坑。
公共 CA 面向的是:
- 公网可验证的名字
像下面这些私有 / 内部命名:
service.localinternal.landb.cluster.local
并不适合向公网 CA 申请。
原因很简单:
- 公共 CA 无法用统一的公共机制验证你对这类内部命名空间的控制权
所以当你面对:
- 集群内服务
- 内部 API
- Service Mesh
- 公司内网
时,常常更适合:
- 私有 CA
- 企业 PKI
- SPIFFE / mTLS
而不是 Let's Encrypt。
ACME 里最核心的 4 个对象:Order、Authorization、Challenge、Finalize
这一段必须讲透,因为你以后排障就是围绕这几个词转。
1. Order
可以把它理解成:
我想申请一张包含这些域名的证书,请受理这个订单。
在这次实验里,实际出现了:
order.acme.cert-manager.io/acme-web-tls-1-1911881332
它处于:
state: pending
2. Authorization
Order 不会直接签发。
对每个域名,ACME server 都会要求你先拿到:
- authorization
本质意思是:
你必须先证明自己控制了这个 identifier。
3. Challenge
Authorization 的具体验证任务就是 challenge。
ACME server 会给出不同挑战类型,例如:
http-01dns-01tls-alpn-01
在这次实验里,Order 的状态里能看到 ACME server 给了多个 challenge 类型。
而 cert-manager 根据 issuer solver 配置选择了:
http-01
随后创建了:
challenge.acme.cert-manager.io/acme-web-tls-1-1911881332-3263821333
4. Finalize
当所有 authorization 都通过以后,Order 才会进入 finalize 阶段,最终拿到证书。
所以:
Order != 已签发
Order 只是申请开始
这和很多人直觉不一样。
这节课第二个最重要的概念:cert-manager 里的 Order / Challenge 不是装饰品,而是协议映射
上一课的内部 CA 模型里,你主要看到的是:
CertificateCertificateRequest
但在 ACME 世界里,cert-manager 还要把协议的中间态映射成 Kubernetes 资源。
这就是:
OrderChallenge
它们的价值在于:
把一个原本隐藏在 ACME 协议里的状态机,暴露成了 Kubernetes 可观察对象。
这非常强大。
因为你以后排障就不需要盲猜:
- 是 DNS 没生效?
- 是入口没暴露?
- 是 CA 拒绝了?
- 是 solver 没起来?
你可以直接看资源状态。
HTTP-01 到底在验证什么
Let's Encrypt 官方说明得非常清楚:
- CA 给客户端一个 token
- 客户端把它放到
http://<YOUR_DOMAIN>/.well-known/acme-challenge/<TOKEN> - 文件内容里包含 token 和 account key 的指纹
- CA 从多个视角去抓这个 URL
这句话里有四个关键点。
第一:它验证的是“这个 HTTP 入口是不是你的”
也就是说:
- 你必须让目标域名在公网访问时,真的能走到你控制的 Web 入口
第二:不是随便一个 URL,而是固定 challenge path
路径必须是:
/.well-known/acme-challenge/<TOKEN>
这就是为什么 cert-manager 会创建专门的 solver Ingress 规则。
第三:不是随便一个内容,而是 key authorization
这不是简单返回 token 本身,而是:
- token + account key thumbprint
在这次实验里,Challenge 对象里就有:
tokenkey
而节点本机 curl 到 solver path 时,实际返回的正是完整 key authorization。
第四:验证是从公网进行的,不是从集群内部进行的
这也是最容易被忽视的地方。
只要你脑子里还有“我在 Pod 里 curl 通了,所以应该能过”这种思路,就还没真正理解 HTTP-01。
HTTP-01 要求的是:
ACME server 必须从公网成功取回 challenge 内容。
为什么 HTTP-01 强依赖公网 80 端口
Let's Encrypt 官方文档说得很明确:
- HTTP-01 只能在 port 80 上完成
- 重定向只接受到
80或443
这是协议和安全模型决定的。
很多人第一反应会问:
- 我能不能用
30080 - 我能不能自定义
8080 - 我能不能把 challenge 放到别的端口
答案是不行。
因为一旦允许任意端口,挑战语义就会变弱:
- 你证明的就不再是“这个标准 Web 入口是你的”
而只是:
- “你能控制某个自定义端口上的某个服务”
这不符合 ACME 标准的安全目标。
所以你必须牢牢记住:
NodePort 30080只是 Kubernetes 的入口实现细节,不是 ACME 标准认可的验证端口。
这节课第三个最关键的工程结论:30080 能通,不等于 HTTP-01 能过
这是这次实验最宝贵的真实证据。
在我们的集群里:
- ingress-nginx 暴露的是
80 -> 30080 - 也就是外部真正能直接打到的是
NodePort 30080
但 ACME HTTP-01 检查的是:
- 目标域名的公网
80
这两者不是同一回事。
你必须从网络分层理解:
ACME 标准期望:
domain -> public IP -> TCP/80 -> HTTP challenge path
当前集群现实:
domain -> public IP -> TCP/30080 -> ingress-nginx NodePort
只要中间缺少:
- 80 到 30080 的映射
HTTP-01 就会失败。
这次实验里失败原因正是:
dial tcp 154.9.27.60:80: connect: connection refused
也就是说:
- solver 已经存在
- challenge 路由已经存在
- challenge 内容也已经能返回
- 但公网
80根本没打通
这不是 cert-manager 的问题,而是入口暴露模型不满足协议前提。
DNS-01 到底在验证什么
如果 HTTP-01 验证的是:
- “这个 Web 入口是我的”
那么 DNS-01 验证的就是:
- “这个域名的 DNS 我能改”
Let's Encrypt 的流程是:
- 给你 token
- 你把它加工后写进
_acme-challenge.<YOUR_DOMAIN>的 TXT 记录 - CA 去 DNS 系统里查这个 TXT 记录
- 记录正确则通过
这个模型和 HTTP-01 的最大区别是:
- 不要求公网
80 - 也不依赖你的 Web 入口
它依赖的是:
- DNS provider API
- DNS 传播完成
为什么 wildcard 基本意味着 DNS-01
这是面试和生产里极高频的问题。
原因很简单:
- HTTP-01 不能用于 wildcard
Let's Encrypt 官方文档明确写了:
- HTTP-01 cannot be used to issue wildcard certificates
而 DNS-01 可以。
为什么?
因为 wildcard 本质上是在证明:
- 你控制的是整个域名空间的 DNS
这种控制权最自然的证明方式就是:
- 你能写
_acme-challenge.example.com的 TXT 记录
而不是:
- 你能让某个具体 host 的 Web 入口返回 challenge 文件
所以你以后看到:
*.example.com
脑子里应该直接联想到:
- 大概率 DNS-01
TLS-ALPN-01 是什么,为什么我们平时较少用它
Let's Encrypt 还支持:
tls-alpn-01
它走的是:
443- TLS 层
- 特定 ALPN 协议
它的定位更偏向:
- 做 TLS 终止的代理
- 大型托管平台
- 想完全在 TLS 层完成验证的人
但现实里:
- 客户端支持没 HTTP-01 / DNS-01 普遍
- 平台复杂度更高
所以在 cert-manager 的日常生产实践里,最常见的仍然是:
- HTTP-01
- DNS-01
cert-manager 为什么要先做 self-check
这点特别关键,而且这次实验提供了非常好的真实证据。
cert-manager 官方文档说明:
- Challenge 在真正告诉 ACME server “我准备好了”之前
- 会先做一次 self-check
为什么?
因为如果不先自检:
- 你每次配置错误都会立刻打到 ACME server
- 很快就会消耗失败配额
- 也更容易撞上 rate limits
所以 cert-manager 的策略是:
- 先创建 solver 资源
- 先确认自己能看到 challenge 内容已经传播到位
- 自检通过后再去接受 challenge
这就是为什么这次实验里虽然:
Order已经创建Challenge已经 presented
但它一直卡在:
pending
因为 self-check 一直过不去。
这其实是在保护你。
self-check 在这次实验里到底检查了什么
根据 Challenge 状态和 cert-manager 日志,我们能非常清晰地看到:
- cert-manager repeatedly did GET
- URL 是
http://154-9-27-60.sslip.io/.well-known/acme-challenge/<TOKEN> - 最终解析为
154.9.27.60:80 - 报错
connect: connection refused
这说明 self-check 验证的是:
从 cert-manager 视角,目标域名的标准 HTTP challenge URL 是否已经真的可访问。
而且官方文档还说明:
- 如果 self-check 失败
- cert-manager 会按固定 10 秒间隔重试
- 直到你修好配置,或者删除 / 重建相关对象
这和我们在日志里看到的周期性报错完全一致。
这次实验里,solver 资源到底生成了什么
这是理解 cert-manager HTTP-01 的核心。
在这次实验里,cert-manager 自动生成了:
1. solver Pod
cm-acme-http-solver-nkzqw
它就是一个极小的临时 Web server。
职责只有一个:
- 在特定 path 上返回 challenge key authorization
2. solver Service
cm-acme-http-solver-hjdz8
它默认是:
NodePort
而 cert-manager 官方 HTTP01 文档也说明:
- solver Service 默认是
NodePort - 也可以改成
ClusterIP
3. solver Ingress
cm-acme-http-solver-8s8sd
它非常精准地路由:
- host:
154-9-27-60.sslip.io - path:
/.well-known/acme-challenge/<TOKEN>
到 solver Service 的 8089
所以整个逻辑是:
public HTTP request
-> ingress-nginx
-> solver ingress exact path
-> solver service
-> solver pod
-> key authorization
这条链路你必须看懂,因为它是 HTTP-01 成功与失败的主战场。
这次实验为什么非常有价值:它证明了“业务入口正常”和“ACME challenge 正常”是两套路由
很多人会有一个误区:
- 只要业务主页能打开,HTTP-01 一定也能过
这不对。
因为业务主页走的是:
/
而 challenge 走的是:
/.well-known/acme-challenge/<TOKEN>
而且 cert-manager 通常会生成单独的 solver Ingress。
也就是说:
- 主业务路由正常
- 不代表 challenge path 一定正常
你必须单独验证 challenge path。
在这次实验里,我们就明确验证了:
127.0.0.1:30080+ 正确Host+ challenge path- 返回了完整 key authorization
这说明:
- solver 路由本身是通的
失败只出在:
- 公网
80没打通
这次实验还证明了一个非常专业的结论:问题不在 solver,而在 north-south 暴露模型
我们在 cp-3 节点本机做了三个关键验证:
验证一:NodePort 30080 的 challenge path 正常
也就是:
curl http://127.0.0.1:30080/.well-known/acme-challenge/<TOKEN> -H 'Host: 154-9-27-60.sslip.io'
返回:
- 完整 key authorization
验证二:solver 自己的 NodePort 30490 正常
说明 solver Service / Pod 没问题。
验证三:127.0.0.1:80 无监听
直接连接失败。
这三条证据合在一起,就能严谨推出:
solver OK
Ingress rule OK
challenge content OK
public-standard-port exposure NOT OK
这就是专家级定位。
不是泛泛说:
- “ACME 不通”
而是能精确说:
当前失败层次是 north-south 入口标准端口暴露不满足 HTTP-01 前提。
为什么 cert-manager 这次用的是 staging,而不是 production
原因有三个。
第一:生产 API 有真实 rate limits
Let's Encrypt 官方文档说明:
- 新 account 有限制
- 新 order 有限制
- 同 registered domain 有限制
- 同 exact set of identifiers 有限制
- authorization failures 也有限制
如果你在调试期直接打 production,很容易把失败次数烧掉。
第二:staging 专门就是给调试和集成测试用的
Let's Encrypt 官方明确建议:
- 在上 production 之前先用 staging
而 staging 的限制更宽松。
第三:staging 证书默认不被浏览器信任
这其实是优点,不是缺点。
因为它提醒你:
- 这是测试环境
- 不要误把 staging 证书当生产证书
也就是说:
staging 是为了调流程,不是为了上线给用户访问。
生产里到底什么时候选 HTTP-01,什么时候选 DNS-01
这是平台设计决策题,不是死记概念题。
适合 HTTP-01 的场景
- 已经有标准公网 Web 入口
- 域名能解析到入口
80可以从公网到达- 不是 wildcard 需求
- 希望尽量少碰 DNS API 凭据
典型例子:
- 官网
- 单域名前台应用
- 普通 API 网关
适合 DNS-01 的场景
- 需要 wildcard
- 入口不方便暴露
80 - 多地域入口,HTTP 验证路径不好统一
- Web 服务不在公网,但 DNS 可控
- 平台有能力安全管理 DNS API 凭据
典型例子:
*.example.com- 多集群共享一套证书策略
- 不想在入口层为 ACME 特意开
80
DNS-01 为什么常常比 HTTP-01 更适合大型平台
虽然 DNS-01 配置更复杂,但对平台工程来说,它有几个巨大优势:
- 不依赖具体某个 Web 入口。
- 多集群、多地域时更容易统一。
- 支持 wildcard。
- 不需要为 challenge path 去协调代理规则。
但代价也非常真实:
- 你要管理 DNS API Secret。
- 你要理解 DNS propagation。
- 你要小心凭据权限范围。
- 你要考虑
_acme-challenge委派策略。
所以:
HTTP-01 更像入口工程题,DNS-01 更像平台治理题。
Rate Limit 为什么是生产设计的一部分,不是“偶发报错”
很多团队只有撞到 429 才开始看 rate limit。
这是不成熟的。
正确思维是:
- rate limit 是 ACME 生产设计的前置约束
Let's Encrypt 官方当前文档写得很明确,截至 2026-04-10,production 常见限制包括:
- 每个 account 每 3 小时最多 300 个新 orders
- 每个 registered domain 每 7 天最多 50 张新证书
- 相同 exact set of identifiers 每 7 天最多 5 张证书
- 同一 identifier 每小时 authorization failures 也有限额
这几个限制意味着什么?
第一:不要把“调试失败”打到 production
否则最容易先撞的是:
- authorization failure limit
- exact set limit
第二:不要让系统每次部署都重新创建 account
否则会额外消耗:
- account registration limit
第三:不要每次都删 Secret 然后重新全量签发
否则会不必要地制造:
- new orders
- exact set duplicates
第四:尽量把续期和新签发分开理解
官方现在还特别强调:
- ARI renewals 对 rate limit 友好,甚至可免受全部限制
所以未来生产最佳实践一定越来越偏向:
- 更智能的续期协调
- 更少无意义重签
为什么“删除重建试试”在 ACME 世界里是危险习惯
在普通 Kubernetes 资源里,很多人遇到问题的反射是:
- 删了重建
但在 ACME 世界里,这个动作可能会:
- 重新创建 Order
- 重新触发 Challenge
- 重复消耗失败配额
- 命中 exact set rate limit
所以 ACME 排障的正确顺序通常应该是:
- 看
ClusterIssuer - 看
Certificate - 看
CertificateRequest - 看
Order - 看
Challenge - 看 solver Pod / Service / Ingress
- 看公网连通性 / DNS / 防火墙
而不是一上来就:
kubectl delete certificatekubectl delete secretkubectl apply -f ...
这是小白和专家的典型分水岭。
ACME 的专家级排障顺序应该是什么
这里给你一个实战顺序,后面你要把它训练成肌肉记忆。
第一层:Issuer 层
看:
kubectl get clusterissuerkubectl describe clusterissuer
确认:
- account 是否注册成功
- email 是否有效
- server URL 是否正确
- account key Secret 是否创建
第二层:Certificate 层
看:
kubectl get certificatekubectl describe certificate
确认:
- 是否开始 issuing
- 目标 Secret 是否存在
- 是否生成了
CertificateRequest
第三层:CertificateRequest 层
看:
- 是否已 Approved
- 是否仍在等待 Order
第四层:Order 层
看:
state: pending / valid / invalid- authorization URL
- events
第五层:Challenge 层
这是 HTTP-01 / DNS-01 排障核心。
看:
PresentedProcessingReasonState
Reason 往往就是最重要的证据。
第六层:Solver 资源层
对 HTTP-01,重点看:
- solver Pod
- solver Service
- solver Ingress
第七层:外部系统层
看:
- 公网 DNS
- 公网
80 - 入口代理
- 防火墙
- 云 LB
DNS-01 则看:
- TXT 记录是否已写入
- nameserver propagation
- DNS provider API 权限
这次实验为什么是一个非常高质量的失败样本
因为它不是“什么都没出来”的那种低信息量失败。
恰恰相反,它把每一层都跑到了:
- ACME account 已注册
Certificate已创建CertificateRequest已创建并 ApprovedOrder已创建Challenge已创建- solver Pod / Service / Ingress 已创建
- challenge path 在 NodePort 上已经能返回正确内容
最后只差一层:
- 公网
80
这类失败样本特别适合教学,因为它把:
- 协议层
- 控制器层
- 资源层
- 网络层
全部串起来了。
这一课你必须记住的 12 个结论
- 公共 CA 的核心难点是“证明域名控制权”,不是“生成证书文件”。
- ACME account key 和业务证书私钥不是一回事。
privateKeySecretRef在 ACME issuer 上存的是账户身份密钥。Order是订单,不是签发完成。Challenge是域名控制权验证任务。- HTTP-01 验证的是标准 Web 入口控制权。
- HTTP-01 只能走
80,30080这种 NodePort 不是标准挑战端口。 - DNS-01 验证的是 DNS 控制权,适合 wildcard。
- wildcard 证书通常要用 DNS-01。
- cert-manager 会先 self-check,再去真正接受 challenge。
- self-check 失败时,首先查 solver 资源和外部连通性,而不是盲删资源。
- staging 适合调试,production 适合上线,二者账号和信任链都不是一回事。
本课实验文件
- 00-namespace-acme-lab.yaml
- 10-web.yaml
- 20-service.yaml
- 30-clusterissuer-letsencrypt-staging-http01.tmpl.yaml
- 31-clusterissuer-letsencrypt-prod-http01.tmpl.yaml
- 32-apply-issuer-template.sh
- 40-ingress-http01.yaml
官方参考资料
- cert-manager ACME: https://cert-manager.io/docs/configuration/acme/
- cert-manager HTTP01: https://cert-manager.io/docs/configuration/acme/http01/
- cert-manager DNS01: https://cert-manager.io/docs/configuration/acme/dns01/
- cert-manager Ingress annotations: https://cert-manager.io/docs/usage/ingress/
- cert-manager Orders / Challenges: https://cert-manager.io/docs/concepts/acme-orders-challenges/
- cert-manager ACME troubleshooting: https://cert-manager.io/docs/troubleshooting/acme/
- Let's Encrypt challenge types: https://letsencrypt.org/docs/challenge-types/
- Let's Encrypt staging environment: https://letsencrypt.org/docs/staging-environment/
- Let's Encrypt rate limits: https://letsencrypt.org/docs/rate-limits/
下一步最自然的学习方向
从教学顺序上,下一课最自然有两个方向:
方向一:把 HTTP-01 真正做成 success case
也就是:
- 把公网
80的暴露链补齐 - 真正拿到一张 staging / production 证书
这会更偏 north-south 入口工程。
方向二:进入 DNS-01 的生产模型
也就是:
- DNS provider API
_acme-challengeTXT- wildcard
- 凭据治理
这会更偏平台与生产治理。
如果你的目标是平台专家,我会更建议下一步重点讲:
DNS-01 + wildcard + DNS 凭据安全 + 生产切换策略
因为这比单纯打通一个 HTTP-01 success case 更接近企业平台真实需求。