在 Kubernetes 里用 cert-manager + Venafi 自动签发和轮换证书

Posted on 三 15 4月 2026 in Journal

Abstract 在 Kubernetes 里用 cert-manager + Venafi 自动签发和轮换证书
Authors Walter Fan
Category Journal
Status v1.0
Updated 2026-04-15
License CC-BY-NC-ND 4.0

在 Kubernetes 里用 cert-manager + Venafi 自动签发和轮换证书

短大纲

  • 手工换证书在 Kubernetes 里为什么迟早翻车
  • cert-manager 和 Venafi 各管什么,别把锅甩错对象
  • 一套最小可用配置:ClusterIssuerCertificateSecretIngress
  • 自动续期和私钥轮转真正的边界:证书会变,应用未必会跟着变
  • 最后给一张思维导图和一份能照抄的上线检查清单

正文

证书这活儿,最怕“差不多就行”

很多团队对 TLS 证书的态度,像对消防演练。平时嫌麻烦,真响警报时才想起来楼道里还堆着纸箱。服务一旦跑进 Kubernetes,这事又多了几层:Secret 怎么更新,Ingress 怎么引用,应用进程到底会不会 reload。要是还靠手工申请、手工导入、手工替换 tls.crt,迟早会在某个周五晚上把值班同学从床上薅起来。

这也是 cert-managerVenafi 这对搭档存在的意义。前者负责在集群里把证书生命周期跑起来,后者负责企业 PKI 的策略、审批、审计和签发。一个像施工队,一个像门卫兼档案管理员。只靠其中一个,都容易把事情做成半拉子工程。

顺手说一句,Venafi 后来并入了 CyberArk,所以有些新文档已经写成 CyberArk Certificate Manager。不过在 cert-manager 这边,字段名还是 spec.venafi。这不是我手滑,也不是你 YAML 写错了,只是产品名变了,接口名还没跟着折腾。

先把角色分工说清楚

很多人第一次配这套东西,脑子里就一个问号:到底是谁向 CA 要证书?谁把证书塞进 Secret?谁来让服务切到新证书?把分工拆开,事情就清爽了。

组件 它负责什么 你最该盯什么
cert-manager controller 监听 Certificate,生成 CSR,请求签发,更新 Secret,到期前续期 能不能成功签发,续期时间算得对不对
Venafi zone / policy 决定谁能签、签多久、字段怎么约束、归属到哪个应用 策略、审批、审计、模板
Issuer / ClusterIssuer 把 cert-manager 和某个 Venafi zone 连起来 凭据、namespace、zone 有没有配错
Certificate 声明我要什么域名、多长有效期、提前多久续、是否轮换私钥 dnsNamesrenewBeforeprivateKey.rotationPolicy
Secret 真正落地的 tls.crt / tls.key 有没有被正确引用
Ingress / 应用进程 消费证书并对外提供 TLS Secret 变化后会不会 reload

一句话:cert-manager 负责“把流程跑起来”,Venafi 负责“按什么规矩签”,你的服务负责“吃到新证书后别装死”。

@startuml
!theme plain
skinparam backgroundColor #FEFEFE
skinparam defaultFontSize 12
skinparam componentStyle rectangle

actor "Developer / GitOps" as Dev
cloud "Venafi / CyberArk\nZone + Policy" as Ven

rectangle "Kubernetes Cluster" {
  component "Certificate CR" as Cert
  component "cert-manager\ncontroller" as CM
  database "Secret\n(tls.crt / tls.key)" as Sec
  component "Ingress / Service" as Svc
}

Dev --> Cert : apply YAML
Cert --> CM : watch
CM --> Ven : request CSR / renew
Ven --> CM : signed cert chain
CM --> Sec : write tls.crt + tls.key
Svc --> Sec : mount or reference

CM --> CM : renew before expiry
CM --> Sec : rotate key if needed
Svc --> Svc : reload or restart

@enduml

cert-manager + Venafi 签发与轮换链路

先跑通一张最小可用的证书

文章不是为了把 YAML 贴满屏,而是为了先跑通一张能签发、能续期、能轮换的证书。下面这套配置,够你从 0 走到 1。

1. 装好 cert-manager

如果集群里还没有 cert-manager,先把它装上去:

helm repo add jetstack https://charts.jetstack.io
helm repo update

helm upgrade --install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true

如果你们公司要求从 CyberArk / Venafi 提供的 OCI registry 拉镜像,就按对应文档换镜像源。核心点不在安装姿势,而在于 controllerwebhookcainjector 这些组件得齐,别装了个壳子就以为万事大吉。

2. 准备 Venafi 凭据

这里先用 Venafi SaaS / Control Plane 那条路径来举例,因为它最顺手。先把 API key 做成一个 Secret:

kubectl create secret generic venafi-vaas \
  -n cert-manager \
  --from-literal=apikey='YOUR_VENAFI_API_KEY'

如果你后面打算用的是 ClusterIssuer,这个 Secret 默认就该放在 cert-manager namespace。很多人第一坑就踩在这里:ClusterIssuer 创建得挺开心,结果 cert-manager 根本找不到凭据。

3. 建一个 ClusterIssuer

接着创建一个连到 Venafi zone 的 ClusterIssuer

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: venafi-platform
spec:
  venafi:
    zone: 'Platform\Kubernetes'
    cloud:
      apiTokenSecretRef:
        name: venafi-vaas
        key: apikey

这里有个很容易被忽略的点:一个 Venafi Issuer 对应一个 zone。 不同业务线、不同审批策略、不同所有权边界,最好老老实实拆成不同的 IssuerClusterIssuer。别想着一个配置打天下,到头来不是权限过大,就是审计说不清。

4. 声明一张 Certificate

然后让 cert-manager 去申请证书:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: hello-api-tls
  namespace: demo
spec:
  secretName: hello-api-tls
  issuerRef:
    name: venafi-platform
    kind: ClusterIssuer
  dnsNames:
    - api.demo.example.com
  duration: 2160h # 90d
  renewBeforePercentage: 25
  privateKey:
    algorithm: RSA
    size: 2048
    rotationPolicy: Always

这里我建议盯住三件事:

  • dnsNames 才是你真正要守住的字段。现在别再迷信 commonName 了,终端证书的域名匹配主要看这里。
  • renewBeforePercentage: 25 表示在证书剩下 25% 生命周期时就开始续期。这个写法比死写 renewBefore: 360h 更稳妥,因为有些 CA 实际发下来的有效期会比你想象的略短。
  • rotationPolicy: Always 我建议写死,不要靠“我记得某个版本默认已经是这样”来混日子。配置文件要写给半年后的自己看,不是写给今天的记忆力看。

有些版本较老的 cert-manager 还不支持 renewBeforePercentage,那就退一步写成:

renewBefore: 360h # 15d

签发成功之后,目标 Secret 里通常会有 tls.crttls.key。如果 CA 链信息可得,还可能带上 ca.crt。不过 tls.crt 里不会把 root cert 也一股脑塞进去,这不是 cert-manager 偷懒,而是正常设计。

5. 让服务真正吃到这张证书

如果你的 TLS 终止点在 Ingress,那事情相对简单,直接引用这个 Secret:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-api
  namespace: demo
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.demo.example.com
      secretName: hello-api-tls
  rules:
    - host: api.demo.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: hello-api
                port:
                  number: 8080

到这里,证书就完成了从 Venafi 到 Kubernetes Secret,再到入口流量的闭环。很多 Ingress Controller 对 Secret 变化的处理都比较成熟,证书更新后能比较平滑地切过去。

如果 TLS 是你的应用自己终止,不是在 Ingress 上,那就把 Secret 通过 volume mount 挂进去,并让进程支持文件变化后的 reload。别把证书塞进环境变量里,这条路和“自动轮转”基本是相看两厌。

6. 如果你用的是 TPP,不是 SaaS

如果你的 Venafi 是 TPP,那配置只是换个认证块,骨架并没有变:

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: venafi-tpp
spec:
  venafi:
    zone: '\VED\Policy\devops\k8s'
    tpp:
      url: https://tpp.example.com/vedsdk
      credentialsRef:
        name: venafi-tpp-credentials

这里要额外记住两件事:

  • ClusterIssuer 用到的 credentials Secret,默认同样要放在 cert-manager namespace。
  • TPP policy 最好允许 User Provided CSR。不然你在 Certificate 里写的 key algorithm、key size 等字段,要么被策略覆盖,要么干脆签发失败。到那时你盯着 YAML 看半天,也只是对空气生气。

如果你的 TPP 用的是自签 CA 或公司内网 CA,还得补上 caBundlecaBundleSecretRef,不然 cert-manager 和 TPP 自己都握不成手。

轮转到底轮了什么

很多文章写到“自动轮转”就一句带过,仿佛配完 YAML 之后世界和平。现实没那么诗意,真正要弄明白的是:到底是哪一层在自动,哪一层还得你自己兜底。

cert-manager 会根据证书实际有效期和你的 renewBefore / renewBeforePercentage 来安排续期。如果你什么都不写,它默认会在证书生命周期走到大约三分之二时尝试续签。也就是说,它确实会替你记日子,这一点比人靠谱。

如果你显式配置了 privateKey.rotationPolicy: Always,那么每次重签时都会生成新的私钥。这个做法更稳妥,一来减少旧私钥长期复用的风险,二来也能尽早暴露“应用是否真的支持换 key”这个现实问题。平时不演练,出事时就只能演砸。

还有一个细节很重要:cert-manager 在拿到新签名证书之前,不会贸然覆盖 Secret 里的 tls.key。这一点是为了尽量减少中间态带来的宕机风险。可即便如此,Secret 更新了,不等于业务进程已经切到新证书

很多 Ingress Controller 会监听 Secret 变化并 reload;你自己写的 Go、Java、Python 服务,就不一定了。如果进程只在启动时读一次证书文件,那新证书只会安安静静躺在磁盘上,业务继续拿旧证书对外服务。碰到这种应用,要么自己实现文件变更监听和优雅 reload,要么在 Secret 变化后做一次 rollout restart,或者上一个专门盯 Secret 变化的重启控制器。

手工演练时,我建议至少看三样东西:

kubectl describe certificate hello-api-tls -n demo
kubectl get certificaterequest -n demo
cmctl renew hello-api-tls -n demo

重点不是把命令背下来,而是看清楚几个状态:证书是不是 Ready=True,有没有反复 Issuing,续期时间是不是符合预期。想手工触发续签,用 cmctl renew。不要靠“删掉 Secret 试试看”这种方式碰运气,那不是自动化,是拉老虎机。

几个最容易踩的坑

坑一:ClusterIssuer 能看到证书,看不到凭据

这是最常见的低级错误。ClusterIssuer 是集群级资源,但它找凭据 Secret 时,默认会去 cert-manager 的 cluster resource namespace,也就是 cert-manager。你把 Secret 放到业务 namespace,表面看资源都在,实际 cert-manager 两手一摊:我也想签,可我没钥匙。

坑二:一个 zone 想管所有业务

Venafi 的 zone 不只是路径,它其实承载了策略、审批和归属关系。把所有场景都压到同一个 zone 里,前期看着省事,后面审计、权限、模板一变,大家一起遭殃。平台类服务、外网服务、内网服务,最好拆开。

坑三:证书续了,进程没换

这类问题特别像“表面一切正常”。Certificate 是绿的,Secret 也更新了,可线上扫出来还是旧证书。问题不在 Venafi,也不在 cert-manager,而在应用进程没 reload。机器没有读心术,文件变了不代表它会自己刷新内存。

坑四:舍不得换私钥

不少团队对私钥有一种莫名其妙的感情,好像“沿用老 key 更稳”。其实很多企业级 issuer 本来就不鼓励,甚至不允许重复使用旧私钥。把 rotationPolicy: Always 写明白,省得以后靠猜版本默认值。

坑五:把续期窗口写得过于激进

renewBefore 不是写得越早越保险。你要是把它贴得离 duration 太近,就可能把自己卷进 renewal loop。新版能用 renewBeforePercentage 的话,我更建议直接用百分比,让 cert-manager 按实际签下来的证书时长去算。

坑六:把问题都怪到 cert-manager 头上

有时签发失败真不是 cert-manager 的锅,而是 Venafi policy 卡住了。比如 zone 配错了,审批策略没过,TPP policy 不接受你提交的 CSR,或者自签 CA 的 trust bundle 没带上。工具链里一共有好几环,别一出错就逮着最前台那个控制器骂。


总结

  • cert-manager 适合当 Kubernetes 里的执行层:监听 Certificate、生成 CSR、续期、更新 Secret。
  • Venafi 适合当企业 PKI 的策略层:管 zone、模板、审批、审计和归属。
  • 真正的自动轮转,不只是“证书能签下来”,还包括“私钥能换”“应用能 reload”“出故障时你知道去哪一层排查”。
  • 配置上我最想强调三件小事:ClusterIssuer 的凭据放对 namespace,Certificate 显式写 rotationPolicy: Always,应用侧提前演练 reload。

思维导图(源码见下节 PlantUML,以下为渲染图):

Kubernetes cert-manager + Venafi 思维导图

@startmindmap
* K8s cert-manager + Venafi
** 分工
*** cert-manager: 申请、续期、写 Secret
*** Venafi: 策略、审批、审计、签发
*** Service/Ingress: reload 新证书
** 最小配置
*** Secret: API key / credentials
*** ClusterIssuer: 绑定 zone
*** Certificate: dnsNames + 生命周期
*** Secret: tls.crt / tls.key
** 轮转
*** renewBefore / renewBeforePercentage
*** privateKey.rotationPolicy = Always
*** cmctl renew 手工演练
*** 证书更新 != 进程已 reload
** TPP 注意项
*** policy folder 单独规划
*** credentials Secret 放对 namespace
*** User Provided CSR 要允许
*** 自签 CA 需要 caBundle
** 常见坑
*** Secret 放错 namespace
*** 一个 zone 想管所有业务
*** renewal loop
*** 应用不支持热更新
*** 误删 Secret 碰运气
@endmindmap

可执行 CheckList

  • [ ] 确认 cert-manager 版本,能用 renewBeforePercentage 就优先用它
  • [ ] ClusterIssuer 的 credentials Secret 放在 cert-manager namespace
  • [ ] 不同业务和不同策略拆成不同 Venafi zone,不要混成一锅
  • [ ] 每张 Certificate 显式写 privateKey.rotationPolicy: Always
  • [ ] 服务通过 Secret volume 消费证书,并验证 reload 逻辑
  • [ ] 做一次 cmctl renew 演练,确认业务侧能平滑切证书
  • [ ] 监控 Certificate 的到期时间和签发失败事件

扩展阅读



本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。