NTPサーバの時刻ソースに対するズレの調整

最近Chronyを使ったNTPサーバ動かしてみたが、Chronyは設定と統計情報の出力がntpdとはちょっと違う。しかも内容がよくわからない。
一応、統計情報をNode Exporterに読ませてPrometheusに送るところまではできるのだけど、その送った情報が何の値を示しているのかが判らないのでそれをグラフにしてもまぁ判らないのね。つまり、Chronyは動かすまでは簡単だけど動かしてからがだいぶ困る。

/etc/chrony.conf (の統計情報出力部分)
1
2
logdir /var/log/chrony
log measurements statistics tracking refclocks
統計ログとして4種類出力する指定。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/bin/sh

refclocklog="/var/log/chrony/refclocks.log"
measurelog="/var/log/chrony/measurements.log"
name="node_ntp_peerstats"
outdir="/tmp/node-exporter/"

offset1="# HELP node_ntpstats_offset_seconds Time offset in between local system and reference clock."
offset2="# TYPE node_ntpstats_offset_seconds gauge"
delay1="# HELP node_ntpstats_delay_seconds Time delay in between local system and reference clock."
delay2="# TYPE node_ntpstats_delay_seconds gauge"
disps1="# HELP node_ntpstats_dispersion_seconds Time dispersion in between local system and reference clock."
disps2="# TYPE node_ntpstats_dispersion_seconds gauge"
skew1="# HELP node_ntpstats_skew_seconds Time skew in between local system and reference clock."
skew2="# TYPE node_ntpstats_skew_seconds gauge"

pps='PPS'  #リファレンスドライバのrefidで指定した識別名
ppsdata=`/bin/cat ${refclocklog} | /bin/grep ${pps} | /usr/bin/tail -1`
ppsoffset=`echo ${ppsdata} | sed -e 's/ \+/ /g' | cut -d " " -f 7` 
ppsdelay=`echo ${ppsdata} | sed -e 's/ \+/ /g' | cut -d " " -f 8`
ppsdisps=`echo ${ppsdata} | sed -e 's/ \+/ /g' | cut -d " " -f 9`
#ppsskew=`echo ${ppsdata} | sed -e 's/ \+/ /g' | cut -d " " -f 8`
ppsl1="node_ntpstats_offset_seconds{device=\"pps\"} ${ppsoffset}"
ppsl2="node_ntpstats_delay_seconds{device=\"pps\"} ${ppsdelay}"
ppsl3="node_ntpstats_dispersion_seconds{device=\"pps\"} ${ppsdisps}"
#ppsl4="node_ntpstats_skew_seconds{device=\"pps\"} ${ppsskew}"

nmea='NMEA'  #リファレンスドライバのrefidで指定した識別名
nmeadata=`/bin/cat ${refclocklog} | /bin/grep ${nmea} | /usr/bin/tail -1`
nmeaoffset=`echo ${nmeadata} | sed -e 's/ \+/ /g' | cut -d " " -f 7`
nmeadelay=`echo ${nmeadata} | sed -e 's/ \+/ /g' | cut -d " " -f 8`
nmeadisps=`echo ${nmeadata} | sed -e 's/ \+/ /g' | cut -d " " -f 9`
#nmeaskew=`echo ${nmeadata} | sed -e 's/ \+/ /g' | cut -d " " -f 8`
nmeal1="node_ntpstats_offset_seconds{device=\"nmea\"} ${nmeaoffset}"
nmeal2="node_ntpstats_delay_seconds{device=\"nmea\"} ${nmeadelay}"
nmeal3="node_ntpstats_dispersion_seconds{device=\"nmea\"} ${nmeadisps}"
#nmeal4="node_ntpstats_skew_seconds{device=\"nmea\"} ${nmeaskew}"	

echo "${offset1}\n${offset2}\n${ppsl1}\n${nmeal1}\n${srv1l1}\n${srv2l1}\n${srv3l1}\n${delay1}\n${delay2}\n${ppsl2}\n${nmeal2}\n${srv1l2}\n${srv2l2}\n${srv3l2}\n${disps1}\n${disps2}\n${ppsl3}\n${nmeal3}\n${srv1l3}\n${srv2l3}\n${srv3l3}\n${skew1}\n${skew2}" > ${outdir}${name}.prom.$$

/bin/mv ${outdir}${name}.prom.$$ ${outdir}${name}.prom
/bin/chmod 666 ${outdir}${name}.prom

原始的だがこんな感じで統計情報をNode Exporterで読める情報にする。ただし、このスクリプトではoffsetだのdelayだのskewだのを○○ログの○番目の要素として抜き出す具体例が書かれているが、実際には○○ログの○番目の要素が何を示しているのか「がとらぼ」の人がよく判ってなくて作ったのでこれをそのまま真似るのはダメ。

一応頑張ってみたが、結局のところアタマが悪いのでドキュメントを見てもChronyのそれぞれのログの値が実際に何を示しているのか判らな過ぎてChronyを諦めてntpdに戻した。

「え? えぇっっっっ?!」

スンマセン

ntpdで統計情報をNode Exporterに渡すのは以前にPrometheus2とGrafana6によるシステム監視 NTP統計情報の表示で書いた。統計情報をグラフ化できないと調整は難しいと思うので、PrometheusとかElastic Searchとかの統計情報を収集する仕組みが無いという場合は、ntpdが出力する統計情報ファイルをNTP plotterに読ませてグラフ化するというようなのでも良い。Windowsならこれが簡単。何にしても、ntpdで時刻の調整をするには相当な時間〜日数で誤差を録り続けないとどれだけズレてるのかがそもそも判らない。

「がとらぼ」の人の調整の考え方

基本的にはPPSが秒替わりの間隔は一番正確。ただし、PPSはそのタイミングしか判らなくて、しかも微妙に遅延してるかも。
NMEAは秒の変わるタイミイグはだいぶデタラメだけど何時何分何秒が判る。
PPSとNMEAは衛星からのデータをGPSモジュールで受信してPCに伝えるまでの遅延がそれぞれどの程度あるのかが判らない。
インターネット上の公開NTPサーバの中には正確な時間を持っているものがあってそれをNTPサーバで参照すると個々の瞬間ではバラツキはあるけど長い時間では遅延を考慮した正しい時間を取得できる。
そこで、ネットから取得した時間とPPS/NMEAの差分を求めてPPS/NMEA側を調整する。つまり時刻自体はインターネット上のNTPサーバから取得した時刻に合わせて、秒のタイミングはインターネット上のNTPサーバに寄せてPPSの遅延(ズレ)を調整し、おまけでPPSに寄せてNMEAを調整するという感じ。PPSとネットの時刻ソースがあればNMEAは無くても良いが、ネットが切れた場合に備えてオマケで組み入れておく。

ntpdでは1つのリファレンスクロックドライバでNMEAとPPSをを組み合わせて日時+秒変わりタイミングを得る方法もあってそれが正確で良いとも聞くが、「がとらぼ」では172.172.20.xでNMEA、172.172.22.xでPPSの2つのリファレンスクロックドライバを使う方法を採用している。(GPSモジュールは1つ)

ntpdの設定で統計ファイルを出力する設定にしていないのであれば、ntpq -p の出力を監視という方法もある下のコマンドはLinux用)
#目視で監視するなら(まぁ無駄)
$ watch -n0.5 "ntpq -p | grep .NMEA."
#ファイルに出力するなら
$ watch -n0.5 "ntpq -pn | grep .NMEA. | tee --append nmea.log"

-n0.5は0.5秒毎に更新の指定。ntpq -pの出力を見るなら1秒よりは小さい方が数値がどう変化するのかよく判る。.NMEA.の部分は/etc/ntp.confのrefidで指定した文字列(上の例ではNMEA)の前後にピリオドを付けたもの。refidで指定した文字列がユニークなものなら前後のピリオドはなくても間違って抽出されることはないと思う。

GPSモジュール交換前
怪しい中華GPSモジュール(ATGM332D)を購入したという記事を書いたときに挙げた何かおかしいGPSモジュール(NEO 6M)のNMEAのグラフ。これがどう見ても異常なので新しいGPSモジュールを購入した。
それでOSも最新版で気分一新してこの記事に挑んでいる。そこで以前の「NanoPi NEOとGPSモジュールでNTPサーバ PPS解決編」では「効果が無い」と書いたsetserialにも再挑戦。

$ sudo apt install setserial    #setserialインストール

$ sudo setserial -g -a /dev/ttyS1    #setserial未適用で/dev/ttyS1の状態を確認
/dev/ttyS1, Line 1, UART: undefined, Port: 0x0000, IRQ: 38
        Baud_base: 1500000, close_delay: 50, divisor: 0
        closing_wait: 3000
        Flags: spd_normal

$ dmesg | grep serial   #システムがシリアルポートをどう認識しているか確認
[    2.472691] 1c28000.serial: ttyS0 at MMIO 0x1c28000 (irq = 37, base_baud = 1500000) is a U6_16550A
[    2.494480] 1c28400.serial: ttyS1 at MMIO 0x1c28400 (irq = 38, base_baud = 1500000) is a U6_16550A

/var/lib/setserial/autoserial.conf (編集 or 新規)
中身無しまたはコメントの行だけにする。

低遅延と速度115200bpsを指定(手動の場合)
$ sudo setserial /dev/ttyS1 low_latency spd_vhi
/dev/ttyS1が対象のシリアルポート、low_latencyが低遅延、spd_vhiが115200bpsの指定。
/lib/systemd/system/ntp.service (編集)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
[Unit]
Description=Network Time Service
Documentation=man:ntpd(8)
After=network.target
#After=rc-local.service
Conflicts=systemd-timesyncd.service

[Service]
Type=forking
# Debian uses a shell wrapper to process /etc/default/ntp
# and select DHCP-provided NTP servers if available
ExecStart=/usr/lib/ntp/ntp-systemd-wrapper
ExecStartPre=/usr/bin/bash -c '/usr/bin/mkdir -p /tmp/ntpstats 2> /dev/null'
ExecStartPre=/usr/bin/chmod 777 /tmp/ntpstats
ExecStartPre=/usr/bin/bash -c '/usr/bin/echo "B5 62 06 00 14 00 01 00 00 00 D0 08 00 00 00 C2 01 00 07 00 03 00 00 00 00 00 C0 7E" | xxd -r -p > /dev/ttyS1'
ExecStartPre=/usr/bin/setserial /dev/ttyS1 low_latency spd_vhi
ExecStartPre=/usr/sbin/ntpdate ntp1.v6.mfeed.ad.jp
ExecStartPre=/usr/sbin/hwclock -w
#PrivateTmp=true

[Install]
WantedBy=multi-user.target

15行目でubloxのGPSモジュールに115200bpsに合わせるようコマンドを送ってから16行目setserialでttyS1ポートを115200bpsのlow_latencyにセット。それからntpdateでネット上のNTPサーバを使って時刻合わせをしてRTCに時刻を書き込み。ここまでExecStartPreで実行してからntpdがサービスをスタートする。
以前の記事でsetserialでlow_latencyを使っても結果が変わらないと書いたが、長い期間で見るとNMEAの振れ幅が少し改善しているように見えたので今回はサービス起動時にsetserialを適用することにした。setserialは使わないということであれば、上の /lib/systemd/system/ntp.service の16行目は要らないと思う。
シングルボードコンピュータ用のLinux(Armbian含む)で /lib/systemd/system/ntp.service 内に PrivateTmp=true の行があったらその行を削除するかコメント化して無効にする。でないと統計情報等の出力先が設定ファイルに書いた出力先を無視?して /var/log.hdd 下になるのでワケワカランことに。理解した上で/var/log.hdd下に置くつもりならそれは構わないが、microSDカードの寿命を縮めることになるので頻繁に書き込むファイルをこの場所に置くのはオススメしない。あと、2020年7月のArmbianではlogrotateの設定の初期値が/var/log.hdd下のファイルしか面倒みないようになっていたので/var/log下のファイルがローテートされずに放置される。長期稼働を予定しているホストであれば/var/log下もローテートする設定を追加する。でないと/var/log(メモリディスク)がパンクする。ただし、logrotateサービス自体がPrivateTmp=trueになっていると/var/log.hdd以外を操作できないので /lib/systemd/system/logrotate.service の PrivateTmp=true も無効化する。/etc/logrotate.d以下も触らないといけないし、他のサービス設定も触らないといけないかも。ヘタに触ると収拾がつかなくなるので悩みどころ。

$ sudo systemctl daemon-reload
/lib/systemd/system/にある *.service を変更したらデーモンリロードしておく。もしくは変更したサービスを無効化して再度有効化する。システム再起動だけでは変更後の設定が適用されない。
/etc/ntp.conf (重要な変更部分)
1
2
3
#前略
server 127.127.20.0 mode 81 minpoll 4 prefer
#後略

忘れやすいが、GPSモジュール側のシリアル速度を変更したらntp.conf側でもmodeで速度を合わせること。(参照クロックドライバ127.127.20.xのmode 81 = 80:115200bps + 1:RMC)

/etc/ntp.conf (ズレ調整のない設定)
1
2
3
4
5
6
7
#前略
server 127.127.20.0 mode 81 minpoll 4 prefer   #NMEA mode 81: 115200bps + RMC
fudge  127.127.20.0 refid NMEA

server 127.127.22.0 minpoll 4 maxpoll 4 true   #PPS
fudge  127.127.22.0 refid PPS
#後略
$ sudo systemctl restart ntp.service
NTPサービスを再起動して24時間以上様子を見る。
「がとらぼ」ではNode ExporterでNTPの統計情報を取得してPrometheusで蓄積、Grafanaでグラフ化していつでも監視できるようにしている。

NTPのズレ調整 1
NMEAのoffsetのグラフ。これは上で書いたsetserialの未適用の状態。これだと表示中の48時間で平均が-154msになっている。ただし、グラフがところどころ数百msも下方向に伸びてしかも数分〜1時間もその状態が続いている。この平均ズレ時間をそのまま信じるのはちょっと考えもの。しかし、おおよそ150〜180msと考える。まぁ、NMEAはオマケで合わせる方針なので調整は後回しでも構わない。

NTPのズレ調整 2
ネット上の時刻ソースとの同期のグラフ。3つのNTPサーバの48時間のoffsetが描かれている。画像では上下に大きく波打っているが振れ幅としては実は2ms程度でしかないので、上のNMEAより150倍も精度が高い。でも本当はそれはちょっとおかしい。というかNMEAの振れ幅が大きすぎる。 平均は3つで1.6〜1.7msほど。これから調整するNTPサーバではこの時刻を正しい時間として合わせる。つまり、現在は設定するNTPサーバが1.6msズレているということにする。

NTPのズレ調整 3
PPSのoffset。これから調整するこのNTPサーバは現在はこのPPSの時刻が正しいと思っているのでズレは0を中心に上下にそれぞれ振れ幅10μmでグラフが描かれている。未調整でこれが上か下にズレていたらNTPサーバが何に時刻を合わせているのかを確認する必要がある。

/etc/ntp.conf (PPSの設定部分 未変更含む)
1
2
server 127.127.22.0 minpoll 4 maxpoll 4 true
fudge   127.127.22.0 time1 0.0017 refid PPS

PPS側。参照クロックドライバが127.127.22.xでは時刻のズレの調整はtime1で行う。単位は「秒」なので1.7msであれば0.0017となる。 他所の設定例のようにflag3 1なんかを設定するとPPSのoffsetが設定分ズレてワケが解らなくなるので「がとらぼ」ではflag3は設定しない。
変更したらNTPサーバを再起動する。このときdriftファイルを指定しているならnet.serviceを停止してdriftファイルの中身を消してnet.serviceを起動する。(driftファイルの中身を消してntp.serviceを再起動してすぐにdriftファイルを確認して空ならそれでもOK.)
36時間以上稼働させて、起動後少なくとも12時間は見ないでその後の24時間のoffsetの平均を見て、まだズレがあるなら更に設定を詰める。なかなか「これかな?」という値が見つからないかもしれないが、それでもおおよそで合わせるしかない。今回設定しているNanoPi NEOの例では6桁程度の精度で設定した。実際には0.00172という設定になった。これはGPSアンテナの設置環境や使用機材など幾つかの要因で値が変わると思うので「がとらぼ」の人が0.00172って書いてたから真似たら全然違ったということになるかと思う。

NMEA側。これはPPSのズレ調整が終わってからの情報で調整した方が良いと思う。この記事のNMEAのグラフではグラフの振れ幅が100ms近くあるけど、なんでこんなに酷いのかわからないが、GPSアンテナがすぐ隣にある別のNTPサーバ(FreeBSDのPCサーバとUSB接続のGPSモジュールを使ったやつ)だと振れ幅が10ms程度なので約1/10。普通はこの10ms以下(もっと小さい?)だと思うのよね。

NTPのズレ調整 4
直前に書いたFreeBSDのPCサーバとUSB接続のGPSモジュールのNTPサーバのNMEAの48時間グラフ。上に5ms、下に5ms程度の振れ幅で平均は表示している48時間でいえば-56μs。(このNTPサーバは調整済みだが、常にわずかにブレるのでこの程度の誤差はあっても異常ではない)

/etc/ntp.conf (NMEAの設定部分 未変更含む)
1
2
server 127.127.20.0 mode 81 minpoll 4 prefer
fudge 127.127.20.0 time2 0.155 refid NMEA
ネットのドキュメントを見ると混乱させられる部分だけど、参照クロックドライバの127.127.20.xでNMEAのオフセットの調整はtime2なので注意。127.127.20.xでPPSも見る場合はtime1も絡んでくるみたい。(未確認)
setserialを有効にしたらそれだけでも10ms以上(僅かだか誤差が少ない方に)ズレた。
調整する設定の単位はこれも「秒」。今回設定しているNanoPi NEOの例では結果的に0.155 (155ms)で設定した。

NTPのズレ調整 5
setserialで低遅延を適用してズレ調整済み状態のNMEAのoffsetのグラフ。
表示している48時間の平均誤差は-378μsとなっているので、振れ幅100msが当たり前の中で約400μmなら優秀ではあるけど、グラフを見たらお判りのように、7/29の早朝に長い時間メチャクチャになってるのが含まれてこれなので「調整完了」とは言い難いと突っ込まれる?でも他の時間帯でもだいたいこれで中央値が0なのよね。
先日記事のようにGPSモジュールを交換して、それでNMEAの結果が改善することを期待したのだが、謎のコノギリ波形から普通のグラフになったのは大改善とはいえ、期待していたより遥かに低い精度なので、もしかしてこのNTPサーバが使っているアンテナ個体が悪いのかな。10cm隣に設置した別のGPSアンテナを使っているFreeBSDのNTPサーバは期待程度には精度があるのでアンテナ設置場所が悪いということではない筈。

NTPのズレ調整 6
インターネット上の公開NTPサーバ3台のoffsetグラフ。7/29の午後にちょっと酷いズレが発生しているが、それで3台の平均では約100μsなのでppsの調整は一応出来ている。

NTPのズレ調整 7
当然だが、ppsのoffsetグラフの振れ幅は変わらず。

まとめ

PPS用のクロックドライバ127.127.22.xでtime1を調整するとネットの時刻ソースのグラフに変化が出るので振れ幅の中央値(≒平均)が0nsに合うようにする。PPSのグラフには変化は出ない。
NMEA用のクロックドライバ127.127.20.xでtime2を調整するとNMEAのグラフに変化が出るので振れ幅の中央値(≒平均)が0nsに合うようにする。

システム監視用のPromethusのデータ保持期間を半月にしていたのにNTPのズレ調整後に暫く放置してしまったので調整前のグラフを取得できなくなってしまった。そこで、8月に入ってからズレ未調整に戻して1週間待って画像を取得した。それでこの記事では7月の画像が調整後で8月の画像が調整前という逆転状態になっている。

関連記事:

コメント: NTPサーバの時刻ソースに対するズレの調整

  1. Linuxだと systemd-timesyncd がデフォルトで起動されようとしますけど、そちらを試されたことはありますでしょうか…?(私はそっちが高い要求水準に耐えうるかどうかの情報は持っておりませんが…)

  2. systemd-timesyncdはntpdを使わないときにntpの真似事で簡易的に時刻合わせをするサービスという認識です。サービス自体は多くのLinuxで標準で有効なのですが、設定ファイルが基本的に全部コメントなので「なんじゃこりゃ?」な感じにしか思っていません。ntpdなりchronyなりを動かすときは無効にしてます。(基本無効化してます)
    認識してないところで重要な役割を果たしてたらスミマセン。

コメントは締め切られています。