TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

cert-manager 是如何生成和轮换证书的

状态:草案(2026-10-26),已反映实测数据。 实测于 2026-10-26 在 TT Lab 集群(cert-manager 1.21 系列)中通过 只读 方式获取。 kubectl get·logs,通过 openssl x509 -noout -dates 读取 Secret 的证书日期,以及通过 openssl s_client 访问公共地址。公开名称和内部对象名称已隐去(<공개 이름 A>, <클러스터 이슈어>)。官方文档和 RFC 在同一天打开原文进行了核对。 编写规则:以实测数据为准,未能测出的记为“未实测”,无法确认的记为“未确认”,推测记为“推测”。

目录

1. 人工续期证书时会发生什么

Let's Encrypt 证书的有效期为 90 天。这意味着每隔三个月,就必须有人查看日历并执行续期命令,而这个人总会有忘记的时候。一旦证书过期,浏览器就会显示警告,服务之间通过 TLS 通信的连接也会完全中断。依赖人工记忆的做法注定终将失败。

cert-manager 用符合 Kubernetes 风格的方式解决了这个问题。用户只需通过对象编写 期望状态,即“此类证书应始终存在于此 Secret 中”。至于其余的工作——比如生成密钥、向证书颁发机构(CA)请求、证明域名所有权、写入 Secret 以及在过期前重新获取——则由控制器持续循环运行来达成。这与编写 Deployment 就会自动创建 Pod、死后会重新拉起的原理如出一辙。

本文将逐层剖析这一“达成状态”的过程。首先了解会按什么顺序生成哪些对象(第 2 节)、与 ACME 服务器交互的内容(第 3 节)、由谁来签名(第 4 节)、何时续期(第 5 节)、Webhook 和 cainjector 为什么独立存在(第 6 节)以及如何与网关对接(第 7 节),最后以 TT Lab 集群的实际读取状态、一次事故(第 8 节)以及常见故障(第 9 节)收尾。

2. 五个对象与驱动它们的控制器

Certificate 하나가 Secret 이 되기까지 왼쪽은 객체(원하는 상태), 오른쪽은 그 객체를 움직이는 컨트롤러 Gateway · Ingress cert-manager.io/cluster-issuer gateway-shim · ingress-shim Secret 이름마다 Certificate 생성 Certificate 이름 목록 · 이슈어 · Secret 이름 trigger · keymanager 갱신 시점·Secret 불일치 판단, 새 키 CertificateRequest CSR 한 장 · 리비전 번호 approver · 이슈어별 서명기 승인 → 서명 → Ready (실패 시 끝) Order (ACME 만) ACME 주문 1건 · finalize URL orders 주문 → 권한 → CSR → 인증서 Challenge (이름마다) token · 방식(http-01/dns-01) challenges 풀이 → 자체 점검 → 준비 알림 Secret (kubernetes.io/tls) tls.crt · tls.key · ca.crt issuing 컨트롤러가 쓴다 → 게이트웨이·Ingress·파드가 읽는다 CR 이 Ready 가 되면 주문과 챌린지는 ACME 이슈어일 때만 생긴다. 자체 서명·CA 이슈어는 CR 에서 바로 서명한다.
图:从 Gateway 注解经由 Certificate、CertificateRequest、Order、Challenge 到变为 Secret 的对象链条及各阶段控制器

cert-manager 共有六种 CRD。其中用户直接使用的只有两个(Issuer 和 Certificate),其余的都是控制器为了拆分工作而创建的中间对象。

对象 范围 创建者 包含内容
Issuer / ClusterIssuer 命名空间 / 集群 用户 谁来签名:ACME(Let's Encrypt 等)、CA、SelfSigned、Vault、外部 Issuer
Certificate 命名空间 用户或 shim 期望的证书:名称列表、Issuer、Secret 名称、寿命与续期时间点、密钥类型
CertificateRequest 命名空间 控制器 一张 CSR。带有修订版本号,经批准(Approved)后签名
Order 命名空间 控制器(仅限 ACME) ACME 服务器的一个订单及其 URL
Challenge 命名空间 控制器(仅限 ACME) 针对一个名称的一项所有权证明

最短的使用示例如下:一个通过 Let's Encrypt 的 HTTP-01 进行验证、并将验证路径通过 Gateway API 的 HTTPRoute 开放的 ClusterIssuer,以及一个期望使用该 Issuer 签发证书的 Certificate。

apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata: { name: <클러스터 이슈어> }
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef: { name: <ACME 계정 키 Secret> }   # 계정 키는 여기 한 번 만들어 계속 쓴다
    solvers:
    - http01:
        gatewayHTTPRoute:
          parentRefs: [{ kind: Gateway, name: <게이트웨이>, namespace: <네임스페이스> }]
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata: { name: <인증서> }
spec:
  secretName: <인증서 Secret>        # 결과가 쓰일 자리
  dnsNames: [<공개 이름 A>]
  issuerRef: { kind: ClusterIssuer, name: <클러스터 이슈어> }

状态机:谁来决定“现在是否需要签发”

Certificate 的状态可以通过两个条件(Condition)来读取。 Ready 表示存在 Secret、未过期、密钥对匹配且名称与规范一致。 Issuing 则显示为“正在重新获取”,在需要时附加,签发结束后由 issuing 控制器移除(官方 API 文档)。

控制器并非单一整体,而是分工合作的多个组件。按顺序来看包含以下几个:

  1. trigger:对比 Certificate 与 Secret,如果判断“需要重新获取”,则附加上 Issuing=True 。实测日志中记录的原因有两个:Secret 完全不存在时的 DoesNotExist,以及 Secret 的私钥与当前请求不同的 SecretMismatch (第 8 节)。当到达续期时间点时,也会在同一位置开始签发。
  2. keymanager:首先在 临时 Secret 中创建用于下一次证书的私钥。Certificate 的 status.nextPrivateKeySecretName 即为其名称,而在实测中,CertificateRequest 的注解 cert-manager.io/private-key-secret-name 中保留了带有诸如 <인증서>-xxxxx 这样随机数后缀的名字。不在 Secret 中直接写入密钥的原因是,若签名失败,不至于破坏当前正在使用的密钥与证书对。从 v1.18 版本开始,密钥轮换策略的默认值从 Never 变更为 Always ,每次续期都会创建新密钥(1.18 发布说明)。
  3. requestmanager:使用该密钥生成 CSR,并创建一个 CertificateRequest。名称末尾的数字即为修订版本(<인증서>-1, -2, …).
  4. approver:为 CertificateRequest 附加 Approved 。默认批准者是 cert-manager 自身,也可以通过策略工具(approver-policy)进行更改。未经批准,任何 Issuer 都不会进行签名。
  5. 各 Issuer 签名器:SelfSigned 和 CA 会当场签名。ACME 则会创建 Order 并等待结果。CertificateRequest 一旦失败便不会重试,重试由 Certificate 侧以新的修订版本发起(官方文档)。
  6. issuing:将变为 Ready 状态的 CertificateRequest 中的证书与临时 Secret 中的密钥配对写入真正的 Secret 中,并移除 Issuing 。
  7. revisionmanager:清理过期的 CertificateRequest。从 v1.18 开始, revisionHistoryLimit 的默认值为 1,因此只保留最后一个(实测中每个证书也确实仅对应一个 CR)。

由于所有控制器都试图同时修改同一个对象,“由于乐观锁将其重新加入队列”的日志会频繁打印。在单次实测签发中,类似这样的行出现了六次,且全部立即重试成功,没有产生任何问题。这是 Kubernetes 控制器的正常现象,无需惊慌。

3. ACME:证明“这个域名确实属于你”的协议

像 Let's Encrypt 这样的公共 CA 不会去核实人的身份。它们唯一核实的是: 你是否真的能够控制这个域名 。规范该流程的正是 ACME(RFC 8555)。

HTTP-01 한 번: 누가 누구에게 무엇을 묻나 cert-manager 컨트롤러 ACME 서버 Let's Encrypt 게이트웨이 + 풀이 파드 ① 새 주문(이름 목록) order: pending ② 권한(authz) 조회 챌린지 3종 + token ③ 풀이 파드 · HTTPRoute 생성 /.well-known/acme-challenge/<token> ④ 자체 점검: 공인 주소로 GET 통과할 때까지 반복 ⑤ 챌린지에 「준비됐다」 POST ⑥ 여러 곳에서 포트 80 GET key authorization 이 맞나 ⑦ authz valid → order ready ⑧ finalize(CSR) processing → valid ⑨ 인증서 내려받기 → Secret, 풀이 객체 삭제 실측(2026-10-09, 새 이름 하나): ①→⑨ 6분 5초. ③은 2초 뒤, 나머지는 ④에서 기다렸다. 같은 이름을 30일 안에 다시 받을 때는 authz 가 이미 valid 라 ③–⑥이 없었다.
图:在 cert-manager、ACME 服务器和网关之间进行一次 HTTP-01 交互的九个步骤

账户、订单、授权、挑战

  1. 账户。 客户端创建一个账户密钥(非对称密钥)并将其注册到 ACME 服务器。此后的所有请求都是用此密钥签名的 JWS。在 cert-manager 中,ClusterIssuer 的 privateKeySecretRef 即为该密钥,而 Issuer 的 Ready 条件则表示“账户已注册”(实测: The ACME account was registered with the ACME server).
  2. 订单(order)。 即“我希望为这些域名签发证书”的请求。根据 RFC 8555 §7.1.6,订单初始为 pending ,当所有授权都变成 valid 时变为 ready,提交 CSR 后变为 processing,签发完成后变为 valid 。如果在任何阶段失败,则为 invalid 。
  3. 授权(authorization)与挑战。 每个域名都有一个授权对象,服务器会提供多种获取该授权的方法(挑战)。实测订单中提供了 http-01·dns-01·tls-alpn-01 三种使用相同 token 的方法,而 cert-manager 仅选择 Issuer 中指定的解析方式(此处为 HTTP-01)。cert-manager 使用的方式仅有 HTTP-01 和 DNS-01 两种。
  4. finalize 与下载。 当所有授权都变为 valid 后,将 CSR 发送到 finalize URL(§7.4),并获取签发的证书链(§7.4.2)。

key authorization:用一个 Token 证明什么

并不是只需将服务器给出的 token 放到网页上就行。如果那样的话,窥探到 token 的第三方也可以做同样的事情。RFC 8555 §8.1 要求在 token 后面附加 账户密钥的指纹。

keyAuthorization = token + "." + base64url(SHA-256(계정 공개키 JWK 의 정규형))   # 지문은 RFC 7638
HTTP-01: http://<이름>/.well-known/acme-challenge/<token> 이 keyAuthorization 을 돌려준다   (§8.3)
DNS-01 : _acme-challenge.<이름> 의 TXT = base64url(SHA-256(keyAuthorization))            (§8.4)

我们使用 Python 标准库确认了计算结果确实如此。指纹的输入采用 RFC 7638 §3.1 中的示例 RSA 公钥,token 则使用 RFC 8555 示例中出现的值。

import base64, hashlib, json
b64u = lambda b: base64.urlsafe_b64encode(b).rstrip(b"=").decode()
jwk = {"e": "AQAB", "kty": "RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWh…"}  # RFC 7638 예제(줄임)
thumb = b64u(hashlib.sha256(json.dumps(jwk, separators=(",", ":"), sort_keys=True).encode()).digest())
ka = "evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ-PCt92wr-oA" + "." + thumb
print(thumb, b64u(hashlib.sha256(ka.encode()).digest()))

结果(2026-10-26,本地 python3):指纹为 NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs ,与 RFC 7638 中记录的期望值一致,该 Token 的 DNS-01 TXT 值则为 ZTRx1Ckl1-tM05o5zaizTTA0yUy5AGereMgSNWC6Ll8 。由于指纹来源于账户密钥,因此只有持有账户密钥的一方才能生成正确的响应。

HTTP-01 与 DNS-01,该如何选择

HTTP-01 DNS-01
验证位置 端口 80 的 /.well-known/acme-challenge/ _acme-challenge.<이름> TXT 记录
是否需要从外部访问 是。必须能够从公网通过端口 80 访问 否。只需公开 DNS 即可
泛域名(*.example.com) 不支持 支持
集群应具有的权限 开放单个路径的权限(解题 Pod、路由) DNS 提供商 API 密钥(通常可以更改该域的整个范围)
容易出问题的地方 防火墙、重定向、集群内发夹式路由(Hairpin) 传播延迟、委托(CNAME)设置

通配符和端口 80 条件已在 Let's Encrypt 官方文档(挑战类型)中确认。由于 DNS-01 的 API 密钥意味着“能够彻底更改 DNS 域的钥匙”将进入集群内部,因此安全成本很高。如果只有几个公开名称且端口 80 已开放,则 HTTP-01 会更简单。

cert-manager 如何解决 HTTP-01

Challenge 控制器为每个名称 解题 Pod(acmesolver 镜像)、Service 以及用于打开该路径的路由(Ingress 或 Gateway API 的 HTTPRoute)。然后,它不会立即通知 ACME 服务器“已准备就绪”,而是进行 自我检查(self check) 。它直接在集群内部使用该公开名称进行 GET 尝试,并且仅在返回正确的 key authorization 时才发出通知。这是一种旨在避免在 ACME 服务器上积累失败的机制。因为验证失败被限制为每个名称/账号每小时 5 次(Let's Encrypt 速率限制)。相反,如果阻止了从集群内部流出并返回到其自身公共地址的路径(发夹式路由),则自我检查将永远无法通过。我们将在第 9 节中再次探讨这一点。

4. 谁来签名:SelfSigned、CA、ACME

公开 CA 并不是唯一的 Issuer。对于像集群内的 Webhook 服务器这样 只需信任我们这一侧客户端 的证书,私有 CA 就足够了。

kind: ClusterIssuer
metadata: { name: bootstrap }
spec: { selfSigned: {} }
---
kind: Certificate
metadata: { name: root-ca }
spec:
  isCA: true
  commonName: example-root-ca          # 기본은 빈 subject 이니 이름을 준다(공식 문서의 경고)
  secretName: root-ca
  issuerRef: { kind: ClusterIssuer, name: bootstrap }
---
kind: ClusterIssuer
metadata: { name: internal-ca }
spec: { ca: { secretName: root-ca } }

私有 CA 真正的难点不在于签名,而在于 信任分发。必须将根证书放入所有客户端的信任列表中,并且在更换根证书时,必须使它们同时信任新旧证书。官方文档为此目的引入了 trust-manager,并警告说运营 PKI 需要轮换、分发和恢复计划。还有一种 csi-driver 可以将证书直接挂载到 Pod 中而无需 Secret。

5. 何时续期

spec.duration 的默认值为 90 天, spec.renewBefore 的默认值为 寿命的 1/3 。也就是说,如果未填写任何内容,则会在 寿命的 2/3 节点处进行续期。从 v1.16 开始,还支持按比例编写的 renewBeforePercentage (官方文档,1.16 发行说明)。如果由 CA 确定寿命(如 ACME),则它不是根据请求的 duration 计算,而是根据 实际收到的证书的 notBefore 和 notAfter 计算。实测值将原样显示出来。

90일 인증서는 60일째에 갱신한다 (기본: 수명의 2/3) 9월 10월 11월 12월 1월 공개 인증서 A 갱신 11/8 만료 12/8 공개 인증서 B 갱신 12/7 만료 1/6 자체 서명 (renewBefore 15일) 갱신 11/6 만료 11/21 읽은 날 10/26 공개 인증서는 Let's Encrypt, 진한 칸은 갱신 구간(만료 전 1/3). 공개 CA 는 기본 수명을 2027-02 에 64일, 2028-02 에 45일로 줄인다. 날짜 대신 비율(2/3)이나 ARI 로 갱신한다.
图:两个公共证书和一个自签名证书的有效期、续期时间和读取日期
证书(2026-10-26 读取) notBefore → notAfter 寿命 renewalTime 距过期剩余时间
公共证书 A(Let's Encrypt,无设置) 09-09 12:09:29 → 12-08 12:09:28 90 天 − 1 秒 11-08 12:09:28 30 天(1/3)
公共证书 B(Let's Encrypt,无设置) 10-08 23:15:09 → 01-06 23:15:08 90 天 − 1 秒 12-07 23:15:08 30 天(1/3)
自签名(duration: 2160h·renewBefore: 360h 中填写的) 08-23 08:45:34 → 11-21 08:45:34 90 天 11-06 08:45:34 15 天
自签名(用于 Webhook,无设置) 10-18 → 01-16 90 天 12-17 30 天(1/3)

所有时间均为 UTC,且 kubectl get certificate -o custom-columns=… 的 status.notBefore·notAfter·renewalTime 与 Secret 的 tls.crt 在 openssl x509 -noout -startdate -enddate 读取的日期彼此相同。经计算,公共证书的 renewalTime 正好是 notAfter − 30일 。

公共 CA 侧的变化

Let's Encrypt 于 2025-12-02 公布了缩短寿命的时间表。默认配置文件将在 2027-02-10 变为 64 天,在 2028-02-16 变为 45 天,并且权限重用期也将从 30 天缩短至 10 天,再缩短至 7 小时。公告断言,像“每 60 天一次”这样以固定间隔续期的方式已不再足够。按比例(2/3)续期的 cert-manager 默认值顺应了这一变化,但 renewBefore: 720h 等 编写了固定值的地方,在 45 天证书中续期余量将减少至 15 天。 还有由 CA 告知“何时续期”的 ARI (RFC 9773)。cert-manager 在 v1.21 中将其置于 ACMEUseARI 功能门控之后进行实验性引入,默认处于关闭状态。

6. Webhook 与 cainjector:为什么有三个组件

安装 cert-manager 后会生成三个 Deployment。在 TT Lab 集群中,controller、webhook 和 cainjector 也分别在各自运行。

如果没有 cainjector,在 Webhook 服务器证书更新当天,API 服务器将无法信任新证书,导致 该类对象的创建全部失败。这就是 Webhook 证书更新极易成为静默故障的原因。

7. 如何与 Gateway/Ingress 衔接:shim

无需逐个编写 Certificate,也可以在 Gateway 侧仅添加一个注释。

在 TT Lab 集群的 controller 配置文件中包含旧名称 enableGatewayAPI: true ,而 Gateway 上则有 cert-manager.io/cluster-issuer: <클러스터 이슈어> 注释。读取其结果可以很好地展现 shim 的特性。

Gateway 何时能察觉证书的变更

Gateway(基于 Envoy)会监视 Secret 并在其更改时重新读取。它不会重启代理。经实测确定的路径如下。TT Lab 集群的 Cilium Gateway 将所引用的 TLS Secret 复制到其专属命名空间中作为带有哈希名称的副本。公共证书 A 的副本于 2026-08-19(首次签发)创建,但内部包含的是在 2026-09-09 重新获取的证书。它没有创建新副本,而是在原地进行了续期。从外部通过 openssl s_client 进行连接确认时,公共地址提供的证书的 notAfter 也与 Dec 8 12:09:28 2026 相同。

相比之下,将 Secret 作为文件挂载且 仅在启动时读取一次 的应用程序则有所不同。官方文档指出,对于此类应用,新证书不会被立即使用,并推荐使用 kubectl rollout restart 或 Reloader 等工具。这是第 9 节中陷阱最常见的一种。

8. 实测:从 TT Lab 集群中读取的内容

环境:cert-manager 1.21 系列(Helm Chart,各 1 个 controller·webhook·cainjector)。读取日期 2026-10-26,所有时间均为 UTC。

项目 值
Issuer 用于公共证书的 ClusterIssuer(ACME、HTTP-01、Gateway API 解题)以及各组件的 SelfSigned Issuer
Certificate 几个公共证书以及多个用于 Webhook/内部 TLS 的自签名证书
Order·Challenge Order 对于每个公共证书都以最后一个 valid 的形式保留,而 Challenge 不存在 —— 成功后会被删除
公共证书的密钥 RSA 2048,签名 sha256WithRSA。签发者为 Let's Encrypt 的中间 CA

首次获取一个新名称时:6 分 5 秒

首次创建公共证书 B(2026-10-09)的 controller 日志保留在上一个容器日志(kubectl logs --previous)中。按时间顺序列出这些行如下。

时间 发生的事情
00:07:36.141 trigger: Certificate must be re-issued,原因 DoesNotExist(无 Secret)→ Issuing
00:07:36.332 创建 CertificateRequest 修订版本 1·Approved
00:07:38.209 创建解题 Pod
00:07:38.274 创建用于解题的 HTTPRoute(以 Gateway 为父级)
00:07:48 – 00:13:28 重复检查解题 Pod·Service·HTTPRoute 是否存在(前 10 秒,后 20 秒间隔,共 23 次)
00:13:41.037 certificate issued,CR Ready
00:13:41.075 Certificate Ready=True

准备工作在 2 秒内完成,其余 6 分钟都在等待。至于在等待什么,即自我检查为何通过较晚,并未留在该日志阶段(--v=2)中,因此属于 无法确认。由于这是一个新创建的公开名称,因此推测可能是在等待 DNS 传播。订单对象中原样保留了服务器提出的三个挑战,并且权限的初始状态为 pending 。

再次获取同一个名称时:4 秒

公开证书 A 的修订版本 2 于 2026-09-09 13:07:57 生成 CR,并在 13:08:01 变为 Ready。 4秒。查看订单对象就能找到原因。权限的初始状态为 initialState: valid ,挑战列表中只有 http-01 一个。我们在 21 天前(首次签发)已经获取过该名称的权限,且 Let's Encrypt 会将权限重用 30 天,因此既不需要测试 Pod 也不需要进行自检。如果按照上述计划将这个重用期限缩短到 10 天 7 小时,这种捷径将几乎不复存在。至于修订版本 2 为什么会生成,由于没有残留日志而 无法确认,我们只知道 Certificate 的 generation 为 3,说明其规范曾经被修改过。

notBefore 早于签发时间

两个公开证书的 notBefore 都比 CR 变为 Ready 的时间准确早了 58 分 32 秒(A:13:08:01 对比 12:09:29,B:00:13:41 对比 23:15:09)。关于为了防止时钟较慢的客户端将新证书拒绝为“尚未生效”,Let's Encrypt 会将 notBefore 提前约 1 小时的解释,仅在 Let's Encrypt 社区论坛的回复中找到,在官方文档中未能确认。

事故:一个 Secret 有两个主人会导致数千次重新签发

集群内某个组件所使用的 Webhook 证书(自签名)的修订版本号超过了 1 万。从 2026-10-18 Certificate 被创建到死循环停止, 在 73 分钟内重新签发了约 1 万次(平均每秒约 2.7 次)。剩下的日志只有最后 1 分钟左右,仅在该时间段内就重复了 300 多次相同的行。

"Certificate must be re-issued" … reason="SecretMismatch"
  message="Secret contains a private key that does not match the current CertificateRequest"

签发 → 写入 Secret → 随即出现“Secret 的 Key 与当前请求不符” → 再次签发,这是一个死循环。 这意味着有人在覆盖 cert-manager 写入的 Secret。情况是这样的:当天该组件从“自己的控制器自行创建 Webhook 证书”的安装方式过渡到了“由 cert-manager 创建”的方式,并且 这两种方式使用了相同的 Secret 名称。 可以推测,以旧配置启动的控制器继续将自己的证书写入同一个 Secret,从而导致了死循环。由于死循环停止的时间与该控制器重启的时间相差几分钟,因此无法确认究竟是什么中断了死循环。由于是自签名证书,所以没有触及外部限制,但 如果是 ACME 签发机构,在头几分钟内就会触及 Let's Encrypt 的每周限制(同一名称集合 5 张),从而导致长达一周左右无法签发。

教训很简单: 一个 Secret 只能有一个主人。 另外, revision 通过监控诸如数字或 CertificateRequest 生成速度等“平时几乎不怎么变动的值”,可以提早发现这种死循环。

9. 常见故障与排查顺序

① 挑战无法完成。 从上到下逐一排查 describe 。在哪一个对象处停滞,故障原因就在哪里。

kubectl -n <ns> describe certificate <이름>            # Issuing 이 붙어 있나, 메시지
kubectl -n <ns> get certificaterequest,order,challenge  # 어디까지 생겼나
kubectl -n <ns> describe challenge <이름>               # Reason: 자체 점검 실패 문구가 여기 있다
curl -v http://<이름>/.well-known/acme-challenge/<token> # 바깥에서 직접

HTTP-01 常见的三个原因是:80 端口在外部被阻断、将所有 HTTP 强制重定向到 HTTPS 的规则先生效于验证路径、以及 回环(Hairpin)。最后一种情况是,集群内部通往自己公网地址的流量被路由器或负载均衡器阻断,导致从外部可以访问,但 cert-manager 自身的检查失败。在路由器后的小型集群中尤为常见。如果是 DNS-01,则首先检查 TXT 传播延迟和委派(CNAME)设置。

② 触发了速率限制(rate limit)。 Let's Encrypt 当前的限制是:每个注册域名 7 天 50 张、 同一名称集合 7 天 5 张、每个名称/账户验证失败每小时 5 次、每个账户新订单 3 小时 300 个(官方 rate limit 文档)。同一名称集合的限制每 34 小时才恢复一张。Let's Encrypt 和 cert-manager 的教程都建议在测试配置时必须先使用 staging 服务器。如果第 8 节中的死循环发生在 ACME 上,就会直接触发此限制。

③ 证书已更换,但应用仍在使用旧证书。 代理会自动重新加载,但在启动时仅创建一次密钥库的应用(例如在启动脚本中创建 Java PKCS12 密钥库的应用等)必须重启。请同时记住,如果将 Secret 以 subPath 挂载,kubelet 是不会更新文件的。解决方法是使重启自动化(如使用 Reloader 等),或者让应用监听文件变动并重新加载。

④ 一个 Secret 有两个主人。 即第 8 节中的事故。检查其他工具(Chart 自带的证书生成器、手动创建的 Secret、其他控制器)是否使用了相同的名称。 SecretMismatch 如果不断重复且 revision 持续上升,基本上就是这个问题。

⑤ Webhook 证书更新日的故障。 如果 cainjector 停止运行或目标对象的注解错误,caBundle 就会停留在旧的 CA,导致所有经过该 Webhook 的请求都因 TLS 错误而失败。对比 Webhook 配置中的 caBundle 指纹与 Secret 的 ca.crt 即可一目了然。

⑥ shim 与手动创建的 Certificate 发生冲突。 如第 7 节所述,shim 不会干涉没有主人的 Certificate。如果“添加了注解但证书没有改变”,请检查是否存在同名的手动创建的 Certificate。

10. 总结

蒂蒂小贴士。 证书就像是网球拉力赛。如果传球的人有两个,比赛就无法结束。一个 Secret 只能有一个主人!而且必须在“生命周期的三分之二”(而不是“每三个月”)时进行交接,这样即使球的飞行时间变短了(45 天!)也不会漏接。

参考资料

登录后即可点赞

评论

还没有评论。

登录后即可发表评论