動機
孤立したローカルネットワーク内にある複数のホスト(Linux)の時刻合わせのため、そのうちの1台でNTPサーバを動かし、他をNTPクライアントで時刻同期をとることとした。各クライアントホストでは、DHCPで配信されたNTPサーバアドレスを設定することにした。
クライアントの設定の方が難儀だった。
NTPサーバの設定、およびDHCPサーバでNTPサーバ情報を配信する設定に関しては、色々な所に書かれている。一方でクライアントの設定に関しては環境依存が大きく適切な情報を見つけるのに時間を要した。クライアント側(Linux)が起動時に行なっている処理は、概ね下記のような手順をとっているようだ。
- DHCPクライアントが終了時にスクリプト(hook)を起動する。
- DHCPクライアントが起動したスクリプトがNTPクライアントの設定ファイルを更新する。
- 更新された設定ファイルを読んで、NTPクライアントが起動される。
ここで環境依存が発生するのは、DHCPクライアント(dhclient
(ISC-DHCP), dhcpcd
, NetworkManager
のinternal dhcp client, ...)と、NTPクライアント(ntpd
, openntpd
, chrony
, systemd-timesyncd
, ...)の組み合わせが多いことによる。Linuxディストリビューションによってはバージョンアップの際にデフォルトとして採用するDHCP,NTPパッケージを変わることがあるようで、それに気づかないとハマる。デフォルトでない(=使用されない)DHCPクライアントやNTPクライアントのパッケージもインストールされていたりすると、使用されることのない設定ファイルを書き換えて所期した動作をせず悩むということがおきる。
Raspberry Pi OS の場合
Raspberry Pi OS (64bit, Debian buster based)では、デフォルトのDHCPクライアント=dhcpcd
, NTPクライアント=systemd-timesyncd
となっている。DHCPで呼ばれるhookは、/lib/dhcpcd/dhcpcd-hooks/50-ntp.conf
のようである。このファイルには、ntpd
,openntpd
,chrony
用の記述はあるがsystemd-timesyncd
用の記述がない。公式サイトのフォーラムの情報を元に、下記を追記したところ、システム起動時にNTPサーバが動的に設定されて時刻合わせがされるようになった。/etc/dhcp/dhclient-exit-hooks.d/timesyncd
というファイルが存在してそれらしい実装が含まれているが、こちらは使われていないので注意が必要である。
--- /etc/dhcpcd.conf.orig 2019-11-13 23:44:50.000000000 +0900
+++ /etc/dhcpcd.conf 2020-09-16 08:28:53.595999273 +0900
@@ -30,7 +30,7 @@ option classless_static_routes
option interface_mtu
# Most distributions have NTP support.
-#option ntp_servers
+option ntp_servers
# A ServerID is required by RFC2131.
require dhcp_server_identifier
# Set NTP servers for systemd-timesyncd
confd=/run/systemd/timesyncd.conf.d
if [ -n "$new_ntp_servers" ] ; then
set_servers() {
mkdir -p "$confd"
( echo "# Created by dhcpcd hook";echo "[Time]"; echo "NTP=$new_ntp_servers" ) > "$confd/dhcp-ntp.conf"
# Tell timesyncd it has an updated configuration
systemctl try-reload-or-restart systemd-timesyncd
}
if $if_up; then
set_servers
fi
fi
ちなみに、systemd-timesyncd
のデフォルトの設定ファイル(/lib/systemd/system/systemd-timesyncd.service.d/disable-with-time-daemon.conf
)では、他のNTPクライアントやVirtualBoxのサービスの実行ファイルが存在していたら何もしないようになっているので、50-ntp.conf
は置き換えでなく追記にしておいた方が無難とおもわれる。
Ubuntu MATE 20.04 LTS の場合
Raspberry Pi
用のイメージ(ubuntu-mate-20.04.1-beta2-desktop-arm64+raspi.img
)の場合、使われているのはNetworkManager
のinternal dhcp clientと、systemd-timesyncd
の組み合わせのようである。デフォルトでdhclient
もインストールされており、/etc/dhcp/dhclient-exit-hooks.d/timesyncd
も用意されているので、コマンドラインで手動でdhclient
を実行すると時刻の同期がされる。そこで、/etc/NetworkManager/NetworkManager.conf
の[main]
セクションにdhcp=dhclient
と追記してinternal dhcpの代わりにdhclient
が使われるようにすれば良いかと思いきや、ログを見るとdhclient
がエラーを吐いており、また思ったタイミングでhookが呼ばれない。
しかたがないのでdhclient
を使うのは諦め、/etc/dhcp/dhclient-exit-hooks.d/timesyncd
を参考にNetworkManager
のDispatherスクリプトを用意することにした。公式マニュアルにしたがって環境変数や判定文字列を修正して、/etc/NetworkManager/dispatcher.d/90-dhcp-timesyncd
を作成する。
#!/bin/sh
TIMESYNCD_CONF=/run/systemd/timesyncd.conf.d/01-dhclient.conf
timesyncd_servers_setup_remove() {
if [ -e $TIMESYNCD_CONF ]; then
rm -f $TIMESYNCD_CONF
systemctl try-restart systemd-timesyncd.service || true
fi
}
timesyncd_servers_setup_add() {
if [ ! -d /run/systemd/system ]; then
return
fi
old_ntp_servers=$(sed -ne 's/^NTP=//gp')
if [ -e $TIMESYNCD_CONF ] && [ "x$DHCP4_NTP_SERVERS" = "x$old_ntp_servers" ]; then
return
fi
if [ -z "${DHCP4_NTP_SERVERS}" ]; then
timesyncd_servers_setup_remove
return
fi
mkdir -p $(dirname ${TIMESYNCD_CONF})
cat <<EOF > ${TIMESYNCD_CONF}.new
# NTP server entries received from DHCP server
[Time]
NTP=$DHCP4_NTP_SERVERS
EOF
mv ${TIMESYNCD_CONF}.new ${TIMESYNCD_CONF}
systemctl try-restart systemd-timesyncd.service || true
}
logger -i -t "$0" "action=${NM_DISPATCHER_ACTION}:NTP=${DHCP4_NTP_SERVERS}"
case $NM_DISPATCHER_ACTION in
up|dhcp4-change)
timesyncd_servers_setup_add
;;
down)
timesyncd_servers_setup_remove
;;
*) :
;;
esac
このファイルは、オーナーをrootにして実行パーミッションを与えておく必要がある。
% ls -l /etc/NetworkManager/dispatcher.d/90-dhcp-timesyncd
-rwxr-xr-x 1 root root 1140 Apr 2 02:55 /etc/NetworkManager/dispatcher.d/90-dhcp-timesyncd
このスクリプトを置くことで、時刻の同期がされるようになった。
JetPack 4.4 (Ubuntu 18.04 LTS based)の場合
Ubuntuの一つ前のバージョンでも使われているのはNetworkManager
と、systemd-timesyncd
の組み合わせのようである。そのため、前節(Ubuntu MATE 20.04 LTS)と同じスクリプトで動くと思いきや、そのままでは期待した通りに動作してくれなかった。networkmanager-dispatcher
のバージョンの違いのせいか、環境変数$NM_DISPATCHER_ACTION
がセットされないようである。そのため、かわりに実行時の第2引数を評価するように下記のように修正することで期待した動作がされるようになった。
--- Ubuntu_MATE-20.04LTS/etc/NetworkManager/dispatcher.d/90-dhcp-timesyncd 2020-09-20 21:52:17.950941231 +0900
+++ JetPack-4.4/etc/NetworkManager/dispatcher.d/90-dhcp-timesyncd 2020-09-22 01:18:46.160691469 +0900
@@ -34,9 +34,9 @@ EOF
systemctl try-restart systemd-timesyncd.service || true
}
-logger -i -t "$0" "action=${NM_DISPATCHER_ACTION}:NTP=${DHCP4_NTP_SERVERS}"
+logger -i -t "$0" "action=${NM_DISPATCHER_ACTION:-${2}}:NTP=${DHCP4_NTP_SERVERS} $@"
-case $NM_DISPATCHER_ACTION in
+case ${NM_DISPATCHER_ACTION:-$2} in
up|dhcp4-change)
timesyncd_servers_setup_add
;;
余談だが、Ubuntu 18.04 LTSのNetworkManager
は、internal dhcp clientではなく、dhclient
を利用しているようだ。
Ubuntu Server 20.04 LTSの場合
systemd-networkd
のinternal dhcp clinetとsystemd-timesyncd
の組み合わせとなっている模様。とくに何もしなくてもDHCPのNTPサーバが設定されるようになっている。systemd-networkd-dispatcher
に関してはあまり情報を見つけられなかったので、どのような仕組みでDHCPで取得されたアドレスがsystemd-timesyncd
に渡されているのかは未解明。
CentOS 7.8 (20.03), CentOS 8.2 (20.04)の場合
NetworkManager
とchrony
の組み合わせになっていて、何もしなくてもDHCPのNTPサーバが設定されるようになっている。余談だが、CentOS 7では、dhclient
が使われていて、CentOS 8ではNetworkManager
のinternal dhcp clientが使われているようだある