前两年有一次线上告警,业务同事反馈订单接口突然大量超时。我登上去一查,应用日志和数据库日志的时间戳差了四分钟,SSL握手的“not valid yet”直接把请求全拒了。从那次之后,我每到一个新环境,第一件事就是先看时间同步机制是不是够健壮。CentOS这类Linux服务器,早期大家习惯装ntpd,现在从CentOS 7开始默认集成的是chrony,CentOS 8/9更是直接把它放进base源里。这篇文章要聊的“基于CentOS搭建Chrony时间同步服务器”,就是把服务端、客户端、验证和排错整条链路掰开讲清楚,适合刚接手Linux服务器的新手,也适合想把内网时间源规范化的人。1. 为什么这年头还要自己搭时间同步服务器1.1 时间漂移对业务的影响范围服务器时间不准,表面看不是什么大事,但一旦出了事,全部是隐蔽而致命的问题。我整理了一张对照表,都是实际生产里遇到过的场景:问题场景表现后果日志分析多台机器时间偏差,日志顺序错乱排查问题时无法按时间线还原故障链路,浪费大量时间SSL/TLS证书校验客户端证书或服务端证书被判“未生效”或“已过期”HTTPS握手直接失败,接口大面积报错定时任务cron表达式按本地时间触发,机器时间不准任务提前或延后执行,数据导出、备份窗口错乱数据库主主/集群各节点时间差异过大复制冲突、自增主键判断异常,严重时集群脑裂监控与计费指标时间戳不一致监控曲线很难横向对比,按时间计费的服务出现账单偏差很多人觉得“服务器时间不准,手动date改一下不就行了”,但手动改时间只能撑一阵。操作系统里有硬件时钟RTC和系统时钟,系统时钟靠晶振驱动,晶振本身有频率误差,加上温度变化、CPU负载波动,时间会持续漂移。只有靠NTP协议持续校正,才能让系统时间保持在一个稳定基线。1.2 从ntpd到chrony:CentOS默认时间服务的切换逻辑早期的CentOS 6时代,大家默认用ntpd,也就是传统的NTP守护进程。ntpd工作得并不差,但它有两个明显的短板:一是启动后首次同步很慢,遇到初始偏差较大的场景要熬很久才能收敛;二是它对网络断断续续的环境适应能力差,在虚拟机挂起恢复、笔记本睡眠唤醒、网络临时断开又重新连上这些场景下,经常出现长时间无法锁定时间源的情况。CentOS 7开始,系统默认安装的就是chrony,同时保留了对ntpd的兼容支持。等到CentOS 8和CentOS Stream 9,基础仓库里干脆不再提供传统的ntp包,chrony成了唯一默认选项。chrony由两个核心组件组成:后台守护进程chronyd负责实际的时间获取和同步计算,命令行客户端chronyc负责查看状态和下发指令。它最大的特点是“反应快”:初次同步通常几秒内就能完成,时钟偏差较小时通过微调频率慢慢校正,偏差较大时可以通过makestep直接步进。对现代数据中心的虚拟化环境,这种设计明显更实用。1.3 什么时候真的需要自建内部时间源很多读者会问:既然CentOS默认就能连公网NTP池,为什么还要自己搭一台时间服务器?我的判断标准有三条:如果内网服务器无法访问外网,或者外网访问质量极差,那必须在内网搭一台时间源,让所有设备都指向它。如果有几十台甚至几百台服务器、虚拟机、网络设备,让它们各自连接公网NTP,每台机器的网络延迟和抖动不同,最终时间基线会有细微差异。搭一台统一的时间服务器,所有客户端指向同一个源,整体一致性要好得多。如果部门里有审计、合规要求,需要可追溯的统一时钟源,自建NTP服务并提供访问日志,也是常见做法。反过来,如果你只有三五台服务器,又能正常访问公网,直接用默认配置连接公共NTP池完全够用,不必增加维护节点。自建时间服务器是“规模上来之后”才值得做的事。2. 部署前先摸底:版本差异、端口与已有服务检查2.1 CentOS版本和chrony包的关系不同CentOS版本对chrony的支持细节不完全一样,先看清楚自己手上的系统是哪个版本再动手,能省掉很多不必要的折腾。系统版本默认时间服务包管理器常见chrony版本CentOS 7chrony(默认安装)yum3.xCentOS Stream 8chrony(默认安装)dnf3.xCentOS Stream 9chrony(默认安装)dnf4.xCentOS 7的chrony虽然版本偏老,但基础功能和配置语法与新版基本一致,本文的配置在三个版本上都能用。CentOS Stream 9里的chrony 4.x支持了更多硬件时间戳相关的选项,对于追求极致精度的场景更友好,普通内网环境用不上。安装命令很简单:# CentOS 7使用yum,CentOS 8/9使用dnf yum install -y chrony # 或者 dnf install -y chrony安装之后不要急着做任何配置,先确认base源正常,然后检查一下系统里有没有残留的旧时间同步工具。2.2 安装前要确认的三件事第一件事:检查是否已经存在ntpd或chronyd。新装的系统通常没有启动冲突,但很多服务器是运维从旧版本升级上来的,可能同时留有ntpd服务和旧的ntpdate定时任务。如果chronyd和ntpd同时运行,它们会互相抢占端口、互相校正,出现严重的时钟抖动。所以部署前先执行:systemctl status chronyd systemctl status ntpd rpm -qa | grep -E "chrony|ntp"如果发现ntpd在运行,先停掉并禁用:systemctl stop ntpd systemctl disable ntpd如果发现crontab里有调用ntpdate的定时任务,建议直接删除。ntpdate是“粗暴式”校时,会和chronyd的平滑校正机制打架,时间会反复跳变。第二件事:确认UDP 123端口没有被防火墙挡掉。NTP协议的传输层是UDP,服务端和客户端都使用123端口。CentOS默认开启了firewalld,如果没有放行,外部客户端会一直拿不到时间。检查命令:firewall-cmd --list-all ss -ulnp | grep 123如果服务已经启动但ss看不到UDP 123监听,多半是chronyd启动失败,或者端口被占用了。第三件事:确认服务器有固定IP。自建时间源最忌讳DHCP动态分配,一旦IP变了,所有写死旧IP的客户端会瞬间失去时间源。给时间服务器配静态IP,并把主机名和IP的对应关系写进内部DNS,这是很多教程不会特意强调、但实际中特别重要的一点。2.3 装好后先看状态,别急着改配置安装完chrony并启动服务后,先跑一遍基本验证:systemctl enable --now chronyd systemctl status chronyd chronyc tracking journalctl -u chronyd --no-pager | tail -n 20chronyc tracking输出的核心字段是“System time”,表示系统当前时间与参考时间源之间的偏差。如果刚启动能看到较大的偏差值,不用着急,等一两分钟再看,通常会自动收敛。如果journalctl日志里出现Can't synchronize: no selectable sources,说明没有可用时间源,后续就要按第5章的排查思路去看防火墙和上游可达性了。3. 服务端配置逐行拆解:pool、allow、local与日志3.1 一份可以直接抄的server配置修改/etc/chrony.conf前,建议先备份原文件。下面是适合大多数内网场景的服务端配置:# 上游时间源,使用CentOS默认公共NTP池域名 pool 2.centos.pool.ntp.org iburst # 当本机无法同步任何上游时,仍以本地时间对外提供服务 local stratum 10 # 保存时钟漂移率,重启后避免从头学习 driftfile /var/lib/chrony/drift # 初始3次轮询中,如果偏差超过1秒,允许直接步进 makestep 1 3 # 默认拒绝所有客户端 deny all # 只允许内网指定网段访问 allow 192.168.10.0/24 # 管理命令通道只绑定本机,防止局域网其他机器下发管理指令 bindcmdaddress 127.0.0.1 # 让内核把系统时间与硬件时钟RTC定期同步 rtcsync # 开启日志,方便之后排错 logdir /var/log/chrony log measurements statistics tracking把这个配置里面的网段192.168.10.0/24换成你自己的实际内网网段,上游地址换成你实际能访问的公共NTP或上级时间源,然后重启:systemctl restart chronyd systemctl enable --now chronyd3.2 关键参数背后的设计逻辑pool和server的区别要搞清楚。server后面跟单个IP或主机名,一般用于明确知道唯一上游地址;pool后面跟域名,chrony会通过DNS解析得到多个地址,自动选择可用性最高的上游。自建时间服务器建议用pool配合iburst,iburst的作用是首次启动时快速连发多个请求,缩短初始同步时间,而不是按默认间隔慢慢等。local stratum 10这个参数很多人不理解。NTP协议用“层级”(stratum)表示时间源距离原子钟的跳数,公共NTP服务器一般是一层或二层,客户端是三层。数值越小,优先级越高。local stratum 10的意思是:当本机因为网络故障完全失去上游时,chrony会宣称自己可以作为第10层的时间源,继续为内网客户端提供服务。10这个值比真实上游的层级大,所以正常同步时不会干扰NTP层级排序;只有在失去上游连接后,本地层级才会被启用。这样内网设备至少能拿到一个连续可用的时间参考,而不是直接失去时间源。deny all和allow的顺序值得单独说。chrony对访问规则的处理方式和很多防火墙不一样,它按顺序匹配,最后匹配的规则优先。如果配置写成allow 192.168.10.0/24然后再写deny all,那deny all会覆盖前面的allow,所有客户端都会被拒绝。所以正确姿势是先deny all,再把allow写在后面。这也是很多人配置看起来没问题、实际客户端连不上的经典原因。bindcmdaddress 127.0.0.1是限制chronyc管理命令通道的监听地址。chrony除了NTP端口,还有一个用于管理命令的通道,默认UDP 323端口。如果不限制,内网任何机器都能用chronyc向服务端下发配置修改指令,这是一个容易被忽略的安全隐患。对于纯NTP服务节点,管理通道绑定到本机就够了。3.3 启动后的第一轮验证:怎么看有没有真正成为时间源配置完先看服务端自己的状态:chronyc sources -v输出里如果看到^* 2.centos.pool.ntp.org这样的行,^*表示已经成功同步到该源。^?表示当前不可达,^+表示候选源但尚未主用。接着看chronyc tracking,如果Leap status显示Normal,说明系统时间处于正常同步状态。确认服务端自己已同步后,再用chronyc clients看本机是否被客户端请求过:chronyc clients这个命令只有服务端能看,输出里会列出请求客户端的IP、发送请求的速率等信息。如果客户端已经指向了新搭建的源,这里应该能看到对应IP。最后再确认端口监听:ss -ulnp | grep 123看到chronyd进程监听UDP 123,说明服务端对外服务已正常。到这一步,服务端基本就算立住了。4. 客户端接入:从一根命令到批量部署4.1 客户端最小配置客户端的chrony配置可以非常精简,仍然在/etc/chrony.conf里操作:# 指向内网时间服务器,把IP换成你的实际服务器地址 server 192.168.10.1 iburst # 保存漂移率 driftfile /var/lib/chrony/drift # 初始同步时允许步进 makestep 1 3 # 保持系统时间与硬件时钟同步 rtcsync这里必须强调一点:如果客户端原本有指向公网NTP池的pool或server配置,一定要注释掉或删除。我见过不少案例,客户端同时配置了内网源和公网源,chrony会在两个源之间反复横跳,最终时间一直处于不稳定状态。因为内网源和公网源的网络延迟不同,而且层级结构会让客户端不知道优先选谁。既然内网已经有了统一时间源,客户端就只保留一个指向内网的源,这是最干净的做法。4.2 验证是否真正同步的三种方式配置完成后重启chronyd:systemctl restart chronyd验证方式有三种,层层递进。第一种,看来源状态:chronyc sources -v如果输出里有^* 192.168.10.1,说明客户端已经成功锁定内网时间源。如果显示^?,说明客户端发出去的NTP请求没有得到回应,需要回到服务端检查防火墙和allow规则。第二种,看系统全局状态:timedatectl重点关注System clock synchronized: yes这一行。如果显示yes,说明系统已经确认时间同步正常;显示no则还需要排查。第三种,看当前偏差:chronyc trackingSystem time字段显示的是本地系统时间与参考时间源之间的误差。正常稳定状态下应该小于1毫秒。刚完成同步时稍大一些是正常的,过几分钟再看就会稳定下来。4.3 批量部署与配置模板生产环境里不可能一台一台手动改配置文件,建议用配置管理工具批量下发。以Ansible为例,写一个简单的playbook:- name: Deploy chrony client config hosts: all tasks: - name: Install chrony package: name: chrony state: present - name: Copy chrony.conf template: src: chrony.conf.j2 dest: /etc/chrony.conf notify: restart chronyd - name: Ensure chronyd is running service: name: chronyd state: started enabled: true handlers: - name: restart chronyd service: name: chronyd state: restarted模板文件里把服务器IP作为变量填充,例如在Jinja2模板中写server {{ ntp_server }} iburst,在inventory或group_vars里定义ntp_server变量。这样新增机器时,只要指定变量就能自动同步时间配置。还有一种使用场景是Windows客户端。Windows自带的时间服务w32tm也可以指向内网NTP源:w32tm /config /manualpeerlist:"192.168.10.1" /syncfromflags:manual /update w32tm /resync稍微提一下这个是因为很多运维环境是Linux和Windows混合的,统一让Windows也指向同一台时间服务器,可以减少不少排查时间。5. 真实踩坑排查链路:一台明明配好了却始终报^?的服务器5.1 案例一:sources显示^?——上游不可达的完整排查有一次我给一台CentOS Stream 8配置好chrony后,chronyc sources -v输出里始终是^?,看起来就想不通:服务重启了,配置文件也检查过,上游地址能ping通,为什么拿不到时间?我的排查链路是这样的:第一步,看日志:journalctl -u chronyd --no-pager | tail -n 50日志里报No source for synchronization,说明chronyd确实在尝试连接上游,但没有收到有效的NTP响应。第二步,确认UDP端口。NTP走的是UDP,ping用的是ICMP,能ping通不代表UDP 123能通。我在客户端和服务端分别执行:ss -ulnp | grep 123 tcpdump -i eth0 udp port 123 -c 10如果客户端发出请求但服务端或公网源没有响应,用tcpdump看数据包是最直接的。这条命令能抓取UDP 123端口的数据包,观察请求是否发出、响应是否回来。第三步,检查防火墙和安全组。服务端的firewall-cmd --list-all里如果没有123/udp的放行规则,就补上:firewall-cmd --permanent --add-port=123/udp firewall-cmd --reload如果是云主机,还要检查安全组规则是否允许UDP 123入方向。这轮排查下来,大多数^?问题都能解决。如果检查完一切正常但依然^?,尝试在配置里多写几个上游源,比如同时配置两个公共NTP池,避免单一源在高负载或网络故障时无响应。5.2 案例二:allow规则顺序导致客户端被拒另一个高频问题是客户端配置完全正确,服务端也配置了allow,但客户端依然无法同步,chronyc sources -v里显示的不是^?,而是类似^-的拒绝状态。我最后在服务端配置里发现的问题是这样的:allow 192.168.10.0/24 deny all前面已经强调过,chrony的规则是“最后匹配优先”。这个配置里最后匹配的是deny all,于是所有客户端都被拒绝。修复方法很简单,把顺序调换,先deny all再allow:deny all allow 192.168.10.0/24另外还需要检查另一个地址段问题。很多公司的内网不止一个网段,服务器在192.168.10.0/24,客户端却在10.0.0.0/16,这时候allow 192.168.10.0/24覆盖不到客户端,客户端除非有公网源,否则也会一直无法同步。检查客户端实际IP所在网段,确保allow范围准确覆盖。5.3 案例三:虚拟化环境挂起恢复后时间大幅漂移虚拟机的时钟问题比物理机更多。有一次一台KVM虚拟机在快照恢复后,时间直接慢了5分钟。chrony日志里能看到它一直在尝试微调,但5分钟的偏差靠频率微调需要很长时间才能收敛。这里要说清楚makestep 1 3的局限性。它只对“初始3次轮询”生效,也就是说在服务刚启动的前几轮检查中,如果偏差超过1秒,chrony会直接大步跳到正确时间。但系统运行很久之后突然产生巨大偏差,这个参数已经过了生效区间,chrony默认只会通过微调频率来慢慢追。等它追上,业务早就出问题了。遇到这种场景,最直接的方法是手动触发一次即时同步:chronyc makestep在客户端执行后,时间会立即跳到参考时间源对应的值。同时建议在虚拟化平台上为虚拟机启用时间同步功能,比如KVM或VMware中的host时间同步选项,防止挂起恢复后再次大幅漂移。对于精度要求更高的场景,还需要检查rtcsync是否启用以及硬件RTC是否正常工作。虚拟机的RTC往往不够精确,但rtcsync可以让系统时间在每次启动时以硬件时钟为基准,同时chrony校正后的系统时间也会回写RTC,形成良性循环。5.4 排查工具组合速查把上面这些经验整理成一张速查表,遇到问题可以直接按顺序查:现象先看命令预期结果异常方向服务没起来systemctl status chronydrunning查日志上游状态报错chronyc sources -v^*换上游/查防火墙客户端连不上chronyc clients看到客户端IP查allow规则UDP不通tcpdump -i eth0 udp port 123有来有回查安全组/防火墙时间偏差大chronyc trackingSystem time < 1ms手动makestep时间反复跳变timedatectlsynchronized yes查是否存在ntpd冲突6. 长期稳定运行的几个优化点与我的运维习惯6.1 多时间源与主备NTP设计自建NTP服务最忌讳单点。如果整个内网只有一台时间服务器,它一旦挂掉,所有客户端立刻失去时间源。我的建议是至少准备两台:两台时间服务器各自同步公共NTP池,互为独立上游。客户端配置里同时写两个地址:server 192.168.10.1 iburst server 192.168.10.2 iburst这样即使第一台NTP服务器宕机,客户端会自动切换到第二台,实现无缝衔接。两台服务器之间不需要互相同步,因为它们都直接同步公网源,偏差本身就在毫秒级。防火墙策略上,只放行客户端到这两台NTP服务器的UDP 123端口,其余跨网段访问都拒绝,避免把NTP服务暴露到不必要的网络环境里。6.2 用漂移文件与makestep保持环境稳定driftfile文件我会特别保护。它记录的是系统时钟硬件晶振的频率漂移率,chrony每次启动后先读取这个文件,能立刻知道本机时钟的漂移特性,不需要重新花时间学习。正常情况下不要手动删除/var/lib/chrony/drift,否则重启后会经历一段重新学习的“不稳定期”。另外建议在系统重启后的初始化脚本里加一条chronyc makestep,或者在服务启动后执行一次强制同步,确保重启后时间立即对齐,而不是等chrony慢慢收敛。6.3 监控时间偏差:阈值与告警时间同步不是配完就完了,必须纳入监控。我的做法很朴素:在一台监控机上写一个定时任务,定期执行chronyc tracking,把System time字段提取出来,超过100毫秒就告警。如果是生产环境,更推荐用自带系统时钟监控能力的指标采集工具,比如node_exporter提供的node_timex_offset_seconds指标,它反映的是系统时间与硬件时钟/参考时钟之间的偏差。把这个指标接入现有监控系统,设置告警阈值,时间漂移能在影响业务之前就被发现。告警阈值怎么定?一般业务服务器建议100毫秒以内;涉及金融交易、电力等强一致场景,建议50毫秒甚至更小。不要设置太小,否则网络抖动导致的正常波动会频繁误报。6.4 安全建议与时钟相关小细节NTP服务依赖UDP 123端口,这个端口本身并不“危险”,但如果暴露在公网,可能被外部扫描器探测,也可能被用来探测网络拓扑。我的原则是:NTP服务绝对不对公网开放,只在内网网段内提供服务,并在chrony配置中使用deny all加allow的白名单模式。还有一个和系统初始化相关的细节:Linux系统时钟建议统一使用UTC,硬件时钟也建议保持UTC。如果要和Windows双系统共存,可以使用:timedatectl set-local-rtc 0这个命令关闭本地时间模式,确保硬件时钟以UTC保存,避免两个系统之间时间换算错乱。服务器如果开着set-local-rtc 1,经常会出现重启后时间对不上的问题。日志目录/var/log/chrony如果开启了log measurements statistics tracking,会产生不少文件,但logrotate通常已经默认处理好轮转,不需要手工清理,也不要在日志里写无关内容占用存储空间。我在实际部署中还有个习惯:服务端配置里始终保留至少一个公共NTP池作为兜底上游,同时保留内网主备两台NTP服务器之间的独立同步链路。这样即使某个上游故障,chrony会自动切换,时间不会出现大面积失准。时间同步这件事,从来不应该是“装上就算完成”,而是要持续观测、定期检查偏移量,把它当成基础设施的一部分认真对待。
前两年有一次线上告警,业务同事反馈订单接口突然大量超时。我登上去一查,应用日志和数据库日志的时间戳差了四分钟,SSL握手的“not valid yet”直接把请求全拒了。从那次之后,我每到一个新环境,第一件事就是先看时间同步机制是不是够健壮。CentOS这类Linux服务器,早期大家习惯装ntpd,现在从CentOS 7开始默认集成的是chrony,CentOS 8/9更是直接把它放进base源里。这篇文章要聊的“基于CentOS搭建Chrony时间同步服务器”,就是把服务端、客户端、验证和排错整条链路掰开讲清楚,适合刚接手Linux服务器的新手,也适合想把内网时间源规范化的人。1. 为什么这年头还要自己搭时间同步服务器1.1 时间漂移对业务的影响范围服务器时间不准,表面看不是什么大事,但一旦出了事,全部是隐蔽而致命的问题。我整理了一张对照表,都是实际生产里遇到过的场景:问题场景表现后果日志分析多台机器时间偏差,日志顺序错乱排查问题时无法按时间线还原故障链路,浪费大量时间SSL/TLS证书校验客户端证书或服务端证书被判“未生效”或“已过期”HTTPS握手直接失败,接口大面积报错定时任务cron表达式按本地时间触发,机器时间不准任务提前或延后执行,数据导出、备份窗口错乱数据库主主/集群各节点时间差异过大复制冲突、自增主键判断异常,严重时集群脑裂监控与计费指标时间戳不一致监控曲线很难横向对比,按时间计费的服务出现账单偏差很多人觉得“服务器时间不准,手动date改一下不就行了”,但手动改时间只能撑一阵。操作系统里有硬件时钟RTC和系统时钟,系统时钟靠晶振驱动,晶振本身有频率误差,加上温度变化、CPU负载波动,时间会持续漂移。只有靠NTP协议持续校正,才能让系统时间保持在一个稳定基线。1.2 从ntpd到chrony:CentOS默认时间服务的切换逻辑早期的CentOS 6时代,大家默认用ntpd,也就是传统的NTP守护进程。ntpd工作得并不差,但它有两个明显的短板:一是启动后首次同步很慢,遇到初始偏差较大的场景要熬很久才能收敛;二是它对网络断断续续的环境适应能力差,在虚拟机挂起恢复、笔记本睡眠唤醒、网络临时断开又重新连上这些场景下,经常出现长时间无法锁定时间源的情况。CentOS 7开始,系统默认安装的就是chrony,同时保留了对ntpd的兼容支持。等到CentOS 8和CentOS Stream 9,基础仓库里干脆不再提供传统的ntp包,chrony成了唯一默认选项。chrony由两个核心组件组成:后台守护进程chronyd负责实际的时间获取和同步计算,命令行客户端chronyc负责查看状态和下发指令。它最大的特点是“反应快”:初次同步通常几秒内就能完成,时钟偏差较小时通过微调频率慢慢校正,偏差较大时可以通过makestep直接步进。对现代数据中心的虚拟化环境,这种设计明显更实用。1.3 什么时候真的需要自建内部时间源很多读者会问:既然CentOS默认就能连公网NTP池,为什么还要自己搭一台时间服务器?我的判断标准有三条:如果内网服务器无法访问外网,或者外网访问质量极差,那必须在内网搭一台时间源,让所有设备都指向它。如果有几十台甚至几百台服务器、虚拟机、网络设备,让它们各自连接公网NTP,每台机器的网络延迟和抖动不同,最终时间基线会有细微差异。搭一台统一的时间服务器,所有客户端指向同一个源,整体一致性要好得多。如果部门里有审计、合规要求,需要可追溯的统一时钟源,自建NTP服务并提供访问日志,也是常见做法。反过来,如果你只有三五台服务器,又能正常访问公网,直接用默认配置连接公共NTP池完全够用,不必增加维护节点。自建时间服务器是“规模上来之后”才值得做的事。2. 部署前先摸底:版本差异、端口与已有服务检查2.1 CentOS版本和chrony包的关系不同CentOS版本对chrony的支持细节不完全一样,先看清楚自己手上的系统是哪个版本再动手,能省掉很多不必要的折腾。系统版本默认时间服务包管理器常见chrony版本CentOS 7chrony(默认安装)yum3.xCentOS Stream 8chrony(默认安装)dnf3.xCentOS Stream 9chrony(默认安装)dnf4.xCentOS 7的chrony虽然版本偏老,但基础功能和配置语法与新版基本一致,本文的配置在三个版本上都能用。CentOS Stream 9里的chrony 4.x支持了更多硬件时间戳相关的选项,对于追求极致精度的场景更友好,普通内网环境用不上。安装命令很简单:# CentOS 7使用yum,CentOS 8/9使用dnf yum install -y chrony # 或者 dnf install -y chrony安装之后不要急着做任何配置,先确认base源正常,然后检查一下系统里有没有残留的旧时间同步工具。2.2 安装前要确认的三件事第一件事:检查是否已经存在ntpd或chronyd。新装的系统通常没有启动冲突,但很多服务器是运维从旧版本升级上来的,可能同时留有ntpd服务和旧的ntpdate定时任务。如果chronyd和ntpd同时运行,它们会互相抢占端口、互相校正,出现严重的时钟抖动。所以部署前先执行:systemctl status chronyd systemctl status ntpd rpm -qa | grep -E "chrony|ntp"如果发现ntpd在运行,先停掉并禁用:systemctl stop ntpd systemctl disable ntpd如果发现crontab里有调用ntpdate的定时任务,建议直接删除。ntpdate是“粗暴式”校时,会和chronyd的平滑校正机制打架,时间会反复跳变。第二件事:确认UDP 123端口没有被防火墙挡掉。NTP协议的传输层是UDP,服务端和客户端都使用123端口。CentOS默认开启了firewalld,如果没有放行,外部客户端会一直拿不到时间。检查命令:firewall-cmd --list-all ss -ulnp | grep 123如果服务已经启动但ss看不到UDP 123监听,多半是chronyd启动失败,或者端口被占用了。第三件事:确认服务器有固定IP。自建时间源最忌讳DHCP动态分配,一旦IP变了,所有写死旧IP的客户端会瞬间失去时间源。给时间服务器配静态IP,并把主机名和IP的对应关系写进内部DNS,这是很多教程不会特意强调、但实际中特别重要的一点。2.3 装好后先看状态,别急着改配置安装完chrony并启动服务后,先跑一遍基本验证:systemctl enable --now chronyd systemctl status chronyd chronyc tracking journalctl -u chronyd --no-pager | tail -n 20chronyc tracking输出的核心字段是“System time”,表示系统当前时间与参考时间源之间的偏差。如果刚启动能看到较大的偏差值,不用着急,等一两分钟再看,通常会自动收敛。如果journalctl日志里出现Can't synchronize: no selectable sources,说明没有可用时间源,后续就要按第5章的排查思路去看防火墙和上游可达性了。3. 服务端配置逐行拆解:pool、allow、local与日志3.1 一份可以直接抄的server配置修改/etc/chrony.conf前,建议先备份原文件。下面是适合大多数内网场景的服务端配置:# 上游时间源,使用CentOS默认公共NTP池域名 pool 2.centos.pool.ntp.org iburst # 当本机无法同步任何上游时,仍以本地时间对外提供服务 local stratum 10 # 保存时钟漂移率,重启后避免从头学习 driftfile /var/lib/chrony/drift # 初始3次轮询中,如果偏差超过1秒,允许直接步进 makestep 1 3 # 默认拒绝所有客户端 deny all # 只允许内网指定网段访问 allow 192.168.10.0/24 # 管理命令通道只绑定本机,防止局域网其他机器下发管理指令 bindcmdaddress 127.0.0.1 # 让内核把系统时间与硬件时钟RTC定期同步 rtcsync # 开启日志,方便之后排错 logdir /var/log/chrony log measurements statistics tracking把这个配置里面的网段192.168.10.0/24换成你自己的实际内网网段,上游地址换成你实际能访问的公共NTP或上级时间源,然后重启:systemctl restart chronyd systemctl enable --now chronyd3.2 关键参数背后的设计逻辑pool和server的区别要搞清楚。server后面跟单个IP或主机名,一般用于明确知道唯一上游地址;pool后面跟域名,chrony会通过DNS解析得到多个地址,自动选择可用性最高的上游。自建时间服务器建议用pool配合iburst,iburst的作用是首次启动时快速连发多个请求,缩短初始同步时间,而不是按默认间隔慢慢等。local stratum 10这个参数很多人不理解。NTP协议用“层级”(stratum)表示时间源距离原子钟的跳数,公共NTP服务器一般是一层或二层,客户端是三层。数值越小,优先级越高。local stratum 10的意思是:当本机因为网络故障完全失去上游时,chrony会宣称自己可以作为第10层的时间源,继续为内网客户端提供服务。10这个值比真实上游的层级大,所以正常同步时不会干扰NTP层级排序;只有在失去上游连接后,本地层级才会被启用。这样内网设备至少能拿到一个连续可用的时间参考,而不是直接失去时间源。deny all和allow的顺序值得单独说。chrony对访问规则的处理方式和很多防火墙不一样,它按顺序匹配,最后匹配的规则优先。如果配置写成allow 192.168.10.0/24然后再写deny all,那deny all会覆盖前面的allow,所有客户端都会被拒绝。所以正确姿势是先deny all,再把allow写在后面。这也是很多人配置看起来没问题、实际客户端连不上的经典原因。bindcmdaddress 127.0.0.1是限制chronyc管理命令通道的监听地址。chrony除了NTP端口,还有一个用于管理命令的通道,默认UDP 323端口。如果不限制,内网任何机器都能用chronyc向服务端下发配置修改指令,这是一个容易被忽略的安全隐患。对于纯NTP服务节点,管理通道绑定到本机就够了。3.3 启动后的第一轮验证:怎么看有没有真正成为时间源配置完先看服务端自己的状态:chronyc sources -v输出里如果看到^* 2.centos.pool.ntp.org这样的行,^*表示已经成功同步到该源。^?表示当前不可达,^+表示候选源但尚未主用。接着看chronyc tracking,如果Leap status显示Normal,说明系统时间处于正常同步状态。确认服务端自己已同步后,再用chronyc clients看本机是否被客户端请求过:chronyc clients这个命令只有服务端能看,输出里会列出请求客户端的IP、发送请求的速率等信息。如果客户端已经指向了新搭建的源,这里应该能看到对应IP。最后再确认端口监听:ss -ulnp | grep 123看到chronyd进程监听UDP 123,说明服务端对外服务已正常。到这一步,服务端基本就算立住了。4. 客户端接入:从一根命令到批量部署4.1 客户端最小配置客户端的chrony配置可以非常精简,仍然在/etc/chrony.conf里操作:# 指向内网时间服务器,把IP换成你的实际服务器地址 server 192.168.10.1 iburst # 保存漂移率 driftfile /var/lib/chrony/drift # 初始同步时允许步进 makestep 1 3 # 保持系统时间与硬件时钟同步 rtcsync这里必须强调一点:如果客户端原本有指向公网NTP池的pool或server配置,一定要注释掉或删除。我见过不少案例,客户端同时配置了内网源和公网源,chrony会在两个源之间反复横跳,最终时间一直处于不稳定状态。因为内网源和公网源的网络延迟不同,而且层级结构会让客户端不知道优先选谁。既然内网已经有了统一时间源,客户端就只保留一个指向内网的源,这是最干净的做法。4.2 验证是否真正同步的三种方式配置完成后重启chronyd:systemctl restart chronyd验证方式有三种,层层递进。第一种,看来源状态:chronyc sources -v如果输出里有^* 192.168.10.1,说明客户端已经成功锁定内网时间源。如果显示^?,说明客户端发出去的NTP请求没有得到回应,需要回到服务端检查防火墙和allow规则。第二种,看系统全局状态:timedatectl重点关注System clock synchronized: yes这一行。如果显示yes,说明系统已经确认时间同步正常;显示no则还需要排查。第三种,看当前偏差:chronyc trackingSystem time字段显示的是本地系统时间与参考时间源之间的误差。正常稳定状态下应该小于1毫秒。刚完成同步时稍大一些是正常的,过几分钟再看就会稳定下来。4.3 批量部署与配置模板生产环境里不可能一台一台手动改配置文件,建议用配置管理工具批量下发。以Ansible为例,写一个简单的playbook:- name: Deploy chrony client config hosts: all tasks: - name: Install chrony package: name: chrony state: present - name: Copy chrony.conf template: src: chrony.conf.j2 dest: /etc/chrony.conf notify: restart chronyd - name: Ensure chronyd is running service: name: chronyd state: started enabled: true handlers: - name: restart chronyd service: name: chronyd state: restarted模板文件里把服务器IP作为变量填充,例如在Jinja2模板中写server {{ ntp_server }} iburst,在inventory或group_vars里定义ntp_server变量。这样新增机器时,只要指定变量就能自动同步时间配置。还有一种使用场景是Windows客户端。Windows自带的时间服务w32tm也可以指向内网NTP源:w32tm /config /manualpeerlist:"192.168.10.1" /syncfromflags:manual /update w32tm /resync稍微提一下这个是因为很多运维环境是Linux和Windows混合的,统一让Windows也指向同一台时间服务器,可以减少不少排查时间。5. 真实踩坑排查链路:一台明明配好了却始终报^?的服务器5.1 案例一:sources显示^?——上游不可达的完整排查有一次我给一台CentOS Stream 8配置好chrony后,chronyc sources -v输出里始终是^?,看起来就想不通:服务重启了,配置文件也检查过,上游地址能ping通,为什么拿不到时间?我的排查链路是这样的:第一步,看日志:journalctl -u chronyd --no-pager | tail -n 50日志里报No source for synchronization,说明chronyd确实在尝试连接上游,但没有收到有效的NTP响应。第二步,确认UDP端口。NTP走的是UDP,ping用的是ICMP,能ping通不代表UDP 123能通。我在客户端和服务端分别执行:ss -ulnp | grep 123 tcpdump -i eth0 udp port 123 -c 10如果客户端发出请求但服务端或公网源没有响应,用tcpdump看数据包是最直接的。这条命令能抓取UDP 123端口的数据包,观察请求是否发出、响应是否回来。第三步,检查防火墙和安全组。服务端的firewall-cmd --list-all里如果没有123/udp的放行规则,就补上:firewall-cmd --permanent --add-port=123/udp firewall-cmd --reload如果是云主机,还要检查安全组规则是否允许UDP 123入方向。这轮排查下来,大多数^?问题都能解决。如果检查完一切正常但依然^?,尝试在配置里多写几个上游源,比如同时配置两个公共NTP池,避免单一源在高负载或网络故障时无响应。5.2 案例二:allow规则顺序导致客户端被拒另一个高频问题是客户端配置完全正确,服务端也配置了allow,但客户端依然无法同步,chronyc sources -v里显示的不是^?,而是类似^-的拒绝状态。我最后在服务端配置里发现的问题是这样的:allow 192.168.10.0/24 deny all前面已经强调过,chrony的规则是“最后匹配优先”。这个配置里最后匹配的是deny all,于是所有客户端都被拒绝。修复方法很简单,把顺序调换,先deny all再allow:deny all allow 192.168.10.0/24另外还需要检查另一个地址段问题。很多公司的内网不止一个网段,服务器在192.168.10.0/24,客户端却在10.0.0.0/16,这时候allow 192.168.10.0/24覆盖不到客户端,客户端除非有公网源,否则也会一直无法同步。检查客户端实际IP所在网段,确保allow范围准确覆盖。5.3 案例三:虚拟化环境挂起恢复后时间大幅漂移虚拟机的时钟问题比物理机更多。有一次一台KVM虚拟机在快照恢复后,时间直接慢了5分钟。chrony日志里能看到它一直在尝试微调,但5分钟的偏差靠频率微调需要很长时间才能收敛。这里要说清楚makestep 1 3的局限性。它只对“初始3次轮询”生效,也就是说在服务刚启动的前几轮检查中,如果偏差超过1秒,chrony会直接大步跳到正确时间。但系统运行很久之后突然产生巨大偏差,这个参数已经过了生效区间,chrony默认只会通过微调频率来慢慢追。等它追上,业务早就出问题了。遇到这种场景,最直接的方法是手动触发一次即时同步:chronyc makestep在客户端执行后,时间会立即跳到参考时间源对应的值。同时建议在虚拟化平台上为虚拟机启用时间同步功能,比如KVM或VMware中的host时间同步选项,防止挂起恢复后再次大幅漂移。对于精度要求更高的场景,还需要检查rtcsync是否启用以及硬件RTC是否正常工作。虚拟机的RTC往往不够精确,但rtcsync可以让系统时间在每次启动时以硬件时钟为基准,同时chrony校正后的系统时间也会回写RTC,形成良性循环。5.4 排查工具组合速查把上面这些经验整理成一张速查表,遇到问题可以直接按顺序查:现象先看命令预期结果异常方向服务没起来systemctl status chronydrunning查日志上游状态报错chronyc sources -v^*换上游/查防火墙客户端连不上chronyc clients看到客户端IP查allow规则UDP不通tcpdump -i eth0 udp port 123有来有回查安全组/防火墙时间偏差大chronyc trackingSystem time < 1ms手动makestep时间反复跳变timedatectlsynchronized yes查是否存在ntpd冲突6. 长期稳定运行的几个优化点与我的运维习惯6.1 多时间源与主备NTP设计自建NTP服务最忌讳单点。如果整个内网只有一台时间服务器,它一旦挂掉,所有客户端立刻失去时间源。我的建议是至少准备两台:两台时间服务器各自同步公共NTP池,互为独立上游。客户端配置里同时写两个地址:server 192.168.10.1 iburst server 192.168.10.2 iburst这样即使第一台NTP服务器宕机,客户端会自动切换到第二台,实现无缝衔接。两台服务器之间不需要互相同步,因为它们都直接同步公网源,偏差本身就在毫秒级。防火墙策略上,只放行客户端到这两台NTP服务器的UDP 123端口,其余跨网段访问都拒绝,避免把NTP服务暴露到不必要的网络环境里。6.2 用漂移文件与makestep保持环境稳定driftfile文件我会特别保护。它记录的是系统时钟硬件晶振的频率漂移率,chrony每次启动后先读取这个文件,能立刻知道本机时钟的漂移特性,不需要重新花时间学习。正常情况下不要手动删除/var/lib/chrony/drift,否则重启后会经历一段重新学习的“不稳定期”。另外建议在系统重启后的初始化脚本里加一条chronyc makestep,或者在服务启动后执行一次强制同步,确保重启后时间立即对齐,而不是等chrony慢慢收敛。6.3 监控时间偏差:阈值与告警时间同步不是配完就完了,必须纳入监控。我的做法很朴素:在一台监控机上写一个定时任务,定期执行chronyc tracking,把System time字段提取出来,超过100毫秒就告警。如果是生产环境,更推荐用自带系统时钟监控能力的指标采集工具,比如node_exporter提供的node_timex_offset_seconds指标,它反映的是系统时间与硬件时钟/参考时钟之间的偏差。把这个指标接入现有监控系统,设置告警阈值,时间漂移能在影响业务之前就被发现。告警阈值怎么定?一般业务服务器建议100毫秒以内;涉及金融交易、电力等强一致场景,建议50毫秒甚至更小。不要设置太小,否则网络抖动导致的正常波动会频繁误报。6.4 安全建议与时钟相关小细节NTP服务依赖UDP 123端口,这个端口本身并不“危险”,但如果暴露在公网,可能被外部扫描器探测,也可能被用来探测网络拓扑。我的原则是:NTP服务绝对不对公网开放,只在内网网段内提供服务,并在chrony配置中使用deny all加allow的白名单模式。还有一个和系统初始化相关的细节:Linux系统时钟建议统一使用UTC,硬件时钟也建议保持UTC。如果要和Windows双系统共存,可以使用:timedatectl set-local-rtc 0这个命令关闭本地时间模式,确保硬件时钟以UTC保存,避免两个系统之间时间换算错乱。服务器如果开着set-local-rtc 1,经常会出现重启后时间对不上的问题。日志目录/var/log/chrony如果开启了log measurements statistics tracking,会产生不少文件,但logrotate通常已经默认处理好轮转,不需要手工清理,也不要在日志里写无关内容占用存储空间。我在实际部署中还有个习惯:服务端配置里始终保留至少一个公共NTP池作为兜底上游,同时保留内网主备两台NTP服务器之间的独立同步链路。这样即使某个上游故障,chrony会自动切换,时间不会出现大面积失准。时间同步这件事,从来不应该是“装上就算完成”,而是要持续观测、定期检查偏移量,把它当成基础设施的一部分认真对待。