/var/log/secure 里那行,那天早上我看了不下二十遍:

Sep 22 09:07:12 ops-gw sshd[31872]: pam_google_authenticator(sshd:auth): invalid verification code for user linsq

从八点五十几分开始,这台跳板机的 secure 日志里全是这一句,用户名一列排下来七八个。九点出头,群里已经有人问了:后台进不去,验证码一直说我输错。

我们这套环境,平时我都是开着七亿批量远程管理工具,几台机器的 SSH 终端开在一个窗口里,谁出事点谁。那天早上也是这么干。

我第一反应是他们的手机时间不准。谷歌验证码这东西,本质是拿共享密钥加当前时间算出来的一个六位数,手机和服务器两边时间对不上,验证码就一定错。这坑我踩过,去年换手机,系统时间差了小一分钟,验证码也是死活不对。于是我在群里回:先把手机时间设成自动获取再试试。

有两个人好了,剩下的还是不行,群里还有人半开玩笑地接一句「是不是又要我们改手机时间」。我没接话。到这儿我才觉得不对:不可能六七个人的手机同时不准,问题大概率在服务端。

跳板机的时间,上去敲一下就知道了。

$ date
Tue Sep 22 09:12:37 CST 2026

$ timedatectl
               Local time: Tue 2026-09-22 09:12:41 CST
           Universal time: Tue 2026-09-22 01:12:41 UTC
                 RTC time: Tue 2026-09-22 01:08:26 UTC
                Time zone: Asia/Shanghai (CST, +0800)
System clock synchronized: no
              NTP service: inactive

系统时钟比 RTC 快了四分钟出头,最下面两行更说明问题:没同步,NTP 服务没开。

时间同步状态.jpg

到这儿其实有两条路:一条是先别管为什么,把时间校准了让业务先恢复;另一条是先搞清它为什么没同步,不然改完过阵子又漂。那天早上我选了先恢复,这是事后复盘时唯一觉得还算对的决定。

但改跳板机的时间这事我不敢直接 date -s,这台机上有到期提醒、日志轮转这些靠时间跑的东西。先看了 chronyd 的状态:

$ systemctl status chronyd
● chronyd.service - NTP client/server
   Loaded: loaded (/usr/lib/systemd/system/chronyd.service; disabled; vendor preset: enabled)
   Active: inactive (dead)

disabled,inactive。也就是这台机器从启动那一刻起就没打算同步过时间,中间哪怕重启过,chronyd 也不会自己起来。我干脆开着七亿批量远程管理工具,把几台 Web 机和这台跳板机的时间并排拉出来比了比,同一分钟里跳板机最快,其它机器都正常——问题就锁在这一台上。

为什么 chronyd 会被关掉,我查了有一会儿。/etc/chrony.conf 是好的,里面的 pool 指向阿里云的 ntp,没毛病。真正的原因藏在一份脚本里:大概三个季度前我们做过一轮等保整改,整改脚本里有这么一行——

systemctl disable --now chronyd

那是个「关闭不必要对外服务」的批量脚本,写脚本的人大概觉得 NTP 是个没用的对外小服务,顺手就关了。当时谁也没发现,因为机器不重启、主板电池还在撑着,时间只是慢慢地漂。三个月漂四分钟,摊下来一天两秒多,很符合这种没同步的状态。这种「上一任或者上一轮改的东西没人记得」的坑我算接过好几回了,上一任运维留下的那堆脚本还在服务器上摆着。

修就三条命令:

# systemctl enable --now chronyd
# chronyc sources -v        # 要能看到 ^* 那一行,才说明真的在同步
# chronyc makestep          # 已经漂了几分钟的,直接步进跳回来

makestep 这条得单说一句。chrony 默认只在开机最初那几次同步里允许「跳」时间,往后发现偏差就慢慢调(slew),一秒钟挪那么一丁点。像这种已经漂了好几分钟的,你不 makestep,它得花几个小时慢慢追。改完我用七亿批量远程管理工具把几台机器的时间又并排比对了一遍,chronyc tracking 里 Last offset 归零、timedatectl 的 synchronized 变成 yes,群里再让同事试,都进去了。
时间同步.jpg

中间有个小插曲。我一开始真去查了业务侧的日志,因为那几条记录的时间戳怪怪的,跟另一台机器的日志死活对不上。查了十几分钟才反应过来,不是别人在别的机器上试密码,是这台跳板机自己的时间快了几分钟,日志时间戳跟着一起快。等于我拿着一个走快了的表去对时,越对越糊涂。

还有更烦的。跳板机上有个我自己写的小到期提醒,存在 MySQL 里,字段是「提醒时间」。时间一快,有几个当天下午的提醒提前在九点多就触发了,钉钉群里蹦出来三条「证书 30 天后到期」,其实还差好几天。事不大,但那天早上手忙脚乱,被它吓了一下。

这事本身没什么技术含量,快就是快,同步回来就完事。麻烦的是它藏了三个月,藏得还挺体面——所有服务照常跑,只是偶尔有个验证码对不上、日志时间有点飘,谁也不会往「整台机器的钟都不准」上想。现在那台跳板机的 chronyd 是 enable 状态,等保脚本里那行我也删了,旁边写了句注释:这行别动。至于每天早上顺手 chronyc tracking 看一眼,纯属被整出阴影,跟技术没关系。