适用场景

适用于运行在 Linux 虚拟机、物理机或容器节点上的 Web 服务。业务表现为同一版本在部分机器上间歇出现登录失效、JWT token is not active / token expired、HTTPS 握手失败或调用云 API 返回签名过期;重试、重启应用有时短暂恢复,但问题会再次出现。

本文以“应用节点时钟慢了 90 秒”为例,给出不依赖猜测的排查和修复流程。时间问题的风险不只在日志顺序:它会直接破坏令牌有效期、TLS 证书有效期和带时间戳的请求签名。

现象与影响范围

常见告警或日志包括:

JWT validation failed: token is not active
x509: certificate has expired or is not yet valid
RequestTimeTooSkewed: the difference between the request time and the current time is too large

先确认是否具有“按节点聚集”的特征。若同一用户请求落到 A 节点成功、落到 B 节点失败,且 B 的日志时间比监控、数据库或其他节点明显靠前或滞后,优先排查时钟,而不是先扩大 JWT 容忍窗口。

排查步骤

1. 同时采集本机时间、时区和同步状态

在故障节点执行:

date --iso-8601=ns
timedatectl status
chronyc tracking
chronyc sources -v

重点阅读以下字段:

  • System clock synchronized: yes 表示 systemd 已观察到同步状态;no 需要继续定位。
  • Leap status: Normal 表示 chrony 认为时间源可用;Not synchronised 不能只靠重启应用解决。
  • Last offset 是最近一次测得偏差,System time 是当前估计偏差。生产环境通常应控制在毫秒到几十毫秒级,具体阈值须与业务签名和监控精度匹配。
  • chronyc sources -v 中以 ^* 标记的来源为当前选中的时间源;只有 ^?^x 或来源数量不足时,应检查网络、DNS、NTP 防火墙策略和上游服务。

不要把 date 与手机时间对比作为唯一证据。应在同一时刻从至少两台健康节点和一个可信时间源交叉比对,并保留命令输出与 UTC 时间。

2. 判断是持续漂移、启动未同步还是虚拟化问题

journalctl -u chronyd --since '2 hours ago' --no-pager
systemctl status chronyd
grep -E '^(server|pool|makestep|rtcsync)' /etc/chrony.conf /etc/chrony.d/*.conf 2>/dev/null

可按证据分类:

  1. 服务刚启动就接收流量,且 chronyc tracking 未同步:启动顺序或就绪检查有问题。
  2. 偏差以稳定速度持续扩大:宿主机时钟、虚拟机时间同步或硬件时钟质量异常。
  3. 偏差突然跳变:检查虚拟机迁移、暂停恢复、快照回滚和手工执行 date -s 的审计记录。
  4. 只有部分时段失步:检查 UDP 123 是否被防火墙拦截、NTP 域名解析是否不稳定,以及时间源是否被错误限速。

容器内通常使用宿主机时钟。不要在普通业务容器中运行第二个 NTP 客户端;应修复节点时钟,再滚动重建受影响 Pod。

3. 量化与健康节点的差值

若允许从故障节点访问一台受管健康节点,可临时执行:

ssh ops@healthy-node 'date -u +%s.%N'
date -u +%s.%N

两条命令之间包含网络往返时间,因此只能用于确认明显偏差,不能用于毫秒级定标。精确判断以 chrony 的多源测量和监控中的 node_timex_offset_seconds 为准。

修复方案

1. 让 chrony 使用多个受控时间源

以下是 /etc/chrony.conf 的示例;时间源地址应替换为组织批准的 NTP 服务:

pool ntp1.example.internal iburst
pool ntp2.example.internal iburst
pool ntp3.example.internal iburst

# 启动初期偏差过大时允许校正,避免长时间带着错误时间提供服务。
makestep 1.0 3
rtcsync

iburst 会在启动时快速发起测量;makestep 1.0 3 仅允许前 3 次更新中、偏差超过 1 秒时跳变校时。不要在高并发业务运行中随意执行大幅跳时:倒退或前跳都会影响定时任务、缓存过期和日志排序。若偏差很大,应先摘流量,再校时并验证。

修改后执行:

sudo systemctl restart chronyd
sleep 5
chronyc tracking
chronyc sources -v

Leap status 仍不是 Normal,不要宣布修复;继续检查 NTP 网络路径和上游时间源健康。

2. 阻止未同步节点接收业务流量

在 systemd 服务中把时间同步作为启动前置条件:

[Unit]
Wants=network-online.target time-sync.target
After=network-online.target time-sync.target

[Service]
ExecStartPre=/usr/bin/timedatectl show --property=NTPSynchronized --value

注意:上面的 ExecStartPre 只会打印状态,不能单独作为拦截条件。更可靠的做法是在部署脚本或健康检查中显式判断返回值,例如:

timedatectl show --property=NTPSynchronized --value | grep -qx yes

命令返回非零时让发布失败,并将节点保持在负载均衡器摘除状态。Kubernetes 场景可通过节点启动脚本保证 chrony 就绪,并用 readiness probe 延迟业务接流;不要让 probe 直接修改系统时间。

验证与回归

修复完成后至少确认:

  1. chronyc tracking 显示 Leap status: Normal,并有选中的 ^* 时间源。
  2. 连续观察 10 至 30 分钟,偏差未持续扩大。
  3. 故障节点上的 JWT 校验、TLS 调用和带签名的 API 请求恢复正常;用真实请求而非只看 chrony 服务状态验证。
  4. 对比应用日志、访问日志和监控时间线,事件顺序不再出现跨节点倒置。

预防措施

  • 将 chrony 配置纳入镜像或配置管理,至少配置三个受控时间源,并定期演练单一时间源不可用。
  • 采集 node_timex_offset_seconds、同步状态和 NTP 源可达性;偏差超过业务阈值时告警,并按节点维度聚合。
  • 在发布、扩容和虚拟机恢复流程中加入“时间已同步”门禁,避免刚恢复的节点立刻接流量。
  • JWT 的时钟容忍只应作为数秒级网络抖动的缓冲,不能替代时钟治理;过大的容忍窗口会扩大令牌可被重放的时间。
  • 记录时钟跳变、虚拟机迁移和宿主机维护事件,事故复盘时将其与认证失败、证书告警关联分析。

总结

当认证或 TLS 故障只集中在少数节点时,先用 chrony 的同步状态和偏差数据建立证据,再决定是否调整应用配置。可靠的治理路径是:多时间源同步、未同步节点不接流量、偏差可观测、异常可回滚。这样既能恢复当下请求,也能避免同类问题在扩容或故障恢复时再次出现。