Linuxのうるう秒おさらい

7/1 午前9時(JST)にうるう秒が挿入されますが、注意すべきポイントのおさらいです。

うるう秒って何よ

NICTの資料の先頭7ページ目まで読んでください。

ざっくり言うと、現在の時計というのは「原子時計」が基準になっています。太陽の周りを回る公転周期に合わせて微調整するのがうるう年で、地球自体が回転する自転時間に合わせて微調整するのがうるう秒です。
現象としては、「月末」の日付が変わる直前に1秒追加されます。ただし、これはUTC(協定世界時)での話なので、日本標準時では9時間の時差があるので朝9時(の直前)になるわけです。

時間の進みを表にするとこんな感じです。

UTC(協定世界時) JST(日本標準時) うるう秒を知らない時計
2015年 6月30日 23時59分57秒 2015年 7月 1日 8時59分57秒 2015年 7月 1日 8時59分57秒
2015年 6月30日 23時59分58秒 2015年 7月 1日 8時59分58秒 2015年 7月 1日 8時59分58秒
2015年 6月30日 23時59分59秒 2015年 7月 1日 8時59分59秒 2015年 7月 1日 8時59分59秒
2015年 6月30日 23時59分60秒
うるう秒発生!
2015年 7月 1日 8時59分60秒 2015年 7月 1日 9時 0分 0秒
2015年 7月 1日 0時 0分 0秒 2015年 7月 1日 9時 0分 0秒 2015年 7月 1日 9時 0分 1秒
ここで1秒進んでしまう!
2015年 7月 1日 0時 0分 1秒 2015年 7月 1日 9時 0分 1秒 2015年 7月 1日 9時 0分 2秒
2015年 7月 1日 0時 0分 2秒 2015年 7月 1日 9時 0分 2秒 2015年 7月 1日 9時 0分 3秒
2015年 7月 1日 0時 0分 3秒 2015年 7月 1日 9時 0分 3秒 2015年 7月 1日 9時 0分 4秒

おわかり頂けただろうか。

本当はこわいうるう秒

前回のうるう秒は2012年7月1日(JST)に挿入されましたが、このときにLinuxカーネルでいくつかの不具合が判明しました。
一番影響が大きかったものが「CPU利用率が100%に張り付く」というもので、以下のブログ記事が詳しいです。

これは「不具合」による想定外の問題でしたが、そもそも「想定内」の範囲でも以下のような問題があります。

  • タイムゾーンの設定
    • Linuxでは、例えばJSTであれば right/Asia/Tokyo という特殊なタイムゾーンを設定した場合にのみ、59分60秒といううるう秒の時刻を取得できます。
    • Asia/Tokyo のままだと、59分59秒を二周します。IBMの技術記事の表記が分かりやすいので引用します。
      • 23:59:59.000000(→ 23:59:59.999999)→ 23:59:59.000000 → 00:00:00.000000 (小数点以下が 1 秒以下の時刻を表します。)
  • ログの前後関係の逆転
    • たとえば操作履歴の分析を想像して、手動で全力出しすぎて1秒以内に連続で操作したような場合、59分59秒を2周するような設定だと、操作の前後関係が逆転する可能性があります。
      • 8時59分59秒 800ミリ秒 操作A
      • 8時59分60秒 400ミリ秒 操作B → 8時59分59秒 400ミリ秒と記録され操作Aより前とみなされてしまう
    • また、監査ログの紐付けなどでも同様の問題が発生します。
  • 時刻が修正されるタイミングの不一致
    • 誤ったやり方でうるう秒を回避しようとした場合に発生します。
    • 広く使われているNTPソフトウェアでは、うるう秒の事前情報(後述するLeap Indicator)が無い場合には、時間のずれを検知してから15分間たってから一気に時刻を調整します。上位のNTPサーバに接続する間隔は、サーバの起動時間などの理由でバラバラなので、時刻が調整されるタイミングもバラバラになります。これではログ上の時刻突き合わせもできなくなり死ぬしかなくなります。

このように、きちんと考えると奥が深いのがうるう秒の処理です。

うるう秒を正しく扱う

比較的新しいサービスのみ面倒を見ているのであれば、おそらくうるう秒に起因する不具合もないでしょうから、全力でうるう秒を受け入れてあげるのが良いと思います。2012年以上前のディストリビューションでは上記不具合が修正されていない可能性がありますが、それ以外は特に何の問題も無く動くと期待されています。

「60秒」を保存できることを確認できているなら、right/Asia/Tokyo などのタイムゾーンに変更するとより良いです。

本来のうるう秒の処理

例えば、インターネットマルチフィードのNTPサーバから時刻を受信し、データセンター内の自前NTPサーバを立てているようなケースを想定すると、全体像は以下のようになります。

うるう秒の情報は、最終的には3つの方法で各サーバのLinuxカーネルに届きます。

1. 清く正しいLinuxカーネルうるう秒を処理

うるう秒の情報は、NTPのLeap Indicator(LI)というフラグを経由して各サーバに配布されます。最新のRFC上は一ヶ月前からフラグを立てても良いことになっていますが、NICTでは以前のRFC等も考慮してうるう秒の24時間前からフラグを立てる運用となっています。

Leap Indicatorを受け取ったNTPクライアントは、adjtimex(2)というシステムコールを呼んでLinuxカーネルうるう秒の情報を伝えます。Linuxカーネルはtime_stateという変数にそれを保存しておき、月末(UTC)になったらその変数を見て必要ならばうるう秒を挿入します。

カーネル内部のシステムクロックは正しくうるう秒を扱いますが、プロセスから現在時刻を聞かれたときには、right/Asia/Tokyo などうるう秒対応のタイムゾーンが設定されていれば、59分60秒という正しいうるう秒時刻を返し、そうでなければ59分59秒を2周するような時刻を返すようになります。2周する場合も、カーネルにより厳密にループが行われるので午前9時0分0秒(JST)になることなく繰り返されます。

標準ntpdのデフォルト設定(STEPモード)ではこの動作となります。また、ChronyではSLEWモードで設定をしていてもLinuxカーネル側でうるう秒を挿入するようです。

万が一、カーネルうるう秒情報を受け取ってしまった後になって回避策を実施したい場合は、ntptimeコマンドでカーネル内のtime_stateを変更することができます。外部NTPサーバを参照するntpdが起動しっぱだとまた受け取ってしまうのでそちらの対応もお忘れ無きよう。

% sudo ntptime
ntp_gettime() returns code 1 (INS)
time d93c801b.a8d4dc28 Tue, Jun 30 2015 11:40:27.659, (.659498716),
maximum error 207343 us, estimated error 490 us, TAI offset 0
ntp_adjtime() returns code 1 (INS)
modes 0x0 (),
offset -30.648 us, frequency -0.114 ppm, interval 1 s,
maximum error 207343 us, estimated error 490 us,
status 0x2011 (PLL,INS,NANO),
time constant 8, precision 0.001 us, tolerance 500 ppm,
% sudo ntptime -s0
(省略)
% sudo ntptime
ntp_gettime() returns code 0 (OK)
time d93c8023.30a87000 Tue, Jun 30 2015 11:40:35.190, (.190070),
maximum error 211343 us, estimated error 490 us, TAI offset 0
ntp_adjtime() returns code 0 (OK)
modes 0x0 (),
offset -30.000 us, frequency -0.114 ppm, interval 1 s,
maximum error 211343 us, estimated error 490 us,
status 0x0 (),
time constant 8, precision 1.000 us, tolerance 500 ppm,

2. NTPがうるう秒の処理

何らかの設定により、NTPクライアントがうるう秒の処理をする場合があります。
具体的には、ntp-4.2.6p5-1.el6、ntp-4.2.6p5-2.el6_6、および ntp-4.2.6p5-18.el7 以下を実行しているRHELディストリビューションでSLEWモード(後述)を設定した場合などが挙げられます。これらの環境では、Leap IndicatorによりNTPクライアントはうるう秒を把握しますが、その情報をLinuxカーネルには渡しません。その代わり、うるう秒になったらstetimeofday(2)やclock_settime(2)などのシステムコールを用いて1秒間時刻を戻します。
カーネルの処理では無いため、一瞬だけ9時0分0秒(JST)を過ぎてから8時59分0秒(JST)に戻されます(検証例あり)。

これは、安定した結果を保障できないため、オススメできません。

3. NTPを利用せずtzdataからうるう秒を取得

タイムゾーンの情報をまとめたtzdataというパッケージには、最新のうるう秒の情報が含まれます。そのため、最新のtzdataパッケージに更新しておくことで、NTPを利用できない場合にもうるう秒の処理を適切に行うことができます。
実際のうるう秒前後の挙動は、1の場合と同じです。

うるう秒を全力を挙げて見逃すには

このように、うるう秒を上手い具合に処理するためにいくつかの方法が用意されているわけですが、古いカーネルで動けど止めづらいサーバがあったり、サーバ数が多すぎて漏れの無い対応がし切れないなどの理由で回避が必要であれば、全力を挙げて見逃す必要があります。その為には、以下の2つをどうするかを考えなくてはいけません。

  • 本来のうるう秒の処理を行わせない。
  • 別の方法でうるう秒の1秒を「うまく」さしこむ。

うるう秒三原則の厳守

回避すると決めたら、大事なのは「うるう秒をもたず、つくらず、もちこませず」の非うるう秒三原則をどう厳守するかを考えます。

1. 持たない

ntpdにパッチを当てることで、強制的にLeap Indicatorをクリアする方法があります。
私は未検証なので紹介に留まりますが、IBMの記事では01や10に強制セットするパッチが下部「図3」として記載されているので、00に強制クリアすることも可能でしょう。

2. 作らない

ntpdが落ちている場合でもうるう秒が不用意に挿入されないよう、意図しないサーバでtzdataが最新にならないようにします。
なお、最新のtzdataが入っているようなケースは他のパッケージでもうるう秒の影響を受けづらいとは思います。

3. 持ち込ませない

Leap Indicatorを除去するためには、仮想化環境でない物理サーバがあるのであれば、ハードウェアクロック(RTC)もそれなりに正確ですので、一時的に外部のNTPサーバへの参照を辞めてしまうのが一番手っ取り早いです。

前述したとおり、NICT系のNTPサーバではうるう秒の24時間前からLeap Indicatorが設定されるので、それより前からうるう秒の後まで、24時間以上を外部への参照を止める必要があります。当然ながら、RTCの品質があまりにも悪い場合や仮想サーバなどでは秒単位で時刻がずれてしまう可能性もあります。

今後Leap Indicatorが一ヶ月前から設定されるようになると現実的では無くなるでしょう。それまでにはうるう秒を誰もが受け入れられる優しい世界か、なんらかの代替策が用意されていると嬉しいです。

1秒の挿入方法

うるう秒を無視するためには、その1秒を何らかの形で「より影響が少ない方法」で反映する必要があります。これは、時計をゆっくり動かして1秒を生み出すのが一般的です。実際に、パブリッククラウドAWSGCP、携帯キャリア、東京証券取引所などではこのやり方を採用しています。

一般的には、ntpdのSLEWモードを使う事になります。特に自前でNTPサーバを立てず、NTPクライアント側で設定を入れるのであれば、「-x」オプションを付ければ良いです。ただし、前述したとおりNTPのバージョンによってはSLEWモードにしていても、うるう秒だけは1秒ガツンと戻されるような不具合がすぐ最近まで残っていたので、注意が必要です。

その一方で、前述の図のように自前NTPサーバを立ててそこを参照しているような場合は、NTPクライアントの設定によりNTPサーバに時刻を聞きに来る頻度がデフォルト設定でも1024秒(約17分強)だったりするので、NTPサーバに「-x」オプションを付けるだけではダメです。また、自前NTPサーバも冗長化している場合にはそのサーバ間での時刻同期も重要となってきます。


このように、結構厄介な地獄が待っていますが、詳しくはそのうちどっかで記事に載るのでは無いでしょうか(他人事)。

(2016-07-04追記) 掲載済みです⇒ CyberAgentでの「うるう秒」対策

確認方法

自由に偽時刻やLeap Indicator、うるう秒を発生させられる検証用NTPサーバNICTさんが配っているので、それを上位NTPサーバとして検証ができます。

まとめ

うるう秒でも正しく動くシステムを作りましょう。
そして、うるう秒の廃絶に向けて、廃止の動きを応援しましょう。

更新履歴