TL;DR

  • 2017 年元日のうるう秒で時刻が巻き戻り、Cloudflare の DNS サーバ RRDNS で値が負になって panic が起き、一部の名前解決が失敗した。
  • 原因は「時刻は巻き戻らない」という思い込みで、負の時間を記録しないように直した。

内容

何が起きたか

2017 年 1 月 1 日の 0 時(UTC)に、Cloudflare の DNS ソフトウェア RRDNS で、0 未満にならないはずの値が負になった。この panic は Go の recover で捕まえられたものの、Cloudflare が管理するサイトの一部で名前解決が失敗した。影響を受けたのは、オリジンサーバを CNAME レコードで指定している顧客だけである。102 のデータセンターのうち一部のマシンで起き、ピーク時でも DNS クエリの約 0.2%、HTTP リクエストの 1% 未満にとどまった。

CNAME の名前解決

顧客はオリジンサーバのアドレスを、A レコード(IP アドレス)か CNAME で Cloudflare に渡す。CNAME の場合、Cloudflare は社内のリゾルバに対して自分で再帰的な問い合わせを行う。RRDNS はリゾルバごとの性能を記録し、速いものほど選ばれやすくなる重み付きの選択を行う。バグはこの CNAME の問い合わせのコードにあった。

原因: 時刻は巻き戻らないという思い込み

根本の原因は、時刻は巻き戻らないという思い込みである。Go の time.Now() は単調に増えることを保証しないので、うるう秒のときなどに時計が戻ることがある。

RRDNS は 2 回の time.Now() の差でリゾルバの往復時間(RTT)を測っていた。リゾルバは数ミリ秒で応答するよう調整されていたので、問い合わせの途中で時計が戻ると RTT が負になった。負の値が 1 回出るだけなら害はないが、RRDNS は測定値を平滑化しているので、平滑化した値がやがて負になった。重み付きの選択はこの値を rand.Int63n に渡しており、rand.Int63n は引数が負だと panic する。

対応

修正では負の時間を記録しないようにした。時刻が巻き戻ったときは、その時点の性能の値を捨て、時計が戻らなくなってから計算し直す。残っていた不正な状態は、RRDNS のサーバを再起動して消した。

影響は 0 時に始まり、0 時 34 分に確認された。修正は 0 時 55 分にカナリアのノードへ、1 時 23 分に最も影響の大きいデータセンターへ入り、1 時 48 分に全体へ行き渡った。影響が終わったのは 6 時 45 分(UTC)である。Cloudflare は、うるう秒の影響を受ける時間間隔の使い方がほかにないか、コード全体を調べているとしている。

キーワード

  • DNS
  • うるう秒
  • CNAME
  • RRDNS