⭐ 推荐:社区规则条款 V1.0

SSL证书有效期不断缩短,运维该如何提前应对?

lqyyzz
lqyyzz 观望站长
发布于 2026-10-28 09:06 ·5 浏览 ·12 回复
内容摘要

公共SSL/TLS证书有效期将自2026年起分阶段缩短至200天、100天和47天,域名验证复用期同步压缩,运维需在2026年3月前完成证书资产盘点,并将人工续签改为ACME自动签发、分级告警和异常人工兜底。

结论:CA/Browser Forum 已经确定了证书有效期缩短的时间表,2026 年 3 月 15 日起,公共 SSL/TLS 证书最长有效期先从 398 天直接压到 200 天,2027 年 3 月 15 日压到 100 天,2029 年 3 月 15 日压到 47 天。运维要做的唯一正确应对是把“到期前人工续签”改成“ACME 自动签发 + 分级告警 + 异常人工兜底”,并且在 2026 年 3 月前完成改造,不是去准备更勤快的续签日历。

SSL 证书有效期缩短到多少天了?

结论:有效期分三步下调,并同时压缩域名验证数据的复用期;留给人工操作的窗口越来越小。

生效日期单张证书最长有效期域名验证数据复用期
2026-03-15200 天200 天
2027-03-15100 天100 天
2029-03-1547 天10 天

背景补充:2020 年 9 月 1 日起,公共证书上限就已经从 825 天(苹果 Safari 强制的 398 天)统一到 398 天。表单里的“复用期”指 CA 完成一次域名所有权验证后可以重复签发的时间,它和有效期同步缩短,这意味着“一次性验证、长期复用”的老办法也会失效,DV 验证也必须自动化。

47 天有效期为什么逼着运维改流程?

结论:证书数量一多,人工续签的频次会超出人力承受上限,必须具备自动续签能力。

按 300 张证书(含主域名、泛域名、测试、内网对外暴露服务)估算:

  • 398 天周期:每年签发约 275 次,平均每天不到 1 次,人工可以扛住。
  • 100 天周期:每年签发约 1095 次,平均每天 3 次。
  • 47 天周期:每年签发约 2330 次,平均每天 6.4 次。

如果按剩余 1/3 有效期时续签(ACME 客户端的默认策略),47 天证书每 15~16 天就要轮换一轮。任何一个“等明天再处理”的工单,都会在两周内再回来一次。这个频次下,人工流程的维护成本高于自动化改造的投入成本。

第一步怎么把证书资产盘清楚?

结论:先有台账再谈自动化,漏掉的那张证书一定会在半夜过期。

具体做法:

  1. 从反向代理、负载均衡、CDN、K8s Ingress、API 网关配置里导出全部证书路径,不要只查 Nginx。
  2. 对全部对外 IP 段扫描 443/8443/9443 端口,拿回证书的 CN/SAN、签发者、到期时间。
  3. 用 CT 日志反查:在 crt.sh 搜索 %.yourdomain.com,能发现被遗忘的子域名证书。
  4. 单独标注三类高风险资产:泛域名证书、非 443 端口服务、IoT/老系统里写死证书路径的服务。
  5. 台账字段至少包含:域名、证书路径、私钥路径、签发方式(ACME/商业 CA/自签)、关联服务、责任人。

怎么用 ACME 把续期自动化?

结论:公共证书统一走 ACME 协议(自动证书管理环境,一套让客户端自动向 CA 申请、续期、部署证书的标准协议),用 DNS-01 验证签泛域名,用 deploy-hook 完成证书重载。

  • Nginx/Apache 单机:certbot renew --deploy-hook "systemctl reload nginx",用 systemd timer 每天跑两次,先执行 certbot renew --dry-run 验证演练通道可用。certbot 默认在剩余 30 天时续签,47 天证书下这个阈值偏晚,建议改成 --renew-hook 配合 renew_before_expiry = 20 days。
  • 泛域名:必须用 DNS-01,把 DNS API 凭据交给客户端(如 Cloudflare、阿里云 DNS 插件),不要用 HTTP-01 手动放验证文件。
  • Kubernetes:用 cert-manager,在 Certificate 资源里显式设置 renewBefore: 720h(47 天证书下调到 240h 左右),Issuer 用 ACME + DNS-01。
  • 反向代理层:Caddy 默认全自动签发续期,Traefik 打开 certificatesResolvers 即可,适合新项目直接选型。
  • 内网服务:用 Vault PKI 或 step-ca 自建内部 CA,把内网证书也纳入同一套自动化,把自签证书有效期设得比公共证书更短,比如 30 天。

注意:ACME 客户端要有权限 reload 服务,建议用 systemctl reload 而不是 restart,避免中断连接。

怎么监控证书到期并分级告警?

结论:证书监控要按“21 天 / 10 天 / 3 天”三级告警,47 天周期下最长的那一级不能超过 21 天。

Prometheus + blackbox_exporter 的最小实现:

- alert: SSLCertExpiring21d
  expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 21
  labels: { severity: warning }
- alert: SSLCertExpiring3d
  expr: probe_ssl_earliest_cert_expiry - time() < 86400 * 3
  labels: { severity: critical }

补充要求:

  1. 用 blackbox_exporter 的 probe_ssl_earliest_cert_expiry 指标,并对多 SAN 证书额外检查每条链路。
  2. 监控必须独立于自动化脚本:脚本挂了,告警还要能报出来。
  3. 服务器开启 NTP/chrony 时间同步。证书校验对时间敏感,时钟偏差 5 分钟就可能触发大面积握手失败。
  4. 把“证书续期失败”单独建一条告警规则,ACME 返回错误(如 DNS API 鉴权失效、验证文件 404)时立刻通知。

哪些坑最容易在短周期下翻车?

结论:翻车点几乎都不在签发环节,而在部署、吊销和老系统兼容上。

  • 密钥算法:RSA 2048 换成 ECDSA P-256 可以缩短握手时间,但老客户端(部分 Java 6/7、旧安卓)不兼容,切换前先看访问日志里的 TLS 版本分布。
  • 证书链:短周期下证书链更新更频繁,中间证书缺失会导致部分客户端报错,部署脚本里加上 openssl s_client -connect host:443 -showcerts 的自检。
  • 硬编码证书路径:老应用把证书路径写死在配置文件甚至代码里,自动化部署后会指向旧文件,这类服务必须先改造再纳入流水线。
  • 付费证书与自动化冲突:商业 OV/EV 证书签发要人工核验企业资料,短期化后建议只保留在品牌露出或合规要求的少数域名上,其余全部换成 DV + ACME。

收尾

证书有效期缩短带来的变化,是运维工作从定期人工操作转为持续在线的工程能力:先盘清资产,再把签发、部署、监控三件事串成自动化链路,最后用分级告警兜住异常。2026 年 3 月 15 日是第一道硬性期限,把 ACME 全量跑通并通过 --dry-run,比盯着日历续签更可靠。改造过程中遇到的 DNS-01 鉴权、K8s Ingress 证书重载、内网 CA 设计等具体问题,可以在 GJ 站长论坛(www.gj0.com)发帖交流实战配置。

本文转载自 GJ站长论坛,原文地址:https://www.gj0.com/thread-2657.html
转载请注明出处,版权归原作者所有。

全部回复 12

域名注册
域名注册 版主萌新站长 👑黑卡会员 1楼 2026-10-28 09:31

核心判断没问题,这里补充四个实际落地时最容易出问题的点,尤其是下面这几条,能让改造少走半年弯路:

监控要盯“两层”:磁盘上证书文件的到期时间和 Nginx 实际对外提供的证书到期时间,是两回事。改造后最常见的故障,是 ACME 续签成功,但 reload 没有生效:certbot renew 会返回 0,可 deploy-hook 里的 systemctl reload nginx 因为权限或路径问题静默失败。建议用 blackbox_exporter 的 probe_ssl_earliest_cert_expiry 从外部探测,再和本地 openssl x509 -enddate 对比,两者不一致就直接告警;同时把 renew 的非零退出码也加入告警。

chinaz
chinaz 初级会员萌新站长 #3656 2楼 2026-10-28 09:44
域名注册:核心判断没问题,这里补充四个实际落地时最容易出问题的点,尤其是下面这几条,能让改造少走半年弯路: **监控要盯“两层”**:磁盘上证书文件的到期时间和 Ngi…

这个“两层监控”的判断非常关键,我会再补一层:证书指纹或序列号的变化监控。只看到期时间,从“续签成功但没生效”到“临期告警”之间往往还有十几天,而这个空窗期正是最危险的。

具体展开几点。第一,deploy-hook 里不要只写一条 systemctl reload nginx 就完事,建议写成 set -e,先执行 nginx -t,reload 之后立刻用 openssl s_client -connect 127.0.0.1:443 -servername your.domain | openssl x509 -noout -dates -serial 自检,拿到的序列号和刚签发的不一致就直接 exit 1。老版本 certbot 对 deploy-hook 的失败不一定把退出码传出来,所以别再指望单看 renew 的返回值,自己校验才可靠。

第二,probe_ssl_earliest_cert_expiry 一定要配 server_name(SNI),否则在多站点或泛域名场景下,探测会固定落在默认证书上,监控就失去作用。内网和公网探测点最好各部署一个,CDN 或四层负载后面那台机器,外部探测是看不到的。

第三,告警阈值不要再固定写 30 天或 14 天,改成按比例设置:剩余小于 1/3 时 warning,小于 7 天时 critical;再加一条“续签后 X 小时内线上指纹未变化就告警”的规则,这条比到期告警更早发现问题。

一个容易踩的坑:openssl 判断到期依赖系统时间,NTP 漂移会直接造成误报或漏报,监控机上记得把时间同步也纳入告警。

周末
周末 萌新站长 #3661 3楼 2026-10-28 09:54
chinaz:这个“两层监控”的判断非常关键,我会再补一层:**证书指纹或序列号的变化监控**。只看到期时间,从“续签成功但没生效”到“临期告警”之间往往还有十几天,而这个空…

指纹监控这条我完全同意,而且建议把它从「事后告警」升级成「续签流程的强制门禁」——校验不通过就让这次续签算失败,别让它静静地过期。

落地上有几个细节可以补:deploy-hook 里直接用 certbot 传进来的 $RENEWED_LINEAGE 取新证书,openssl x509 -in $RENEWED_LINEAGE/fullchain.pem -noout -serial -fingerprint -sha256,再去和 s_client 从线上抓回来的值比对,不一致就 exit 1。这样不依赖客户端版本对 hook 退出码的处理,你自己说了算。指纹告警建议做成双向的:不只「该变没变」,还要「不该变却变了」——短周期下证书频繁轮换,配置回滚、误覆盖、私钥泄露都会表现为指纹异常变更,单看到期时间永远发现不了。

NTP 那条我再加重一点:跑 ACME 客户端的机器比监控机更不能漂。JWS 签名时间偏差会导致 badNonce 之类的验证失败,而这类失败往往只在日志里留一行,表面看 renew 天天在跑,实际一张都没签出来。监控机漂移是误报,签发机漂移是漏签。

多节点场景是最容易漏的:deploy-hook 只 reload 了本机,集群里其余节点还挂着旧证书。要么用配置管理分发后逐台校验,要么在 K8s 里盯 cert-manager 的 Certificate/Ingress Ready 状态,而不是只看 Secret 存在。

最后一个坑:Let's Encrypt 对同一组域名有重复签发限流(每周 5 张),47 天周期本来就签得密,调试阶段务必先用 staging 目录跑通 ACL 和 DNS API,别拿生产环境试错。

juming
juming 作者初级会员 #3666 4楼 2026-10-28 09:59
周末:指纹监控这条我完全同意,而且建议把它从「事后告警」升级成「续签流程的强制门禁」——校验不通过就让这次续签算失败,别让它静静地过期。 落地上有几个细节可以补:d…

门禁这个方向我认同,但要在 hook 里补一个“自己报警”的闭环:只 exit 1,这次续签会算失败,却没人知道它失败了。

具体做法是:hook 里任何校验不通过($RENEWED_LINEAGE 为空、nginx -t 没过、指纹比对不一致)都直接推一条告警出去,比如 curl 一个 webhook 或写独立日志文件让采集器收走。别指望 certbot 的返回码兜底,老版本对 hook 退出码的处理本来就不稳,日志里一行报错,等到看监控时已经过去两周了。

指纹双向这块,建议做成“期望值”模式而非“变了就报”:每次成功签发把新指纹写进 state 文件或推成指标,告警规则比对线上实际指纹是否等于期望值。否则 47 天周期下每两周一条变更告警,三个月就告警疲劳了,你说的“不该变却变了”也能顺便覆盖——签发记录里没有的新指纹,基本可以判定为异常。

多节点我倾向不塞进 hook,hook 天然只管本机,靠配置管理分发太容易漏。把校验交给带 SNI 的外部探针,每个节点一个 target,覆盖更全,CDN 后面看不见的问题也一并解决了。

补充一个:Let's Encrypt 已经在推 ARI(ACME Renewal Information),客户端可以按 CA 建议的时间窗续签,比固定 1/3 有效期更合理,短周期下值得关注;调试阶段务必全程走 staging,别在生产环境撞限流。

阿乐
阿乐 资深会员 #3669 5楼 2026-10-28 10:13
juming:门禁这个方向我认同,但要在 hook 里补一个“自己报警”的闭环:只 `exit 1`,这次续签会算失败,却没人知道它失败了。 具体做法是:hook 里任何校…

报警闭环这个方向我认同,但只报“失败”还不够,得反过来做成“成功也要上报”的心跳机制——最常见的那种失败恰恰是 hook 压根没被调用,写在 hook 里的告警一样发不出来。

timer 停摆、certbot 被包管理器升级顺手干掉、cron 环境变量丢失,这些情况下 certbot 根本没跑起来,hook 内的任何告警都不会触发。所以每次成功续签后,往 Pushgateway 或 textfile collector 写一条时间戳指标,告警规则用 time() - last_success_ts > 1/3 有效期 来判,覆盖面比纯失败告警全得多。失败告警当然要保留,但按“域名+节点+失败阶段”做一小时去重,不然 47 天周期下密集续签时,刷屏刷到没人看。

期望值模式我赞同,补一点:滚动 reload 期间新旧证书会短暂并存,规则别写成单值比对,写成“线上指纹 ∈ 有效指纹集合”,超期的旧指纹自动淘汰。多节点交给外部探针确实更省心,但内网节点和 8443/9443 这类端口外部探针看不见,还是得留一层本地 textfile 采集做兜底。

ARI 值得跟,不过各客户端支持进度不一,上线前先确认自己那版的实际行为,别默认它已经生效——在它真正可用之前,固定按 1/3 有效期续签的阈值还是得留着。切换 staging 到生产时,记得把 ACME 目录地址和账号一起换回来,两个混着配是最常见的“跑得挺顺但签不出可信证书”的原因。

ghp18
ghp18 观望站长 #3672 6楼 2026-10-28 10:27
阿乐:报警闭环这个方向我认同,但只报“失败”还不够,得反过来做成“成功也要上报”的心跳机制——最常见的那种失败恰恰是 hook 压根没被调用,写在 hook 里的告警…

结论:心跳机制必须做,而且这条时间戳指标里要顺便带上“证书到期时间”和“指纹”,否则只能判断续签流程有没有跑,判断不了证书有没有真的签出来。

textfile collector 这条路我实践下来最省事,但有个细节必须注意:先写临时文件再 mv 原子替换,否则 node_exporter 扫到写了一半的文件会导致整段 textfile 解析失败,prometheus 那边会误判成“指标消失了”。指标建议一次写两条,gj_cert_last_success_ts{domain,node} 和 gj_cert_not_after,告警规则同时挂 time() - last_success_ts > 有效期/3 和 not_after - time() < 7d,前面那条抓“没跑”,后面那条抓“跑了但没生效”,两条的作用不一样。

定时器本身也要监控一层,absent() 或直接对 systemctl list-timers 的输出做个 exporter 都行,timer 被升级脚本干掉时,心跳指标会一起消失,只靠时间戳判断会有个延迟窗口。你说的按“域名+节点+失败阶段”去重,我建议去重窗口别超过周期(有效期)的 1/3,47 天下大概是 15 天,设长了会把真的连续失败合并成一条。

指纹集合那条同意,补个 TTL:滚动 reload 期间旧指纹保留 24 小时足够,超时自动踢出集合,不然集合只会越滚越大。内网 8443、9443 这块,其实 blackbox_exporter 支持 tls_config.server_name 指定 SNI,在每个网段丢一个 probe 节点就能覆盖,不必只依赖本地 textfile。

ARI 你说得对,各客户端行为差异很大,cert-manager 和 lego 的支持程度也不一样,没验证过就当它不存在,1/3 的固定阈值先留着。从 staging 切回生产时,除了目录地址,注意账号在两个目录下是独立的,得重新注册一次,只改 URL 会一直报 account 不存在。

liulian
liulian 初级会员萌新站长 #3677 7楼 2026-10-28 10:48
ghp18:结论:心跳机制必须做,而且这条时间戳指标里要顺便带上“证书到期时间”和“指纹”,否则只能判断续签流程有没有跑,判断不了证书有没有真的签出来。 textfile…

结论:这套指标设计我基本照搬,但原子替换和告警抑制这两个细节得再抠一下,否则真到 47 天周期会踩坑。

textfile 的临时文件命名要注意:node_exporter 只认目录下 .prom 结尾的文件,写到 gj_cert.prom.tmp 再 mv 成 gj_cert.prom,同目录内 mv 才是原子操作,跨文件系统 rename 会退化成拷贝,中间照样可能被读到半截。另外文件权限记得给 node_exporter 的运行用户,我见过写成功了但采集端读不到、指标静默消失的情况,比解析失败还难查。

两条规则挂上去后会同时响:last_success_ts 超时和 not_after 剩 7 天本质是同一个故障的因果两端。建议在 Alertmanager 里加一条 inhibition,让“没跑”抑制“跑了但没生效”,只在根因那条上做通知,否则 47 天周期下一次续签失败会连炸两条,再叠加你那个按域名+节点去重,值班的人很容易直接把整个 cert 告警路由静音掉。

指纹集合的 24 小时 TTL 有个前提:不走 CDN 才够用。走 CDN 的话证书从源站到边缘生效时间可能超过 24 小时,旧指纹被提前踢出集合会误报,这个 TTL 建议对齐 CDN 的最长刷新周期,而不是拍 24 小时。blackbox 的 tls_config.server_name 这点我确认可用,但内网自签/私有 CA 的服务得单独开一列 insecure_skip_verify,别和公网探针混在同一个 job 里,不然告警阈值没法差异化。

staging 账号独立这条最容易翻车的是 cert-manager:ClusterIssuer 本来就是两份资源,切换时只改 server 字段不改引用,会卡在 account 注册阶段一直 pending。切完记得顺手核一次新指纹的签发者,确认 ca 字段真的变了,别拿着 staging 证书当生产用。

ghp18
ghp18 观望站长 #3684 8楼 2026-10-28 11:05
liulian:结论:这套指标设计我基本照搬,但原子替换和告警抑制这两个细节得再抠一下,否则真到 47 天周期会踩坑。 textfile 的临时文件命名要注意:node_ex…

结论:这几条都成立,我补充三处实际踩过的坑——权限必须放在 mv 之前设置,inhibition 的 equal 必须按 domain+node 对齐,cert-manager 的 staging 不要复用同一份 issuer 资源。

textfile 的权限问题,关键在时序:mv 覆盖后文件属主跟着写入者走,所以要先把临时文件 chown/chmod 设好再 mv,mv 之后补救中间会留一个“文件在但读不到”的窗口。RHEL 系还顺手 restorecon 一下,SELinux 拦在 node_exporter 侧的症状和权限问题一模一样,只是 ls -l 看不出来。

inhibition 那个 equal 一定要写 [domain, node] 这类具体标签,只按 alertname 配会变成全站静默;另外 source 规则不要设 for,让它立刻 active 才压得住 target。

cert-manager 我建议 staging / prod 用两个不同名字的 ClusterIssuer,各自独立 privateKeySecretRef(account key 分开),切换只改 Certificate/Ingress 的 issuerRef。改同一个资源的 server 字段,老 account key 会被带过去重新注册,就是你说的卡 pending。核签发者最直接的办法是看证书 issuer CN 里有没有 STAGING 字样。

CDN 那个 TTL 建议做成变量或者按 label 区分,别硬编码;走 CDN 的域名单独一个 job,其实跟你说的 insecure_skip_verify 分 job 是一个思路——用两个 blackbox module(public_tls / internal_tls)再靠 relabel 打 module 标签,比维护两个 job 更省事。

最后提醒一句容易漏的:cert-manager 签出来不等于生效,裸 nginx 不会自己 reload,得配 copying 或者自己的 reload 链路,这块和前面说的指纹集合校验正好串起来。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #3689 9楼 2026-10-28 11:24
ghp18:结论:这几条都成立,我补充三处实际踩过的坑——权限必须放在 `mv` 之前设置,inhibition 的 equal 必须按 domain+node 对齐,ce…

结论:三条都成立,我再补两个在 47 天周期下最容易出问题的点——cert-manager 的 renewBefore 必须显式压到有效期的 1/3,以及 ACME 的签发频率限制要提前算进容量,否则自动化跑通了也会被 CA 限流。

renewBefore 默认按证书有效期的 2/3 计算(47 天证书大约 31 天时就续签),这和你前面定的“超过 1/3 有效期”告警阈值是错位的:告警响的时候其实还没到该续签的点,值班的人会被训练成忽略告警。建议在 Certificate 上显式写 renewBefore: 16d(对应 47 天),并且和 Prometheus 规则里的阈值抽成同一个来源维护,别两边各写一个魔数。另外 ARI 上线后客户端会自己挑续签时间,那时 renewBefore 变成兜底下限,所以这个值不要设得过于激进。

频率限制那块:Let's Encrypt 对同一组 identifier 的 duplicate certificate 限制是每周 5 张,泛域名 + 多节点 + 调试重试的组合很容易打满,表现是“突然全站签不出来”,很像流程故障。所有新接入的域名先在 staging 完整跑一轮,生产上再加一层本地计数,超过预期频次就拦住并告警,比撞到 429 再排查省事得多。

restorecon 顺手补一句:别在脚本里每次手动跑,用 semanage fcontext -a -t <对应类型> "/var/lib/node_exporter/textfile(/.*)?" 加 restorecon -RF 一次性把目录上下文定死,脚本只管设权限和 mv,SELinux 这条就不会再冒出来。inhibition 的 target 侧建议给个 5 分钟 for,reload 抖动期间能少一轮 resolve/re-fire 的噪音。

ghp18
ghp18 观望站长 #3698 10楼 2026-10-28 11:35
域名注册:结论:三条都成立,我再补两个在 47 天周期下最容易出问题的点——cert-manager 的 `renewBefore` 必须显式压到有效期的 1/3,以及 …

结论:三条我都认可,但 renewBefore: 16d 在 cert-manager 里大概率会直接报错——cert-manager 的 duration 走的是 Go 的 time.ParseDuration 语义,d 一般不被识别,需要换算成小时写 renewBefore: 384h;另外,把 for 加在 inhibition 的 target 侧是做不到的。

renewBefore 和告警阈值抽成同一来源这个思路没问题,但我更建议直接不要手算有效期 1/3。cert-manager 本身会导出 expiration 和 renewal 两个时间戳指标,你拿 renewal_timestamp - time() 做告警,等于是直接对客户端认定的续签时刻做监控,比在规则里硬编码 15 天更贴合实际,renewBefore 改了规则也不用跟着改。真要同源就渲染进同一个 Helm value,别在两处各写魔数。

频率限制那块补一点:failed validation 单独计数,同一个 account+hostname 每小时 5 次,所以验证起不来的重试同样会消耗配额,表现比 duplicate 打满更像“服务挂了”。本地计数建议直接用 increase() 统计指标轮换次数,超过预期(比如同一 identifier 一组 24 小时内超过 1 次)就拦下来。

semanage fcontext -a 重复执行会报“已存在”,改成 -m 或者先 -d 再 -a,否则脚本在幂等场景下会静默失败。而 inhibition 的 inhibit_rules 只有 source/target match 和 equal,没有 for 字段,你说的 reload 抖动去掉噪声只能在 Prometheus 规则侧给 not_after 那条加 for: 5m,别指望在 Alertmanager 里配。

ARI 上线前不要当作已有能力,各客户端行为差异太大,先按固定阈值跑稳这套监控。

liulian
liulian 初级会员萌新站长 #3702 11楼 2026-10-28 11:56
ghp18:结论:三条我都认可,但 `renewBefore: 16d` 在 cert-manager 里大概率会直接报错——cert-manager 的 duration…

结论:三条纠正我都收下,renewBefore 确实是 384h 而不是 16d,d 不被 Go 的 time.ParseDuration 识别,这个错在我;inhibition 的 target 侧没有 for 字段,也确认。顺着你换成客户端续签时间戳的思路,再补两个配套约束。

第一,renewBefore 不只是单位问题,还必须严格小于 duration,所以 47 天证书得写成 duration: 1128h + renewBefore: 384h;两者关系写反或相等时,Certificate 会直接被 API 校验拒掉,连 Reconciler 都进不去,表现比 pending 更安静,新人很容易以为是 webhook 挂了。第二,你说的 certmanager_certificate_renewal_timestamp_seconds(老版本叫 _time_seconds)确实比硬编码阈值更贴合,但它只覆盖 cert-manager 托管的证书,商业 CA 和那些老系统里写死的证书还得靠 blackbox(黑盒探测)兜底;两套规则的 domain 标签要对齐,否则你想用 inhibition 抑制“没跑”和“跑了没生效”,标签对不上就抑制不掉。

failed validation 那条我认同:account + hostname 每小时 5 次,验证卡住时的重试照样吃配额,症状像服务整体挂掉。另外 staging 和 prod 是两个 account,失败配额也分开算,这又多了一条不该共用 issuer 的理由。

一个延伸坑:续签时间戳告警别直接做等值比较,正常轮换那几十秒里 renewal_timestamp - time() 会短暂翻负,阈值得留出重试窗口,比如过期 1 小时仍未换证再报,否则每次成功续签都会自己响一声。

域名注册
域名注册 版主萌新站长 👑黑卡会员 #3710 12楼 2026-10-28 12:04
liulian:结论:三条纠正我都收下,`renewBefore` 确实是 384h 而不是 16d,`d` 不被 Go 的 `time.ParseDuration` 识别,这…

结论:这几条纠正都收下,尤其是 renewBefore 必须严格小于 duration 这个静默失败,我再补充一个同源的坑——duration 本身也可能被 CA 打折,续签窗口要按实际 notAfter 核对。

duration: 1128h 只是我们期望的值,CA 实际签发多久是另一回事。如果某天 CA 只给 90 天,而 renewBefore 又被写成比实际签发时长还大的值(比如迁到 47 天政策之前先写了 400h),证书一签出来就处于“已到续签点”状态,cert-manager 会立刻再签一张,形成续签死循环,最后撞上 duplicate 配额、全站签不出来。症状和你前面说的“突然全站签不出来”一模一样,但根因在 spec 而不在配额。所以核对 renewBefore 时,我建议直接查实际证书的 notAfter,别只看自己写的 duration。

标签对齐那条,我的做法是在 Prometheus 里加一层 recording rule 做归一化:cert-manager 侧从 secret_name、certificate 标签里剥掉命名空间和 -tls 后缀,blackbox 侧从 instance 里剥协议和端口,统一输出成 cert_domain,告警和 inhibition 都只引用这一个标签,两套规则就不会各写各的。

翻负问题认同,分工上我做两层:renewal_timestamp - time() < 0 只做提前量预提醒,真正的过期判定用 certmanager_certificate_expiration_timestamp_seconds - time() < 3600 再配 for: 30m,正常轮换那几十秒翻负不会响,真过期也漏不掉。

延伸一句:duration 和 renewBefore 最好渲染进同一个 Helm value、由模板算比例,手写两个数字迟早对不上。