K8s Lab 把当前仓库文档整理成一个可阅读的网页站点

Repository Reading Site

第十七课:ACME、Let's Encrypt、HTTP-01 / DNS-01、Orders / Challenges 与生产限制原理

上一课你已经把 cert-manager 的“内部签发模型”打通了: 但上一课解决的是: 这当然很重要,但它还不是公网生产 HTTPS 的完整答案。 真实生产里,前台站点通常面对的是: 这些客户端不会默认信任你实验室自建的 root CA。 所以如果你想让浏览器默认信任,一般要么: 最典型的就是: 而公共 CA 不会像你自己的 CA 一样,直接听你说“这是我

Markdown17-第十七课-ACME-Lets-Encrypt-HTTP01-DNS01-Orders-Challenges与生产限制原理.md2026年4月10日 09:04

第十七课:ACME、Let's Encrypt、HTTP-01 / DNS-01、Orders / Challenges 与生产限制原理

为什么这一课必须接在 cert-manager 之后

上一课你已经把 cert-manager 的“内部签发模型”打通了:

  • Certificate 是期望状态
  • Secret 是结果状态
  • ClusterIssuer 决定“由谁签”
  • ingress-shim 会根据 Ingress 自动补出 Certificate
  • CertificateRequest 是签发过程中的关键中间态

但上一课解决的是:

我自己在集群里当 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-01
  • DNS-01

而是要能准确解释:

  1. 它们在协议上到底验证什么
  2. 为什么一个要依赖公网 80
  3. 为什么另一个要依赖 DNS API
  4. 为什么 wildcard 常常必须用 DNS-01
  5. 为什么 cert-manager 会先做 self-check
  6. 为什么 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.local
  • internal.lan
  • db.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-01
  • dns-01
  • tls-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 模型里,你主要看到的是:

  • Certificate
  • CertificateRequest

但在 ACME 世界里,cert-manager 还要把协议的中间态映射成 Kubernetes 资源。

这就是:

  • Order
  • Challenge

它们的价值在于:

把一个原本隐藏在 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 对象里就有:

  • token
  • key

而节点本机 curl 到 solver path 时,实际返回的正是完整 key authorization。

第四:验证是从公网进行的,不是从集群内部进行的

这也是最容易被忽视的地方。

只要你脑子里还有“我在 Pod 里 curl 通了,所以应该能过”这种思路,就还没真正理解 HTTP-01。

HTTP-01 要求的是:

ACME server 必须从公网成功取回 challenge 内容。


为什么 HTTP-01 强依赖公网 80 端口

Let's Encrypt 官方文档说得很明确:

  • HTTP-01 只能在 port 80 上完成
  • 重定向只接受到 80443

这是协议和安全模型决定的。

很多人第一反应会问:

  • 我能不能用 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 的流程是:

  1. 给你 token
  2. 你把它加工后写进 _acme-challenge.<YOUR_DOMAIN> 的 TXT 记录
  3. CA 去 DNS 系统里查这个 TXT 记录
  4. 记录正确则通过

这个模型和 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 的策略是:

  1. 先创建 solver 资源
  2. 先确认自己能看到 challenge 内容已经传播到位
  3. 自检通过后再去接受 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 配置更复杂,但对平台工程来说,它有几个巨大优势:

  1. 不依赖具体某个 Web 入口。
  2. 多集群、多地域时更容易统一。
  3. 支持 wildcard。
  4. 不需要为 challenge path 去协调代理规则。

但代价也非常真实:

  1. 你要管理 DNS API Secret。
  2. 你要理解 DNS propagation。
  3. 你要小心凭据权限范围。
  4. 你要考虑 _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 排障的正确顺序通常应该是:

  1. ClusterIssuer
  2. Certificate
  3. CertificateRequest
  4. Order
  5. Challenge
  6. 看 solver Pod / Service / Ingress
  7. 看公网连通性 / DNS / 防火墙

而不是一上来就:

  • kubectl delete certificate
  • kubectl delete secret
  • kubectl apply -f ...

这是小白和专家的典型分水岭。


ACME 的专家级排障顺序应该是什么

这里给你一个实战顺序,后面你要把它训练成肌肉记忆。

第一层:Issuer 层

看:

  • kubectl get clusterissuer
  • kubectl describe clusterissuer

确认:

  • account 是否注册成功
  • email 是否有效
  • server URL 是否正确
  • account key Secret 是否创建

第二层:Certificate 层

看:

  • kubectl get certificate
  • kubectl describe certificate

确认:

  • 是否开始 issuing
  • 目标 Secret 是否存在
  • 是否生成了 CertificateRequest

第三层:CertificateRequest 层

看:

  • 是否已 Approved
  • 是否仍在等待 Order

第四层:Order 层

看:

  • state: pending / valid / invalid
  • authorization URL
  • events

第五层:Challenge 层

这是 HTTP-01 / DNS-01 排障核心。

看:

  • Presented
  • Processing
  • Reason
  • State

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 已创建并 Approved
  • Order 已创建
  • Challenge 已创建
  • solver Pod / Service / Ingress 已创建
  • challenge path 在 NodePort 上已经能返回正确内容

最后只差一层:

  • 公网 80

这类失败样本特别适合教学,因为它把:

  • 协议层
  • 控制器层
  • 资源层
  • 网络层

全部串起来了。


这一课你必须记住的 12 个结论

  1. 公共 CA 的核心难点是“证明域名控制权”,不是“生成证书文件”。
  2. ACME account key 和业务证书私钥不是一回事。
  3. privateKeySecretRef 在 ACME issuer 上存的是账户身份密钥。
  4. Order 是订单,不是签发完成。
  5. Challenge 是域名控制权验证任务。
  6. HTTP-01 验证的是标准 Web 入口控制权。
  7. HTTP-01 只能走 8030080 这种 NodePort 不是标准挑战端口。
  8. DNS-01 验证的是 DNS 控制权,适合 wildcard。
  9. wildcard 证书通常要用 DNS-01。
  10. cert-manager 会先 self-check,再去真正接受 challenge。
  11. self-check 失败时,首先查 solver 资源和外部连通性,而不是盲删资源。
  12. staging 适合调试,production 适合上线,二者账号和信任链都不是一回事。

本课实验文件


官方参考资料


下一步最自然的学习方向

从教学顺序上,下一课最自然有两个方向:

方向一:把 HTTP-01 真正做成 success case

也就是:

  • 把公网 80 的暴露链补齐
  • 真正拿到一张 staging / production 证书

这会更偏 north-south 入口工程。

方向二:进入 DNS-01 的生产模型

也就是:

  • DNS provider API
  • _acme-challenge TXT
  • wildcard
  • 凭据治理

这会更偏平台与生产治理。

如果你的目标是平台专家,我会更建议下一步重点讲:

  • DNS-01 + wildcard + DNS 凭据安全 + 生产切换策略

因为这比单纯打通一个 HTTP-01 success case 更接近企业平台真实需求。