全集群钟差 300 毫秒会发生什么:证书悄悄过期,日志倒流

全集群钟差 300 毫秒会发生什么:证书悄悄过期,日志倒流

凌晨两点,网关 5xx 告警。你打开日志平台按时间排序,看到服务 B「处理完请求」的时间戳,比服务 A「发出请求」还早 300 毫秒------应答早于请求,因果倒置。

一个构造出来的典型案例:你重放流量、比对配置、抓包看网络,折腾二十分钟一无所获,直到有人顺手敲了一句 date。两台机器的钟差了 0.3 秒------系统什么都没坏,只是从这一刻起变得不可推理。

这就是时钟病:它很少直接弄挂服务,专攻的是你的排障能力。

一、杀伤清单:300 毫秒到几小时,三档死法

先把杀伤分层说清,别把所有锅甩给时钟,也别以为 300 毫秒人畜无害。

第一档,几百毫秒:杀可观测性。

跨节点日志排序是重灾区。链路上一次内部调用真实耗时 20 毫秒,钟差 300 毫秒,时间线上因果完全倒置------你按时间戳排序重建出来的「故障过程」,是小说,不是记录。排障从看日志变成考古。

追踪系统同样中招。OTel SDK 测单条 span 的耗时用的是单调钟,所以单条 span 可信;但瀑布图上服务 A 到 B 那段「网络耗时」,边界靠的是两台机器各自的墙钟。钟差 300 毫秒时,那段要么算出负数,要么显示 280 毫秒的假延迟。

判据很干脆:A 到 B 的耗时与两节点钟差同量级,结论就是「测不准」,别硬归因。

第二档,秒到分钟级:杀分布式协调。

etcd 的 lease 到期判断拿墙钟做算术,时钟跳变会让租约提前过期或迟迟不过期------上游租约以为还握着锁,下游已经判定过期把锁分给了别人。

节点间偏差过大还会放大 apiserver 到 etcd 的超时误判,一次慢查询被翻译成「etcd 挂了」。所以 etcd 的官方运维要求里明确列了各成员要有可靠时钟源【第三方转引,etcd 官方运维文档】。

Kerberos 更苛刻:默认 5 分钟的时钟容差,超出直接认证失败,报错还往往不提时间半个字【从业者判断】。300 毫秒远在容差内,但漂移不治理,方向就是朝那 5 分钟走。

第三档,小时级:杀信任链。

节点时钟比 CA 快几小时,一张还没到生效时间的证书会被判 x509: certificate has expired or is not yet valid,kubelet 直接起不来;反过来钟慢了,合法证书被判「已过期」。

kubeadm 签的证书一年有效期,怕的就是节点钟被拨到窗口之外。新节点加入集群被拒、先怀疑半天证书链,是经典弯路------正确顺序是先修时间再排证书。

同族症状还有 Jenkins agent 无故掉线,常见根因表里写着的就是「agent 机器时间漂移,检查 NTP」。

时钟病的阴险在于:每一档的症状都长得像别的病。

补一个真实事故当注脚:2017 年闰秒,Cloudflare 的 RRDNS 服务大量 panic,自家 DNS 解析失败约一小时。根因是 Go 1.9 之前的标准库在墙钟回拨时算出负的运行时长【第三方转引,Cloudflare 官方复盘,2017】。教训只有一句:代码里算耗时绝不能用墙钟相减,要用单调钟。

二、物理时钟没有「对齐」,只有「接近」

每台机器一块石英表,频率误差在几十 ppm 量级,没人校准的话每天能漂几秒;虚机更惨,宿主机的调度抖动会再叠一层。

所以「把所有机器调到同一时刻」是伪命题。NTP 类工具做的从来不是「对齐」,而是把偏差压进一个上界,并且持续看守。两种修正手段,杀伤完全不同:

修正方式 机制 危害
漂移校正(slew) 钟快了调慢点走,慢了调快点,时间轴连续 温和,几乎无害;但累积偏差本身要监控
跳变(step) 差距太大,直接一次性拨过去 时间轴不连续:定时任务重复跑或整体跳过;TLS 证书校验瞬间失败;算耗时的逻辑出现负数或尖刺

问「时钟对齐了吗」是问错了问题,该问的是:偏差上界多少、谁在看守、跳没跳过。

三、step 与 slew:调快慢,还是直接拨针

这是 chrony 的核心设计:发现偏差后默认不直接改表,而是微调时钟频率慢慢追平------slew。时间轴始终连续,依赖时间顺序的东西(TLS 证书校验、数据库事务序、应用自己的定时器)不会被一次倒跳打坏。

只有偏差大到追不动时才允许步跳,阈值由 makestep 控制:

ini 复制代码
# 文件: /etc/chrony/chrony.conf(Ubuntu 默认自带官方源池,按需调整后 restart)
server 172.30.30.1 iburst          # 内网自建源:优先指向公司内 NTP,少绕公网
makestep 1.0 3                     # 仅前 3 次测量中偏差超 1 秒,才允许步跳
rtcsync                            # 定期把系统时钟同步回硬件 RTC

makestep 1.0 3 的语义逐字拆开:只在前 3 次测量里生效,偏差超过 1 秒就 step。翻译成人话:开机时容许大力拨针,跑起来以后只许调速------一台刚启动、钟差了几分钟的机器,等 slew 追平要以几十分钟计。

运行中的机器被 step 一下会怎样?cron 任务重复执行或整体跳过;rate() 类指标出现假尖刺;业务里所有拿墙钟相减的代码集体出幻觉。所以纪律是:跳变尽量限定在启动时;运行中的节点要拨钟,先摘流量。

slew 什么时候算追平了?chronyc tracking 里的 System time : 0.000xxx seconds fast of NTP time 就是进度条------「快了多少、正在追」。

四、chrony 为什么取代了 ntpd

chrony 已经是 RHEL/CentOS 系发行版的默认 NTP 实现;Ubuntu 默认装的是 systemd-timesyncd,chrony 要手动装(apt install chrony,装上后接管时间同步)。虚机场景普遍值得这一换,三个理由。

第一,收敛快:虚机宿主调度抖、网络时断时续,chrony 断网一段时间后重新拿到源,能快速把大偏差拉回来。第二,步进可控:上一节的 makestep 就是证据------把「允许拨针」严格限定在启动初期,策略清晰。第三,它对网络抖动大的源会做估计与降权,烂源少拖累好源【从业者判断】。

反过来,ntpd 在「长期在线、网络平稳的物理机」上并不差,但今天的集群里大部分节点是 VM------虚机的钟生在乱世,守护进程就选收敛快的那个。

五、三连判读:timedatectl、sources、tracking

时钟排障有个固定开场,三步走:先看同步亮没亮,再看上游通不通,最后看差多少。

bash 复制代码
timedatectl                     # 时区/UTC/NTP 同步状态一行看全
# 预期: NTP service: active / System clock synchronized: yes

chronyc sources -v              # 上游源清单,看左列符号
# MS Name/IP address         Stratum Poll Reach LastRx Last sample
# ==============================================================================
# ^* 172.30.30.1                  2      6   377   512   +231us[+250us] +/-  11ms
# ^? 10.0.0.99                    0      8     0     -     +0ns[   +0ns] +/-    0ns

chronyc tracking | grep -E 'System time|Stratum|Leap'
# System time     : 0.000231 seconds fast of NTP time
# Stratum         : 3
# Leap status     : Normal

sources 左列符号是判读核心:

符号 含义
^* 当前选中并已同步的源------必须至少有一个,否则没对上表
^+ 通过筛选的备选源
^- 被合并算法淘汰的候选
^? 不可达/无响应------满屏都是它,就是 NTP 断了

stratum 是这个源离时间基准的跳数,数字越小说明离基准越近、越可信【从业者判断】;本机的 stratum 等于选中源加一。上游选择的实操要点就一条:内网有自建源就指向内网,公网池当兜底------公网源多一跳网络抖动,还依赖你的出口链路活着。

单机判完,还要做节点间对照。土办法最直接:

bash 复制代码
# 三行连跑:本地时刻 → 远端时刻 → 本地时刻
date +%s.%N && ssh cka000002 'date +%s.%N' && date +%s.%N
# 解读: 远端值应落在两次本地采样之间;差值超 0.1s,这台机器的 NTP 大概率出问题了

节点间对照的偏差有两个经验线:超 0.1 秒,NTP 大概率出了问题;超 0.5 秒,明确值得追查。chronyc sourcestats 还能看各源的频偏与抖动估计,上游质量一目了然。

三步的顺序本身就是结论:先问「在跟谁对」,再问「差多少」------没对上源的机器,偏差数字无从谈起。

六、VM 与云的坑:问题往往在宿主机那一层

云上的 VM,时钟通常由宿主虚拟化层提供,guest 的钟跟着宿主走【从业者判断】。这带来两个盲区。

盲区一:共同偏移。十台机器互相对表,个个只差 2 毫秒,整齐地慢 4 分钟------节点间对照完全发现不了。所以第五节的判读对象必须是「对源」,不是「对彼此」;宿主机自己的 NTP 也得有人管,否则全体 guest 一起错,错得还特别一致。

盲区二:快照恢复。VM 从快照拉起,钟停在过去,瞬间偏差几十分钟【从业者判断】。

注意因果链:makestep 的「前 3 次」窗口只在 chronyd 启动后有效------快照恢复若伴随 chronyd 重启,它会在头几次测量里按设计一次性拨正;若 chronyd 只是从快照状态继续跑,窗口早已用掉,只能靠 slew 慢慢追,几十分钟的偏差可能要追几个小时。

这时要主动 chronyc makestep 或重启 chronyd------拨针纪律照旧:先摘流量。无论哪种方式,拨正前后落在这台机器上的日志时间戳都可能错位,事后回看别被骗。

云上时钟问题要向上看一层:先宿主,后快照,再查 guest。

七、监控偏移量,而不是等事故

时钟出事前的信号一直都在,前提是你在看。为什么监控看斜率、不看绝对差?三个理由。

一是绝对差有「正常的大」。不同机型、不同温度、不同 NTP 源,稳态偏差就是几十毫秒量级------阈值设紧了天天误报,设松了形同虚设。危险的不是偏差大,是偏差在变大:NTP 失联、钟开始自由漂移,这是斜率信号。

二是 counter 类指标全靠时间戳算速率。rate() 的分母就是样本时间戳之差,目标端时钟一跳变,速率立刻出现假尖刺或空洞------你会在大盘上看到一次不存在的流量突增,容量排查从此走偏。

三是告警要可归因。斜率突增指向的就是「这台机器的 NTP 刚断」,一眼定位;绝对差告警触发了,你还得先查半天是不是一直如此。

promql 复制代码
# 看偏差(绝对差),再看偏差的变化率(斜率)
max by (instance) (node_timex_offset_seconds)      # 当前与 NTP 源的偏差秒数
deriv(node_timex_offset_seconds[30m])              # 持续增长 = 正在失联漂移
max by (instance) (node_timex_sync_status)         # 0 = 已失联,最直接的硬告警
# 指标来自 node_exporter 的 timex 采集器

运维底线三条:集群所有节点统一 NTP 源;对 sync_status=0 与 offset 斜率持续非零做告警;跳变操作限定在启动时。告警盯「失联」和「正在变大」,不盯「不为零」。

八、反方诚实:钟全对,日志照样不能用时间排

把话说满之前先划边界:统一 NTP、chrony 配好,买到的是「墙钟可信度有一个上界」,买不到因果序。

跨机日志的正确串联方式是 ID,不是时间------trace_id、request_id、Kafka 消息 key 与 offset、etcd revision,它们才是因果序的载体,墙钟只是展示格式。

消息自带的时间戳也要分清语义:Kafka record 的 timestamp 是生产端钟的 CreateTime 还是 broker 钟的 LogAppendTime,含义完全不同。

更进一步:etcd、ZooKeeper 靠日志序(Raft/ZAB)达成线性一致,压根不依赖物理时间------一致性可以从日志序来,但「几点发生的」只能从钟来。时钟管展示与对账,因果要靠 ID 和日志序,两件事别混。

现在就能做的三件事

第一,挑三台机器把三连跑一遍:timedatectl、chronyc sources -v、chronyc tracking。重点确认每台至少有一个 ^*------没有的机器,此刻就在自由漂移。

第二,用 ssh 三连量一次节点间偏差。今天量出来的数字,就是你下次排障时日志时间线里自带的误差棒。

第三,把 sync_status=0 和 deriv(node_timex_offset_seconds[30m]) 持续非零这两条告警补上------二十分钟能配完的事,别等它变成凌晨两点的证书事故。

留一道晒单题:你亲眼见过的最大集群钟差是多少?被时钟坑过最贵的一次,是证书、租约还是日志考古?评论区对个账。

三连判读、节点对表、PromQL 告警的完整版,在我的学习仓库:GitHub 搜 sre-learning-hub。

命令以 Ubuntu(apt 系)为准,RHEL 系替换包管理器即可。

相关推荐
金銀銅鐵1 小时前
[Java] 借助GUI展示class文件的版本号
后端·python·ai编程
马剑威(威哥爱编程)1 小时前
【AI全栈后端12-12】Spring Boot 3.x 到 4.x 迁移实操:Jakarta 11、Jackson 3 与 AI 2.0
java·开发语言·spring boot·后端
是jin奥1 小时前
Ubuntu 访问 Windows 共享目录
linux·运维·ubuntu
Gauss松鼠会1 小时前
【GaussDB】破除gaussdb ugin索引支持中文模糊查询的迷思-字符序
java·运维·服务器·网络·数据库·gaussdb·经验总结
科技象限1 小时前
数据防泄密系统怎么选?看它怎么把几道防线叠成网
运维·安全·网络安全·安企神
Mr_韩2 小时前
子网路由实战:无需公网 IP,异地直接访问家庭内网全部设备
运维·网络·物联网·nas
星恒随风2 小时前
Linux基础IO万字详解:从文件描述符fd到重定向、FILE缓冲机制及MiniShell升级
linux·运维·笔记·学习·状态模式
136096757232 小时前
一台 4 核 8G 已经跑了 6 个站点,我是怎么把第 7 个塞进去的
后端
对象存储与RustFS2 小时前
JuiceFS + 对象存储:把 S3 变成 POSIX 文件系统实测
后端·rust·开源