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

Repository Reading Site

第十六课:cert-manager、Ingress 自动签发、证书生命周期与 ACME 工作流原理

上一课你已经搞懂了这些关键事实: 但如果只停在这里,你仍然离生产差得很远。 因为真实公司的入口 HTTPS,不可能靠人肉去做这些事: 1. 手工生成私钥 2. 手工生成 CSR 3. 手工请求签发 4. 手工写 Secret 5. 证书过期前手工续期 6. 更新 Secret 后人工通知入口层 reload 这套流程在生产里几乎一定会出问题。 真正的生产难点

Markdown16-第十六课-cert-manager-Ingress自动签发-证书生命周期与ACME工作流原理.md2026年4月10日 08:06

第十六课:cert-manager、Ingress 自动签发、证书生命周期与 ACME 工作流原理

为什么这一课必须接在 HTTPS / TLS 之后

上一课你已经搞懂了这些关键事实:

  • HTTPS 不只是“HTTP 多了一层 s”
  • TLS 握手发生在 HTTP 之前
  • 证书选择主要看 SNI
  • 路由匹配主要看 HTTP Host
  • Ingress 可以做 TLS termination
  • 自签证书可以加密,但默认不被客户端信任

但如果只停在这里,你仍然离生产差得很远。

因为真实公司的入口 HTTPS,不可能靠人肉去做这些事:

  1. 手工生成私钥
  2. 手工生成 CSR
  3. 手工请求签发
  4. 手工写 Secret
  5. 证书过期前手工续期
  6. 更新 Secret 后人工通知入口层 reload

这套流程在生产里几乎一定会出问题。

真正的生产难点不是“证书怎么生成一次”,而是:

证书怎样以声明式方式被申请、签发、存储、轮换、续期,并在入口层自动生效。

这就是 cert-manager 要解决的问题。

这一课的目标不是“装一个 cert-manager”,而是把下面这条链真正看懂:

Ingress / Certificate 声明
  -> cert-manager 控制器观察到期望状态
  -> 生成私钥 / CertificateRequest
  -> 调用 Issuer / ClusterIssuer 签发
  -> 写入 TLS Secret
  -> Ingress Controller 使用新证书提供 HTTPS

你必须把这条控制链、数据链、信任链全部串起来。


先建立一个专家级问题意识:TLS 不是“有没有证书”,而是“证书生命周期有没有被系统化管理”

很多初学者一提 HTTPS,只盯着证书文件本身:

  • tls.crt
  • tls.key

但专家不会只看文件,而是会问:

  1. 这张证书是谁签的
  2. 为什么客户端信它
  3. 它何时过期
  4. 过期前谁来续
  5. 续期后谁来分发
  6. 新证书落盘后谁来 reload
  7. 失败时去哪一层排障

所以你要把“证书生命周期”理解成 7 个阶段:

  1. 生成密钥对
  2. 形成证书请求
  3. 证明自己有资格拿这张证书
  4. 由某个签发者签发
  5. 将结果写到客户端或入口层可消费的位置
  6. 在过期前续期
  7. 让新证书真正进入数据平面

cert-manager 的价值,不在于“能签证书”这四个字,而在于:

它把证书生命周期做成了 Kubernetes 风格的控制循环。


先分清 6 个最容易混淆的对象

第一:Secret

Secret 只是证书材料的存储位置。

它通常放的是:

  • tls.crt
  • tls.key
  • 有时还有 ca.crt

它不是签发者,也不是续期逻辑,更不是申请流程。

第二:Certificate

Certificate 是“我想要一张什么样的证书”的声明。

它会描述:

  • 域名
  • 用哪个 Issuer
  • 结果写到哪个 Secret
  • 有效期多久
  • 什么时候提前续期

所以:

Certificate 是期望状态,Secret 是结果状态。

第三:Issuer

Issuer 是命名空间级别的签发者。

它的可见范围只在当前 namespace。

适合:

  • 某个团队只在自己 namespace 内自用
  • 你不想把签发能力开放给全集群

第四:ClusterIssuer

ClusterIssuer 是集群级签发者。

适合:

  • 整个集群统一一套签发入口
  • 多个业务 namespace 共用同一个 ACME / CA 签发策略

生产里最常见的是:

  • 公网证书使用统一的 ClusterIssuer

第五:CertificateRequest

它是签发过程中的“中间态对象”。

你可以把它理解成:

cert-manager 把“我要证书”这件事,具体化成一次带有审批语义的签发请求。

很多人只盯着 CertificateSecret,却忽略 CertificateRequest

这是不行的。

因为大量排障都卡在这里:

  • 请求没被批准
  • 请求没被某个 issuer 处理
  • 请求被处理了但没能写回

第六:Order / Challenge

这两个对象只在 ACME 场景里出现。

也就是当你的 issuer 是:

  • Let's Encrypt
  • ZeroSSL
  • 其他 ACME CA

时,才会出现它们。

含义可以理解成:

  • Order:向 ACME CA 下的“证书订单”
  • Challenge:为了证明你控制了某个域名而必须完成的校验任务

这两个对象在自建 CA 签发里不会出现。

这点你必须非常清楚,因为很多人会把所有 cert-manager 签发都想成 ACME。

不是。


cert-manager 到底是什么架构

从工程角度说,cert-manager 不是一个单一进程,而是一组组件:

1. Controller

这是主控制器。

它负责 watch 和 reconcile:

  • Issuer
  • ClusterIssuer
  • Certificate
  • CertificateRequest
  • Order
  • Challenge
  • 带 cert-manager 注解的 Ingress

它是真正推动签发流程向前的人。

2. Webhook

它负责准入与校验。

简单理解:

  • 你提交 cert-manager 的 CRD 对象时
  • webhook 会帮你做字段校验、默认值处理、版本转换相关工作

这一步很重要,因为 CRD 不等于自动有复杂校验逻辑。

3. CA Injector

cainjector 负责把 CA bundle 注入到需要信任 cert-manager webhook 的对象里。

例如:

  • ValidatingWebhookConfiguration
  • MutatingWebhookConfiguration

如果没有这一步,API Server 在和 webhook 走 HTTPS 通信时,可能不知道该信谁。

这也是很多人第一次安装 cert-manager 时容易忽略的地方:

它自己也是一套基于 TLS 信任链工作的控制面组件。


为什么说 cert-manager 很 Kubernetes

因为它完全遵循 Kubernetes 的核心哲学:

  • 你声明期望状态
  • 控制器不断靠近这个期望状态

比如你写:

apiVersion: cert-manager.io/v1
kind: Certificate
spec:
  dnsNames:
    - auto-secure.k8s-lab.local
  secretName: cm-web-tls
  issuerRef:
    name: lab-root-ca-issuer
    kind: ClusterIssuer

你不是在命令式地说:

  • 先生成私钥
  • 再生成 CSR
  • 再签发
  • 再写入 Secret

你只是说:

我希望有一张给 auto-secure.k8s-lab.local 用的证书,最终存到 cm-web-tls

然后控制器自己去补齐实现细节。

这就是 Kubernetes 世界里非常典型的:

  • desired state
  • reconciliation loop

这一课我为什么不用 Let's Encrypt 直接做实验

这是一个非常重要的工程判断。

截至 2026-04-10,当前实验环境有两个现实限制:

限制一:实验域名是 *.k8s-lab.local

这不是公网可解析域名。

而 Let's Encrypt 这类 ACME CA 要求:

  • 你必须证明自己控制这个域名

限制二:当前入口暴露方式是 NodePort 30080/30443

但公网 HTTP-01 验证常见要求是:

  • 域名能被公网解析到你的入口
  • CA 能从公网访问你的 80 端口

而我们当前实验并没有构造出这个公网验证前提。

所以这一课我采用两层讲法:

  1. 在集群里真实安装 cert-manager
  2. SelfSigned -> CA -> Ingress 自动签发 做出一个完整可跑的闭环
  3. 再把 ACME 的工作流、适用场景、限制和排障方法讲透

这样做的好处是:

  • 实验可控
  • 原理清楚
  • 不依赖公网 DNS 和外部 CA

这比一上来直接接 Let's Encrypt 更适合小白建立底层认知。


这一课为什么选择 v1.18.6

这个点非常值得你养成“版本矩阵意识”。

当前集群版本是:

  • Kubernetes v1.30.14

cert-manager 的较新发布线已经面向 Kubernetes 1.31+

所以这次实验我没有盲目追“最新大版本”,而是选了:

  • cert-manager v1.18.6

原因是:

  • 它仍然落在和当前 Kubernetes 1.30.x 更匹配的兼容线里

但你也必须知道:

这不等于生产推荐方案。

生产正确动作通常是:

  1. 先把 Kubernetes 升到 cert-manager 官方支持的较新版本窗口
  2. 再升级 cert-manager 到当前受支持的发布线

也就是说,专家不会机械地“永远最新”,而是会先看:

  • 兼容矩阵
  • 支持周期
  • 升级路径

实验拓扑:这一课真实搭出了什么

这一课的真实实验链路如下:

lab-selfsigned-bootstrap (ClusterIssuer, selfSigned)
  -> lab-root-ca (Certificate, isCA=true)
  -> lab-root-ca-secret (root CA Secret)
  -> lab-root-ca-issuer (ClusterIssuer, type=ca)
  -> Ingress(cm-web, annotated)
  -> ingress-shim 自动创建 Certificate(cm-web-tls)
  -> CertificateRequest(cm-web-tls-1)
  -> Secret(cm-web-tls)
  -> ingress-nginx 在 30443 上提供 HTTPS

这里面有三层非常关键:

第一层:Bootstrap 层

selfSigned issuer 签出一张“根 CA 证书”。

为什么要先这样做?

因为你如果想拥有一个可以签别人证书的 CA,就必须先有 CA 自己的密钥和证书。

SelfSigned 在这里的角色不是长期业务签发者,而是:

用来引导出第一张 root CA 证书。

第二层:CA 签发层

有了 root CA 的 Secret 之后,再用 type=caClusterIssuer 去签发业务叶子证书。

这时候它不再是“证书自己签自己”,而是:

  • root CA 签发 leaf certificate

这才形成了真正意义上的“证书链”。

第三层:入口自动签发层

业务 Ingress 上带注解:

  • cert-manager.io/cluster-issuer: lab-root-ca-issuer

ingress-shim 看到后会自动创建 Certificate

这就是很多生产环境里最常见的体验:

  • 你写一个带 tls.secretName 的 Ingress
  • 再加一个 cert-manager annotation
  • 证书对象就自动被补出来

SelfSignedCA 到底有什么本质区别

很多人第一次学 cert-manager 时最容易混淆这两个 issuer。

selfSigned

意思是:

用这张证书自己的私钥去签自己。

它适合:

  • 引导出 root CA
  • 做最小教学实验

它不适合直接做大规模业务签发体系的终局方案。

ca

意思是:

已经有一个现成 CA 的私钥和证书,用它去给别的证书签名。

它适合:

  • 内部服务证书
  • 实验室环境
  • 企业内网 PKI

你可以把关系理解成:

selfSigned: 先把“根”造出来
ca: 再用这个“根”去签别人

为什么 Ingress 注解能自动变出 Certificate

这不是 Kubernetes 原生行为,而是 cert-manager 的一个控制器能力:

  • ingress-shim

它会 watch 带有 cert-manager 注解的 Ingress。

当它看到下面这些信息同时出现时:

  • 某个 issuer 注解
  • spec.tls.hosts
  • spec.tls.secretName

它就会知道:

这个 Ingress 期望拥有一张 TLS 证书。

于是它会自动创建一个 Certificate 对象。

这一点在实验里有非常强的证据:

  • 自动生成出来的 Certificate cm-web-tls
  • 它的 ownerReferences 指向 Ingress cm-web

这说明:

Certificate 不是我手工创建的,而是被 ingress-shim 补出来的。


CertificateSecret 的中间到底发生了什么

这是 cert-manager 最重要的一段控制循环。

从这次实验的事件顺序看,大致是:

  1. Ingress 被创建
  2. ingress-shim 创建 Certificate
  3. cert-manager 发现目标 Secret 不存在
  4. key-manager 先生成私钥,放进一个临时 Secret
  5. request-manager 创建 CertificateRequest
  6. approver 对请求进行批准
  7. issuer 根据请求签发证书
  8. 结果写入最终 Secret
  9. Certificate 进入 Ready=True

这条线在事件里能看到很清楚的字眼:

  • CreateCertificate
  • Generated
  • Requested
  • Approved
  • CertificateIssued

所以以后你排障,不要只会看最后有没有 Secret。

你应该按阶段拆:

  1. Ingress 有没有触发 shim
  2. Certificate 有没有创建
  3. 私钥有没有生成
  4. CertificateRequest 有没有出来
  5. 有没有被批准
  6. issuer 有没有签出来
  7. Secret 有没有写成

这就是专家式排障思维。


为什么这次实验里没有 OrderChallenge

因为这次使用的是:

  • ClusterIssuer with ca

而不是:

  • ClusterIssuer with acme

Order / Challenge 只属于 ACME 世界。

也就是说:

  • 如果是内部 CA 直接签发
  • 不需要向外部 CA 证明域名控制权

那就不需要:

  • Order
  • Challenge

这也是这次实验一个很好的对比点:

cert-manager 不等于 ACME,ACME 只是 cert-manager 的一类 issuer 后端。


为什么叶子证书的 subject 可以是空的

这次实验里 openssl 抓到的叶子证书,subject= 是空的,但 SAN 正常存在:

  • DNS:auto-secure.k8s-lab.local

这不是异常。

恰恰相反,它体现了现代 TLS 实践:

  • 主机名校验主要看 SAN
  • CN 已经不是主机名匹配的核心位置

这点非常值得你记住。

很多老文章会强调:

  • CN=example.com

但今天更重要的是:

  • subjectAltName

所以以后你排证书问题,必须优先看:

  • SAN 里有没有目标域名

为什么 curl 不带 -k 会失败,但带 --cacert 会成功

这是“加密”和“信任”关系的再一次强化。

在这次实验里,叶子证书是由我们自己的 root CA 签发的。

所以:

  • 流量确实是加密的
  • 证书链也确实是完整的

但客户端默认并不认识我们自建的 root CA。

于是:

  • curl 默认校验失败
  • curl -k 跳过校验能成功
  • curl --cacert /path/to/ca.crt 在显式信任这张 root CA 后也能成功

这三种行为分别对应三种含义:

curl 失败

说明:

  • 客户端不信任该 CA

curl -k 成功

说明:

  • 握手和加密没问题
  • 只是跳过了身份校验

curl --cacert 成功

说明:

  • 证书链本身没问题
  • 只是客户端原本缺少信任根

这个判断能力非常关键。

因为生产里大量“证书有问题”的问题,真正问题不是 TLS 坏了,而是:

  • 客户端 trust store 不完整
  • 证书链没带全
  • 域名和 SAN 不匹配

durationrenewBeforerevision 分别代表什么

Certificate 状态里你会看到这些字段:

  • notBefore
  • notAfter
  • renewalTime
  • revision

它们分别反映:

notBefore

证书开始生效的时间。

notAfter

证书失效时间,也就是过期点。

renewalTime

cert-manager 计划开始续期的时间点。

注意,这不是“真正过期那一刻再续”。

因为那样太危险。

revision

第几版证书。

如果以后证书被重新签发,这个值会增加。

这在观察轮换、诊断是否真的续过期时非常有用。


rotationPolicy 为什么值得关注

在这次实验里,创建 root CA Certificate 时出现过一个提示:

  • cert-manager >= v1.18.0
  • privateKey.rotationPolicy 默认值从 Never 变成 Always

这件事告诉你:

默认值也是版本行为,不能把它当成永远不变的常识。

所以在实验清单里,我后来把它显式写到了 YAML 里。

这样做的工程价值是:

  • 避免重复 apply 时靠“记忆”理解行为
  • 避免未来升级版本后被默认值变化坑到

专家写 YAML,不只是为了“跑起来”,还要尽量把关键意图显式化。


为什么 Ingress 这次会自动带上 Strict-Transport-Security

这次 HTTPS 响应头里你能看到:

  • strict-transport-security: max-age=31536000; includeSubDomains

这说明入口层在做的不只是“转发流量”,它也可能附带一部分安全头策略。

但你不要因此误解:

HSTS 头存在,不等于你的信任体系已经完善。

因为:

  • HSTS 解决的是浏览器后续强制 HTTPS 的策略
  • 证书信任解决的是 TLS 身份校验

这两者不是同一个问题。


这套实验再次证明了什么叫 TLS termination

这次业务 Pod 本身仍然只监听:

  • 8080 HTTP

但客户端访问的是:

  • https://...:30443

这说明:

  • 客户端到 ingress-nginx 是 HTTPS
  • ingress-nginx 到后端 Service / Pod 仍然可以是 HTTP

也就是说:

证书自动签发和入口终止 TLS,是可以解耦后端应用的。

这是平台层价值非常大的地方:

  • 业务方不必每个应用自己做 HTTPS
  • 平台统一处理入口证书生命周期

当然,这不代表永远不需要端到端加密。

在更高安全要求场景里,你仍可能需要:

  • re-encrypt
  • mTLS
  • service mesh

但那是后续课题。


进入生产世界:ACME 的完整工作流到底是什么

现在你已经看懂了内部 CA 的自动签发模型,接下来要把公网证书自动化也装进脑子里。

如果 issuer 是 ACME,比如 Let's Encrypt,流程会多出几层:

Certificate
  -> CertificateRequest
  -> Order
  -> Challenge
  -> solver 暴露验证入口
  -> ACME CA 验证域名控制权
  -> Order finalize
  -> Secret 写入证书

Order

相当于你向 ACME CA 说:

我想申请这些域名的证书,请给我验证任务。

Challenge

ACME CA 会返回一个或多个挑战任务,要求你证明:

这些域名确实由你控制。

Solver

cert-manager 会按 challenge 类型,临时创建 solver 相关对象。

例如 HTTP-01 时,常见会临时创建:

  • solver Pod
  • solver Service
  • solver Ingress

它们的作用是把 ACME 要访问的验证路径暴露出来。


HTTP-01 和 DNS-01 到底有什么区别

这是 cert-manager 生产实践里最重要的判断题之一。

HTTP-01

原理:

  • ACME CA 访问 http://你的域名/.well-known/acme-challenge/...
  • 看你是否能正确返回 challenge token

适合:

  • 单域名、普通站点
  • 入口已经有公网 HTTP 流量
  • 你能控制 Ingress / LB / 80 端口

限制:

  • 依赖公网从 80 端口访问进来
  • 不适合通配符 *.example.com

DNS-01

原理:

  • ACME CA 去查某条 _acme-challenge.example.com TXT 记录
  • 看你是否能把 challenge token 正确写进 DNS

适合:

  • 通配符证书
  • 多入口 / 多地域
  • 不方便开放 80 端口

限制:

  • 需要自动化控制 DNS 提供商 API
  • DNS 生效有传播延迟
  • 权限设计更敏感

你可以这样记:

HTTP-01:证明“这个 Web 入口是我的”
DNS-01:证明“这个域名 DNS 我能改”

为什么通配符证书通常要走 DNS-01

因为通配符证书要证明的是:

  • *.example.com

而这类控制权更适合通过 DNS 来证明。

如果你以后在生产里要统一给很多子域做入口证书:

  • api.example.com
  • admin.example.com
  • grafana.example.com
  • argocd.example.com

那 DNS-01 常常是更稳定的方案。

但代价是:

  • 你要安全地管理 DNS API 凭据

这会把问题从“入口层”延伸到:

  • 凭据治理
  • 外部系统权限
  • Secret 生命周期

为什么 ACME 很容易踩到“环境问题”,而 CA Issuer 相对简单

因为 CA Issuer 主要是集群内部控制问题:

  • issuer Ready 吗
  • Secret 存在吗
  • 证书请求对象正常吗

而 ACME 除了集群内部,还多了外部依赖:

  • 域名解析
  • 公网连通性
  • LB / NodePort / 防火墙
  • DNS API 权限
  • ACME CA 速率限制

所以 ACME 排障永远比内部 CA 更复杂。

这也是为什么这一课先让你把内部 CA 模型吃透。

如果你连:

  • Certificate
  • CertificateRequest
  • Secret
  • Ingress

之间的关系都没搞明白,就直接上 ACME,很容易只会“撞命令”。


cert-manager 的排障应该按哪几层看

这里给你一个非常实用的分层模型。

第一层:安装层

先看:

  • kubectl -n cert-manager get pod
  • kubectl get crd | grep cert-manager

确认:

  • controller 在不在
  • webhook 在不在
  • cainjector 在不在
  • CRD 有没有注册成功

第二层:签发者层

看:

  • kubectl get issuer,clusterissuer
  • kubectl describe clusterissuer <name>

确认:

  • Ready 吗
  • 用的 Secret 存在吗
  • 状态原因是什么

第三层:证书声明层

看:

  • kubectl get certificate -A
  • kubectl describe certificate <name>

确认:

  • 目标 Secret 是否存在
  • 是否计划续期
  • 事件停在哪一步

第四层:请求层

看:

  • kubectl get certificaterequest -A
  • kubectl describe certificaterequest <name>

确认:

  • 有没有被批准
  • 是谁请求的
  • issuerRef 对不对

第五层:ACME 中间态层

仅在 ACME 场景:

  • kubectl get orders,challenges -A

确认:

  • challenge 卡在 DNS 还是 HTTP
  • solver 资源有没有创建

第六层:Secret / 数据面层

看:

  • Secret 里有没有 tls.crt / tls.key / ca.crt
  • Ingress controller 有没有加载
  • openssl s_client
  • curl --cacert

确认:

  • 证书是不是那张
  • 信任链是不是正确
  • 数据面是否真的在用新证书

你必须建立的 10 条 cert-manager 心智模型

  1. Certificate 不是证书文件,而是期望状态对象。
  2. Secret 不是签发流程,而是签发结果落点。
  3. Issuer / ClusterIssuer 决定“由谁签”。
  4. CertificateRequest 是一次具体的签发请求,不看它就不算会排障。
  5. Order / Challenge 只属于 ACME,不属于所有签发场景。
  6. ingress-shim 只是自动补 Certificate,它不是证书本身。
  7. 证书签出来不等于客户端会信,信任根分发是另一件事。
  8. curl -k 成功只能说明加密通了,不能说明身份校验正确。
  9. 版本选择要看兼容矩阵,不能只看“最新”。
  10. 自动续期不是终点,数据面是否真的使用了新证书同样要验证。

这一课你真正学会后,应该能回答哪些面试 / 实战问题

  1. IssuerClusterIssuer 的区别是什么?
  2. CertificateSecret 的关系是什么?
  3. 为什么带 cert-manager 注解的 Ingress 会自动出现 Certificate
  4. CertificateRequest 在签发链路里起什么作用?
  5. 为什么自建 CA 签发时没有 Order / Challenge
  6. 为什么 curl -k 成功不代表证书没问题?
  7. 为什么现代证书有时 CN 为空但仍然正常?
  8. HTTP-01 和 DNS-01 分别适合什么场景?
  9. 为什么通配符证书常常要走 DNS-01?
  10. 如果 cert-manager 装好了但证书没出来,你按什么顺序查?

如果你能把这些问题讲清楚,而且能结合这次实验给出证据,你就已经不是“只会抄 YAML”的阶段了。


本课实验文件


官方参考资料

下面这些资料建议你后续配合本课反复看:


下一步最自然的学习方向

从教学顺序上,下一课最自然的是:

  • 真正进入 ACME / Let's Encrypt / HTTP-01 / DNS-01
  • 把“公网域名自动签发”和“生产级故障排查”单独做成一课

因为到这里为止,你已经把:

  • TLS 握手
  • 证书信任
  • Ingress 终止 TLS
  • cert-manager 内部 CA 自动签发

这一整条链打通了。

下一步就该把“公网自动化”讲透。