ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

NTP时间同步原理与生产环境高可用配置实战

NTP时间同步原理与生产环境高可用配置实战

凌晨两点被电话叫醒,线上数据库主从复制断了。切到备库顶了一阵子,第二天查日志的时候才发现,主备两个实例记录同一个事务的时间竟然差了47秒。就是这47秒,让同步线程在判断日志顺序时直接放弃,硬生生把一套高可用架构玩成了故障演练。

那次之后,我对服务器时间同步的态度彻底变了。NTP这东西,平时没人注意,一旦出问题,排查成本高得离谱。日志乱序、证书校验失败、分布式事务锁死、定时任务错乱,甚至监控告警曲线失真,背后都可能只是“系统时间没对准”。这篇文章我想把NTP协议从原理到实操、从单机配置到生产高可用设计完整拆一遍,也把我踩过的坑和排障思路一并整理出来,希望对正在做运维、后端、SRE的同学有帮助。

1. 时间不同步的代价,远比你想象的大

先别急着看配置文件,我们先把“为什么需要NTP”这个问题说透。计算机里的时间来源有两层:硬件时钟(RTC/CMOS)和操作系统维护的软件时钟。硬件时钟靠电池供电,断电了也能走,但精度很差,一天漂移几秒很正常;系统时钟由内核维护,精度相对高,但每次开机都是从硬件时钟读初值,所以同样不准。

如果一台孤立的个人电脑时间不准,最多就是闹钟叫你早起一小时。但在分布式系统里,时间不一致是灾难性的。举个最常见的例子:主从数据库复制。备库拿到主库的binlog之后,要通过时间戳来判断日志执行的先后顺序,如果主备时间差太多,同步线程会认为日志乱序,直接报错停摆。再比如微服务调用链追踪,A服务调用B服务,A记的“开始时间”比B记的“结束时间”还晚,你查全链路耗时的时候看到的全是负数,这还怎么定位性能问题?

还有更隐蔽的:HTTPS证书校验。证书的生效时间和过期时间都依赖系统时钟,如果服务器时间倒退到证书生效之前,TLS握手会直接失败,用户访问你的站点会看到“连接不安全”的大红页。Kubernetes集群也是这样,节点的时钟不同步,Pod调度、kubelet证书轮换都会出问题。定时任务更不用说,cron表达式里写着“每天凌晨2点执行”,有的机器执行了,有的机器没执行,因为它们的“凌晨2点”根本不是同一个时刻。

NTP要解决的就是这件事:让网络中的设备与一个可靠、统一的时间源持续校准,把系统时间的误差控制在毫秒级甚至微秒级。NTP全称是Network Time Protocol,基于UDP协议工作,默认端口123。它不只是在启动时对一次时间,而是在设备运行期间持续地、周期性地修正时间漂移,同时通过复杂的算法抵抗网络延迟和异常数据源的影响。

这篇文章适合谁看?如果你是运维工程师,你需要掌握服务端和客户端的部署配置;如果你是后端开发,你需要理解分布式系统里时间一致性的底层逻辑;如果你是刚入行的新手,跟着阅读把原理和实操过一遍,以后排查问题会少走很多弯路。

2. NTP协议的工作原理:把“对表”做到极致的协议设计

很多工程师会用NTP,但说不清楚NTP为什么能把时间校得那么准。这部分我把原理拆开讲。

2.1 分层的时间源体系

NTP采用分层模型,英文叫stratum,层级从0到15。这个设计有点像现实社会里的标准时间传递体系:

  • Stratum 0:原子钟、GPS授时接收机、无线电授时系统这些高精度时间源。它们本身不直接对外提供NTP服务,只是作为“基准钟”。
  • Stratum 1:直接连接Stratum 0时间源的NTP服务器,相当于“一级授时中心”,精度最高。
  • Stratum 2:通过网络从Stratum 1服务器同步时间的服务器,精度低一级。
  • Stratum 3、4……依此类推,层级每增加一级,误差就会累积一点。

为什么分层?两个原因。第一是保护时间源。如果成千上万的客户端都直接请求一台Stratum 1服务器,它肯定扛不住,分层可以分散压力。第二是容错。某一层挂了,下一层还能从其他上游继续同步。

层级还有一个作用就是防止循环同步。NTP协议规定,一台stratum N的服务器只能向stratum N-1或更高级别的时间源同步,如果发现上游的stratum比自己还高,就会拒绝同步。客户端在选时间源的时候,也倾向于选择stratum更低的服务器。

这里要特别提一下:stratum 16表示“不可用”状态。如果一台NTP服务器长时间无法从上游同步,它的stratum会逐渐增加,最终变成16,此时客户端会把它标记为不可达,转而去选其他可用源。这个机制保证了整个时间同步网络不会因为某个节点失联而整体瘫痪。

2.2 四个时间戳消除网络延迟误差

NTP校准时间的核心思想,其实和我们日常“对表”很像。两个人想校准手表,A问B“你那边几点了”,B回答之后A再结合自己看到答案的时间,就能估算出双方手表的偏差。只不过NTP把这个过程用四个时间戳精确地建模了。

假设有一个客户端C和一个服务器S:

  • t1:客户端发送NTP请求报文时的本地时间。
  • t2:服务器收到请求报文时的本地时间。
  • t3:服务器发出响应报文时的本地时间。
  • t4:客户端收到响应报文时的本地时间。

网络传输是需要时间的。客户端从发出请求到收到响应,一共花费了t4 - t1,这段总耗时减去服务器在内部处理报文的时间t3 - t2,就是网络上往返消耗的总时间,也就是NTP里说的往返延迟delay:

delay = (t4 - t1) - (t3 - t2)

但要算出客户端和服务器之间的时间偏差,不能只靠延迟,还需要看四个时间戳的交叉关系。常用的偏差计算公式是:

offset = ((t2 - t1) + (t3 - t4)) / 2

为什么这么算?这里有一个隐藏假设:网络上行和下行的延迟大致对称。也就是说,请求报文从客户端到服务器所花的时间,约等于响应报文从服务器回客户端所花的时间,都是总延迟的一半。在这个前提下,t2 - t1约等于“服务器接收时刻相对于客户端发送时刻的偏差 + 单程延迟”,t3 - t4约等于“服务器发送时刻相对于客户端接收时刻的偏差 - 单程延迟”,两者相加,单程延迟被抵消,除以2就得到了相对时间差。

我找一个具体数字走一遍。假设客户端本地时间是1000.000秒时发出请求,服务器收到时记录的本地时间是1000.100秒,服务器回复时刻是1000.200秒,客户端收到响应时本地时间是1000.350秒。那么:

offset = ((1000.100 - 1000.000) + (1000.200 - 1000.350)) / 2 = (0.100 - 0.150) / 2 = -0.025 秒 delay = (1000.350 - 1000.000) - (1000.200 - 1000.100) = 0.350 - 0.100 = 0.250 秒

offset为-25毫秒,意思是客户端的本地时钟比服务器快了25毫秒,需要往后调25毫秒左右。

当然,实际网络环境不可能做到上下行完全对称,单次计算出来的偏差会有噪声。所以NTP不会拿一次采样的结果直接改系统时间,而是会连续多次采样,再用过滤算法、选择算法把异常值剔除掉,最终估算出一个相对可信的偏差。过滤算法会综合考虑最近几次采样的延迟和偏差,给偏差小、延迟低的样本更高的权重。

这里我要强调一个容易忽略的概念:NTP调整时间有两种方式,step和slewing(微调)。step是直接把时间跳变到目标值,slewing则是通过减慢或加快系统时钟的滴答速率,在一段时间内逐渐把偏差抹平。为什么生产环境要尽量避免step?因为时间突然往前跳,会导致定时任务重复触发、日志时间戳错乱,甚至让数据库在写事务时产生不可预知的行为。NTP默认的策略是:偏差较小时用微调,偏差很大(比如超过128ms)且刚启动时,才允许step。chrony里makestep指令就是用来控制这个策略的。

2.3 UTC、时区和闰秒:三个容易混淆的概念

讲NTP之前必须把这些时间概念理清楚,否则配置的时候很容易被“服务器时间显示为UTC还是CST”这个问题绕晕。

  • UTC:协调世界时,是原子时和天文时折中后的全球统一标准时间。它不随地理位置变化,是所有系统同步的“基准坐标”。
  • 时区:是人为划分的显示规则。北京时间是UTC+8,东京时间是UTC+9。时区只影响人类阅读,底层系统时间通常都存成UTC或Unix时间戳。
  • 闰秒:因为地球自转速度不均匀,UTC偶尔会和基于地球自转的天文时间(UT1)产生偏差,国际机构会不定期在6月30日或12月31日的最后一分钟插入或删除1秒。这个“多出来的1秒”就是闰秒。

NTP同步的是UTC,不是“某个时区的墙上时间”。你在配置文件的server参数里填的地址,代表的是“能提供标准UTC时间的服务器”。系统层面记录的是从1970年1月1日0时0分0秒到现在的秒数,也就是Unix时间戳,它天然与时区无关。date命令显示成CST还是UTC,只是展示层的事,不会影响NTP的同步逻辑。

闰秒在生产环境里是个大麻烦。早期很多程序假设一分钟恒定60秒,闰秒出现时,系统时间会出现“23:59:60”这种不存在的时间,或者一秒内时间重复,导致一些高并发的交易系统处理异常。所以现代操作系统和NTP实现普遍采用“闰秒涂抹”(leap smear)策略:把多出的1秒分散到当天很长一段时间里,让系统时间平滑过渡,而不是真的在那一瞬间跳变。普通业务不用太关注这个,但如果你在写金融、日志、调度类系统,最好明确知道NTP服务端对闰秒的处理方式。

3. 手把手搭建一套NTP同步链路

原理讲完,进入实操。这一节带你在一组Linux服务器上把NTP服务完整搭起来,包含服务端和客户端。所有配置都以现代Linux发行版和chrony为主,因为chrony已经成为多数主流发行版的默认NTP实现,性能和可观测性都优于老牌ntpd。

3.1 选型:chrony还是ntpd?

先说结论:新环境无脑选chrony,除非你维护的是必须沿用旧配置的老系统。看到下面这张对比表,你就明白为什么了。

维度chronyntpdsystemd-timesyncd
定位完整NTP服务端+客户端完整NTP服务端+客户端简易客户端
启动同步速度快,秒级达到可用状态慢,需要等多次采样收敛较快
对网络抖动适应性强,采样子系统更完善一般一般
支持NTS安全认证支持(4.0+)不支持不支持
是否适合当服务端适合适合不适合
是否适合临时校准适合适合不适合

chrony还有一个明显优势:它能在网络不稳定的情况下持续跟踪时间漂移,并在网络恢复后快速重新同步,特别适合虚拟机、云主机和经常休眠的工作站。ntpd则是“老前辈”,大量存量文档和脚本都是针对它写的,如果你接手的是老环境,还是得懂。systemd-timesyncd内置在很多发行版里,默认只做客户端同步,功能简单,不推荐在需要对外提供时间服务的场景用。

安装chrony很简单,主流发行版的包管理器都有:

# Debian/Ubuntu apt install chrony -y # RHEL/CentOS/Fedora/Rocky yum install chrony -y dnf install chrony -y # 启动并设置开机自启 systemctl enable --now chronyd

严格来说,chrony的守护进程名字叫chronyd,配置文件在/etc/chrony.conf,管理工具是chronyc。安装完之后先别急着改配置,先确认它已经跑起来:

systemctl status chronyd

3.2 服务端配置:以chrony为例

在机房或者云平台内部,通常要自己维护一到几台NTP服务器,作为内网所有机器的时间源。服务端既要从上游同步时间,也要对内网客户端提供授时服务。

下面是一个典型的服务端配置示例,假设这台服务器的内网IP是198.51.100.11:

# 上游时间源,iburst表示启动后快速连续采样几次 pool 203.0.113.10 iburst pool 203.0.113.11 iburst # 允许哪些网段访问本机NTP服务 allow 10.0.0.0/8 allow 192.168.0.0/16 # 如果上游全部不可达,本机仍然作为低层级时间源对外服务 local stratum 10 # 本机时间偏差超过1秒时,在前三次同步中允许step makestep 1 3 # 将系统时间同步到硬件时钟,防止重启后漂移 rtcsync

逐项解读。pool和server都是用来指定上游时间源的,pool适用于一个域名对应多个IP地址的情况,server用于指定单个地址。iburst非常关键,它让客户端在启动时连续发送多个请求,而不是老老实实等一个轮询周期,这样几秒内就能完成初始校准。

allow是chrony的访问控制指令,只有匹配网段的客户端才能查询时间。这一步至关重要,尤其是当你的服务器暴露在公网上时,限制访问范围能防止被恶意利用。deny all可以放在具体allow之前作为默认拒绝,比如:

deny all allow 10.0.0.0/8

local stratum 10的意思是:当所有上游时间源都不可达时,本机依然可以对外宣称自己是stratum 10的服务器。这个配置是内网NTP的兜底方案,防止上游断连后内网完全失去授时能力。但要记住,它也会带来风险——本机时间如果已经漂移,下游客户端会跟着错,所以只能是“兜底”,不能替代正常的上游同步。

makestep 1 3的解释值得多说两句。第一段1表示阈值秒数,第二段3表示时间窗口次数。含义是:如果当前系统时间与目标时间的偏差超过1秒,并且这是chronyd启动后的前3次时钟更新,那么允许直接step,而不是慢慢微调。这个机制让刚开机时时间偏差较大的机器能快速归位。如果你希望运行期间也允许大幅跳变,可以写成makestep 1 -1,但我强烈不建议这样,生产环境里突发时间跳变是所有中间件最怕的事。

配置改完后重载:

systemctl restart chronyd

同时检查防火墙。NTP走UDP 123端口,服务端必须放行入方向的UDP 123。用firewalld的话:

firewall-cmd --permanent --add-service=ntp firewall-cmd --reload

用ufw的话:

ufw allow 123/udp

提示:曾经有新手把NTP端口开成了TCP 123,结果客户端一直连接失败。NTP使用的是UDP协议,先确认防火墙规则里的协议类型。

3.3 客户端配置:如何正确同步时间

客户端的配置更简洁,一台内网机器只需要把上游指向服务端地址即可:

# 指向内网主时间服务器 server 198.51.100.11 iburst # 指向备用时间服务器(可选) server 198.51.100.12 iburst makestep 1 3 rtcsync

如果你用的是老式ntpd,对应配置是:

server 198.51.100.11 iburst restrict default nomodify notrap nopeer noquery restrict 127.0.0.1

ntpd的restrict配置比较繁琐,这里不展开,你只要知道它能控制查询权限即可。我建议看这篇文章的读者尽量都迁移到chrony,省心很多。

客户端启动验证:

systemctl enable --now chronyd chronyc sources -v chronyc tracking

第一行命令没什么好说的。chronyc sources -v会列出当前时间源的同步状态,重点看前缀符号:

符号含义
*当前正在使用的同步源
+可作为备用的候选源
-被算法标记为不可用/离群
?无法访问的时间源
x该时间源被判定为时间假源,已拒绝

看到^*就说明同步正常,看到^?说明该源不可达,需要检查网络或配置。chronyc tracking会输出更详细的信息,包括当前系统与时间源的偏差(System time)、每轮更新的均方根误差(Root delay)、以及时钟漂移速率(Frequency)等。我通常重点看这两行:

System time : 0.000123456 seconds slow of NTP time Last offset : +0.000023456 seconds

System time接近0就说明同步效果理想。另外还可以用timedatectl确认NTP服务已经激活:

timedatectl

输出中NTP service: active代表系统层面已经启用时间同步。如果你之前手动改过系统时间,建议重启一下chronyd再观察。

3.4 一次性校准与特殊场景处理

有些场景下,比如刚接管一台时间漂移了10分钟的旧服务器,直接启动chrony也不会立刻把时间拉回来,因为makestep 1 3规定了只有启动后前三次更新允许step,而实际上chrony可能需要几分钟才能完成前几次轮询。

这种时候可以手动触发一次立即校准:

chronyc makestep

这条命令会强制把系统时间跳到当前同步源的时间。执行之前请务必确认这是业务低峰期,因为时间跳变会影响正在运行的事务。

如果你只是临时想查一下某台NTP服务器的时间而不修改本机时钟,可以用chronyc的查询模式:

chronyc -n sources 198.51.100.11

或者用老牌命令ntpdate的查询模式:

ntpdate -q 198.51.100.11

注意,ntpdate -q只做查询,不会改时间。如果去掉-q,它会直接同步时间。生产环境不建议用ntpdate做日常同步,它一次性拉齐时间,完全忽略微调策略,对运行中的业务有风险。我见过有人把ntpdate写进crontab每10分钟跑一次,后来时间确实准了,但监控图上一片锯齿状跳变。正确做法是让chronyd常驻微调。

4. 生产环境里把NTP做成高可用基础设施

客户端的同步链路搭起来之后,很多团队的NTP工作就算“完成”了。但如果你负责的是一个稍具规模的基础设施,哪怕只有几十台服务器,也应该把时间同步当成一个高可用服务来设计。这一节讲生产环境的架构考量。

4.1 单点时间源的坑

很多小团队的习惯是:在内网放一台NTP服务器,所有机器都指向它。这个方案看起来简单,但问题也非常典型。

第一,单点故障。这台NTP服务器一旦宕机,内网所有机器失去时间源。好消息是客户端不会立刻崩溃,它会在本地继续走,但时间漂移开始悄悄累积。一天两天可能看不出问题,等某台机器时间偏了十几秒时,数据库复制、缓存过期、调用链追踪都会开始出现诡异的错误,而且排查方向很容易被误导。

第二,上游故障传递。如果这台NTP服务器自身配置的上游地址不可达,而你又没配置local stratum,它会把自身stratum提升到16并停止向外授时,等于整个内网时间服务“静默失联”。

第三,性能瓶颈。几百台客户端同时轮询一台服务器,虽然NTP报文很小,但单机连接数和处理能力总是有上限的,一旦超负荷,响应延迟变大,同步精度反而下降。

所以不管规模多大,至少配置两台NTP服务器。一台主授时,一台备用,客户端把两个地址都写上:

server 198.51.100.11 iburst server 198.51.100.12 iburst

客户端会同时向两台服务器采样,然后根据算法选出更可信的那个作为当前同步源。

4.2 多时间源、层级与防环设计

生产环境里常见的NTP架构是这样一层套一层的:

  1. 边界层:2到4台NTP服务器直接同步公共NTP地址池(或云平台提供的内网NTP服务),同时向内网其他机器提供授时。这些服务器之间也要相互监视。
  2. 汇聚层:规模较大的机房或VPC内,可以在每个子网放一台NTP中继服务器,上游指向边界层的服务器。这样边界层压力被分摊,跨机房的同步链路也更加清晰。
  3. 接入层:所有业务服务器直接指向本机房的NTP中继服务器。

这个设计背后的原则是控制同步路径的层数和fan-out。分层太多会累积误差,fan-out太大则容易压垮上层。

还有一个必须规避的问题:环路。如果A服务器把B当作上游,B把C当作上游,C又把A当作上游,形成一个环,这三台机器的stratum会交替增长,最终互相找不到可用源。chrony自身内置了一部分防环逻辑(stratum等级不高于自身时不会采用该源),但配置时仍然要人工检查,避免把上下游关系写反。

断网场景也要考虑。如果整个内网与公网断开,边界NTP服务器依然要能对内继续授时,这就是前面配置local stratum 10的用途。但是要特别注意两级及以上中继的stratum:边界层local stratum 10,中继层stratum会变成11,如果中继层也配置local stratum 10,就会产生层级回退甚至环路。正确的做法是只有边界层设置local,中继层一律不加。

4.3 安全加固与监控告警

NTP协议本身是明文的,存在两种主要安全风险。

第一种是NTP反射放大攻击。早期的ntpd版本支持一个叫monlist的查询命令,攻击者可以用伪造源地址的方式向NTP服务器发送小请求,服务器返回大量监控数据到受害者IP,形成流量放大。要避免这个问题:一是使用chrony或及时升级ntpd;二是在NTP服务器上严格控制访问来源,不要让公网任意IP都能查询。

chrony里默认不开放monlist,访问控制靠allow和deny组合。如果你的NTP服务器有公网IP,尤其要注意不要裸奔:

# 禁止所有来源,然后只放行可信网段 deny all allow 10.0.0.0/8 allow 192.168.0.0/16

第二种是时间源欺骗/篡改。如果攻击者能伪造NTP响应,就能让服务器时间跳到任意时刻,从而影响依赖时间的业务逻辑,甚至可能导致某些鉴权机制失效。在不可信网络上,可以启用NTS(Network Time Security)认证。chrony 4.0以上版本支持NTS,通过TLS获取加密密钥,之后NTP报文可以验签,防止中间人篡改。虽然目前公共NTP服务全面支持NTS的还不多,但在内网自建时间源时完全可以用对称密钥认证。配置方式是在客户端和服务端同时设置:

# 服务端 /etc/chrony.conf keyfile /etc/chrony.keys authkey 1
# 客户端 /etc/chrony.conf keyfile /etc/chrony.keys server 198.51.100.11 iburst key 1

监控方面,时间同步必须纳入统一的告警体系。你不需要每台机器都盯着,但至少要满足这几个条件:

  • NTP服务进程存活。
  • 当前存在可用的同步源(chronyc sources里有*前缀的源)。
  • 系统时间与同步源的偏差在合理范围内(建议告警阈值为偏移绝对值超过100毫秒,金融类业务可以更严格)。
  • 同步源长期不可达或者轮询跳变频率异常时要告警。

node_exporter本来就暴露了NTP相关指标,比如node_timex_offset_seconds和node_ntp_sync,接入Prometheus后一条规则就搞定:

- alert: NtpOffsetHigh expr: abs(node_timex_offset_seconds) > 0.1 for: 5m labels: severity: warning annotations: summary: "时间偏移过大"

5. 排障实录:我在线上踩过的NTP坑

配置写对不难,真正考验人的是出了问题之后怎么快速定位。这一节我把实际工作中遇到过的典型NTP故障和排查思路整理成一份速查手册。

5.1 服务器时间突然倒退或大幅跳变

现象:某一台机器上的业务在某个时间点突然大面积报错,登录服务器一看,系统时间比标准时间倒退了好几分钟。

这种问题我第一个怀疑对象就是“是否有人手动改过时间”。排查顺序:

  1. 查shell历史。history | grep -E "date|ntpdate|chronyc",看看有没有人执行过手动时间设置。
  2. 查系统日志。journalctl里通常会记录时间变更相关事件,比如“Time has been changed”。
  3. 确认是否与其他时间同步工具冲突。有些云主机的镜像里预装了systemd-timesyncd,如果你又装了chrony,两者会争抢时钟源,导致时间反复跳动。这种情况要先停掉其中一个:
systemctl stop systemd-timesyncd systemctl mask systemd-timesyncd

看到没,mask很关键,只stop不够,系统可能把它重新拉起来。同样,如果你用的是ntpd和chrony并存的老环境,也要只保留一个。

  1. 检查是否有虚拟化平台或云厂商agent在调整时间。比如某些虚拟化工具内置了“客户机时间同步”功能,它和guest内部NTP同时启用会产生冲突。建议要么靠宿主机,要么靠guest自身NTP,不要两个都开。

如果是chronyd因为某种原因执行了step,chronyc tracking的Last offset会显示一个很大的值。你可以用chronyc的子命令查看详细的刷新记录。遇到这类跳变,最重要的是先恢复业务,然后再追根因,不要为了“修正时间”又手动踩一脚。

5.2 内网NTP同步失败

现象:客户端启动后,chronyc sources -v显示所有源都是^?,同步永远不成功。

这是最最常见的故障形态,排查从下往上走:

排查项命令说明
服务状态systemctl status chronyd是否active,有没有报错
端口监听ss -ulpn | grep 123服务端必须在UDP 123上监听
网络连通性ping 198.51.100.11确认内网可达
防火墙iptables -L -n/firewall-cmd --list-allUDP 123是否放行
上游状态chronyc sources -v查看每个源返回的状态

有一个隐蔽问题我之前遇到过:云平台的安全组。每台云主机有两层防火墙,一层是操作系统内部的iptables/firewalld,另一层是云平台控制台上的安全组规则。你改完了系统防火墙,却忘了安全组只放行了TCP端口,UDP 123照样过不来。所以排查时先确认安全组的“入方向规则”里有没有UDP 123,再查系统防火墙。

还有一个是源地址配置错误。比如上游时间服务器本身只监听在内网网卡上,客户端却用一个外部IP去访问,请求打过去立刻被路由丢弃。用ss -ulpn看一下服务端实际监听地址,客户端配置的server地址必须和它匹配。

如果你用的是ntpd,还可以用ntpq -pn查看状态。第一列的时间源地址前如果有*表示当前同步源,没有则说明还没建立同步。ntpq -pn输出里remote一栏的177.16.16.16之类表示stratum 16,也就是不可用状态。

5.3 云虚拟机时钟漂移剧烈

现象:chrony显示已经同步成功,但几分钟后系统时间又偏了,偏移量增长得特别快,一天能差好几秒甚至更多。

这个问题在云主机和虚拟化环境里特别常见。原因在于虚拟机体验的“时间”来自虚拟化层模拟的时钟,而不是真实硬件时钟。常见的半虚拟化时钟设备(比如KVM的kvm-clock)能提供比传统模拟RTC更好的精度,但宿主机的负载波动、VM迁移、快照休眠都可能造成虚拟时钟跳变。

排查思路:先确认当前用的时钟源是什么:

cat /sys/devices/system/clocksource/clocksource0/current_clocksource

如果是tsc,在物理机上通常没问题,在虚拟机上则要看宿主机CPU调度是否稳定;如果是kvm-clock,一般就对了。遇到漂移严重的虚机,可以考虑让chrony的轮询更积极一点,把最小轮询间隔调小:

# 默认是64秒起,可以调整到16秒起 minpoll 4 maxpoll 6

minpoll 4表示最小轮询间隔为2^4=16秒,maxpoll 6表示最大轮询间隔为2^6=64秒。调小轮询间隔能加快对漂移的修正,但也增加了网络请求量,不建议全局都用这个参数,只对漂移严重的机器调整即可。

如果虚拟机是从快照恢复的,时间可能倒退到快照创建时刻。由于日志、数据库时间戳已经往前走了,系统时间倒流是灾难性的。出这类问题的虚拟机,往往不是NTP配置错了,而是虚拟化平台的管理策略问题。处理办法是恢复后立即执行chronyc makestep强制对齐,同时检查平台是否开启了“恢复后自动同步客户机时间”的选项。

5.4 快速排查命令清单

每次排查NTP问题,我基本就是这几条命令组合使用。整理成清单,你可以直接抄作业:

# 1. 查看当前时间和NTP服务状态 timedatectl # 2. 查看同步源状态 chronyc sources -v # 3. 查看详细同步跟踪信息 chronyc tracking # 4. 查看chronyd最近日志 journalctl -u chronyd --since "10 minutes ago" # 5. 查看服务端是否在监听UDP 123 ss -ulpn | grep 123 # 6. 测试源服务器是否可达(老环境用ntpq) chronyc -n sources 198.51.100.11 # 7. 直接抓包看看客户端有没有收到响应 tcpdump -i eth0 udp port 123 -n -c 20

tcpdump那一步是终极大招。抓包能看到客户端是否有请求发出,服务器是否有响应返回,能直接定位到是网络问题还是服务端问题。如果抓包发现请求一直在发但没有任何响应,十有八九是防火墙/安全组把UDP 123丢了。

另外,排障时不要忘了看一眼客户端的本地时间到底差了多少。如果系统时间差了几个小时,chrony默认的同步策略(makestep 1 3)可能不会立即生效,看起来就会像“无法同步”。这种场景我经常用一个快速验证法:手动改正时间,然后重启chronyd,再看同步状态。如果业务允许,也可以用chronyc makestep直接强制拉齐。

前面说的这些坑,我基本都在生产环境里踩过一遍。最后再分享一个我个人的习惯:每上线一批新服务器,第一件事不是装业务环境,而是先把NTP配好并确认同步成功。这个动作只要花两分钟,但它能帮你在后续的日志分析、事务排障、证书检查里省下无数个半夜被叫醒的代价。时间同步不是一个“配好就忘”的静态配置,它和负载均衡、DNS一样,是需要持续监控和维护的基础设施。如果你现在还没把NTP状态接进告警平台,我建议你明天就把它补上。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进