ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux时间管理:硬件时钟、系统时钟与时区配置及同步实战

Linux时间管理:硬件时钟、系统时钟与时区配置及同步实战 1. 被搞混的三块表Linux 的时间到底存在哪刚接手服务器运维那会儿我最怕看到的就是日志里时间和现实对不上。用户说我下午三点提交的订单日志里怎么是早上七点第一反应总是去调系统时间结果调完发现应用层还是错的来回折腾一晚上。后来才明白Linux 里的时间根本不是一个值而是被拆成好几层在管每一层由不同组件负责你改错了层命令跑完看着没问题业务该错还是错。这一章先把底层的账算清楚。理解这三块表的分工之后后面所有命令你都能自己推导出该敲哪一条而不是背命令。1.1 硬件时钟、系统时钟、时区规则是三个独立的东西先把三个概念摆开。**硬件时钟RTC**是主板上那颗纽扣电池养着的芯片机器断电它照样走。它只记录一个绝对的时间点本身不带时区属性至于这个数值代表 UTC 还是本地时间完全取决于操作系统往里写了什么。系统时钟是内核在内存里维护的计时器开机时从 RTC 读一次初值之后靠时钟中断和定时器自己累加跟 RTC 就没关系了。date命令读到的就是这个。时区规则则是第三个东西它本质上是一张查找表放在/usr/share/zoneinfo/目录下。这张表告诉你某个绝对时间点在东八区应该显示成几点几分。它不改变时间本身只改变翻译的结果。打个比方世界时间像一条没有起点也没有终点的直线时区就是给你配的一副眼镜。换眼镜能让你看到不同的读数但那条线本身一动没动。很多人的误区在于把换眼镜和挪直线当成同一件事。1.2 改时区不会改动时间值改时间值也修不好时区这条推论很重要直接决定了排查方向。执行date看到的是把系统时钟的绝对值按照当前时区规则格式化之后的字符串。你改时区date的输出会从07:30变成15:30但底层的 epoch 秒数1970-01-01 00:00:00 UTC 起到现在的秒数分毫不差。所有依赖这个绝对值的地方比如文件 mtime、数据库写入的时间戳、日志的排序依据都不会因为你换了时区而变化。反过来也一样。如果你发现date显示的时间点本身就是错的比如比标准时间慢了三分钟那跟时区一点关系都没有这是时间同步没做好属于另一套机制的事。所以排查时区问题第一个要问的是错的是偏移量还是绝对值差整整 8 小时、16 小时、或者半小时印度 05:30基本可以锁定时区配置。差几秒到几分钟那是时钟漂移去查同步服务。差得毫无规律、每次重启都变那要去查 RTC 是不是没电了或者被写成了本地时间。1.3 为什么推荐让 RTC 存 UTCtimedatectl的输出里有一行RTC in local TZ它回答的就是硬件时钟里存的是 UTC 还是本地时间。推荐值是no也就是存 UTC。原因不复杂UTC 是连续的、没有跳变的。而本地时间会因为你所在地区的规则调整而出现重复或缺失的时间点最典型的就是有些地区每年切换夏令时凌晨两点会变成三点或者两点会重复出现两次。如果 RTC 存的是这种有洞的时间系统在做时间换算和恢复时就必须依赖额外信息去猜容易出岔子。中国大陆不使用夏令时所以这个坑平时不太容易碰到但如果你管理的机器分布在不同地区或者机器上还装着另一个把 RTC 当本地时间用的系统问题就会冒出来。典型症状是两个系统来回切换每次切完时间都要偏上几个小时切回去又对了。处理办法很简单把两边都统一成RTC 存 UTC谁也别自作主张。2. 动手之前三条命令摸清当前状态我见过太多人上来就timedatectl set-timezone改完发现没生效又开始怀疑人生。实际上改之前花一分钟看清楚现状能省掉后面半小时的瞎猜。2.1 交叉验证date、timedatectl、hwclock 各看一层三条命令看的是三个不同的层面缺一不可。命令读取对象你要关注什么date系统时钟 时区格式化结果偏移量对不对末尾的时区缩写是什么timedatectl系统时钟、时区、NTP 状态、RTC 模式Time zone行、NTP service行、RTC in local TZ行hwclock --show硬件时钟原始值和date -u的输出是否接近timedatectl的典型输出长这样$ timedatectl Local time: Wed 2024-05-22 15:30:12 CST Universal time: Wed 2024-05-22 07:30:12 UTC RTC time: Wed 2024-05-22 07:30:12 Time zone: Asia/Shanghai (CST, 0800) System clock synchronized: yes NTP service: active RTC in local TZ: no这六行信息基本把前面讲的三块表全覆盖了。Local time和Universal time差 8 小时说明时区是东八区RTC time和Universal time一致说明 RTC 存的是 UTC这是理想状态。System clock synchronized: yes说明同步服务已经完成过一次校准。如果Local time显示的是 UTC 时间而Time zone行写着Etc/UTC或UTC那就是时区没设置这是新装系统最常见的情况。2.2 时区配置到底存在哪几个文件里不同发行版、不同年代的方案不一样我把常见的几处列一下。路径作用常见发行版/etc/localtime时区数据文件副本或软链接几乎所有 Linux/usr/share/zoneinfo/时区规则数据库几乎所有 Linux/etc/timezone纯文本时区名Debian / Ubuntu/etc/sysconfig/clock启动脚本读的配置老版 RHEL / CentOS 6TZ环境变量进程级覆盖优先级最高全部glibc 在格式化时间时的查找顺序是先看进程的TZ环境变量有了就直接用没有就去读/etc/localtime。所以/etc/localtime是真正决定绝大多数程序显示结果的文件/etc/timezone更像是给工具和别的程序看的声明两者最好保持一致不然timedatectl和实际表现可能对不上。可以用这两条命令确认现在指向哪个时区readlink -f /etc/localtime # 输出/usr/share/zoneinfo/Asia/Shanghai cat /etc/timezone # Debian/Ubuntu 系如果/etc/localtime是个普通文件而不是软链接说明它是被复制过来的这本身没问题只是改的时候要重新复制一份。2.3 容器、虚拟机、云主机的差异要先想明白同一个命令在不同环境里的效果可能完全不同这点必须提前想清楚。物理机最直接timedatectl一次性把系统时钟、时区、RTC 全管了改完持久生效。云主机通常不让你碰 RTC虚拟化层管的set-local-rtc可能无效或者报错但系统时钟和时区照常能改。部分云厂商的镜像还会在启动时从元数据服务同步时间改完时区重启一下会更保险。容器是最容易被忽略的。容器的时区默认继承镜像跟宿主机没有必然关系。你宿主机改成了东八区容器里date照样是 UTC这是两套独立配置。这一点在排查为什么同一台机器上应用和系统时间不一致的时候是头号原因。3. 四种场景对应四套改时区的实操方案方案的选择逻辑很简单有 systemd 就用 timedatectl没有就手工搞文件容器就在镜像里固化机器多就批量推。3.1 主流发行版timedatectl 一步到位CentOS 7 以后、Ubuntu 16.04 以后、Debian 9 以后、Fedora、openSUSE 这些带 systemd 的系统统一用sudo timedatectl set-timezone Asia/Shanghai执行完立刻用date验证$ date Wed May 22 15:32:04 CST 2024末尾出现CSTChina Standard Time就对了。这条命令背后做了什么它把/usr/share/zoneinfo/Asia/Shanghai链接或复制到/etc/localtime同时更新/etc/timezone如果存在再通知 systemd 刷新各服务的时区环境。所以它比手工建软链接更完整能覆盖到一些只认/etc/timezone的程序。想要列出所有可选时区timedatectl list-timezones | grep -i shanghai timedatectl list-timezones | grep -i asia输出是一屏一屏的可以配合grep或者less看。时区名的命名规则是大洲/城市用城市名而不是国家名因为同一个国家可能有多个时区。提示set-timezone只改时区不改时间不需要也不会导致时间跳变。但如果你之前把时间手动调歪过改完时区后会发现歪得更明显那是另一回事接着看第 4 章。3.2 没有 systemd 的老系统手工建软链接CentOS 6、Debian 8 这类系统或者某些精简过的定制系统没有timedatectl就按老办法来sudo ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime顺手把声明文件也写一下echo Asia/Shanghai | sudo tee /etc/timezoneCentOS 6 还要改启动配置sudo sed -i s#^ZONE.*#ZONEAsia/Shanghai# /etc/sysconfig/clock如果文件里没有ZONE这一行就手工加上ZONEAsia/Shanghai和UTCtrue。最后把系统时间写回硬件时钟保证重启后不跑偏sudo hwclock --systohc--systohc的意思是 system to hardware clock方向千万别弄反。反过来--hctosys是从硬件读进系统一般只在排查时用。注意hwclock --systohc之前一定要先确认系统时钟本身是准的至少做过一次同步。否则等于把错的时间固化进硬件重启之后错误会被继承下来排查起来更绕。3.3 容器镜像里的时区固化容器场景要分两步走构建时固化 运行时覆盖。构建时固化以 Debian/Ubuntu 基础镜像为例FROM ubuntu:22.04 ENV TZAsia/Shanghai RUN apt-get update \ DEBIAN_FRONTENDnoninteractive apt-get install -y --no-install-recommends tzdata \ ln -snf /usr/share/zoneinfo/$TZ /etc/localtime \ echo $TZ /etc/timezone \ rm -rf /var/lib/apt/lists/*这里有两个细节值得说。第一DEBIAN_FRONTENDnoninteractive必须加否则tzdata安装过程中会弹出交互式选择时区的界面在 CI 里直接把构建卡死。第二apt安装完之后顺手清掉缓存镜像体积能小几十兆。如果用 Alpine 做基础镜像命令不一样FROM alpine:3.19 RUN apk add --no-cache tzdata \ cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \ echo Asia/Shanghai /etc/timezone ENV TZAsia/ShanghaiAlpine 默认不带时区数据库不加tzdata包的话/usr/share/zoneinfo目录是空的软链接会指向一个不存在的文件程序读到空值退回 UTC症状很隐蔽。运行时覆盖不想重建镜像的时候可以直接挂载宿主机的时区文件docker run -d \ -v /etc/localtime:/etc/localtime:ro \ -v /etc/timezone:/etc/timezone:ro \ -e TZAsia/Shanghai \ your-imageKubernetes 里则是挂hostPathspec: containers: - name: app env: - name: TZ value: Asia/Shanghai volumeMounts: - name: tz-config mountPath: /etc/localtime readOnly: true volumes: - name: tz-config hostPath: path: /usr/share/zoneinfo/Asia/Shanghai type: File这里我建议同时设置TZ环境变量和挂载文件。原因是有相当一部分语言运行时和框架优先读TZJava、Go、Node.js 都这样而另一些只读/etc/localtime两边都配上就不用逐个去猜了。3.4 机器多的时候循环和 Ansible 两种推法三五台机器手工改还能接受上到几十台就必须批量。最朴素的做法是写个循环#!/bin/bash while read -r host; do echo $host ssh -o ConnectTimeout5 $host \ sudo timedatectl set-timezone Asia/Shanghai date done hosts.txthosts.txt一行一个主机名或 IP。加-o ConnectTimeout5是防止某台机器挂掉导致整个循环卡住。稍微正规一点用 Ansible一条 task 搞定- hosts: all become: yes tasks: - name: 设置时区为东八区 community.general.timezone: name: Asia/ShanghaiAnsible 的timezone模块会自动判断目标机有没有 systemd有就用timedatectl没有就退回软链接方案比手写 shell 省心。批量操作的经验是先拿一台非核心机器试确认输出正常再全量。并且要在执行记录里保留每台的date输出方便事后回溯到底哪台没改成功。我有一次批量推了三十台其中一台因为 sudo 免密没配好静默失败了一周后才在日志里发现这种漏网之鱼靠人眼是盯不过来的。4. 时区对了时间还是不准问题出在同步上时区改完只是让显示对了时间本身的准确度是另一套机制在管。这两件事经常被混为一谈所以分开讲。4.1 不同步会发生什么比你想的严重服务器上的晶体振荡器精度有限便宜的硬件一天漂几百毫秒很正常一个月下来偏几十秒到几分钟都不稀奇。这个量级看起来无所谓但实际影响不小。集群内部多台机器时间不一致会导致日志分布在时间轴上错位排查问题时按时间戳排序事件的顺序完全乱套因果关系都理不清。分布式系统里做版本比较、租约判断、故障检测很多逻辑都隐含时钟大体一致这个前提。对外交互HTTPS 证书的有效期校验、签名请求的时间戳校验一般都允许几分钟的偏差。如果服务器时间偏了几个小时接口会直接拒绝报的错还往往是签名无效这类让人摸不着头脑的信息。数据库主从复制、基于时间戳的增量拉取、定时任务的触发都依赖时钟。两台机器差几秒基于时间窗口的增量同步就可能漏数据。这也是为什么做增量同步的工具第一步都是先校时。所以同步不是可选项是标配。4.2 chrony目前最省心的选择chrony 是现在主流发行版默认的时间同步服务相比老一代的 ntpd它在虚拟机和网络波动环境下的收敛速度更快对间歇性联网的机器也更友好。先装再配# RHEL / CentOS / Fedora sudo yum install -y chrony # Debian / Ubuntu sudo apt-get install -y chrony配置文件是/etc/chrony.confDebian 系路径是/etc/chrony/chrony.conf核心几行这样写# 时间源按网络延迟从近到远排 server ntp.aliyun.com iburst server ntp1.aliyun.com iburst server ntp2.aliyun.com iburst # 漂移记录文件帮助重启后快速收敛 driftfile /var/lib/chrony/drift # 前三次校时允许直接跳变之后只做缓慢微调 makestep 1.0 3 # 把系统时间定期写回硬件时钟 rtcsync # 允许哪些网段作为客户端来同步按需内网统一校时时用 allow 192.168.0.0/16 # 日志目录 logdir /var/log/chrony几个参数值得展开说。iburst的作用是在启动时快速发一批请求把首次同步的时间从默认的几十秒压缩到几秒。这个选项几乎总是该加。makestep 1.0 3是最关键的。chrony 默认采用渐进式调整也就是把时间偏差摊到一段时间里慢慢抹平好处是时间单调递增、不会倒退对程序友好。但如果偏差本来就很大比如刚开机差了几小时慢慢抹就要等很久。这行的意思是前 3 次校准时如果偏差超过 1 秒就直接跳变纠正3 次之后老老实实渐进调整。这是一个兼顾快速收敛和后期平稳的折中。rtcsync让 chrony 定期把当前时间写进硬件时钟省去了你手动hwclock --systohc的麻烦。配好之后启动并设为开机自启sudo systemctl enable --now chronyd sudo systemctl status chronydDebian/Ubuntu 上服务名可能是chrony而不是chronyd用systemctl list-units | grep chrony确认一下。注意如果系统里同时装了 chrony 和老式 ntpd两个服务会互相打架。用systemctl status ntp检查一下有的话先systemctl disable --now ntp再启动 chrony。另外timedatectl set-ntp true在部分系统上会去启用systemd-timesyncd它和 chrony 是竞争关系二选一就行不要都开。内网环境如果没有外网出口可以自建一台时间服务器在它上面开allow段允许内网访问其他机器把server指向它。这台机器自己再向公网对时形成层级。4.3 systemd-timesyncd轻量场景够用如果机器资源很紧张或者只是一台边缘小主机不想多装一个服务可以用 systemd 自带的systemd-timesyncd。sudo timedatectl set-ntp true sudo systemctl status systemd-timesyncd它的配置文件在/etc/systemd/timesyncd.conf[Time] NTPntp.aliyun.com ntp1.aliyun.com FallbackNTPntp.tencent.com能力上它比 chrony 弱不做漂移记录、调整策略简单、没有丰富的监控接口。但对于只要时间大致准的场景完全够用而且不用额外装包。判断当前实际是哪套在跑timedatectl # 看 NTP service 这一行输出可能是 active 或 inactive systemctl status systemd-timesyncd chronyd chrony 2/dev/null | grep -E Active|Loaded4.4 同步状态怎么读chronyc 输出解读配好了不代表同步成功了必须会看状态。最常用的两条命令$ chronyc sources -v MS Name/IP address Stratum Poll Reach LastRx Last sample ^* ntp.aliyun.com 2 6 377 35 -21us[ -51us] /- 28ms ^ ntp1.aliyun.com 2 6 377 34 12us[ 45us] /- 31ms ^? ntp2.aliyun.com 0 6 0 - 0ns[ 0ns] /- 0nsMS列那一个字符是重点符号含义说明*当前选中的同步源系统正在跟它对齐备选源可用但当前没选它-被排除的源算法认为它不准不参与?不可达网络不通或对方没响应x错误源时间明显异常被拉黑看到^*出现基本就没问题了。如果所有条目都是?先检查网络和防火墙NTP 走的是 UDP 123 端口sudo ss -lunp | grep 123 # 看本机是否在监听 ntpdate -q ntp.aliyun.com # 只查询不修改用来测连通性第二条命令里的-q很重要它让ntpdate只查询不动手。不加-q会直接改系统时间而现在大多数系统上 NTP 服务在跑两者同时改时间容易产生冲突。再看整体状态$ chronyc tracking Reference ID : 5BF1A1A2 (ntp.aliyun.com) Stratum : 3 System time : 0.000021 seconds fast of NTP time Last offset : -0.000012 seconds RMS offset : 0.000034 seconds Frequency : 12.345 ppm slowSystem time是当前和参考源的偏差Frequency是本机晶振的固有偏差率。这个偏差率会被记录进 drift 文件下次重启就能更快对齐不用从零开始。如果偏差特别大又想立刻纠正可以手动来一下sudo chronyc makestep这条命令跳过渐进调整直接跳变。适合刚接手一台时间已经偏了几小时的机器或者在维护窗口里做一次性纠正。日常千万不要脚本化定时执行否则会让时间频繁跳变反而影响应用。5. 踩坑实录几类典型故障的排查路径前面讲的是怎么做对这一章讲做错了怎么找。都是我自己或者周围人真踩过的。5.1 症状对照表从现象反推原因排查效率最高的方式是从现象出发先缩小范围再验证。这张表可以贴在工位上。现象最可能的原因第一步验证时间正好差 8 小时时区未设置为东八区timedatectl看 Time zone 行时间差 8 小时但容器内才这样容器镜像时区未固化进容器date和cat /etc/timezone每次重启后时间回到旧值RTC 未同步或 RTC 存本地时间hwclock --show与date -u对比时间慢几秒到几分钟同步服务未运行或未收敛timedatectl看 NTP service部分机器对部分机器不对批量操作遗漏或配置不一致逐台拉date和timedatectl输出对比应用日志时间和系统时间不一致应用/JVM/数据库自己有独立时区配置查应用启动参数和环境变量时间偶尔跳变或倒退多个校时服务同时在跑systemctl查 ntp、chrony、timesyncd 是否共存时间频繁被拉回某个错误值虚拟化平台在强制同步查宿主机或云平台的时钟同步设置这张表的用法是先看现象落在哪一行再顺着第一步验证去确认不要一上来就改东西。改之前先记录现状改完再对比这样才知道是哪一步起了作用。5.2 日志时间戳错乱的完整排查过程分享一个比较典型的过程。现象是应用日志的时间和date差 8 小时但系统时区明明是东八区。第一步确认系统层面没问题date timedatectl | grep -E Time zone|Local time输出正常Asia/Shanghai本地时间也对。说明问题不在系统。第二步确认应用运行环境。这台上面跑的是 Java 服务ps -ef | grep java | head -1 # 看启动参数里有没有 -Duser.timezone cat /proc/pid/environ | tr \0 \n | grep -i tz/proc/pid/environ这个技巧很实用它能看到进程启动时实际继承的环境变量比翻启动脚本可靠因为脚本里可能被覆盖过。结果发现TZ没设置但系统时区是东八区按理说 Java 会读/etc/localtime。第三步检查 JVM 的实际时区。写一段最简单的代码打印System.out.println(java.util.TimeZone.getDefault().getID()); System.out.println(new java.util.Date());输出是Etc/UTC。到这一步就清楚了Java 在启动阶段读/etc/localtime的时机可能早于 systemd 完成时区初始化或者是某个基础镜像里带了/etc/timezone写了UTCJava 优先读了那个。解决办法有两个任选其一我一般两个都做# 启动参数显式指定 -Duser.timezoneAsia/Shanghai # 或者环境变量 export TZAsia/Shanghai顺带说这一类应用自身时区的坑在几种技术栈里的表现技术栈时区来源推荐做法Java / JVM-Duser.timezoneTZ/etc/localtime启动参数显式指定Node.jsTZ环境变量容器里 ENV 设置PythonTZ环境变量需调用time.tzset()程序启动时显式设置Go编译时嵌入或运行时读TZ、/etc/localtime容器里同时挂载文件和设 ENVMySQLdefault-time-zone配置 系统时区配置里写死08:00Go 有个细节比较绕它会把时区数据编译进二进制如果编译环境和运行环境不一致可能出现同一份代码在两台机器上显示不同时间。稳妥做法是容器里同时挂载/etc/localtime并设置TZ。5.3 数据库的时间字段TIMESTAMP 和 DATETIME 差别很大数据库这块单独拎出来说因为它的行为和文件系统、应用层都不太一样。MySQL 里TIMESTAMP和DATETIME两种类型在时区处理上完全不同。TIMESTAMP是带时区语义的写入时按当前会话时区转成 UTC 存储读取时再按当前会话时区转回来。DATETIME则是不带时区语义的写什么存什么。这就导致一个很隐蔽的现象同一份数据你在时区改了之前写入、之后读取TIMESTAMP字段的值会跟着变而DATETIME不变。如果业务代码里两种类型混用改时区的时候就会出现一部分数据看起来对了另一部分没变的诡异情况。先看当前配置SELECT global.time_zone, session.time_zone, NOW(), UTC_TIMESTAMP();如果time_zone显示SYSTEM意思是跟随操作系统的时区。服务器时区改了这个也会跟着变行为就变得不可预测。推荐在配置里写死[mysqld] default-time-zone 08:00改完重启生效。写死的好处是不管宿主机时区怎么变数据库的行为始终确定排查问题时变量少一个。写入时间也建议统一用 UTC-- 存 UTC展示时由应用层转换 INSERT INTO orders (created_at) VALUES (UTC_TIMESTAMP());或者干脆在应用层用带时区的类型Java 的Instant、OffsetDateTime把时区问题收敛到展示层。这种做法在跨地区业务里几乎是必需的。另外MySQL 主从复制对时钟的敏感性也要留意。基于时间戳做增量拉取的工具如果主从机器时间不一致很容易出现漏读或重复读。做这类同步之前先确认两端时钟偏差在毫秒级再动手配同步策略能省掉大量对不上账的麻烦。5.4 容器内外时间不一致的三个检查点容器相关的时区问题我总结了三个固定检查点按顺序走基本能定位。检查点一容器内的/etc/localtime指向什么。docker exec -it container date docker exec -it container cat /etc/timezone docker exec -it container ls -l /etc/localtime如果/etc/localtime是个指向/usr/share/zoneinfo/Etc/UTC的软链接或者/usr/share/zoneinfo里压根没有 Asia 目录那问题就在这。检查点二容器的TZ环境变量。docker exec -it container env | grep -i tz没有输出或者输出TZUTC就要在编排文件里补上。检查点三宿主机的时区文件挂载是否正确。docker inspect container --format {{json .Mounts}} | python3 -m json.tool看/etc/localtime是不是以只读方式挂进来了Source指向的宿主机路径是否存在。挂载一个不存在的路径容器里会创建一个空目录程序读到空文件不会报错只会默默退回 UTC这是最难发现的一种。提示用-v /etc/localtime:/etc/localtime:ro挂载时如果宿主机后来改了时区已经运行的容器不会自动跟着变因为 bind mount 建立时绑定的是那个文件。稳妥做法是让容器读TZ环境变量或者重建容器。6. 生产环境做时区变更流程和习惯在小机器上改时区是小事在生产集群上是变更得有流程。这一章聊聊我从几次不太愉快的经历里攒下的做法。6.1 变更前必须确认的三件事第一件确认变更窗口和影响面。改时区本身不会让时间跳变这一点前面解释过原理但改同步策略、执行chronyc makestep、或者用timedatectl set-time手动调时间是会让时间跳变的。跳变的影响面比想象中大依赖时间序列的监控会画出断崖、定时任务可能被触发两次或漏掉一次、基于时间戳的增量任务可能丢数据。所以我的做法是把变更拆成两步先只改时区无风险观察一个业务周期确认没问题之后再单独安排窗口处理时间同步的纠正。第二件确认所有机器的基线一致。不要假设大家都是默认配置。我在一台机器上遇到过/etc/timezone写的是Asia/Shanghai但/etc/localtime指向Etc/UTC的情况两个文件打架不同程序读不同的文件表现完全随机。变更前把所有机器的这几个文件都拉一遍做成表格对比心里有底。第三件准备好回滚。记录变更前的状态{ date timedatectl ls -l /etc/localtime cat /etc/timezone 2/dev/null hwclock --show 2/dev/null } /tmp/tz_backup_$(date %s).log这份日志存在本地别放临时目录太深的地方。真出问题了照着它反着敲一遍就能回去。6.2 监控里给时间加几条规则时间是个不出事就没人看、一出事就是大事的指标值得单独监控。最基本的几条规则监控项采集方式告警阈值建议与参考源的时间偏差chronyc tracking里的 System time超过 500ms 提醒超过 5s 告警NTP 服务是否存活systemctl is-active chronyd非 active 立即告警是否存在多个校时服务检查 ntp / chronyd / timesyncd 进程超过一个就告警时区配置是否符合预期读/etc/timezone或readlink /etc/localtime与基线不一致即告警硬件时钟与系统时钟偏差比较hwclock --show和date -u超过 5 分钟告警第一条用 Zabbix 或者 Prometheus 的 textfile collector 都能采chronyc tracking的输出用awk切一下就行chronyc tracking | awk /System time/ {print $4*1000} # 输出毫秒级的偏差值最后一条容易被忽略但很有用。它能在 RTC 电池快耗尽的时候提前发现比等到机器断电重启后时间回到几年前要好得多。6.3 我自己固定的几条操作习惯最后说几个习惯都是踩坑之后养成的。习惯一改之前先记录改之后立刻验证。不要连续敲三条命令然后一起看结果中间任何一步出错都会污染后面的判断。一条一条来每条都看一眼输出。习惯二配置文件的改动一律走版本管理。/etc/chrony.conf这种东西我会在改之前先cp一份带时间戳的备份改完diff一下确认只动了想动的地方。批量环境下就走配置管理工具下发不在机器上手工编辑。习惯三容器镜像里的时区固化写进基线镜像。不要指望每个业务团队自己记得加tzdata和ENV TZ这种事交给基础镜像统一做业务镜像继承下来就有了。我在一个团队推行过这个做法之后容器时间相关的问题从每月好几次降到基本归零。习惯四不轻易用timedatectl set-time。除非是在完全隔离的测试环境里做实验生产环境我宁可用chronyc makestep让同步服务自己处理。手动设时间等于人为制造了一次跳变还把 NTP 的调整逻辑打乱了。习惯五遇到时间不对先问三句话。第一句是偏移量错了还是绝对值错了第二句是系统层还是应用层第三句是宿主机还是容器这三句问下来八成的问题不用敲命令就能猜到大概方向。还有一个后续可以扩展的方向如果你管理的机器规模上到几百台可以考虑在内网自建两到三台时间服务器让它们作为内网的统一参考源其余机器都指向它们。这样既减少对外网的依赖也让整个集群的时间偏差收敛到很小的范围内。配置时注意给这几台机器配上稳定的硬件、固定在同一个机房区域、并且互相之间也做交叉校验避免参考源本身就是错的这种尴尬情况。
返回列表