Blog
うるう秒の検証はしましたか?エンジニアが安心して年を越すために
Dynalystの黒崎です。
今年も残すところあと5日となり、あっという間でしたね。
ご存知の方がほとんどかとは思いますが、来年2017年1月1日にうるう秒が挿入される事が決まっています。
参考: 「うるう秒」挿入のお知らせ ~ 来年の元日はいつもより1秒長い日となります ~
簡単に言うと、原子時計に基づく時刻と天文時に基づく時刻のずれを調節するために、JSTで言うところの2017年の1月1日8時59分59秒と9時00分00秒の間に「8時59分60秒」が挿入されるというものです。
直近だと2015年7月1日にうるう秒の調整が行われました。
2015年におおよそのトラブルとなるような要因は修正されたかと思いますが、今回はUTCで年をまたぐ時の調整ですし、安心して年を越すために検証方法を確認しておきましょう。
検証環境
AWSのt2.microインスタンスを2台つかって実験しました。
OSはAmazon Linux 2016.09.1.20161221です。
AWSのうるう秒の対応状況については事前にご確認ください – AWSでのうるう秒対応【更新】が詳しいです。
EC2インスタンス上で動くAmazonLinuxのサーバに関してはうるう秒に対して特別な対応はされていないので、UTCで23時59分59秒が2回繰り返されます。
SNTPサーバ
うるう秒を再現するにあたって、うるう秒前後の時刻情報を配信してくれるNTPサーバが必要です。
NICTの日本標準時グループのサイトにあるうるう秒テスト用のスクリプトを使いました。 うるう秒とコンピュータクロック
2005年のうるう秒の検証用になっているので、2016年用に書き換えたものが以下です。
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 43 44 45 46 47 48 49 50 51 52 53 54 |
#!/usr/bin/perl -wT use strict; use Socket; use Time::HiRes qw( gettimeofday ); use Time::Local; my ( $rb, $sb, $vn, $client, $sec, $msec, $proto, $paddr ); my ( $NTP_LI, $NTP_LEAP, $MODE, $FLAG, $STR, $POLL, $PREC ); my ( $USO_LEAP, $USO_OFFSET ); $NTP_LI = timegm( 0, 0, 0, 31, 12-1, 16 ); # 2016/12/31 00:00:00 UTC $NTP_LEAP = timegm( 0, 0, 0, 1, 1-1, 17 ); # 2017/01/01 00:00:00 UTC $USO_LEAP = timegm( 0, 0, 10, 27, 12-1, 16 ); # 現在時刻より20分くらい前に設定するとよいです $USO_OFFSET = $NTP_LEAP - $USO_LEAP; $MODE = 4; # mode = 4 (server) $STR = 1; # stratum 1 $POLL = 6; # poll interval = 2^6 sec $PREC = -16; # precision = 2^-16 sec $proto = getprotobyname('udp'); socket(NTP, PF_INET, SOCK_DGRAM, $proto) or die "socket: $!"; $paddr = sockaddr_in( 123, INADDR_ANY ); bind(NTP, $paddr) or die "bind: $!"; $sb = "\0" x 48; while ("You hate leapseconds") { ($client = recv(NTP, $rb, 256, 0)) or next; ( length($rb) >= 48 ) or next; ($sec, $msec) = gettimeofday; $sec += $USO_OFFSET; $FLAG = $MODE | ( unpack("C",substr($rb,0,1)) & 0x38 ); # version if(($sec >= $NTP_LI) && ($sec <= $NTP_LEAP)) { $FLAG |= 0x40; } if(($sec > $NTP_LEAP)) { $sec -= 1; } # offset substr($sb, 0, 4) = pack("CCCc", $FLAG, $STR, $POLL, $PREC); substr($sb, 4, 4) = pack("N", 0); # delay substr($sb, 8, 4) = pack("N", 0); # dispersion substr($sb,12, 4) = "LOCL"; # Ref ID substr($sb,16, 4) = pack("N", $sec + 0x83AA7E80); substr($sb,20, 4) = pack("N", 0 ); substr($sb,24, 8) = substr($rb,40, 8); substr($sb,32, 4) = pack("N", $sec + 0x83AA7E80); substr($sb,36, 4) = pack("N", ($msec/500000) * 0x80000000 ); ($sec, $msec) = gettimeofday; $sec += $USO_OFFSET; if(($sec > $NTP_LEAP)) { $sec -= 1; } substr($sb,40, 4) = pack("N", $sec + 0x83AA7E80); substr($sb,44, 4) = pack("N", ($msec/500000) * 0x80000000 ); send(NTP, $sb, 0, $client); } |
ハイライトした15行目の”$USO_LEAP = timegm( 0, 0, 10, 27, 12-1, 16 );”ですが、実験を行う実時間に合わせて書き換えてください。
現在時刻が2016/12/27 09:00 (UTC)だったとして、2016/12/27 10:00 (UTC)にうるう秒を発生させたい場合はこの例のような設定になります。
5分後にうるう秒が発生するようにして実験したところ、うまくいかなかったので余裕を見て20分くらい後に発生するようにすると良いです。
実験用サーバ
うるう秒による影響を確認したいアプリケーションを動作させておきます。
ここでは、単純にタイムスタンプを出力し続けるアプリケーションを作成しました。
1 2 3 4 5 6 7 8 |
#!/usr/bin/ruby require 'time' STDOUT.sync = true loop do sleep 0.1 puts Time.now.iso8601(3) end |
実験
SNTPサーバを起動します。
SNTPサーバを起動するためにはntpdを止める必要があるので、ntpdは事前に止めます。
1 2 3 |
[ec2-user@ip-10-0-0-10 ~]$ sudo service ntpd stop Shutting down ntpd: [ OK ] [ec2-user@ip-10-0-0-10 ~]$ sudo ./sntp.pl |
もう一方のサーバでNTPサーバの参照先を書き換えます。
1 2 3 4 5 6 |
# server 0.amazon.pool.ntp.org iburst # server 1.amazon.pool.ntp.org iburst # server 2.amazon.pool.ntp.org iburst # server 3.amazon.pool.ntp.org iburst restrict 10.0.0.10 server 10.0.0.10 iburst |
serverの項目でAWSのNTPサーバが指定されているのでそれを全てコメントアウトして、下の二行を書き足します。IPアドレスはSNTPサーバのものを指定してください。
1 2 3 4 5 6 |
[ec2-user@ip-10-0-0-11 ~]$ sudo service ntpd stop && sudo ntpdate 10.0.0.10 && sudo service ntpd start Shutting down ntpd: [ OK ] 31 Dec 23:37:13 ntpdate[4171]: step time server 10.0.0.10 offset -7798.967246 sec Starting ntpd: [ OK ] [ec2-user@ip-10-0-0-11 ~]$ date Sat Dec 31 23:37:20 UTC 2016 |
時刻がうるう秒が発生する約20分前になったのがわかります。一足早く年越し気分が味わえますね。
時刻同期の確認をするコマンドを打ちます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
[ec2-user@ip-10-0-0-11 ~]$ ntpq -c rv associd=0 status=4614 leap_add_sec, sync_ntp, 1 event, freq_mode, version="ntpd 4.2.6p5@1.2349-o Thu Jul 28 22:19:51 UTC 2016 (1)", processor="x86_64", system="Linux/4.4.35-33.55.amzn1.x86_64", leap=01, stratum=2, precision=-23, rootdelay=0.573, rootdisp=12.258, refid=10.0.0.10, reftime=dc12c319.609db8db Sat, Dec 31 2016 23:51:53.377, clock=dc12c334.0a8c4c50 Sat, Dec 31 2016 23:52:20.041, peer=52002, tc=6, mintc=3, offset=-0.081, frequency=0.000, sys_jitter=0.000, clk_jitter=3.664, clk_wander=0.000 [ec2-user@ip-10-0-0-11 ~]$ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== *ip-10-0-0-10.ap .LOCL. 1 u 19 64 17 0.573 -10.445 8.992 |
一つ目のコマンドでleap=01となっていればうるう秒が発生するというフラグを受信できていることになります。
この状態でアプリケーションを起動して、挙動の確認をおこないます。
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 |
[ec2-user@ip-10-0-0-11 ~]$ ruby ./time.rb | tee time.log ~省略~ 2016-12-31T23:59:58.756+00:00 2016-12-31T23:59:58.857+00:00 2016-12-31T23:59:58.957+00:00 2016-12-31T23:59:59.057+00:00 2016-12-31T23:59:59.158+00:00 2016-12-31T23:59:59.258+00:00 2016-12-31T23:59:59.358+00:00 2016-12-31T23:59:59.459+00:00 2016-12-31T23:59:59.559+00:00 2016-12-31T23:59:59.660+00:00 2016-12-31T23:59:59.760+00:00 2016-12-31T23:59:59.860+00:00 2016-12-31T23:59:59.961+00:00 2016-12-31T23:59:59.067+00:00 2016-12-31T23:59:59.167+00:00 2016-12-31T23:59:59.267+00:00 2016-12-31T23:59:59.368+00:00 2016-12-31T23:59:59.468+00:00 2016-12-31T23:59:59.569+00:00 2016-12-31T23:59:59.669+00:00 2016-12-31T23:59:59.769+00:00 2016-12-31T23:59:59.870+00:00 2016-12-31T23:59:59.970+00:00 2017-01-01T00:00:00.071+00:00 2017-01-01T00:00:00.171+00:00 2017-01-01T00:00:00.271+00:00 2017-01-01T00:00:00.372+00:00 |
1回目の59分59秒が訪れたあとに、もう一度59分59秒が記録されています。
ちなみに、うるう秒によって1秒挿入された場合は/var/log/messagesにログが出ます。
1 2 3 4 5 6 7 8 9 10 |
[ec2-user@ip-10-0-0-11 ~]$ sudo cat /var/log/messages | grep -e ntpd -e kernel | tail Dec 31 23:37:00 ip-10-0-0-11 ntpd[3863]: ntpd 4.2.6p5@1.2349-o Thu Jul 28 22:19:51 UTC 2016 (1) Dec 31 23:37:00 ip-10-0-0-11 ntpd[3864]: proto: precision = 0.101 usec Dec 31 23:37:00 ip-10-0-0-11 ntpd[3864]: 0.0.0.0 c01d 0d kern kernel time sync enabled Dec 31 23:37:00 ip-10-0-0-11 ntpd[3864]: Listen and drop on 0 v4wildcard 0.0.0.0 UDP 123 Dec 31 23:37:00 ip-10-0-0-11 ntpd[3864]: Listen normally on 1 lo 127.0.0.1 UDP 123 Dec 31 23:37:00 ip-10-0-0-11 ntpd[3864]: Listen normally on 2 eth0 10.0.0.11 UDP 123 Dec 31 23:37:00 ip-10-0-0-11 ntpd[3864]: Listening on routing socket on fd #19 for interface updates Dec 31 23:59:59 ip-10-0-0-11 kernel: [ 4417.088504] Clock: inserting leap second 23:59:60 UTC Jan 1 00:02:48 ip-10-0-0-11 ntpd[3864]: kernel reports leap second has occurred |
時間遡行とかタイムリープと聞くとかっこよさそうですが、どう考えてもやっかいですね。
この状況でアプリケーションが正常に動作して特に問題が起きなさそうであれば何も対策する必要はありません。
アプリケーション内にタイムスタンプの前後関係を利用している処理があると意図しない動作が起きる可能性があるので、許容できるものなのかをきちんと検討した方がよさそうです。
対策
もし困る事がある場合はNTPサーバの参照先をtime.google.comに向けるとよさそうです。
今年からGoogleが誰でも使えるNTPサーバを公開しています。https://developers.google.com/time/
このNTPサーバはうるう秒を発生させずに、徐々に時間をずらして差を埋める実装がされており、利用者はうるう秒の事を気にしなくてよいようになっています。
外部のNTPサーバが参照できない場合は、ntpdの設定(/etc/sysconfig/ntpd)のOPTIONSに”-x”というオプションを足すことで、うるう秒が追加されずに、徐々に時刻を修正するSLEWモードで動作してくれるので、時間遡行が発生しません。
1 2 3 |
[ec2-user@ip-10-0-0-11 ~]$ cat /etc/sysconfig/ntpd # Command line options for ntpd OPTIONS="-g -x" |
SLEWモードで動作させた場合は/var/log/messageに
1 |
Jan 1 00:00:00 ip-10-0-10-11 ntpd[4188]: Ignoring leap second |
といううるう秒を無視するよ、というログがUTCで年をまたぐ時に出力されます。
これでうるう秒に関しては気にせず正月が過ごせますね。
それではよい年越しを!
Author