一、一台机器的时间为什么会自己走偏
服务器的时间看起来是最不需要操心的一件事,但它是所有这些事情的基础:日志时间戳、数据库事务顺序、分布式锁的过期判断、TLS 证书的有效期判断、定时任务的触发、备份文件的命名、CDN 的签名链接、邮件的时间标注。时间一旦偏了,故障的表现会非常分散——你在日志里找不到任何报错,只是觉得「有些东西对不上」。
物理机的主板时钟靠一颗小晶振维持,精度受温度影响,一天偏差几十毫秒到几秒是常态。虚拟机的偏差更夸张,因为虚拟时钟是由宿主机分配的,宿主机一忙、vCPU 被抢占,计时就丢。有些云主机在迁移、快照恢复、暂停后恢复时,时钟会一次性跳很远。而我见过最常见的情况是:机器开着不动时间也准,但只要做过一次整机快照恢复,时间就偏了几分钟甚至几小时,而现场没有任何人注意到。
这篇文章讲清楚 NTP 的同步链路在 Linux 上到底怎么跑、chrony 与 ntpd 该选哪个、偏移到什么程度才需要人工介入、以及那几种「看起来装了 NTP 但时间照样偏」的典型故障。
二、先测:偏差到底有多大
不要先改配置,先量。Linux 上查看时间的三个层次分别是「当前系统时间」「内核维护的时钟状态」「上游服务器可达性」,对应三条命令:
# 1) 系统时间与时区
timedatectl
# 2) 与上游的实际偏差(只查询不改,最安全)
chronyc tracking
# 3) 上游源列表与状态
chronyc sources -v如果用 ntpd,对应的是 ntpq -p 和 ntpstat。其中最该看的是 chronyc tracking 输出里的 System time 那一行,它表示内核时钟与真实时间的偏移量。几十微秒是正常,几百毫秒说明同步在跑但有抖动,超过一秒就值得查,超过一分钟基本可以确定同步没生效。
还有一个更容易被忽略的判断:timedatectl 输出里的 System clock synchronized: yes/no 和 NTP service: active/inactive。前者是「当前时钟是否被判定为已同步」,后者是「同步服务是否在运行」。这两个状态可以同时为「服务在跑、但没同步成功」,而这恰恰是最常见的坏状态。
三、chrony 为什么比 ntpd 更适合现在的服务器
ntpd 是上一个时代的实现,它的工作方式是逐步微调,每次只调整一点点,所以首次同步很慢,通常需要十几分钟才能收敛。chrony 的设计目标不同:它一开始就会用较大步长快速逼近,然后再回到平滑调整。对频繁开关机的虚拟机、云主机、笔记本来说,这个差别非常明显。
Debian 和 Ubuntu 从 9/18 起默认就是 chrony,CentOS 8 起也一样。如果机器上还装着 ntpd,建议换掉,不要两套并存——两套同步程序同时修改同一个时钟,结果就是它们在打架,偏移量来回跳。
# Debian / Ubuntu
apt-get install -y chrony
systemctl disable --now ntp
# 检查是否还有残留服务在抢时钟
systemctl list-units --type=service | grep -iE 'ntp|chrony|timesyncd'
# CentOS / Rocky
dnf install -y chrony
systemctl enable --now chronyd第三条命令很关键。systemd 自带一个 systemd-timesyncd,很多精简镜像默认开的就是它。它和 chrony 功能重叠,如果两个同时启用,timedatectl 会显示 NTP service 为 active,但实际生效的是哪一个并不确定。规则是:一台机器只允许一个时间同步客户端。装了 chrony,就把 systemd-timesyncd 停掉。
四、配置文件:四个参数决定它能不能收敛
chrony 的配置在 /etc/chrony/chrony.conf(Debian 系)或 /etc/chrony.conf(RHEL 系)。默认内容通常已经够用,但有四个参数值得按场景调:
# 上游源:至少 3 个,且尽量分布在不同网络路径上
pool 2.debian.pool.ntp.org iburst
server ntp.aliyun.com iburst
# 首次同步时允许的最大步进(秒)。默认 0 表示前 3 次只允许微调
makestep 1.0 3
# 记录频率:偏移较大时多记录,稳定后减少,省磁盘也省得日志被刷爆
driftfile /var/lib/chrony/chrony.drift
# 允许本机之外的客户端同步(只在内网做时间服务器时打开)
# allow 10.0.0.0/24iburst 的作用是首次连接时快速发一串包,缩短收敛时间,几乎应该无条件加上。makestep 1.0 3 的含义是「在前三次同步中,如果偏移超过 1 秒就直接跳过去」,这是虚拟机场景必须的:机器暂停后恢复,偏移可能是几小时,如果只允许微调,要花几个小时才能追回来,而这段时间里所有依赖时间的逻辑都是错的。
driftfile 是很多人忽略的一环。它记录本机晶振的漂移速率,重启后可以立刻用这个值做补偿,避免每次开机都从零开始收敛。如果这个文件所在目录不可写,chrony 会静默地不记录,表现就是「每次重启后时间都要重新偏一遍」。
五、几种「装了 NTP 但时间照样偏」的典型故障
故障一:防火墙把 UDP 123 挡了。NTP 走 UDP 123 端口,很多安全组的默认策略只放行 TCP。现象是 chrony 服务正常运行、日志里也没有明显报错,但 chronyc sources 显示所有源都是 ?(不可达)。排查用一条命令就够:chronyc sources 的输出里,状态列 ^* 表示当前选中的同步源,^? 表示不可用。
故障二:宿主机与虚拟机的时间源不一致。虚拟化平台一般会提供「虚拟机时间同步」的增强功能,它和客户机内部的 NTP 是同一条时间来源的两个入口。如果两个都开着,客户机的时间就会被两个来源来回拉扯。标准做法是:只在客户机内部跑 chrony,把虚拟化平台的时间同步关掉;或者反过来完全依赖宿主机。二选一,不要两个都要。
故障三:时区对但时间错,被误判成时区问题。这是最浪费时间的形态。date 显示的时间差八小时,第一反应永远是「时区没设对」,于是一顿改 /etc/localtime、改 TZ 变量、改 PHP 的 date.timezone,全都没有用,因为真正的问题是 UTC 时间本身错了。判据很简单:date -u 看 UTC,timedatectl 看 Timezone。如果 UTC 偏了而时区正确,那就是同步问题,不是时区问题。
故障四:容器里的时间。容器共享宿主机内核,容器内看到的时钟就是宿主机的时钟,容器里装 chrony 毫无意义(而且在默认命名空间下会失败)。所以「容器时间不准」这个报障,答案永远在宿主机上。唯一例外是容器读取了宿主机的 /etc/localtime 副本,那个问题表现是时区错,不是时间错。
六、偏移超过多少才需要人工介入
不同的业务对时间的敏感度差别很大,用一张表来定阈值比较实际:
- 纯静态站:偏移在 5 秒内完全无所谓,超过 1 分钟才开始影响日志关联与证书判断。
- 带登录与订单的应用:偏离超过 1 秒,JWT 的
exp校验、签名链接的时效、限流窗口都会出问题,建议阈值设在 200 毫秒。 - 数据库主从:偏离超过 1 秒会直接影响基于时间戳的复制判断与
pt-heartbeat的延迟读数,建议阈值设在 100 毫秒。 - 分布式锁与跨机协作:锁的过期判断依赖时间一致性,超过 1 秒就可能出现「两个节点同时认为自己持锁」,阈值同样建议 200 毫秒以内。
阈值定了就要能报警。chrony 提供了一个很实用的机制,在配置里加一段让它越界时执行动作:
# /etc/chrony/chrony.conf 末尾
logdir /var/log/chrony
log tracking measurements statistics
# 每天写一条采样,方便事后回看偏移趋势
配合 chronyc tracking 的解析脚本,写一个每十分钟跑一次的检查:
#!/bin/sh
# 偏移超过 200 毫秒就输出一行,交给 cron 邮件或告警平台
OFF=$(chronyc tracking | awk '/System time/ {print $4}')
[ -z "$OFF" ] && OFF=0
# 用 awk 做浮点比较,sh 本身不支持
awk -v o="$OFF" 'BEGIN{ if (o < 0) o = -o; if (o > 0.2) { print "clock offset too large:", o; exit 1 } }'这个脚本里有两个容易被写错的地方:chronyc tracking 输出的偏移单位是秒,而且可能是负数,所以必须先取绝对值再比较;sh 没有浮点比较能力,必须交给 awk。这两点写错就会出现「实际偏了一分钟但脚本一直不报」的情况,而你还以为自己有监控。
七、时间同步与其它系统的连带关系
时间准了之后,很多原本看起来互不相关的问题会一起消失。最典型的是日志排查:多台机器的日志做关联分析时,如果时钟差几十秒,「先后顺序」就完全错了,你会看到「请求返回之后才收到请求」这种荒唐的结论。其次是证书:客户端校验服务端证书时会检查本机时间是否在有效期内,机器时间快了半年,会报「证书尚未生效」,而时间慢了半年会报「证书已过期」,报错文本还完全不同,容易误判成两个问题。
再一个是备份与定时任务。如果机器时钟被快照恢复带回过去,cron 会认为「这个任务已经跑过了」,于是当天的备份直接跳过;反向如果时间被推到未来,任务会在一个小时内反复触发。本站之前就见过「备份文件缺了一天」的事故,最后发现是机器时间被恢复到了更早的日期。
八、日常维护的三条固定动作
第一,把时间同步纳入每次新机上线的检查清单,而且要在装业务之前做完。装完站点再回头发现时间偏了几分钟,你还要逐个校正日志、证书、缓存,成本高得多。
第二,每周看一眼 chronyc sources 的状态列。^* 是正常,出现 ^? 或者全是 ^+ 而没有 ^*,说明选不出主源,值得花五分钟看下网络或上游不可达。这一步可以写进周检脚本,和其他健康检查一起做。
第三,虚拟机做快照恢复之后,立刻执行一次 chronyc makestep 强制跳步。这是一个显式命令,比等 chrony 自己收敛快得多,也避免「恢复了几小时后才发现时间不对」。记住这条之后,虚拟机恢复流程就完整了。
九、小结
时间同步这件事的难点不在配置,而在于「偏了以后不容易被发现」。所以正确的做法是:先量化偏差,选对工具(chrony,且保证只有一套在跑),把 makestep 和 driftfile 配好,再用一个简单的脚本把偏移量变成可报警的数字。做到这四件事,时间这一层就基本不会再给你添麻烦。