如何正确地设置 NTP 服务器

Ceph Cluster 的 clock skew detected 问题

这是一个由于 Ceph Cluster clock skew 引发的问题。

我们公司的生产环境里有一个 3 节点的 Ceph Cluster 用来做对象存储使用。运行了一段时间,发现其中一个节点上的 OSD 经常过上个把月就会重启一次,系统也没什么异常,查来查去发现最可疑的就是这个节点有时候会报 ceph:health_warn clock skew detected on mon。由于我们的服务器都设置了每天通过定时任务调 ntpdate 去同步时间,所以这个问题一直没放在心上,不过,既然其它地方找不出问题,只好把这个问题根治了再观察。

修正 clock skew 的问题,基本上有两种做法,一种是把最大容忍的时钟偏移调大,一种是每个节点都安装 NTP 服务,让节点之间可以精确地保持一致。第一种这种头疼医头,脚疼医脚的方案明显是不可取的。于是,我们在每个节点上都安装了 NTP 服务,由于是内网隔离环境,所以把 Server 都指向了我们内网一台原本就用来作时间同步的 NTP 服务器。

本以为这样就没问题了,可惜没过几天,clock skew detected 的问题又出现了。

NTP Server 配置问题

NTP 服务的安装可能过于简单了,简单到我们认为只要安装了 NTP 时间就一定会被同步。而实际上,除了时间相差太大了,NTP 会拒绝同步以外,NTP Server 的设置也会影响到同步的结果。

NTP 服务本身可以让时间精确到亚秒级的同步,在互联网上精度可以达到十个毫秒左右,理想情况下的局域网内可以达到1个毫秒。不管怎样误差都是不能完全避免的,而且 NTP 服务器是一层传一层,分层传递的,最上面一层精度是最高的,越往下层的服务器其本身的精度也不一定能得到保证。所以 NTP 服务器需要对比不同的上游服务器,才能确定怎样才能得到最准确的时间。这里面可能是一个非常复杂的算法,但是道理非常易懂。

  • 如果只设置了 1 台上游服务器,那只能从这一台服务器去确定时间。
  • 如果设置了 2 台上游服务器,这时就会存在问题,NTP 服务是无法确定哪个是对的。
  • 如果设置了 3 台上游服务器,这时可以三选二,确定一个更准确的时间,但是如果其中的一台上游服务器挂了,那又会回到两台服务器的冲突情况。
  • 如果设置了 4 台上游服务器,则即使有服务器挂了,也不会影响计算

所以,在配置 NTP 服务时,至少要配置 4 台 Server。千万不能只配置两台。而我们内网的 NTP 服务器,恰好配置了两台 Server。不求甚解的我们注释掉了默认的服务器,自己添加了两台服务器地址,还以为万一有一个挂了,另一个还可以做备份。实际上,Ceph 节点的 NTP 服务根本就没有同步上,因为我们自己的 NTP 服务器认为自己的时间并不准确。

如何确认 NTP 服务工作正常?

查看 NTP 是否同步了,有多种方法,比较便捷的是:

> timedatectl status
or
> ntpstat

这两个命令都可以显示 NTP 是否同步了,但实际实用中,我发现并不是很准确。真正准确的是使用 ntpq。使用 ntpq -pn 可以显示当前配置的上游服务器列表,前面有 * 号的表示这是有效的上游服务器,正在从这个服务器同步时间。前面有 + 的表示这也是有效的服务器,作为备选使用。

> ntpq -pn
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
+203.107.6.88    10.137.38.86     2 u   65  128  377    0.616   10.624  58.738
*100.100.61.88   .BD.             1 u   17  128  377    0.123   13.530  63.963
+120.25.115.20   10.137.53.7      2 u  112  128  377   45.060   41.388  71.340
+100.100.3.2     10.137.55.181    2 u  110  128  377   25.113   20.375  58.763

使用 ntpq -c as,显示上游服务器的状态,condition 列中 sys.peer 表示正在同步的服务器,reject 表示拒绝同步的服务器。如果上游服务器时间无法确定,则会显示 reject 不能同步。

> ntpq -c as

ind assid status  conf reach auth condition  last_event cnt
===========================================================
  1 30561  8043   yes    no  none    reject unreachable  4
  2 30562  911a   yes   yes  none falsetick    sys_peer  1
  7 30567  9424   yes   yes  none candidate   reachable  2
  8 30568  963a   yes   yes  none  sys.peer    sys_peer  3

ntpdate 与 NTP 服务同步时间的区别

使用 ntpdate 可以直接使用一个 NTP 服务器进行时间同步,相对于 NTP 服务,ntpdate 简单粗暴了一些。NTP 服务在同步时间时是渐进的,保证一个时间不会出现两次,所以时间偏差较大时,并不会马上同步到最准确的时间,而 ntpdate 则会直接同步时间,并没有这个保证。这在某些情况下是致使的,假如你在 12:00 计划了一个批处理,准时执行了,但是过了一会儿,ntpdate 把时间重新同步到了12:00前,这个计划任务就会被执行两次。

另外一个情况是计时,我们在开发时,如果需要对一个任务进行计时,一般的做法是先获得当前时间,任务执行完毕后,再读取当前时间,减去之前保存的时间,得到任务耗费的时间,这其实并不严谨,因为系统的时间是会变的。严谨的做法是使用 tick count 时间。例如,在 Java 中,System.nanoTime 表示系统启动之后经过的时间,这个时间是个相对值,无法表达当前具体的时间,但是是个无法修改的只能前进的计数。System.currentTimeMillis 表示 1970年1月1日UTC零点之后经过的毫秒数,这是个具体的时间值,但是是基于系统时间的,是可以被人为调整的。

Tick Counting 和 Tickless Timekeeping

计算机是如何计算时间的呢?通常计算机都有一个带电池的 CMOS 计时器,即使关机了,时间也不会丢失。操作系统启动后,先通过 BIOS 读取这个时间作为起始时间,然后操作系统会保留一个计数器来记录启动后过去的时间,起始时间加上启动后过去的时间,便是当前时间。所以说,操作系统启动后,存在两个时间,一个硬件时间,一个计数时间,启动完毕后,两者并没有什么关联。在 Linux 中,hwclock 就可以用来读取和设置硬件时间。

很多 PC 采用 Tick Counting 的方式来计数,其原理是让硬件按固定频率发生中断,操作系统处理这些中断并计数,这样便得到了当前的时间。在过去,这个开销并不算大,但是在虚拟化时间,这就是一个问题了,设想一下,一个虚拟机本没有什么工作负载,但是因为计时器的中断,仍然需要不断地唤醒它,同时还要在不同虚拟机的上下文之间切换,这无疑是一笔很大的浪费。

越来越多的 PC 采用 Tickless Timekeeping 的方式来计时,其原理是使用硬件来计数,这样就不需要耗费 CPU 的中断,但是必须有硬件支持才行。