跳转至

Linux 时间同步实战与排障:chrony 配置模板 + 5 个真实时钟漂移故障

一、时钟漂移为什么是生产隐形杀手

我干了快十年的 Linux 运维,处理过几百起线上事故,其中最让我头大的不是磁盘写满、不是内存 OOM,反而是最不起眼的系统时间。它平时安安静静,一旦出事,症状五花八门,排查方向却常常被带偏。

先说三个我真实踩过的坑,你会发现它们八竿子打不着,根子却是同一个。

1.1 TLS 证书报 "certificate is not yet valid"

有一年公司对外提供 HTTPS 服务,客户反馈浏览器直接红屏,报错 ERR_CERT_DATE_INVALID。我登录服务器一看,证书明明在有效期内,签发时间和到期时间都正常。再 date 一看——这台服务器的系统时间比真实时间快了 5 分多钟。

证书的 notBefore 是真实时间往后推几个月的,服务器时间快 5 分钟,在客户端眼里,当前时间已经越过 notBefore 吗?不会。但反过来的场景更常见:服务器时间慢了。客户端用服务器时间做本地验证,或者反向代理做双向校验,时间一错位,就会在握手阶段报:

openssl s_client -connect api.example.com:443
Verify return code: 9 (certificate is not yet valid)

或者干脆:

error: certificate is not yet valid

Verify return code: 9 对应的就是 X509_V_ERR_NOT_YET_VALID,意思是「证书尚未生效」。这几乎 100% 意味着某台机器的时间比真实时间慢,导致证书看起来还没到生效时刻。

1.2 Kerberos 认证 "Clock skew too great"

另一个高频事故是内部系统用 Kerberos 做 SSO,某天突然批量报:

kinit: krb5_get_credentials: Clock skew too great (37) - PROCESS_TGS

Kerberos 协议本身就是基于时间戳的。客户端向 KDC 请求票据时,会带上自己的时间戳,KDC 校验这个时间戳与自身时间之差是否超过默认 300 秒(5 分钟) 的容差。一旦客户端与域控时间差超过 5 分钟,认证直接拒绝。

有一次我排查一台老旧的日志服务器,发现它的时间比域控慢了整整 40 分钟。原因很讽刺——这台机器 NTP 服务压根没跑,靠 RTC 电池苟着,主板电池快没电了,一断电时间就回退。

1.3 日志时间错乱与监控数据错位

第三类症状最隐蔽:日志和监控数据时间对不上。应用日志时间戳由 A 机生成,采集端打上 B 机的时间,存储端又打上 C 机的时间,三台机器时间各差几秒到几分钟,导致:

  • 故障排查时,日志顺序颠倒,同一请求的日志散落在错误的时间窗;
  • Prometheus 里 rate()、increase() 这类按时间窗计算的函数,在时间戳回跳时产生负数或锯齿;
  • 告警规则的 for: 5m 持续判定逻辑,因时间错位被反复触发或漏触发;
  • 分布式数据库(etcd、Cassandra、TiKV)的线性一致性依赖时间戳,时钟大幅跳变会破坏数据一致性。

结论先行:时间同步不是「锦上添花」,而是所有依赖时间戳的协议、证书、分布式系统、监控告警的地基。地基歪了,上面全塌。

二、60 秒决策:chrony vs ntpd vs systemd-timesyncd

选型之前,先搞清楚手上这台机器在同步链里的角色:是只做客户端,还是要当 NTP 服务端给内网其他机器供时?

特性 chrony ntpd systemd-timesyncd
定位 完整 NTP 客户端+服务端 传统 NTP 客户端+服务端 轻量客户端,只能当客户端
服务端能力 ✅ 可做服务端 ✅ 可做服务端 ❌ 无服务端功能
高延迟/高抖动网络 ✅ 最稳,支持 NTP over Unix socket ⚠️ 容易丢同步源 ⚠️ 依赖网络质量
启动收敛速度 快,秒级可同步 慢,分钟级起步 较快
一步跳变(makestep) ✅ 原生支持 ⚠️ 需 -x 或配置 ✅ 支持
硬件时间戳 ✅ 支持 ✅ 支持 ❌ 不支持
虚拟化环境 ✅ 最佳选择 ⚠️ 在 VM 里容易漂 ✅ 可用
配置文件 /etc/chrony/chrony.conf /etc/ntp.conf /etc/systemd/timesyncd.conf
控制工具 chronyc ntpq timedatectl
默认安装 现代发行版(RHEL8+/Debian10+) 老旧系统 systemd 自带

我的选型结论:

  1. 新装系统一律 chrony。RHEL/CentOS 8 之后、Debian 10 之后、Ubuntu 18.04+ 都默认它。它对高延迟、高抖动的网络(比如跨境、跨运营商)抗性最好,收敛快,还支持一步校正。
  2. 老系统(CentOS 6、RHEL 7 默认装 ntpd) 如果迁移成本高,可以保留 ntpd,但要记得它默认不跳变,只做 slewing(渐进微调),漂移严重时收敛极慢。
  3. 最小化容器、嵌入式、无状态节点 用 systemd-timesyncd 足够,因为它只当客户端。但千万别指望它给内网其他机器供时。

一句话:能上 chrony 就上 chrony,需要做 NTP 服务端必须 chrony 或 ntpd,systemd-timesyncd 永远只能当客户端。

三、前置:确认时区与 RTC

装任何 NTP 软件之前,先确认两件事:时区对不对、硬件时钟(RTC)模式对不对。时区错了,同步得再准,显示出来的时间也是错的。

3.1 timedatectl 三行解读

timedatectl 不带参数,输出三块核心信息,一次看全:

timedatectl

期望输出(以一台阿里云 ECS 为例):

               Local time: 三 2026-10-28 10:15:32 CST
           Universal time: 三 2026-10-28 02:15:32 UTC
                 RTC time: 三 2026-10-28 02:15:32
                Time zone: Asia/Shanghai (CST, +0800)
System clock synchronized: yes
              NTP service: active
          RTC in local TZ: no

逐行解读:

  • Local time / Universal time:本地时间与 UTC 时间。两者相差正是时区偏移,这里 +0800。
  • RTC time:硬件时钟(CMOS/主板电池)当前时间。正常情况应等于 Universal time(UTC)。
  • Time zone:时区,必须是你业务所在时区。
  • System clock synchronized: yes:系统时钟已被同步过。这是关键指标。
  • NTP service: active:NTP 服务是否在跑。看到 inactive 说明同步根本没启动。
  • RTC in local TZ: no:RTC 是否使用本地时区。生产环境必须是 no,让 RTC 跑 UTC,否则跨时区、跨 DST 会乱。

3.2 硬件时钟 vs 系统时钟

Linux 有两个「时钟」:

  • 系统时钟(System Clock / kernel clock):内核维护的软件时钟,精度高,靠 NTP 持续校准,关机就没了。
  • 硬件时钟(RTC / CMOS clock):主板电池供电的时钟,关机后靠电池走时,精度差,开机时被读入作为系统时钟的初始值。

开机流程是:BIOS/UEFI 读 RTC → 内核据此初始化系统时钟 → NTP 接手后持续校准系统时钟 → 定期把校准后的时间写回 RTC(chrony 的 rtcsync 干这事)。

3.3 设置时区与 UTC

生产环境推荐时区用 Asia/Shanghai,RTC 用 UTC。一条命令设置时区:

timedatectl set-timezone Asia/Shanghai

让 RTC 跑 UTC(生产必须这样,别学一些老教程把 RTC 设成本地时区):

timedatectl set-local-rtc 0

set-local-rtc 0 表示 RTC 使用 UTC,timedatectl 里会显示 RTC in local TZ: no。

⚠️ 注意:设置时区后,/etc/localtime 会被指向 /usr/share/zoneinfo/Asia/Shanghai。如果发现手动改过 /etc/localtime(比如直接 cp 了某个 zoneinfo),建议用 timedatectl 规范操作,避免时区数据库和系统时区不一致。

时区数据校验,看本地时间是否与预期一致:

date && timedatectl show -p Timezone --value

期望输出:

三 10月 28 10:15:32 CST 2026
Asia/Shanghai

四、chrony 配置实战:完整可复制模板

4.1 安装

RHEL/CentOS 系:

yum install -y chrony
systemctl enable --now chronyd

Debian/Ubuntu 系:

apt install -y chrony
systemctl enable --now chrony

确认版本:

chronyd --version

期望输出:

chronyd 4.3

4.2 完整 chrony.conf 模板

以下是生产环境可直接复制的 /etc/chrony/chrony.conf(RHEL/Debian 路径略有差异,Debian 是 /etc/chrony/chrony.conf,老版本在 /etc/chrony.conf,以实际为准)。我逐段注释,每项都是实测参数。

# ============================================================
# 生产环境 chrony 配置模板
# 适用:内网 NTP 服务端 + 客户端,CentOS/RHEL 7+/Debian 10+
# 作者:一线运维,实测于 2026-10
# ============================================================

# --- 上游时间源:国内主源 + 备用池 ---
server ntp.aliyun.com iburst
server cn.pool.ntp.org iburst
# 可选备用,避免单点
server ntp.tencentcloud.com iburst
server ntp1.aliyun.com iburst

# --- 漂移文件:chrony 据此估算本地时钟的固有漂移率,自动生成,勿手动改 ---
driftfile /var/lib/chrony/drift

# --- 步进校正策略:前 3 次同步若偏移 > 1 秒则一步跳变,之后渐进 slewing ---
# 关键参数,生产必配,详见第七章
makestep 1.0 3

# --- RTC 同步:chrony 定期把系统时间写回硬件时钟,防关机丢时间 ---
rtcsync

# --- 日志目录:chrony 的统计/测量日志(chronyc tracking 用)---
logdir /var/log/chrony

# --- 允许内网网段作为客户端:按实际网段修改,禁止 0.0.0.0/0 ---
# 只允许内网子网同步,不暴露公网
allow 192.168.0.0/24
allow 10.0.0.0/8

# --- 拒绝其他所有来源(默认拒绝,配合上面 allow 白名单)---
deny all

# --- 当无法到达所有上游源时,仍以本地 stratum 10 对外提供时间 ---
# 防止上游全挂后内网完全失去同步能力(仅内网服务端需要)
local stratum 10

# --- 网络绑定:只监听内网接口,最小化暴露面 ---
# 若有多个网卡,建议显式绑定内网 IP,例如 bindaddress 192.168.1.10
# bindaddress 192.168.1.10

# --- 客户端连接数上限,防资源滥用 ---
maxclients 256

# --- 客户端请求速率限制(单位:每秒),防 NTP 放大攻击 ---
ratelimit interval 4 burst 32

# --- 仅监听 UDP 123(NTP 默认端口),无需改 ---

4.3 关键指令逐条说明

指令 作用 生产建议
server <ip> iburst 指定上游源,iburst 快速突发采样加速首次同步 2-4 个国内源
driftfile 记录时钟漂移率,重启后能快速估算偏移 必配
makestep 1.0 3 前 3 次同步偏移 >1s 则跳变,之后 slewing 生产必配
rtcsync 定期写回硬件时钟 必配,防关机丢时
allow <网段> 允许哪些客户端来同步 只允许内网
deny all 默认拒绝所有 配合 allow 用
local stratum 10 上游全挂时仍提供本地时间 内网服务端配
bindaddress 绑定监听地址 多网卡建议显式
maxclients 并发客户端上限 防资源耗尽
ratelimit 请求速率限制 防 DDoS/放大攻击

4.4 应用配置

改完配置,语法检查 + 重启:

chronyd -t /etc/chrony/chrony.conf
systemctl restart chronyd
systemctl enable chronyd

chronyd -t 是测试模式,会解析配置并打印错误(若有)而不真正启动,适合改配置后先验证。

4.5 放行防火墙

NTP 走 UDP 123,内网服务端需要放行。firewalld:

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

iptables(如果用的 iptables):

iptables -A INPUT -p udp --dport 123 -s 192.168.0.0/24 -j ACCEPT

五、国内网络下的 NTP 源选择

这是国内生产环境最容易被忽略的一环。很多人习惯性抄国外教程写 server 0.pool.ntp.org,结果同步质量极差。我的经验是:在国内机房,选源第一优先级是「地理与网络路径近」,而不是「源本身准不准」。

5.1 推荐源清单

源 类型 延迟实测(华东 BGP 机房) 评价
ntp.aliyun.com 阿里云公共 NTP 2-15 ms 首选,国内多节点,准且快
ntp1.aliyun.com 阿里云备用 2-15 ms 常用备用
cn.pool.ntp.org 中国 NTP 池 5-40 ms 推荐,国内节点池
ntp.tencentcloud.com 腾讯云公共 NTP 3-20 ms 推荐,腾讯内网友好
ntp.tencent.com 腾讯 5-30 ms 可用
ntp.aliyun.com(主)+ cn.pool.ntp.org(备) 组合 — 我的默认组合
time.windows.com 微软 30-80 ms 一般,仅 Windows 用
time.nist.gov 美国 NIST 150-300+ ms,抖动大 不推荐,跨境
pool.ntp.org 全球池 100-200 ms,抖动大 不推荐,跨境
time.google.com Google 跨境被墙/高延迟 不推荐

5.2 为什么坚决不用 time.nist.gov / pool.ntp.org

我在一台华东机房服务器上实测过,对比国内源和国外源:

chronyc sources -v

用 pool.ntp.org 时,典型输出:

===============================================================================
  .-- Source remote  -   |   Stratum ... Reach  Last Rx  |  Offset   Delay  Disp
 ===============================================================================
  ^- 162.159.200.1     |  1         |  12%   20m        |  +38ms   185ms  12ms
  ^* 162.159.200.123   |  1         |  23%   15m        |  -52ms   210ms  20ms

Reach(可达性)只有 12%、23%,说明跨境包大量丢,Delay(延迟)180-210ms,Offset(偏移)几十毫秒来回跳。这种源对 chrony 来说是灾难——它需要稳定的低抖动输入来估算频率误差,跨境源抖动一上来,同步精度根本没法保证。

换 ntp.aliyun.com 后:

===============================================================================
  .-- Source remote  -   |   Stratum ... Reach  Last Rx  |  Offset   Delay  Disp
 ===============================================================================
  ^* 120.25.115.20     |  2         | 100%   1s          |  +0.2ms   6ms    0.3ms
  ^- 182.92.12.11      |  2         | 100%   1s          |  -0.4ms   9ms    0.5ms

Reach 100%,Delay 6-9ms,Offset 0.2ms 级别。这才是国内该有的同步质量。

经验法则:Delay 超过 50ms 或 Reach 经常低于 50%,这个源就该换掉。国内机房用国内源,延迟个位数毫秒才是正常的。

5.3 源选择原则总结

  1. 优先就近:华东选阿里云/腾讯云华东节点,华北选华北节点,优先内网或同运营商。
  2. 至少配 2 个源:一个主源 + 一个备用,避免单点。别只配一个。
  3. 不同运营商各来一个:跨运营商互访质量差,最好选同运营商或 BGP 任播源(阿里云、腾讯云都是 BGP)。
  4. 内网若有更高级别的 NTP 服务器,直接指向它,别让所有服务器都打公网。
  5. 不要用 IP 直接写 server,除非有特殊原因;域名解析失败时 chrony 会重试,问题不大。

六、验证与监控:确认时间真的同步了

配完不是终点,必须验证。很多「配了不生效」的案例,都卡在验证这步。

6.1 chronyc sources -v:看上游源状态

chronyc sources -v

这是最核心的验证命令。关注 Reach(可达性,100% 最好)、Offset(偏移,越小越好)、Delay(延迟)。

6.2 chronyc tracking:看同步详情

chronyc tracking

期望输出(同步正常时):

Reference ID    : C633DC14 (120.51.220.20)
Stratum         : 2
Ref time (UTC)  : Wed Oct 28 02:15:32 2026
System time     : 0.000000123 seconds fast of NTP time
Last offset     : +0.000123456 seconds
RMS offset      : 0.000234567 seconds
Frequency       : 4.567 ppm
Residual freq   : +0.012 ppm
Skew            : 0.234 ppm
Root delay      : 0.005678 seconds
Root dispersion : 0.001234 seconds
Update interval : 64.2 seconds
Leap status     : Normal

关键字段解读:

  • Stratum 2:表示这台机器离权威时间源还有 2 级,正常。stratum 越大,时间越「间接」。
  • System time ... fast/slow of NTP time:当前系统时钟相对 NTP 时间的偏差。这是最关心的指标,正常应在微秒到毫秒级。
  • Last offset / RMS offset:最近一次与平均偏移,越小越好。
  • Frequency ... ppm:本地时钟漂移率,单位 ppm(百万分之一)。几 ppm 正常,若长期漂到几百 ppm,说明主板晶振老化或虚拟化时钟有问题。
  • Leap status: Normal:闰秒状态,Normal 表示正常。

6.3 chronyc makestep:立即强制同步

想立刻做一次同步(不等下次轮询):

chronyc makestep

期望输出:

200 OK

强制做一次跳变校正后,再 chronyc tracking 确认偏移归零。

6.4 用 chronyc 手动加/删源

临时测试某个源能不能用(不重启服务,即时生效):

chronyc add server ntp.aliyun.com iburst
chronyc delete server 0.pool.ntp.org

6.5 timedatectl 二次确认

timedatectl

确认 System clock synchronized: yes 且 NTP service: active。如果 System clock synchronized: no,说明 chronyd 还没完成首次同步。

6.6 ntpd 的验证(老系统)

如果还在用 ntpd:

ntpq -p

期望输出:

     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*120.25.115.20   120.25.115.20   2 u   62  128  377    6.234    0.123   0.456
+182.92.12.11   182.92.12.11    2 u   45  128  377    9.876   -0.234   0.321

* 表示当前选中的同步源,reach 377(八进制,377=0xFF,全可达),offset 越小越好。

6.7 时间偏移监控:接入 Prometheus

生产环境光靠人去看不够,要把时间偏移打进监控。两个思路:

思路一:用 node_exporter 的 time 指标。 node_exporter 自带两个时间相关指标:

node_time_seconds
node_time_seconds_seconds

node_time_seconds 是系统时钟的 Unix 时间戳,node_time_seconds_seconds(较新版本为 node_time_clock_seconds)是同一时刻读取的 clock_gettime。两者差值即偏移。但更直接的是加一个自定义 exporter。

思路二:写个简单脚本,暴露 offset 到 Prometheus。 用 chronyc 拿偏移,输出为 Prometheus 格式:

cat > /usr/local/bin/time_offset_exporter.sh <<'EOF'
#!/bin/bash
# 从 chronyc tracking 提取 System time 偏移,输出 Prometheus 指标
offset=$(chronyc tracking | awk -F'[ :]' '/System time/{print $4}')
echo "# HELP time_offset_seconds System clock offset from NTP time in seconds"
echo "# TYPE time_offset_seconds gauge"
echo "time_offset_seconds $offset"
EOF
chmod +x /usr/local/bin/time_offset_exporter.sh

配合 node_exporter 的 --collector.textfile.directory 定时执行,或直接用 chrony_exporter(社区有现成的 Prometheus exporter,直接部署即可)。

我实际用的告警规则(Prometheus):

groups:
  - name: time-sync
    rules:
      - alert: SystemClockOffsetHigh
        expr: abs(time_offset_seconds) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "系统时钟偏移超 100ms: {{ $labels.instance }}"
      - alert: SystemClockOffsetCritical
        expr: abs(time_offset_seconds) > 1
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "系统时钟偏移超 1s: {{ $labels.instance }}"

0.1s(100ms)告警 warning,1s 告警 critical,这是我多年运维总结的阈值。因为一旦超过 1 秒,TLS/Kerberos/分布式一致性的风险就开始显现了。

七、时钟跳变 vs 渐进调整:makestep 的核心语义

这是很多运维没吃透、又最容易踩雷的点。理解它,你才知道为什么生产环境要避免大步跳变。

7.1 两种校正方式

时间同步校正系统时钟,有两种方式:

  1. 跳变(Step):直接把时钟「拨」到正确值。一瞬间时间跳了一大截,比如快 5 分钟就立刻回拨 5 分钟。
  2. 渐进调整(Slewing / slew):通过微调内核时钟频率,让时间慢慢逼近真实值,比如每秒调整一点点,几分钟到几十分钟内平滑收敛。时间永远向前走,只是快慢有微小变化。

7.2 为什么生产要避免大步跳变

跳变带来的问题,全是生产级的:

  • K8s 证书:etcd、API Server、kubelet 之间基于时间戳签发/校验证书。时间向后跳(回拨)会让「刚签的证书」看起来还没生效,或「已签的证书」看起来已过期,引发集群证书风暴。
  • 监控告警风暴:时间回拨会让 Prometheus 的 rate()、increase() 在回拨窗口内产生负数、NaN 或突变,触发大量误告警。
  • 数据库复制:MySQL/PostgreSQL 的复制、etcd 的 raft 心跳、TiKV 的 TSO 都依赖时间单调性。时间回拨会破坏主从复制判定,甚至引发复制中断。
  • 日志排序:时间回拨会让新日志的时间戳小于旧日志,ELK/ClickHouse 里排序错乱,排查困难。

所以核心原则是:已经运行稳定、偏移小的系统,只用渐进 slewing,绝不做大跳变;只有刚开机、偏移巨大、或从未同步过的系统,才允许做跳变。

7.3 makestep 1.0 3 的精确语义

makestep <threshold> <limit>:

  • threshold:跳变阈值(秒)。偏移超过这个值才跳变,小于等于这个值用 slewing。
  • limit:在前多少次同步更新中允许跳变。超过这个次数后,即使偏移很大也只 slewing,不再跳变。

所以 makestep 1.0 3 的完整含义是:

在 chronyd 启动后的前 3 次时钟更新中,若偏移超过 1 秒,就一步跳变校正;第 3 次之后,无论偏移多大,都只做渐进 slewing,绝不跳变。

这正好满足生产需求:开机时机器时间可能乱(RTC 没电、VM 快照恢复),允许前几次跳变快速校准;运行稳定后(已过 3 次更新),哪怕漂了 2 秒也慢慢 slewing 回去,不引发跳变风暴。

反例:makestep 0 -1(或很多教程里的 makestep 1.0 -1)表示永远允许跳变。我强烈不建议在生产这么配,因为一旦某次网络异常后偏移累积到几秒,chrony 会直接跳变,瞬间回拨几秒,上面那堆灾难全来了。

7.4 ntpd 的对照

老 ntpd 默认不跳变,只 slewing。这意味着如果偏移很大(比如 30 分钟),它会以极慢速度(几 ppm 调整)收敛,可能要好几个小时。所以老系统若发现时间偏得离谱,要么等它慢慢收敛,要么手动:

# ntpd 停止后手动跳变
service ntpd stop
ntpdate -s ntp.aliyun.com
service ntpd start

(ntpdate 在新系统已废弃,老系统可用。)

八、虚拟化与容器:两个时间同步大坑

我在这上面栽过不止一次。虚拟化和容器的时钟行为跟物理机完全不一样,很多「时间越同步越不准」的诡异案例都出自这里。

8.1 VM 里的时钟漂移:KVM 与 VMware

虚拟机的时间有两个来源在打架:

  1. 虚拟化平台提供的虚拟时钟(KVM 的 kvm-clock、VMware 的虚拟 RTC);
  2. VM 内部运行的 NTP 服务(chrony/ntpd)。

问题在于:如果虚拟化平台本身的时间不准,或者宿主与 VM 时间不同步,VM 里的 NTP 会一直跟一个「不准的虚拟时钟」较劲,表现就是 offset 老是降不下来,或者同步了又飘走。

KVM 场景,确认是否用了 kvm-clock(这是 KVM 里精度最好的时钟源):

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

期望输出:

kvm-clock

如果输出是 tsc 或其他,说明没启用 kvm-clock,虚拟时钟精度差,建议在 KVM 层面启用(-machine accel=kvm + 开启 kvm-clock 通常默认启用)。

我的经验:

  • VM 内跑 chrony 完全正常且必要,前提是虚拟化宿主时间准。VM 的虚拟时钟漂移率通常比物理机大,反而更需要 chrony 持续校准。
  • 别让 VM 的 chrony 去跟宿主的某个「不准的虚拟 RTC」同步,要让它跟真实网络 NTP 源(或宿主里准的 NTP 服务)同步。
  • VMware:如果 VM 漂移严重,可在 VM 里装 open-vm-tools,并用 chrony 配合,别同时依赖 VMware 的「同步客户机时间到主机」选项——二选一,我推荐只用 VM 内 chrony。
  • 快照恢复后时间跳变:VM 从快照恢复,RTC 和系统时间可能回退到快照时刻。这时候 makestep 的前 3 次跳变机制就能派上用场——恢复后 chrony 检测到偏移巨大,直接跳变到正确时间。

8.2 容器内的时间:共享宿主,改不动

容器共享宿主机内核时钟,容器里没有独立的时钟。这意味着:

  • 容器内执行 date -s 会失败(权限拒绝)或无效,因为改的是宿主时钟,且通常被 seccomp/capability 拦住:
    docker exec -it myapp date -s "2026-10-28 10:00:00"
    # 报错:date: cannot set date: Operation not permitted
    
  • 容器内不需要也不能跑自己的 chronyd/ntpd——它没有权限调整时钟,跑了也白跑。容器的时间跟随宿主机。
  • 所以容器的正确做法是:保证宿主机时间准,容器时间自然准。
  • 有个特例:privileged 容器 或 --cap-add SYS_TIME 的容器可以改时钟,但这等于改宿主时钟,极其危险,生产禁用。
  • Docker 的 --clock-... 相关选项(若有)也不会给容器独立时钟。

结论:容器时间 = 宿主机时间。要管容器时间,先管宿主机。别在容器里折腾 chrony。

九、NTP 安全:最小化暴露面

NTP 服务(UDP 123)历史上出过多次放大攻击漏洞(如 2014 年的 monlist 反射放大,单包放大几百倍),所以生产环境必须收敛暴露面。

9.1 只监听内网

多网卡机器,用 bindaddress 只绑定内网 IP,别监听 0.0.0.0:

bindaddress 192.168.1.10

9.2 白名单 allow / 默认 deny

# 只允许内网子网
allow 192.168.0.0/24
allow 10.0.0.0/8
# 其余一律拒绝
deny all

9.3 请求速率限制

ratelimit 防 NTP 放大/洪泛:

ratelimit interval 4 burst 32

含义:每 4 秒最多处理一个客户端的一批(最多 32 个)请求,超出的丢弃。

9.4 认证:symmetric key(可选)

如果内网有不可信网段,或想防中间人篡改 NTP 包,可启用对称密钥认证。生成密钥:

chronyc keygen 0 SHA1 > /etc/chrony/chrony.keys

配置(服务端和客户端都要配):

keyfile /etc/chrony/chrony.keys
server 192.168.1.10 key 1

chronyc keygen 会生成形如 1 SHA1 hexstring 的密钥行,key 1 表示用编号 1 的密钥。

注意:NTP 对称密钥认证不是加密,是「认证」,用来确认包来源可信,防篡改。内网环境如果网络可信,通常不用配;跨不可信网络建议配。

9.5 禁用 monlist(chrony 默认无此漏洞)

老 ntpd 的 monlist 查询命令曾导致反射放大,chrony 默认不开放这个,无需额外处理。但别在公网暴露 UDP 123——防火墙层面只允许内网来源访问 NTP 端口:

firewall-cmd --permanent --add-rich-rule='rule family=ipv4 source address=192.168.0.0/24 port port=123 protocol=udp accept'
firewall-cmd --permanent --remove-service=ntp   # 移除全放行的 ntp 服务,改用更精确的规则
firewall-cmd --reload

十、5 个真实故障排查案例

以下案例都来自我或我带的团队在真实生产环境遇到并解决的,细节做了脱敏,但故障现象、排查路径、根因完全真实。

案例 A:TLS 报 "certificate is not yet valid"

现象:某对外 HTTPS 站点突然全部报 certificate is not yet valid,浏览器红屏,curl 也报错:

curl -v https://api.example.com
# 输出末尾:
# SSL: certificate verification failed (result: 9, error: certificate is not yet valid)

排查:

  1. 先看证书本身:openssl x509 -in cert.pem -noout -dates,确认 notBefore/notAfter 都在合理范围。证书没问题。
  2. 看服务器时间:date,发现系统时间比真实时间快了 5 分钟。
  3. 看是谁让它快了的:timedatectl,NTP service: inactive——这台机器的 chronyd 根本没跑。

根因:这台服务器是刚克隆出来的 VM,克隆时把「源 VM 的时间」带过来了,而源 VM 时间偏快;更关键的是新 VM 没启用 NTP 服务,导致时间一直快着。服务器时间快 5 分钟,对客户端而言,请求到达时「服务器本地时间」已越过证书的某些校验窗口,或客户端把服务器时间当作参考,导致认为证书「尚未生效」。

解决:

systemctl enable --now chronyd
chronyc makestep
date

跳变校正后,时间归位,HTTPS 恢复正常。

教训:克隆 VM 后第一件事就是确认 NTP 服务在跑,并强制 makestep 一次。

案例 B:Kerberos 认证失败 "Clock skew too great"

现象:内网一套基于 Kerberos 的 BI 系统,某天早上用户批量无法登录,日志大量报:

krb5_get_credentials: Clock skew too great (37) - PROCESS_TGS

排查:

  1. 客户端和 KDC 各看时间:date。发现一台客户端服务器(日志服务器)时间比 KDC 慢了 40 分钟。
  2. 看这台机器的 NTP:chronyc tracking,报 Unable to obtain synchronization from source——chrony 根本没同步上。
  3. 查原因:chronyc sources -v,所有源 Reach 0%;ping ntp.aliyun.com 不通;查路由,这台机器被防火墙拦了 UDP 123 出方向。

根因:防火墙策略变更时,误拦了这台日志服务器到 NTP 源的 UDP 123 出站流量。它无法同步,靠 RTC 漂着,一星期下来慢了 40 分钟,超过 Kerberos 300 秒容差,认证全挂。

解决:放行防火墙 UDP 123 出站:

firewall-cmd --permanent --add-rich-rule='rule family=ipv4 destination address=120.25.115.20 port port=123 protocol=udp accept'
firewall-cmd --reload
chronyc makestep

教训:防火墙变更务必检查 NTP 出站。Kerberos 容差只有 5 分钟,比 TLS 更敏感。

案例 C:K8s 集群证书签名时间异常

现象:K8s 集群某节点突然被判定 NotReady,kubectl get nodes 显示该节点 Ready <unknown>,kubelet 日志报证书相关错误:

x509: certificate signed by unknown authority

或 etcd 报:

rafthttp: request was sent to an unavailable peer

排查:

  1. 看 etcd/API Server 所在机器时间:date。发现 etcd 节点时间比 API Server 慢了 3 秒,且漂移在持续增大。
  2. chronyc tracking:etcd 节点 Frequency 高达 +450 ppm,严重偏离正常值(几 ppm),说明这个 VM 的虚拟时钟漂移极大。
  3. cat /sys/devices/system/clocksource/clocksource0/current_clocksource:输出 tsc,没启用 kvm-clock。

根因:KVM 里没启用 kvm-clock,虚拟时钟精度差;etcd 节点上的 chrony 虽然跑了,但持续 slewing 追不上漂移,时间偏移累积到 3 秒。etcd 集群基于时间戳做 leader 选举和 raft 心跳,时间差一拉大,健康检查判定混乱,节点被标记 NotReady。

解决:

  1. 宿主机层面启用 kvm-clock(修改 VM 配置,-machine accel=kvm 并启用 kvm-clock)。
  2. 在 etcd 节点上强制跳变校准:chronyc makestep。
  3. 检查 kubelet 证书重新签发。

教训:K8s 集群节点务必保证时间高度一致,偏差超过 1 秒就可能引发 etcd 选举异常。虚拟化环境务必启用虚拟时钟(kvm-clock)。

案例 D:监控数据错位

现象:Prometheus 告警规则 for: 5m 经常误触发,或者明明该告警了却没告;Grafana 图表上的时间轴和日志时间对不上;rate() 曲线出现突然的负值。

排查:

  1. 对比采集端、Prometheus 存储端、节点导出器所在机器时间:分别在各自机器 date +%s。
  2. 发现 采集端(node_exporter 所在机器)比 Prometheus 快了 1.2 秒。
  3. 查 node_time_seconds 与 time()(Prometheus 查询时)之差:query: time() - node_time_seconds,结果持续 ~1.2。

根因:node_exporter 所在机器 chrony 未正确配置,只配了一个 server pool.ntp.org,跨境源抖动大,chrony 判定源不可靠,一直没锁上,机器时间缓慢漂移了 1.2 秒。虽然 1.2 秒看似不大,但对 for: 5m 这种时间窗判定和 rate 计算已经足够产生误告警和图表错位。

解决:

# 换国内源
sed -i 's/^server pool.ntp.org/server ntp.aliyun.com iburst/' /etc/chrony/chrony.conf
systemctl restart chronyd
chronyc makestep

教训:监控体系的每台机器时间都必须准,1 秒的偏差就会让告警规则失真。node_exporter 的 time_seconds 与 time_offset_seconds 要进告警。

案例 E:systemd-timesyncd 不工作

现象:一台 Ubuntu 22.04 最小化服务器,timedatectl 显示 NTP service: inactive,时间一直不准,且没有安装 chrony。

排查:

  1. timedatectl status:NTP service: inactive,System clock synchronized: no。
  2. systemctl status systemd-timesyncd:inactive (dead)。
  3. 看配置:cat /etc/systemd/timesyncd.conf,发现 #NTP= 和 #FallbackNTP= 都被注释掉了——用的是默认值。默认 FallbackNTP 是 0.debian.pool.ntp.org 等国外池,在这台机器上 DNS 解析到国外,跨境不可达,导致 timesyncd 一直连不上源,服务反复失败后停掉。
  4. 手动测:systemd-resolve --status | grep -i ntp 或 ntpdate -q ntp.aliyun.com 测源可达性。

根因:systemd-timesyncd 默认用 Debian/Ubuntu 的 pool.ntp.org(国外),国内网络不可达;且 NTP= 未显式配置,FallbackNTP 也是国外池。服务连不上源,就持续失败。

解决:显式配国内源。

cat > /etc/systemd/timesyncd.conf <<'EOF'
[Time]
NTP=ntp.aliyun.com cn.pool.ntp.org
FallbackNTP=ntp.tencentcloud.com ntp1.aliyun.com
EOF
systemctl restart systemd-timesyncd
timedatectl set-ntp true
timedatectl

教训:systemd-timesyncd 用之前必须显式配 NTP=,别依赖默认的国外 FallbackNTP。国内环境一定要指向国内源。

十一、排障决策树

把上面所有案例的排查路径收敛成一棵决策树,遇到时间问题照着走:

开始:现象是 TLS/Kerberos/监控/日志时间错乱
  │
  ├─ 1. timedatectl
  │     ├─ NTP service: inactive → 看步骤 2(服务没跑)
  │     ├─ NTP service: active 但 System clock synchronized: no → 看步骤 3
  │     ├─ System clock synchronized: yes → 看步骤 4
  │     └─ RTC in local TZ: yes → 执行 timedatectl set-local-rtc 0(改回 UTC)
  │
  ├─ 2. NTP 服务没跑
  │     ├─ chrony → systemctl enable --now chronyd
  │     ├─ ntpd → systemctl enable --now ntpd
  │     └─ timesyncd → systemctl enable --now systemd-timesyncd
  │
  ├─ 3. 服务在跑但没同步上
  │     ├─ chronyc sources -v
  │     │     ├─ 所有源 Reach 0% → 源不可达,看步骤 5
  │     │     ├─ Reach 有值但 Offset 大 → 源质量差,换源(步骤 6)
  │     │     └─ 有源被选中(*)但 Offset 仍大 → 检查 makestep/是否在 slewing
  │     └─ 若是 ntpd → ntpq -p 同样分析
  │
  ├─ 4. 已同步但 offset 还是大
  │     ├─ chronyc tracking → 看 Frequency ppm 是否异常大(>100)
  │     ├─ 虚拟化环境 → 检查 /sys/devices/system/clocksource/.../current_clocksource
  │     │     └─ 不是 kvm-clock → 启用虚拟时钟(KVM)
  │     └─ 硬件时钟坏 → 看步骤 7
  │
  ├─ 5. 源不可达
  │     ├─ ping 源域名 → 不通:DNS 解析问题,检查 /etc/resolv.conf
  │     ├─ ping 源 IP → 通:域名正常,是 UDP 123 被拦 → 检查防火墙/安全组
  │     ├─ nc -uvz 源IP 123 → 不通:UDP 123 被拦
  │     └─ 出站被拦 → 防火墙放行 UDP 123 出站
  │
  ├─ 6. 源质量差(高 Delay/低 Reach)
  │     ├─ 换国内源 ntp.aliyun.com / cn.pool.ntp.org
  │     └─ 重启 chronyd + chronyc makestep
  │
  └─ 7. 硬件时钟坏(RTC 电池没电/主板故障)
        ├─ 现象:每次断电重启时间都回到 2000 年或乱跳
        ├─ 检查 RTC 电池(纽扣电池电压)
        ├─ 更换主板电池
        └─ 重启后用 chronyc makestep 立即校准

十二、15 条命令速查 Cheat Sheet

# 命令 用途
1 timedatectl 查看时区、RTC、NTP 状态三合一
2 timedatectl set-timezone Asia/Shanghai 设置时区
3 timedatectl set-local-rtc 0 RTC 用 UTC
4 timedatectl set-ntp true 启用 NTP 服务
5 chronyc sources -v 查看上游源状态(核心)
6 chronyc tracking 查看同步详情、偏移、漂移率
7 chronyc makestep 强制一步跳变校正
8 chronyc add server ntp.aliyun.com iburst 临时加源
9 chronyc delete server 0.pool.ntp.org 临时删源
10 ntpq -p ntpd 查看源(老系统)
11 systemctl status chronyd 查看 chrony 服务状态
12 chronyd -t /etc/chrony/chrony.conf 配置语法测试
13 date +%s 快速看当前时间戳
14 hwclock --show 查看硬件时钟(RTC)
15 timedatectl show -p Timezone --value 只输时区名

FAQ

Q1:时钟漂移会造成哪些生产故障?

A:最常见三类。第一,TLS 证书报 certificate is not yet valid(服务器时间偏快或偏慢导致证书校验窗口错位);第二,Kerberos 认证报 Clock skew too great,因为 Kerberos 容差只有 300 秒(5 分钟),超了就拒绝;第三,监控与日志时间错位,导致告警规则失真、rate() 计算异常、日志排序错乱。此外分布式系统(etcd、数据库复制)依赖时间戳,时钟跳变会破坏一致性。

Q2:chrony 和 ntpd 怎么选?

A:新系统(RHEL 8+/Debian 10+)首选 chrony。它对高延迟、高抖动网络更稳,收敛快,启动秒级可同步,原生支持 makestep 一步校正,还能当 NTP 服务端。ntpd 是传统方案,启动慢、收敛慢,适合老系统。systemd-timesyncd 最轻量,但只能当客户端,不能当服务端,适合最小化容器/无状态节点。

Q3:为什么不用 time.nist.gov 或 pool.ntp.org?

A:这些是国外源,国内机房访问要跨境,实测延迟 150-300ms 且抖动大,chrony 的 Reach 常低于 50%,同步质量差。国内生产环境应优先 ntp.aliyun.com、cn.pool.ntp.org、ntp.tencentcloud.com,延迟个位数毫秒,Reach 100%,准确度高得多。

Q4:makestep 1.0 3 是什么意思?

A:makestep <threshold> <limit>。含义是:在 chronyd 启动后的前 3 次时钟更新中,若偏移超过 1 秒,就一步跳变校正;第 3 次之后,即使偏移再大也只做渐进 slewing,绝不跳变。这样开机时能快速校准(允许跳变),运行稳定后避免大步跳变引发告警风暴、证书问题、数据库复制异常。

Q5:容器里能改时间吗?需要跑 chrony 吗?

A:容器共享宿主机内核时钟,没有独立时钟。容器内 date -s 会报 Operation not permitted(除非 privileged 或加 --cap-add SYS_TIME,那会改宿主时钟,极其危险,生产禁用)。容器不需要也不能跑 chrony,时间跟随宿主机。所以正确做法是保证宿主机时间准,容器时间自然准。

Q6:虚拟机时间老是不准怎么办?

A:先确认虚拟时钟。KVM 下 cat /sys/devices/system/clocksource/clocksource0/current_clocksource 应为 kvm-clock,不是 tsc;若不是,在 VM 配置启用 kvm-clock。然后 VM 内跑 chrony 对国内源同步,别依赖虚拟化平台的「同步客户机时间」选项(二选一)。VM 快照恢复后时间会回退,需 chronyc makestep 强制校准。

Q7:NTP 服务怎么防止被滥用/攻击?

A:三招。第一,最小化监听,多网卡用 bindaddress 只绑内网 IP,只监听 UDP 123;第二,白名单,allow 192.168.0.0/24 + deny all,只允许内网子网同步;第三,速率限制,ratelimit interval 4 burst 32 防 NTP 放大攻击。公网防火墙层面只放行内网来源的 UDP 123,绝不在公网暴露 NTP。

Q8:怎么监控时间偏移?

A:用 chronyc tracking 看 System time 字段的偏移,用 chrony_exporter 或自定义 exporter 把它暴露成 Prometheus 指标。我建议告警阈值:偏移超 100ms(0.1s) 告 warning,超 1 秒 告 critical。同时监控 Frequency(漂移率,正常几 ppm,异常超 100 ppm 提示硬件/虚拟时钟问题)。