Repository Reading Site
本轮操作记录:cert-manager 安装、CA 签发与 Ingress 自动证书实验
这一轮我要把下面几件事情做成真实证据: 1. cert-manager 不是只装个 Pod,而是一套 CRD + controller + webhook + cainjector 2. `SelfSigned -> Root CA -> CA ClusterIssuer` 这条引导链能跑通 3. Ingress 注解确实会触发 `ingress-shim`
本轮操作记录:cert-manager 安装、CA 签发与 Ingress 自动证书实验
本轮目标
这一轮我要把下面几件事情做成真实证据:
- cert-manager 不是只装个 Pod,而是一套 CRD + controller + webhook + cainjector
SelfSigned -> Root CA -> CA ClusterIssuer这条引导链能跑通- Ingress 注解确实会触发
ingress-shim自动创建Certificate Certificate -> CertificateRequest -> Secret的控制链能够看见- 这次实验为什么没有
Order/Challenge - 叶子证书为什么
subject可以为空但SAN正常 - 为什么
curl默认失败、-k成功、--cacert也能成功 - 为什么版本选择不能只看“最新”
Step 1:先核对集群版本和 cert-manager 版本策略
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl version
KUBECONFIG=~/.kube/config-k8s-lab kubectl get nodes -o wide
我看到的关键结果
- Client Version:
v1.31.4 - Server Version:
v1.30.14
为什么这一步重要
因为 cert-manager 这种控制面组件,版本选择绝不能拍脑袋。
你不能只问:
- 最新版本是多少
你还必须问:
- 它和当前 Kubernetes 版本是否在官方兼容窗口里
这一步的工程判断
截至 2026-04-10:
- 当前集群是
Kubernetes 1.30.14 - cert-manager 较新的发布线已经面向
1.31+
所以本轮实验我最终采用:
cert-manager v1.18.6
原因不是“它最新”,而是:
- 它属于与当前
Kubernetes 1.30.x更匹配的兼容分支
原理解释
这是典型的平台工程判断。
专家不会一看到新版本就无脑上,而是先看三件事:
- 兼容矩阵
- 支持周期
- 升级路径
这和业务代码升级一样,必须有版本意识。
Step 2:安装 cert-manager 控制面
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.18.6/cert-manager.yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager wait --for=condition=Available deploy --all --timeout=180s
我额外写入仓库的复现脚本
为什么要用官方发布清单安装
因为 cert-manager 本身就是一个成套控制面:
- CRD
- RBAC
- ServiceAccount
- Deployment
- WebhookConfiguration
官方发布清单能保证这些对象组合关系正确。
我看到的关键结果
kubectl apply 成功创建 / 更新了:
namespace/cert-manager- 多个 CRD
- 多个 RBAC 对象
deployment.apps/cert-managerdeployment.apps/cert-manager-cainjectordeployment.apps/cert-manager-webhookmutatingwebhookconfiguration/validatingwebhookconfiguration
随后 wait 显示:
deployment.apps/cert-manager condition metdeployment.apps/cert-manager-cainjector condition metdeployment.apps/cert-manager-webhook condition met
原理解释
你要把这次安装看成“给 Kubernetes 装了一个新的控制面子系统”。
它不是一个单容器工具,而是:
- CRD 提供新 API
- controller 负责 reconcile
- webhook 负责准入校验
- cainjector 负责把 CA bundle 注入到 webhook 相关对象
如果只看 Pod Running,而不看 CRD / webhook / RBAC 是否完整,那不叫会装 cert-manager。
Step 3:确认 cert-manager 的 API 和组件已经真的可用
实际命令
KUBECONFIG=~/.kube/config-k8s-lab kubectl get crd | grep -E 'cert-manager.io|acme.cert-manager.io'
KUBECONFIG=~/.kube/config-k8s-lab kubectl api-resources | grep -E '^certificates|^certificaterequests|^issuers|^clusterissuers|^orders|^challenges'
KUBECONFIG=~/.kube/config-k8s-lab kubectl -n cert-manager get deploy,pod,svc -o wide
我看到的关键结果
CRD
已经出现:
certificates.cert-manager.iocertificaterequests.cert-manager.ioissuers.cert-manager.ioclusterissuers.cert-manager.ioorders.acme.cert-manager.iochallenges.acme.cert-manager.io
API 资源
已经能看到:
CertificateCertificateRequestIssuerClusterIssuerOrderChallenge
控制器镜像
升级后我确认到:
quay.io/jetstack/cert-manager-controller:v1.18.6quay.io/jetstack/cert-manager-cainjector:v1.18.6quay.io/jetstack/cert-manager-webhook:v1.18.6
原理解释
这一步是在回答:
我装进去的到底是“镜像”,还是“整个 API 扩展能力”?
如果 CRD 不存在:
- Kubernetes 根本不认识
Certificate
如果 controller 不存在:
- 这些对象只是摆设,没有人会去处理
如果 webhook 不存在或证书链坏了:
- 你可能在创建 cert-manager 对象时直接被 admission 卡死
所以专家式安装验证一定是:
- API 看得见
- 控制器跑得动
- webhook 可通信
Step 4:验证 webhook 的 TLS 信任链确实被注入了
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get validatingwebhookconfiguration cert-manager-webhook -o yaml | rg -n 'caBundle|name:'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl get mutatingwebhookconfiguration cert-manager-webhook -o yaml | rg -n 'caBundle|name:'
我看到的关键结果
两类 webhook 配置里都存在:
caBundle: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0t...
原理解释
这一步是为了证明:
cainjector不是摆设
API Server 调 webhook 是 HTTPS。
而 HTTPS 要验证服务端证书,就必须知道该信哪张 CA。
caBundle 被注入进去,说明 cert-manager 已经把自己的 webhook 信任链补齐了。
这是一个很容易被忽略,但在控制面里极其关键的细节。
Step 5:创建实验命名空间和签发者引导链
本课清单
- 10-namespace-cert-manager-lab.yaml
- 20-bootstrap-selfsigned-clusterissuer.yaml
- 21-root-ca-certificate.yaml
- 22-root-ca-clusterissuer.yaml
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply \
-f manifests/16-cert-manager/10-namespace-cert-manager-lab.yaml \
-f manifests/16-cert-manager/20-bootstrap-selfsigned-clusterissuer.yaml \
-f manifests/16-cert-manager/21-root-ca-certificate.yaml \
-f manifests/16-cert-manager/22-root-ca-clusterissuer.yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager wait --for=condition=Ready certificate/lab-root-ca --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl wait --for=condition=Ready clusterissuer/lab-root-ca-issuer --timeout=180s
我看到的关键结果
创建成功:
namespace/cert-manager-labclusterissuer/lab-selfsigned-bootstrapcertificate/lab-root-caclusterissuer/lab-root-ca-issuer
Ready 验证成功:
certificate.cert-manager.io/lab-root-ca condition metclusterissuer.cert-manager.io/lab-root-ca-issuer condition met
这里到底发生了什么
这一段非常关键。
第一步:lab-selfsigned-bootstrap
它是一个 selfSigned 类型的 ClusterIssuer。
作用不是给业务直接长期签证书,而是:
- 先生成一张 root CA 自己的证书
第二步:lab-root-ca
这是一个 Certificate,但它不是业务叶子证书,而是:
isCA: true
也就是说,它要产出的不是普通站点证书,而是一张“可用于签发别人”的 CA 证书。
第三步:lab-root-ca-secret
当 lab-root-ca Ready 后,cert-manager 把结果写进了 Secret。
第四步:lab-root-ca-issuer
这是一个 ca 类型的 ClusterIssuer。
它的工作不是自签自己,而是:
- 使用
lab-root-ca-secret里的 CA 私钥和证书去给其他证书签名
原理解释
你可以把这四步理解成:
先把“印章”造出来
再拿这枚“印章”去盖业务证书
如果没有 bootstrap 这一步,你就没有自己的 CA 基座。
Step 6:检查 root CA Secret 里到底有什么
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager get secret lab-root-ca-secret \
-o go-template='{{.type}}{{"\n"}}{{range $k,$v := .data}}{{printf "%s\n" $k}}{{end}}'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager get secret lab-root-ca-secret \
-o go-template='{{index .data "tls.crt"}}' | base64 -d | \
openssl x509 -noout -subject -issuer -dates -text | sed -n '1,40p'
我看到的关键结果
Secret 类型:
kubernetes.io/tls
数据键:
ca.crttls.crttls.key
证书摘要:
subject=O=k8s-lab, CN=lab-root-ca
issuer=O=k8s-lab, CN=lab-root-ca
notBefore=Apr 10 07:40:19 2026 GMT
notAfter=Apr 10 07:40:19 2027 GMT
原理解释
这段输出证明了两件事:
- 这张证书是 root CA
- 它是自签的
为什么?
因为:
subject和issuer相同
这就是典型的 root CA 表现。
这一步让你真正看到:
selfSigned不是抽象概念- 它确实签出了一个可作为信任根的 CA 证书
Step 7:部署业务应用、Service 和带注解的 Ingress
本课业务清单
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl apply \
-f manifests/16-cert-manager/30-web.yaml \
-f manifests/16-cert-manager/31-service.yaml \
-f manifests/16-cert-manager/40-ingress-annotated.yaml
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab wait --for=condition=Available deploy/cm-web --timeout=180s
我创建了什么
Deployment
cm-web
返回一个静态页面,写出:
- pod 名称
- pod IP
- 节点名
- host
- issuer
Service
cm-web-svc
Ingress
cm-web- host:
auto-secure.k8s-lab.local - annotation:
cert-manager.io/cluster-issuer: lab-root-ca-issuer - tls.secretName:
cm-web-tls
原理解释
这一步的关键不是业务本身,而是:
我故意只写 Ingress 注解和
tls.secretName,让 cert-manager 自己去补Certificate。
这样才能看清 ingress-shim 的真实作用。
Step 8:观察 ingress-shim 自动补出的证书对象
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab get ingress,certificate,certificaterequest,secret,pod,svc,endpoints -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab get certificate cm-web-tls -o yaml | sed -n '1,240p'
我看到的关键结果
Ingress
cm-webHOSTS: auto-secure.k8s-lab.localADDRESS: 10.109.198.120
自动生成的 Certificate
certificate.cert-manager.io/cm-web-tlsREADY: TrueSECRET: cm-web-tlsISSUER: lab-root-ca-issuer
更关键的是它的 ownerReferences:
ownerReferences:
- apiVersion: networking.k8s.io/v1
kind: Ingress
name: cm-web
原理解释
这是一条非常重要的证据链:
- 我没有手工创建
cm-web-tls这个Certificate - 但它出现了
- 而且 owner 指向
Ingress cm-web
所以我们可以明确得出结论:
这个
Certificate是 ingress-shim 根据带 cert-manager 注解的 Ingress 自动补出来的。
Step 9:检查 CertificateRequest 和最终 Secret
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab get certificaterequest cm-web-tls-1 -o yaml | sed -n '1,240p'
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab get secret cm-web-tls \
-o go-template='{{.type}}{{"\n"}}{{range $k,$v := .data}}{{printf "%s\n" $k}}{{end}}'
我看到的关键结果
CertificateRequest
- 名字:
cm-web-tls-1 APPROVED: TrueREADY: TrueREQUESTER: system:serviceaccount:cert-manager:cert-managerISSUER: lab-root-ca-issuer
状态里有:
type: Approvedtype: Ready
最终 Secret
type: kubernetes.io/tls- keys:
ca.crttls.crttls.key
原理解释
这一段证明 cert-manager 的签发流程不是“黑盒一步到位”,而是经历了:
CertificateCertificateRequestSecret
三个阶段。
你以后排障时,一定要把 CertificateRequest 单独看。
因为它往往是问题分界点:
- 如果
Certificate有,但CertificateRequest没有,说明前面的控制链没触发 - 如果
CertificateRequest有,但没Approved或没Ready,说明签发链卡住了 - 如果
CertificateRequestReady 了但 Secret 还不对,就要继续看结果写回阶段
Step 10:用事件把整条控制循环串起来
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab get events --sort-by=.lastTimestamp
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab describe certificate cm-web-tls
我看到的关键事件顺序
CreateCertificate ingress/cm-web
Issuing certificate/cm-web-tls
Generated certificate/cm-web-tls
Requested certificate/cm-web-tls
Certificate request has been approved by cert-manager.io
CertificateIssued certificaterequest/cm-web-tls-1
The certificate has been successfully issued
这条时序怎么理解
CreateCertificate
说明 ingress-shim 已经开始工作。
Issuing
说明 cert-manager 发现目标 Secret 不存在,需要开始签发。
Generated
说明它先生成了私钥,并暂存到临时 Secret,例如:
cm-web-tls-k2xl9
Requested
说明它创建了正式的 CertificateRequest。
Approved
说明请求经过了批准流程。
CertificateIssued
说明 issuer 已经把证书签出来。
原理解释
这一串事件就是你以后看 cert-manager 的“时序望远镜”。
你完全可以根据它来判断:
- 卡在 shim
- 卡在 key generation
- 卡在 request
- 卡在 approval
- 卡在 issuer signing
Step 11:确认这次为什么没有 Order / Challenge
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab get orders.acme.cert-manager.io,challenges.acme.cert-manager.io
我看到的关键结果
No resources found in cert-manager-lab namespace.
原理解释
这是正常现象。
因为本轮签发是:
CA ClusterIssuer
不是:
ACME ClusterIssuer
所以不需要:
- 向外部 ACME CA 下单
- 接收 challenge
- 创建 solver
- 对公网证明域名控制权
这一步是在帮你建立一个非常重要的边界感:
cert-manager 是通用证书生命周期控制器,ACME 只是其中一种 issuer 模式。
Step 12:做 TLS 握手层验证,而不是只看 Kubernetes 对象
为什么要从节点本机验证
因为当前工作站存在:
HTTP_PROXY=http://127.0.0.1:7890HTTPS_PROXY=http://127.0.0.1:7890
直接从本机打公网 NodePort,容易被本地代理污染结果。
所以这一轮我优先用:
ssh root@107.148.176.193- 节点本机回环地址
127.0.0.1:30443 --resolveopenssl s_client
这样链路最干净。
实际命令
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@107.148.176.193 \
"echo | openssl s_client -connect 127.0.0.1:30443 -servername auto-secure.k8s-lab.local 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates -ext subjectAltName"
我看到的关键结果
subject=
issuer=O = k8s-lab, CN = lab-root-ca
notBefore=Apr 10 07:41:12 2026 GMT
notAfter=Jul 9 07:41:12 2026 GMT
X509v3 Subject Alternative Name: critical
DNS:auto-secure.k8s-lab.local
原理解释
这段输出很有价值。
第一:issuer 是 lab-root-ca
说明 ingress-nginx 当前加载的已经不是默认假证书,而是 cert-manager 签发出来的业务证书。
第二:subject= 为空
这不是异常。
这说明叶子证书没有显式设置 CN,但:
SAN里有DNS:auto-secure.k8s-lab.local
现代 TLS 主机名校验主要看 SAN,这完全符合预期。
Step 13:验证“未信任”和“跳过校验”是两件不同的事
实际命令
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@107.148.176.193 \
"curl --resolve auto-secure.k8s-lab.local:30443:127.0.0.1 -sS -D - https://auto-secure.k8s-lab.local:30443/"
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@107.148.176.193 \
"curl -k --resolve auto-secure.k8s-lab.local:30443:127.0.0.1 -sS -D - https://auto-secure.k8s-lab.local:30443/"
我看到的关键结果
不带 -k
curl: (60) SSL certificate problem: unable to get local issuer certificate
带 -k
HTTP/2 200
...
<h1>cert-manager ingress-shim demo</h1>
<p>pod: cm-web-7b8d4c85d-g6jmc</p>
<p>pod_ip: 10.244.242.61</p>
<p>node: cp-3</p>
原理解释
不带 -k 失败
说明:
- 客户端默认不信任我们自己的 root CA
带 -k 成功
说明:
- TLS 握手和加密本身没有问题
- 只是跳过了身份校验
这是排障里特别重要的判断能力。
很多人看到 curl -k 成功,就误以为证书没问题。
其实这只能说明:
- 数据面握手通了
不能说明:
- 证书信任链对客户端是成立的
Step 14:给客户端显式灌入 CA 后,再做一次正确校验
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab get secret cm-web-tls \
-o go-template='{{index .data "ca.crt"}}' | base64 -d > /tmp/cm-web-ca.crt
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@107.148.176.193 \
'cat > /tmp/cm-web-ca.crt' < /tmp/cm-web-ca.crt
ssh -o BatchMode=yes -o StrictHostKeyChecking=no -o ConnectTimeout=8 root@107.148.176.193 \
"curl --cacert /tmp/cm-web-ca.crt --resolve auto-secure.k8s-lab.local:30443:127.0.0.1 -sS -D - https://auto-secure.k8s-lab.local:30443/"
我看到的关键结果
HTTP/2 200
...
<h1>cert-manager ingress-shim demo</h1>
<p>pod: cm-web-7b8d4c85d-g6jmc</p>
我随后又做了一个纯状态码验证:
ssh root@107.148.176.193 \
"curl --cacert /tmp/cm-web-ca.crt --resolve auto-secure.k8s-lab.local:30443:127.0.0.1 -sS -o /dev/null -w '%{http_code}\n' https://auto-secure.k8s-lab.local:30443/"
结果:
200
原理解释
这一步证明:
- 证书链本身是正确的
- 只是客户端原本不认识这张根 CA
也就是说,这不是“证书坏了”,而是:
- trust store 缺少对应根证书
这正是企业内网 CA 场景里最常见的现实:
- 平台能自动签
- 但客户端是否信任,还取决于信任根分发策略
Step 15:验证升级到 v1.18.6 后,控制面和业务面仍然正常
为什么要做这一步
因为平台工程里,升级不是“apply 完就结束”,而是:
- 控制面组件是否全部 rollout 成功
- 旧的证书对象是否仍然 Ready
- 数据面请求是否仍然正常
实际命令
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager rollout status deploy/cert-manager --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager rollout status deploy/cert-manager-cainjector --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager rollout status deploy/cert-manager-webhook --timeout=180s
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager get deploy,pod -o wide
KUBECONFIG=~/.kube/config-k8s-lab \
kubectl -n cert-manager-lab get certificate,certificaterequest,secret,ingress -o wide
ssh root@107.148.176.193 \
"curl -k --resolve auto-secure.k8s-lab.local:30443:127.0.0.1 -sS -o /dev/null -w '%{http_code}\n' https://auto-secure.k8s-lab.local:30443/"
ssh root@107.148.176.193 \
"curl --cacert /tmp/cm-web-ca.crt --resolve auto-secure.k8s-lab.local:30443:127.0.0.1 -sS -o /dev/null -w '%{http_code}\n' https://auto-secure.k8s-lab.local:30443/"
我看到的关键结果
rollout
deployment "cert-manager" successfully rolled outdeployment "cert-manager-cainjector" successfully rolled outdeployment "cert-manager-webhook" successfully rolled out
镜像
- controller:
v1.18.6 - cainjector:
v1.18.6 - webhook:
v1.18.6
业务资源
certificate/cm-web-tls:Ready=Truecertificaterequest/cm-web-tls-1:Approved=True, Ready=Truesecret/cm-web-tls:存在ingress/cm-web:ADDRESS=10.109.198.120
数据面
curl -k返回200curl --cacert返回200
原理解释
这一步是在训练你形成“升级闭环验证”的习惯。
很多事故不是出在:
- 安装失败
而是出在:
- 安装成功了
- 但旧资源没有按预期继续工作
所以升级验证一定要同时覆盖:
- 控制面
- 资源状态
- 数据面
Step 16:回看这次实验,整条链到底被证明了什么
已被证实的事实一
cert-manager 安装成功的标准,不只是 Pod Running,而是:
- CRD 存在
- API 可见
- controller 可用
- webhook 可用
- cainjector 注入了
caBundle
已被证实的事实二
SelfSigned 可以用于引导出 root CA。
证据:
lab-root-caReadylab-root-ca-secret内含tls.crt/tls.keysubject == issuer == lab-root-ca
已被证实的事实三
带 cert-manager 注解的 Ingress 会触发 ingress-shim 自动创建 Certificate。
证据:
Certificate cm-web-tls自动出现ownerReferences指向Ingress cm-web
已被证实的事实四
签发链路真实经过:
CertificateCertificateRequestSecret
证据:
- 事件时序
cm-web-tls-1cm-web-tls
已被证实的事实五
本轮没有 Order / Challenge 是正常的,因为我们不是 ACME。
已被证实的事实六
TLS 数据面实际加载的是 cert-manager 生成的证书,而不是默认假证书。
证据:
issuer=O = k8s-lab, CN = lab-root-ca- SAN 为
auto-secure.k8s-lab.local
已被证实的事实七
“是否加密”和“是否被信任”是两件不同的事。
证据:
curl默认失败curl -k成功curl --cacert也成功
这轮实验里最值得你记住的 8 个结论
- cert-manager 是一套控制面,不是一个签证书脚本。
Certificate是期望状态,Secret是结果状态。ingress-shim让 Ingress 也能成为证书声明入口。CertificateRequest是排障关键点,不能跳过。SelfSigned适合引导 root CA,CA Issuer适合拿 root 去签 leaf。Order/Challenge只属于 ACME,不属于所有 cert-manager 场景。SAN比CN更关键,叶子证书subject为空不一定有问题。curl -k成功不代表证书被信任,只代表你跳过了验证。
下一轮最自然的推进方向
基于这一轮成果,下一课最自然就是进入:
ACME / Let's Encrypt / HTTP-01 / DNS-01
届时我会重点讲:
- 公网域名自动签发到底多了哪些对象
- 为什么会出现
Order/Challenge - solver Ingress / Pod / Service 是怎么工作的
- HTTP-01 和 DNS-01 如何选择
- 为什么通配符证书基本离不开 DNS-01
- 生产里最常见的失败点和速率限制