凌晨两点的 502
一、凌晨两点的 502
去年冬天,我们的一个对外 API 网关在凌晨两点开始返回 502。值班同学第一反应是后端挂了,查了 Pod 状态、翻日志、重启服务,折腾四十分钟才发现:后端一切正常,是网关的 TLS 证书在 00:00 过期了,客户端校验证书失败,直接放弃连接。
更尴尬的是,这套服务上线两年,证书是手工申请的,续期靠一张没人维护的 Excel 表格。这次过期的是一张泛域名证书,一次性带走了 7 个二级域名。
二、为什么监控一声没吭
事后翻监控配置,发现三个盲区叠加在了一起:
- 探活只看端口。blackbox_exporter 配的是
tcp_connect模块,只做 SYN/ACK 握手。TLS 握手发生在 TCP 连接建立之后,证书过期完全不影响这个探针的结论。 - 健康检查用了
curl -k。livenessProbe 是curl -k https://localhost:8443/healthz,-k跳过证书校验,容器觉得自己健康,kubelet 也觉得健康。 - 监控和用户在两条路径上。所有探针都部署在同一个 VPC 内,走内网 IP 直连,压根不经过公网 DNS 和 CDN,用户侧的失败路径从未被覆盖。
三个盲区单独看都不致命,叠在一起就是「监控全绿、业务全挂」。
三、把证书当成一个指标来采
第一步是换掉探针模块,改用 https 模块并强制带 SNI 和真实域名:
- 探针目标从
10.0.3.12:443改成https://api.example.com,由内网 CoreDNS 解析到真实入口; - 模块里设置
insecure_skip_verify: false,让探针像真实客户端一样校验证书链和主机名; - 核心指标是
probe_ssl_earliest_cert_expiry,它返回证书链上最早到期的那个时间戳。
告警规则算剩余天数,而不是判断「是否已过期」:
(probe_ssl_earliest_cert_expiry - time()) / 86400 < 21
我们分了三档:21 天 warning 进企业微信,7 天 critical 打电话,1 天全员升级。泛域名证书额外加一档 30 天,因为它影响面大。
四、别只监控证书,还要监控「续期这件事」
真正让我们栽跟头的不是证书,而是续期流程的静默失败——acme.sh 的 cron 因为一次 API token 轮换后再没成功过,日志里只有一行被忽略的报错。后来我们把续期任务改成显式上报:每次执行完向 Pushgateway 推一个 cert_renew_last_success_timestamp,再用 time() - cert_renew_last_success_timestamp > 86400*8 做兜底。这是典型的 dead man's switch——不指望它报错,只指望它别沉默。
顺手补了两件小事:所有证书纳入配置管理,域名、到期时间、负责人、续期方式写进 YAML 由 CI 校验;证书检查进上线 checklist,命令固定为 openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates。
五、复盘
这次事故的成本是一小时全量不可用,换来一条经验:监控的盲区往往不在「没采指标」,而在「采了一个看起来对、其实绕过关键路径的指标」。tcp_connect 探针、curl -k、内网直连,都是为了让监控「更稳定」而做的妥协,代价是它测的东西和用户真正经历的东西不是一回事。
现在做任何探活配置,我都会问一句:这个探针走的路径,和真实用户走的路径差在哪?差的那部分,就是下一个盲区。
- 点赞
- 收藏
- 关注作者
评论(0)