分别检查 timesyncd 服务状态与时钟证据
systemd-timesyncd 是一个精简 SNTP 客户端,用配置的时间源同步本地系统时钟。它可以帮助证书检查与墙钟时间戳保持有意义,但不会无条件保证某个精度范围。守护进程能否运行、是否收到可接受样本,以及系统是否报告同步,是需要分别确认的事实。只看到安装了客户端,或者某条命令成功返回,都不足以推导时钟已经准确。 坏掉的样子 守护进程并不总是平滑校正。文档明确说明,大偏差会直接跳时,小偏差则逐渐调整。因此,开机时离正确时间很远的机器,可能发生时间不连续。经过时长测量应使用单调时钟,解释墙钟时间戳的应用则应处理校时,而不是假定时间永远平滑前进。不能把“精简客户端”误写成“绝不会让应用看见时间跳跃”。 FallbackNTP 不是显式服务器不可达时就一定会启用的通用应急列表,它在没有其他已知服务器信息时才适用。全局 NTP 条目和 networkd 提供的链路时间源,优先于这个后备设置。因此,应检查实际来源选择与配置覆盖文件,不能仅因为主来源连接失败,就断定某个写在 fallback 中的服务器必定已经被尝试。 未同步也不能证明 UDP 流量被阻止。服务可能缺失、未启用、被单元条件排除、没有调整时钟的权限,或者正在拒绝不合适样本。反过来,全局同步属性也不能单独说明哪个进程提供了时间。应区分管理器看到的服务状态、客户端观察到的样本,以及内核层同步指示,避免把来自不同层面的一个布尔值当成完整故障结论。 快速验证 这个失败模式实验在一次性 Debian 12 Docker 容器中以 root 运行,PID 1 为 systemd 252.39。初始化进程通过 /usr/bin/setpriv --bounding-set=-sys_time,-sys_module /sbin/init 启动,移除相关能力以防止调整 Docker 虚拟机共享时钟。脚本在请求任何服务启动前检查该前提,并要求安装包中的容器排除条件存在: from pathlib import Path import json,subprocess def run(*args): result=subprocess.run(args,text=True,capture_output=True,timeout=10) assert result.returncode==0,(args,result.stderr) return result.stdout.strip() def properties(unit): output=run('systemctl','show',unit,'-p','ActiveState','-p','SubState','-p','MainPID','-p','ConditionResult') return dict(line.split('=',1) for line in output.splitlines()) # This failure-mode exercise requires an isolated container that cannot set time. status=dict(line.split(':',1) for line in Path('/proc/1/status').read_text().splitlines() if ':' in line) assert int(status['CapBnd'].strip(),16)&(1<<25)==0,'PID 1 must lack CAP_SYS_TIME' assert run('systemd-detect-virt','--container')=='docker' unit='systemd-timesyncd.service';definition=run('systemctl','cat',unit) assert 'ConditionCapability=CAP_SYS_TIME' in definition assert 'ConditionVirtualization=!container' in definition assert properties(unit)['ActiveState']=='inactive' run('systemctl','start',unit) state=properties(unit) assert state=={'MainPID':'0','ActiveState':'inactive','SubState':'dead','ConditionResult':'no'},state clock=dict(line.split('=',1) for line in run('timedatectl','show','-p','CanNTP','-p','NTP','-p','NTPSynchronized').splitlines()) assert all(value in ('yes','no') for value in clock.values()) print('start command returned zero; unit stayed inactive with PID 0 and failed conditions') print('clock properties:',json.dumps(clock,sort_keys=True)) print('PID 1 lacks time-setting capability; Docker condition also excludes this packaged service') print('no clock-setting command was issued and no NTP synchronization was claimed') 启动命令返回零,但单元仍为非活动,主进程编号为零,ConditionResult=no。条件不满足而跳过激活,与守护进程成功运行,是不同结果。样本安装包要求调整时间的能力,并排除容器,而当前环境两项前提都不满足。因此在假设 NTP 报文被网络丢弃之前,就已经有具体证据解释服务为何没有工作。 ...