tkuchikiの日記

Linux やプログラミングについて書きます。

2015年うるう秒の検証方法と検証結果 (Amazon Linux, CentOS)

日本時間 2015/07/01 08:59:59 から 09:00:00 の間にうるう秒が挿入されます。
検証手順と、どのように対応すれば良さそうか検証した結果をまとめます。
情報は、すべて執筆時(2015/06/18)のものです。

検証環境は Amazon Linux 2015.03、Timezone は Asia/Tokyo です。

  • うるう秒を挿入させない
  • 時間遡行を起こさせない
  • 08:59:60 を刻ませない

の 3 点に対応するための方法を記します。

結論

追記(2015/06/25):

ntpd を事前に止める手順で、すでに Leap Indicator(LI) がセットされている場合は、
ntptime -s 0 を実行する必要があります。
実行する必要があるケースは、ntptime で確認できます。

$ ntptime
ntp_gettime() returns code 1 (INS)
  time d93d0376.885cfeac  Tue, Jun 30 2015 21:00:54.532, (.532669028),
  maximum error 145742 us, estimated error 97 us, TAI offset 0
ntp_adjtime() returns code 1 (INS)
  modes 0x0 (),
  offset -44.746 us, frequency -26.426 ppm, interval 1 s,
  maximum error 145742 us, estimated error 97 us,
  status 0x6011 (PLL,INS,NANO,MODE),
  time constant 10, precision 0.001 us, tolerance 500 ppm,

ntp_adjtime() returns code 1 (INS) や status に INS が入っていることから、
うるう秒が挿入されることがわかると思います。
この場合は、ntptime -s 0 を実行すると status をクリアすることができます。

$ ntptime -s 0
ntp_gettime() returns code 1 (INS)
  time d93d03fa.58b9882c  Tue, Jun 30 2015 21:03:06.346, (.346581769),
  maximum error 211742 us, estimated error 97 us, TAI offset 0
ntp_adjtime() returns code 0 (OK)
  modes 0x10 (STATUS),
  offset -43.000 us, frequency -26.426 ppm, interval 1 s,
  maximum error 211742 us, estimated error 97 us,
  status 0x0 (),
  time constant 10, precision 1.000 us, tolerance 500 ppm,

ntp_adjtime() returns code 0 (OK) と status から INS が消えているので OK です。

詳しくは Leap Second Insertion フラグを受信後にそのフラグを削除する - Red Hat Customer Portal の回避策 1をご確認ください。
ntpd をアップデートして SLEW モードで実行する手順では、
ntptime -s 0 -f 0 を実行したほうが良いそうですが、
SLEW モードで動かしていれば LI がセットされていてもうるう秒が挿入されないので問題ない認識です(あまり自信ないです...)。
-f 0 は周波数オフセットをリセットするそうですが、リセット前後で何がどう変わるかはソースコードを読んでもよくわかりませんでした...(少なくとも、時間遡行が起こることはなさそうです)
-s と -f は、timex 構造体の status と freq に 0 を代入しているようです。

  • s と -f の処理は、以下をご確認ください。

timex 構造体については Man page of ADJTIMEX をご確認ください。


長いので結論から書きます。
ntpd には SLEW モードで動作させていても、うるう秒が挿入されてしまうバグのあるバージョンがあります(Bug 1190619 – ntpd -x steps clock on leap second)。

  1. うるう秒挿入時刻より前に、バグ修正済みの ntpd を SLEW モードで動作させておく
  2. うるう秒挿入時刻には ntpd を止めておき、うるう秒挿入時刻が過ぎたあとに ntpd を SLEW モードで動作させる
    • Timezone を right/Japan にしていた場合は、Asia/Tokyo にする
    • うるう秒を挿入するという情報(LI)をリセットする

のどちらかの方法で前述した 3 点に対応することができます。

環境ごとの対応方法は以下のとおりです。

Amazon Linux 2014.03 以降(と CentOS 6)

  1. ntpd をアップデート
    • Amazon Linux の場合は ntp-4.2.6p5-28 以降
    • CentOS 6 の場合は ntp-4.2.6p5-3 以降
  2. ntpd を SLEW モードで動かす

Amazon Linux 2013.09 以前(と CentOS 5, 7)

  1. Timezone を right/Japan にしている場合は Asia/Tokyo に変更
    • 08:59:60 を刻んでも問題なければ、right/Japan で問題ない(はず)
    • tzdata を更新せず、2015年のうるう秒のデータが入っていない場合も right/Japan で問題なし(08:59:60 を刻まない)
  2. うるう秒挿入時刻の前に ntpd を止めておく
  3. うるう秒を挿入するという情報(LI)をリセットする(ntptime -s 0)
  4. うるう秒挿入時刻の後に ntpd を SLEW モードで起動

Amazon Linux が 2014.03 以降と 2013.09 以前で対応方法が違うのは、
ntpd のアップデートは出来ますが、glibc のバージョンも依存関係で上がってしまうためです。
CentOS 5 と 7 はバグ修正済みの RPM が配布されていませんでした(RPMChangelog しかみていないので間違っている可能性があります)。
また、CentOS 5 は tzdata を更新しても 2015年のうるう秒のデータは更新されないようでした。
tzdata の中身は、以下のコマンドで確認することができます。

/usr/sbin/zdump -v /usr/share/zoneinfo/right/Japan

追記(2015/06/23):

古い Kernel を考慮していない対応方法になっていました。

2012年のうるう秒挿入時に高負荷になる問題が修正されていない Kernel を使っている場合は、
Kernel をアップデートする必要があります。

修正済みの場合、CentOS 6 の Kernel の Changelog に、以下があると思います。

$ rpm -qa --changelog kernel
...
[kernel] timekeeping: Fix leapsecond triggered load spike issue (Prarit Bhargava) [836803]

2012年の Linux Kernel の不具合については、2012 年 7 月 1 日のうるう秒挿入時に発生した Linux カーネルの不具合に関する情報 が参考になりました。
ありがとうございます。

ntpd のアップデート方法

以下のとおりです。

Amazon Linux

releasever=2015.03 にしか ntp のRPM を配布しないそうですので、
yum.conf に releasever を指定してバージョンを固定している場合は、
"--releasever=latest" が必要となります。

yum upgrade --releasever=latest ntp
CentOS 6

Amazon Linux とは違い、releasever はないので普通に yum でバージョンを上げましょう。

yum upgrade ntp

SLEW モードで起動する方法

  1. /etc/sysconfig/ntpd の OPTIONS に "-x" を追加
  2. ntp.conf に "tinker step 0" を設定

のいずれかの対応を行い、ntpd を再起動すれば SLEW モードで起動できます。

ここまでが、結論と対応方法についてです。
以降では、SLEW モードやtzdata、検証方法について説明していきます。

SLEW モード

すでに何度も登場していますが、結論を最初に書いた関係で説明がなかったので書いておきます。
通常、ntpd は STEP モードという時間を一気に調整するモードで動作しています。
SLEW モードは、時間を一気に調整せず 1 秒あたり最大 0.5ms ずつ時間を調整します。
これにより、うるう秒を挿入された場合でも、時間遡行を起こさずに時間を調整することができます。

詳細については Stray Penguin - Linux Memo (ntpd) が大変参考になりました。
ありがとうございます。

tzdata

こちらもすでに何度も登場していますが、タイムゾーンデータです。
サマータイムうるう秒の情報などが記載されています。
ntpd を動かしていない場合は、tzdata を参照してうるう秒を刻むことになります。

right/Japan のデータを見ると、

/usr/sbin/zdump -v /usr/share/zoneinfo/right/Japan
...
/usr/share/zoneinfo/right/Japan  Sat Jun 30 23:59:60 2012 UTC = Sun Jul  1 08:59:60 2012 JST isdst=0 gmtoff=32400
/usr/share/zoneinfo/right/Japan  Sun Jul  1 00:00:00 2012 UTC = Sun Jul  1 09:00:00 2012 JST isdst=0 gmtoff=32400
/usr/share/zoneinfo/right/Japan  Tue Jun 30 23:59:60 2015 UTC = Wed Jul  1 08:59:60 2015 JST isdst=0 gmtoff=32400
/usr/share/zoneinfo/right/Japan  Wed Jul  1 00:00:00 2015 UTC = Wed Jul  1 09:00:00 2015 JST isdst=0 gmtoff=32400
/usr/share/zoneinfo/right/Japan  9223372036854689407 = NULL
/usr/share/zoneinfo/right/Japan  9223372036854775807 = NULL

2015年のうるう秒の情報があります。
この状態で Timezone を right/Japan にして、
ntpd を動かさずにうるう秒の時刻を迎えると 08:59:60 を刻みます。

Asia/Tokyo を見ると、

/usr/sbin/zdump -v /usr/share/zoneinfo/Asia/Tokyo
...
/usr/share/zoneinfo/Asia/Tokyo  Sat May  5 16:59:59 1951 UTC = Sun May  6 01:59:59 1951 JST isdst=0 gmtoff=32400
/usr/share/zoneinfo/Asia/Tokyo  Sat May  5 17:00:00 1951 UTC = Sun May  6 03:00:00 1951 JDT isdst=1 gmtoff=36000
/usr/share/zoneinfo/Asia/Tokyo  Fri Sep  7 15:59:59 1951 UTC = Sat Sep  8 01:59:59 1951 JDT isdst=1 gmtoff=36000
/usr/share/zoneinfo/Asia/Tokyo  Fri Sep  7 16:00:00 1951 UTC = Sat Sep  8 01:00:00 1951 JST isdst=0 gmtoff=32400
/usr/share/zoneinfo/Asia/Tokyo  9223372036854689407 = NULL
/usr/share/zoneinfo/Asia/Tokyo  9223372036854775807 = NULL

2015年のうるう秒の情報がありません。

08:59:60 を刻ませないためには、
ntpd を動かしていない場合 tzdata を参照するので、
うるう秒の情報が含まれていない Timezone である Asia/Tokyo にする必要があることがわかると思います。

検証方法

検証方法は 【RHEL】這いよる閏秒 7月1日9:00(JST)の挙動(3年ぶりだな) | Pocketstudio.jp log3 を参考に致しました。
ありがとうございます。

以降、日本標準時プロジェクト 関連資料 のプログラム(以下、sntp.pl) を動かすサーバを NTP Server、
NTP Server と同期するサーバを NTP Client とします。

NTP Server

sntp.pl をそのまま使うと timegm の引数が多くて検証する度に変更するのが大変だったのと、
うるう秒を発生させる時間をJSTで指定したかったので patch を書きました。

以下のモジュールをインストールします。

$ yum install -y perl-DateTime perl-DateTime-Format-Strptime perl-Time-HiRes

perl-Time-HiRes は patch を当てない場合も必要です。

以下の patch を sntp.pl.patch などと保存し、

patch sntp.pl < sntp.pl.patch

で patch を当てます。

patch を当てたら、以下のように実行します。
NTP は well known port を使いますので、root 権限で実行してください。

./sntp.pl 2015-06-18T20:15:00

これで NTP Server 側の準備は完了です。
この場合、NTP Client 側では 2015/06/18 20:15:00(JST) にうるう秒が挿入されます。
十数分後くらいにうるう秒を挿入するようにすると良いです。

NTP Client

NTP Server と同期する側の設定です。
NTP Server の IP を 192.168.12.34 とします。
Timezone は Asia/Tokyo にします。

ntp.conf を変更して同期するサーバを NTP Server にします。

# ntp.conf
server 192.168.12.34

以下を実行して NTP Server と同期し、ntpd を起動します。
こちらも root 権限が必要です。

$ service ntpd stop && /usr/sbin/ntpdate 192.168.12.34 && service ntpd start
Shutting down ntpd:                                        [  OK  ]
 1 Jul 08:48:35 ntpdate[21386]: step time server 192.168.12.34 offset 60.000945 sec
Starting ntpd:                                             [  OK  ]

NTP Client の時間が 2015/07/01 09:00:00 のおよそ10分前になっていると思います。

時刻の推移を確認するのに以下を実行します。

while :; do date +'%Y-%m-%d %H:%M:%S.%3N'; usleep 100000; done

これで NTP Client 側の準備は完了です。
あとは、うるう秒挿入時刻を待つだけです。

検証時の注意点

以下が、検証時にはまったことです。

  • ntp.conf をデフォルトの状態で使用する場合、うるう秒挿入までの時間が早過ぎると Leap Indicator(LI, leap=01) がセットされない
    • だいたい3 分半 〜 5 分の間くらいで LI がセットされるようでした
  • うるう秒挿入を10分後くらいに設定しないと、うるう秒が挿入されない
  • tzdata を更新した状態で、Timezone を right/Japan などのうるう秒が含まれているものにした場合、2016/07/01 08:59:35 あたりでうるう秒が挿入された

LI がセットされているかどうかは、以下のコマンドで確認することができます。

ntpq -p -c rv

検証結果

ntpd のバージョンを上げて、SLEW モードで起動した場合

/var/log/messages に何も出力されず、うるう秒も挿入されていないと思います。

  • うるう秒を挿入させない
  • 時間遡行を起こさせない
  • 08:59:60 を刻ませない

がすべて達成されている状態です。

ntpd のバージョンを上げず、STEP モードで起動した場合

/var/log/messages に

Jun 30 23:59:59 ntp-client kernel: [2009692.154198] Clock: inserting leap second 23:59:60 UTC

というログが出力されています。

時間の推移を確認すると、

08:59:59.043200
08:59:59.143489
08:59:59.243810
08:59:59.344059
08:59:59.444374
08:59:59.544695
08:59:59.644945
08:59:59.745170
08:59:59.845425
---------------------
08:59:59.945695
08:59:59.045997
---------------------
08:59:59.146308
08:59:59.246599
08:59:59.346916
08:59:59.447196
08:59:59.547398
08:59:59.647677
08:59:59.747925
08:59:59.848165
08:59:59.948395
09:00:00.048650
09:00:00.148907
09:00:00.249210
09:00:00.349473
09:00:00.449769
09:00:00.550032
09:00:00.650247
09:00:00.750502
09:00:00.850745
09:00:00.951033

うるう秒が挿入され、08:59:59 が2回刻まれていることがわかります。

ntpd のバージョンを上げずに、SLEW モードで実行した場合
08:59:59.029329
08:59:59.129500
08:59:59.229704
08:59:59.329902
08:59:59.430197
08:59:59.530375
08:59:59.630553
08:59:59.730777
08:59:59.830986
08:59:59.931246
09:00:00.031513
09:00:00.131779
09:00:00.231966
09:00:00.332222
09:00:00.432509
---------------------
09:00:00.532794
08:59:59.633065
---------------------
08:59:59.733321
08:59:59.833618
08:59:59.933873
09:00:00.034119
09:00:00.134407
09:00:00.234694
09:00:00.334972
09:00:00.435262
09:00:00.535558

09:00:00.532794 を過ぎたあとにうるう秒が挿入され、08:59:59.633065 になっています。
本来 SLEW モードで起動していれば巻き戻らないはずなのに、
時間が巻き戻ってしまいました。

このとき、/var/log/messages にログは出力されませんでした。

まとめ

2015年うるう秒の対応方法と検証結果を示しました。
2012年のうるう秒挿入時は、高負荷になる問題があったようですが、
同年に問題が対応されたようでしたので、
実環境で稼働しているミドルウェアやアプリケーションを動作させながらの検証は行いませんでした。
万全を期すのであれば、実環境と同じものを用意して検証を行うことをおすすめ致します。
08:59:60 を刻んでも良い場合は、うるう秒を含む tzdata を参照することで SLEW モードでの調整はほとんど不要となると思いますが、
ミドルウェアやアプリケーションが 08:59:60 を刻んだ場合にエラーが発生しないか十分に確認する必要がありそうです。

内容に誤りがある場合は、ご指摘いただければと思います。