Repository Reading Site
第十六课:cert-manager、Ingress 自动签发、证书生命周期与 ACME 工作流原理
上一课你已经搞懂了这些关键事实: 但如果只停在这里,你仍然离生产差得很远。 因为真实公司的入口 HTTPS,不可能靠人肉去做这些事: 1. 手工生成私钥 2. 手工生成 CSR 3. 手工请求签发 4. 手工写 Secret 5. 证书过期前手工续期 6. 更新 Secret 后人工通知入口层 reload 这套流程在生产里几乎一定会出问题。 真正的生产难点
第十六课:cert-manager、Ingress 自动签发、证书生命周期与 ACME 工作流原理
为什么这一课必须接在 HTTPS / TLS 之后
上一课你已经搞懂了这些关键事实:
- HTTPS 不只是“HTTP 多了一层 s”
- TLS 握手发生在 HTTP 之前
- 证书选择主要看
SNI - 路由匹配主要看 HTTP
Host - Ingress 可以做 TLS termination
- 自签证书可以加密,但默认不被客户端信任
但如果只停在这里,你仍然离生产差得很远。
因为真实公司的入口 HTTPS,不可能靠人肉去做这些事:
- 手工生成私钥
- 手工生成 CSR
- 手工请求签发
- 手工写 Secret
- 证书过期前手工续期
- 更新 Secret 后人工通知入口层 reload
这套流程在生产里几乎一定会出问题。
真正的生产难点不是“证书怎么生成一次”,而是:
证书怎样以声明式方式被申请、签发、存储、轮换、续期,并在入口层自动生效。
这就是 cert-manager 要解决的问题。
这一课的目标不是“装一个 cert-manager”,而是把下面这条链真正看懂:
Ingress / Certificate 声明
-> cert-manager 控制器观察到期望状态
-> 生成私钥 / CertificateRequest
-> 调用 Issuer / ClusterIssuer 签发
-> 写入 TLS Secret
-> Ingress Controller 使用新证书提供 HTTPS
你必须把这条控制链、数据链、信任链全部串起来。
先建立一个专家级问题意识:TLS 不是“有没有证书”,而是“证书生命周期有没有被系统化管理”
很多初学者一提 HTTPS,只盯着证书文件本身:
tls.crttls.key
但专家不会只看文件,而是会问:
- 这张证书是谁签的
- 为什么客户端信它
- 它何时过期
- 过期前谁来续
- 续期后谁来分发
- 新证书落盘后谁来 reload
- 失败时去哪一层排障
所以你要把“证书生命周期”理解成 7 个阶段:
- 生成密钥对
- 形成证书请求
- 证明自己有资格拿这张证书
- 由某个签发者签发
- 将结果写到客户端或入口层可消费的位置
- 在过期前续期
- 让新证书真正进入数据平面
cert-manager 的价值,不在于“能签证书”这四个字,而在于:
它把证书生命周期做成了 Kubernetes 风格的控制循环。
先分清 6 个最容易混淆的对象
第一:Secret
Secret 只是证书材料的存储位置。
它通常放的是:
tls.crttls.key- 有时还有
ca.crt
它不是签发者,也不是续期逻辑,更不是申请流程。
第二:Certificate
Certificate 是“我想要一张什么样的证书”的声明。
它会描述:
- 域名
- 用哪个
Issuer - 结果写到哪个
Secret - 有效期多久
- 什么时候提前续期
所以:
Certificate是期望状态,Secret是结果状态。
第三:Issuer
Issuer 是命名空间级别的签发者。
它的可见范围只在当前 namespace。
适合:
- 某个团队只在自己 namespace 内自用
- 你不想把签发能力开放给全集群
第四:ClusterIssuer
ClusterIssuer 是集群级签发者。
适合:
- 整个集群统一一套签发入口
- 多个业务 namespace 共用同一个 ACME / CA 签发策略
生产里最常见的是:
- 公网证书使用统一的
ClusterIssuer
第五:CertificateRequest
它是签发过程中的“中间态对象”。
你可以把它理解成:
cert-manager 把“我要证书”这件事,具体化成一次带有审批语义的签发请求。
很多人只盯着 Certificate 和 Secret,却忽略 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:
IssuerClusterIssuerCertificateCertificateRequestOrderChallenge- 带 cert-manager 注解的
Ingress
它是真正推动签发流程向前的人。
2. Webhook
它负责准入与校验。
简单理解:
- 你提交 cert-manager 的 CRD 对象时
- webhook 会帮你做字段校验、默认值处理、版本转换相关工作
这一步很重要,因为 CRD 不等于自动有复杂校验逻辑。
3. CA Injector
cainjector 负责把 CA bundle 注入到需要信任 cert-manager webhook 的对象里。
例如:
ValidatingWebhookConfigurationMutatingWebhookConfiguration
如果没有这一步,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端口
而我们当前实验并没有构造出这个公网验证前提。
所以这一课我采用两层讲法:
- 在集群里真实安装
cert-manager - 用
SelfSigned -> CA -> Ingress 自动签发做出一个完整可跑的闭环 - 再把 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更匹配的兼容线里
但你也必须知道:
这不等于生产推荐方案。
生产正确动作通常是:
- 先把 Kubernetes 升到 cert-manager 官方支持的较新版本窗口
- 再升级 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=ca 的 ClusterIssuer 去签发业务叶子证书。
这时候它不再是“证书自己签自己”,而是:
- root CA 签发 leaf certificate
这才形成了真正意义上的“证书链”。
第三层:入口自动签发层
业务 Ingress 上带注解:
cert-manager.io/cluster-issuer: lab-root-ca-issuer
ingress-shim 看到后会自动创建 Certificate。
这就是很多生产环境里最常见的体验:
- 你写一个带
tls.secretName的 Ingress - 再加一个 cert-manager annotation
- 证书对象就自动被补出来
SelfSigned 和 CA 到底有什么本质区别
很多人第一次学 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.hostsspec.tls.secretName
它就会知道:
这个 Ingress 期望拥有一张 TLS 证书。
于是它会自动创建一个 Certificate 对象。
这一点在实验里有非常强的证据:
- 自动生成出来的
Certificate cm-web-tls - 它的
ownerReferences指向Ingress cm-web
这说明:
Certificate不是我手工创建的,而是被 ingress-shim 补出来的。
Certificate 到 Secret 的中间到底发生了什么
这是 cert-manager 最重要的一段控制循环。
从这次实验的事件顺序看,大致是:
Ingress被创建- ingress-shim 创建
Certificate - cert-manager 发现目标 Secret 不存在
- key-manager 先生成私钥,放进一个临时 Secret
- request-manager 创建
CertificateRequest - approver 对请求进行批准
- issuer 根据请求签发证书
- 结果写入最终
Secret Certificate进入Ready=True
这条线在事件里能看到很清楚的字眼:
CreateCertificateGeneratedRequestedApprovedCertificateIssued
所以以后你排障,不要只会看最后有没有 Secret。
你应该按阶段拆:
- Ingress 有没有触发 shim
- Certificate 有没有创建
- 私钥有没有生成
- CertificateRequest 有没有出来
- 有没有被批准
- issuer 有没有签出来
- Secret 有没有写成
这就是专家式排障思维。
为什么这次实验里没有 Order 和 Challenge
因为这次使用的是:
ClusterIssuerwithca
而不是:
ClusterIssuerwithacme
Order / Challenge 只属于 ACME 世界。
也就是说:
- 如果是内部 CA 直接签发
- 不需要向外部 CA 证明域名控制权
那就不需要:
OrderChallenge
这也是这次实验一个很好的对比点:
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 不匹配
duration、renewBefore、revision 分别代表什么
Certificate 状态里你会看到这些字段:
notBeforenotAfterrenewalTimerevision
它们分别反映:
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.comTXT 记录 - 看你是否能把 challenge token 正确写进 DNS
适合:
- 通配符证书
- 多入口 / 多地域
- 不方便开放 80 端口
限制:
- 需要自动化控制 DNS 提供商 API
- DNS 生效有传播延迟
- 权限设计更敏感
你可以这样记:
HTTP-01:证明“这个 Web 入口是我的”
DNS-01:证明“这个域名 DNS 我能改”
为什么通配符证书通常要走 DNS-01
因为通配符证书要证明的是:
*.example.com
而这类控制权更适合通过 DNS 来证明。
如果你以后在生产里要统一给很多子域做入口证书:
api.example.comadmin.example.comgrafana.example.comargocd.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 模型吃透。
如果你连:
CertificateCertificateRequestSecretIngress
之间的关系都没搞明白,就直接上 ACME,很容易只会“撞命令”。
cert-manager 的排障应该按哪几层看
这里给你一个非常实用的分层模型。
第一层:安装层
先看:
kubectl -n cert-manager get podkubectl get crd | grep cert-manager
确认:
- controller 在不在
- webhook 在不在
- cainjector 在不在
- CRD 有没有注册成功
第二层:签发者层
看:
kubectl get issuer,clusterissuerkubectl describe clusterissuer <name>
确认:
- Ready 吗
- 用的 Secret 存在吗
- 状态原因是什么
第三层:证书声明层
看:
kubectl get certificate -Akubectl describe certificate <name>
确认:
- 目标 Secret 是否存在
- 是否计划续期
- 事件停在哪一步
第四层:请求层
看:
kubectl get certificaterequest -Akubectl 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_clientcurl --cacert
确认:
- 证书是不是那张
- 信任链是不是正确
- 数据面是否真的在用新证书
你必须建立的 10 条 cert-manager 心智模型
Certificate不是证书文件,而是期望状态对象。Secret不是签发流程,而是签发结果落点。Issuer/ClusterIssuer决定“由谁签”。CertificateRequest是一次具体的签发请求,不看它就不算会排障。Order/Challenge只属于 ACME,不属于所有签发场景。ingress-shim只是自动补Certificate,它不是证书本身。- 证书签出来不等于客户端会信,信任根分发是另一件事。
curl -k成功只能说明加密通了,不能说明身份校验正确。- 版本选择要看兼容矩阵,不能只看“最新”。
- 自动续期不是终点,数据面是否真的使用了新证书同样要验证。
这一课你真正学会后,应该能回答哪些面试 / 实战问题
Issuer和ClusterIssuer的区别是什么?Certificate和Secret的关系是什么?- 为什么带 cert-manager 注解的 Ingress 会自动出现
Certificate? CertificateRequest在签发链路里起什么作用?- 为什么自建 CA 签发时没有
Order/Challenge? - 为什么
curl -k成功不代表证书没问题? - 为什么现代证书有时
CN为空但仍然正常? - HTTP-01 和 DNS-01 分别适合什么场景?
- 为什么通配符证书常常要走 DNS-01?
- 如果 cert-manager 装好了但证书没出来,你按什么顺序查?
如果你能把这些问题讲清楚,而且能结合这次实验给出证据,你就已经不是“只会抄 YAML”的阶段了。
本课实验文件
- 00-install-cert-manager-v1.18.6.sh
- 10-namespace-cert-manager-lab.yaml
- 20-bootstrap-selfsigned-clusterissuer.yaml
- 21-root-ca-certificate.yaml
- 22-root-ca-clusterissuer.yaml
- 30-web.yaml
- 31-service.yaml
- 40-ingress-annotated.yaml
官方参考资料
下面这些资料建议你后续配合本课反复看:
- cert-manager installation via kubectl: https://cert-manager.io/docs/installation/kubectl/
- cert-manager supported releases: https://cert-manager.io/docs/releases/supported-releases/
- cert-manager selfSigned issuer: https://cert-manager.io/docs/configuration/selfsigned/
- cert-manager CA issuer: https://cert-manager.io/docs/configuration/ca/
- cert-manager usage with Ingress: https://cert-manager.io/docs/usage/ingress/
- cert-manager ACME Orders and Challenges: https://cert-manager.io/docs/concepts/acme-orders-challenges/
- Let's Encrypt challenge types: https://letsencrypt.org/docs/challenge-types/
- Let's Encrypt rate limits: https://letsencrypt.org/docs/rate-limits/
下一步最自然的学习方向
从教学顺序上,下一课最自然的是:
- 真正进入
ACME / Let's Encrypt / HTTP-01 / DNS-01 - 把“公网域名自动签发”和“生产级故障排查”单独做成一课
因为到这里为止,你已经把:
- TLS 握手
- 证书信任
- Ingress 终止 TLS
- cert-manager 内部 CA 自动签发
这一整条链打通了。
下一步就该把“公网自动化”讲透。