如何在不知道产品中使用的 ntp 客户端的情况下检查系统时间是否与 NTP 服务器同步?

如何在不知道产品中使用的 ntp 客户端的情况下检查系统时间是否与 NTP 服务器同步?

如何在不知道产品中使用的 NTP 客户端的情况下检查系统时间是否与 NTP 服务器同步?我正在开发一个应用程序,该应用程序预计在容器或独立系统中运行。我的应用程序需要确保在尝试执行某些操作之前系统时间已同步。但是,即使主机操作系统中使用了一个或另一个 NTP 客户端,也无法保证 NTP/chrony 包在容器中可用。

所以我正在寻找一个统一的方法来知道系统时间是否同步?

答案1

通用计算上的应用程序无法在所有情况下知道它们所运行的主机上的时间同步是如何工作的。在容器中,您看不到也无法连接到主机上运行的 chronyd 或 ntpd,但这可以很好地保持时间。或者依赖主机时间同步的 VM 客户机也不可见。进一步使一般答案变得困难的是,NTP 实现比您想象的要多:chrony、ntp、ntpsec、openntpd、w32tm。

通常,记录正确时间的重要性就足够了。

在某些平台上,依赖 ntpd 启动相对简单。在 RHEL 上,等待时间同步 systemctl enable chrony-wait并添加到你的 systemd 单元

After=time-sync.target
Requires=time-sync.target

然而,有些应用程序对时间有严格的要求。我能想到的最严格的是时间戳机构,其中一家声称标准要求偏移量小于一秒,否则将不予批准. 这种积极的响应意味着应用程序会进行自己的时间检查。

也许可以捆绑一个 SNTP 客户端,用于检查应用程序中的 NTP 偏移量,并与可配置的 NTP 服务器进行对比。无法检查 ntpd 是否正常运行,但无论主机的时间同步如何,都可以对偏移量进行合理性检查。

答案2

有两种方法可以做到这一点。

如果您运行的容器具有完整的 systemd 实现,那么该timedatectl程序可以通知您主机是否同步。

内部管理的方式是通过 dbus 与systemd-timedated守护进程通信。它正在执行系统调用:adjtimex从中可以获取数据,指示正在进行的内核调整(如果有)的当前状态。

因此,无需完整实现即可自己执行此操作的第二种方法是使用adjtimex()系统调用。

内核不希望在报告时间时出现时间跳跃(或更糟的是,时间倒流),因此它会在时间上实现偏差,在几个小时内纠正系统时间(通过每秒增加或延迟几毫秒直到调整完成来完成)。

NTP 系统通常使用该adjtimex系统调用来改变时钟面临的当前偏差,以使其与真实时钟源正确同步 -它还可用于获取时钟源的当前偏差状态。因此,它使您能够窥视内核中正在进行的同步(如果有的话)。

的手册页adjtimex提供了一些与您的要求相关的有趣部分:

       The  buf.status  field  is a bit mask that is used to set and/or retrieve status bits associated with the NTP implementation.  Some bits in the mask
       are both readable and settable, while others are read-only.
...
       STA_UNSYNC (read-write)
              Clock unsynchronized.

RETURN VALUE
       On success, adjtimex() and ntp_adjtime() return the clock state; that is, one of the following values:
...
       TIME_ERROR  The system clock is not synchronized to a reliable server.  This value is returned when any of the following holds true:

                   *  Either STA_UNSYNC or STA_CLOCKERR is set.

                   *  STA_PPSSIGNAL is clear and either STA_PPSFREQ or STA_PPSTIME is set.

                   *  STA_PPSTIME and STA_PPSJITTER are both set.

                   *  STA_PPSFREQ is set and either STA_PPSWANDER or STA_PPSJITTER is set.

                   The symbolic name TIME_BAD is a synonym for TIME_ERROR, provided for backward compatibility.

因此,如果您没有功能齐全的容器,仍然可以获取这些数据。我编写了一个简单的程序,可以通过adjtimexC 语言获取内核倾斜的状态。例如,您可以编译它gcc -o timex timex.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#include <sys/timex.h>

/* Written for https://serverfault.com/questions/1077601/how-to-check-whether-the-system-time-is-synchronised-to-ntp-server-without-knowi */

void test_status(
    int st) 
{
  if (st & STA_PLL)
    printf("Phase locked loop\n");
  if (st & STA_PPSFREQ)
    printf("Pulse per second frequency discipline\n");
  if (st & STA_FLL)
    printf("PPS Time discipline\n");
  if (st & STA_INS)
    printf("Insert leap second and end-of-day\n");
  if (st & STA_DEL)
    printf("Delete leap second and end-of-day\n");
  if (st & STA_UNSYNC)
    printf("Clock is not syncronized\n");
  if (st & STA_FREQHOLD)
    printf("Hold frequency\n");
  if (st & STA_PPSSIGNAL)
    printf("Valid PPS signal is present\n");
  if (st & STA_PPSJITTER)
    printf("PPS signal jitter exceeded\n");
  if (st & STA_PPSWANDER)
    printf("PPS Signal wander exceeded\n");
  if (st & STA_PPSERROR)
    printf("PPS signal calibration error\n");
  if (st & STA_CLOCKERR)
    printf("Clock hardware fault\n");

  if (st & STA_NANO)
    printf("Nanosecond resolution\n");
  else
    printf("Microsecond resolution\n");

  if (st & STA_MODE)
    printf("Frequency locked loop\n");
  else
    printf("Phase locked loop\n");
}

int main() {
  struct timex tx = {};
  tx.modes = ADJ_OFFSET_SS_READ;
  int err = adjtimex(&tx);

  switch(err) {
    case -1:
      printf("Time error: %s\n", strerror(errno));
    break;

    case TIME_WAIT:
      printf("Leap second insert/delete completed\n");
    break;

    case TIME_INS:
      printf("Leap second to be added next UTC day\n");
    break;

    case TIME_DEL:
      printf("Leap second to be deleted next UTC day\n");
    break;

    case TIME_OOP:
      printf("Leap second insertion in progress\n");
    break;

    case TIME_ERROR:
      printf("Error getting time\n");
    break;

    case TIME_OK:
      printf("Time OK\n");
    break;

    default:
      printf("Time default: %x (%d)\n", err, err);
    break;
  }

  test_status(tx.status);
  exit(0);
}

在未同步的系统上运行:

$ ./timex 
Error getting time
Clock is not syncronized
Microsecond resolution
Phase locked loop

在同一主机上未同步的容器中运行:

# podman run -v /tmp/timex/timex:/timex  docker.io/gammabytehosting/rockylinux /timex
Error getting time
Clock is not syncronized
Microsecond resolution
Phase locked loop

设置要同步的主机系统中的时间:

# systemctl start chronyd
# chronyc sources
210 Number of sources = 9
MS Name/IP address         Stratum Poll Reach LastRx Last sample               
===============================================================================
^* _gateway            2   6     7     1  +5568ns[ -720ms] +/-   32ms
# ./timex 
Time OK
Microsecond resolution
Phase locked loop

在同一主机上的容器中执行相同的程序化检查:

# podman run -v /tmp/timex/timex:/timex  docker.io/gammabytehosting/rockylinux /timex
Time OK
Microsecond resolution
Phase locked loop

时间命名空间可能存在一些问题,我还没有测试过(但它们确实非常新),以查看它们adjtimex在单独的上下文中是否不同或尊重(参见man 7 time_namespaces),但从我读过的内容来看,它可能仍然有效 - 我留给你来决定。

答案3

如何检查系统时间是否与 NTP 服务器同步?

没有。

我的应用程序需要确保在尝试执行某些操作之前系统时间已同步

设置正确的运行环境不是应用程序的责任,这取决于系统及其管理员。

应用程序依赖于系统返回的日期/时间。无论该时间是“正确的”或者“错误的”;应用程序通常无法知道这一点。它只会使用该系统日期/时间。

如果您采用客户端-服务器模型,那么每当交易因(极端的)日期/时间偏移而被拒绝时,提供有用的错误消息将是很好的。
请注意,这种偏移的存在并不能告诉您客户端的时钟是否不正确,服务器是否不正确,或者两者兼而有之。

相关内容