3.5秒の時刻ズレが決済パイプラインを停止させた障害
約半年前、私たちのマイクロサービスクラスタで忘れられないオンコール障害が発生しました。生成されたばかりのJWTトークンが即座に期限切れと判定され、数百件もの決済トランザクションが連続して拒否されたのです。オンコール担当チームがKibanaでトレースログを確認したところ、ノード間でイベントの順序が激しく乱れており、処理フローを完全に追跡できない状態になっていました。
原因はコードやデータベースのバグではありませんでした。APIサーバーとWorkerサーバーの間で、ちょうど3.5秒の時刻ズレが発生していたのです。CassandraやCockroachDB、Kubernetesのetcdなどを運用しているシステムでは、わずか数十ミリ秒のズレでも合意形成アルゴリズム(コンセンサス)にエラーが発生し、データの整合性が損なわれる恐れがあります。
なぜLinuxサーバーの時刻は常にズレていくのか?
マザーボード上のハードウェアクロック(RTC)には水晶発振子が使用されています。その発振周波数はサーバルームの温度、電圧、部品の経年劣化によって変化します。このクロックドリフト(Clock Drift)と呼ばれる現象により、物理サーバーであっても毎日約1〜2秒進んだり遅れたりします。
KVMやVMware、AWS EC2などの仮想化環境では、この問題はさらに深刻です。仮想CPUのリソース競合(CPU steal)やVMのホスト移動(ライブマイグレーション)が発生すると、クロックのカウントが瞬時に途切れます。その結果、わずか数時間でズレが数分にまで拡大することもあります。
各サーバーが個別にインターネット上のパブリックNTPサーバーと同期していると、国際回線の不安定さによって高いジッター(Jitter)が発生します。さらに、無作為に外部へUDPポート123を開放することは、NTP増幅攻撃(NTP Amplification)に悪用されるセキュリティリスクも孕んでいます。
従来の時刻同期アプローチを振り返る
インフラの標準化を進める前、いくつかの手法を試してきました。
1. Cronjobでntpdateを実行する
15分ごとにntpdateを実行するcronを設定している環境を今でも見かけます。しかし、ntpdateは時刻を強制的にジャンプ(ステップ変更)させて調整するため非常に危険です。時刻が過去へ巻き戻ると、MySQLレプリケーションのbinlog記録や定期実行ジョブなど、タイムスタンプを連続して記録するプロセスが停止・ハングアップする原因になります。
2. 従来のNTPdデーモンを使用する
ntpdはLinuxの世界で長年使われてきました。最大のデメリットは、時刻の収束(キャッチアップ)が非常に遅い点です。サーバーの再起動後やネットワーク切断時、ntpdが正確な時刻に同期するまでに20〜40分もかかります。また、実際の要件に対してメモリ消費量(RAM)もやや多めです。
3. デフォルトのsystemd-timesyncdを使用する
UbuntuやDebianに標準搭載されているこのツールは非常に軽量に動作します。単一のスタンドアロン環境で時刻同期を行うだけであれば十分機能します。ただし、systemd-timesyncdは純粋なクライアントであり、社内ネットワーク内の他のマシンへ時刻を提供するNTPサーバーとして動作させることはできません。
実践的な解決策:社内Chrony NTPサーバーの構築
長期にわたる本番運用の結果、Chronyが最も信頼性の高い選択肢であることがわかりました。このデーモンはブート時に極めて高速に時刻を同期し、アプリケーションの時刻を過去へ巻き戻すことなくスムーズに周波数補正(スルー調整 / Slew)を行い、メモリ消費量も15MB未満に抑えられます。
構成アーキテクチャ
標準的な構成では、DMZまたは管理用セグメントに1〜2台のChrony Masterサーバーを配置します。これらのマスターサーバーは信頼できる上位のStratum 1/2ソースから直接時刻を取得します。そして、社内ネットワーク内のすべてのデータベースサーバー、バックエンドサーバー、スイッチ/ルーターはこのMasterクラスタを参照するように設定します。
ステップ1:Chronyのインストール
DebianおよびUbuntuの場合:
sudo apt update
sudo apt install chrony -y
RHEL、AlmaLinux、Rocky Linux、CentOS Streamの場合:
sudo dnf install chrony -y
ステップ2:ローカルNTPサーバーとしてのChrony設定
設定ファイルは /etc/chrony/chrony.conf(Ubuntu/Debian)または /etc/chrony.conf(RHEL/CentOS)にあります。root権限でファイルを開きます:
sudo nano /etc/chrony/chrony.conf
Masterサーバーに最適な設定内容は以下の通りです:
# レイテンシを低減するため地理的に近い上位NTPサーバーを指定
server 0.asia.pool.ntp.org iburst
server 1.asia.pool.ntp.org iburst
server 2.asia.pool.ntp.org iburst
server time.google.com iburst
server time.cloudflare.com iburst
# 再起動時の補正用にクロック周波数の誤差情報を保存するファイル
driftfile /var/lib/chrony/chrony.drift
# 起動後最初の3回の更新でのみ、ズレが1秒を超えた場合にステップ調整を許可
makestep 1.0 3
# OS時刻をハードウェアRTCチップに自動同期
rtcsync
# 時刻同期を許可する社内サブネットを指定
allow 192.168.10.0/24
allow 10.10.0.0/16
# インターネット切断時でもローカルタイムサーバーとして動作を継続
local stratum 10
ネットワーク計算のTips: 複数サーバークラスタ向けにサブネットを分割する際は、toolcraft.app/ja/tools/developer/ip-subnet-calculator を利用すると、正確なネットワーク範囲やブロードキャストアドレスを素早く確認でき、allow ディレクティブでのIP範囲の誤設定を防止できます。
ステップ3:ファイアウォールのポート開放とサービスの有効化
NTPプロトコルはUDPポート123を使用します。Masterサーバーのファイアウォールでこのポートを開放する必要があります。
UFW(Ubuntu/Debian)を使用する場合:
sudo ufw allow 123/udp
sudo ufw reload
Firewalld(RHEL/CentOS/Rocky)を使用する場合:
sudo firewall-cmd --add-service=ntp --permanent
sudo firewall-cmd --reload
Chronyをシステム起動時に自動起動するように有効化し、サービスを再起動します:
sudo systemctl enable --now chrony
sudo systemctl restart chrony
ステップ4:同期状態の確認
以下のコマンドを実行して上位NTPソースの一覧を確認します:
chronyc sources -v
先頭に ^* が付いているソースは、Chronyが現在メインの同期基準として選択していることを示します:
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^* time.cloudflare.com 3 6 377 25 -122us[ -150us] +/- 14ms
^+ 118.69.177.108 2 6 377 24 -450us[ -478us] +/- 22ms
^+ time.google.com 1 6 377 23 +85us[ +57us] +/- 18ms
システムの詳細なクロック状態を確認します:
chronyc tracking
注目すべき4つの主要パラメータ:
- Reference time:直近で正常に同期された時刻。
- Stratum:原子時計からの階層距離(構築したMasterサーバーは通常Stratum 2または3になります)。
- System time:標準時とのズレ(オフセット)。0.0005秒(0.5ms)以下であれば極めて良好です。
- Root delay:ルートタイムサーバーまでの総ネットワーク遅延。
接続中の社内クライアント一覧を確認します:
chronyc clients
ステップ5:社内Masterを参照するクライアントの設定
LAN内の各アプリケーションノードにChronyをインストールし、設定ファイルを編集してパブリックプールを削除、社内MasterのIPのみを指定します:
# 社内Chrony MasterのIPを直接指定
server 192.168.10.10 iburst
makestep 1.0 3
rtcsync
クライアント側でサービスを再起動して同期を確認します:
sudo systemctl restart chrony
chronyc sources
本番運用における重要ポイント
Chronyを本番環境(Production)に導入する際、注意すべき3つの技術的ポイントがあります:
- 他の時刻同期サービスを完全に無効化する:
systemd-timesyncdやntpdをChronyと同時に動作させないでください。カーネルのシステムクロック制御の競合を防ぐため、sudo systemctl disable --now systemd-timesyncdなどを実行して確実に停止・無効化します。 - makestepパラメータを適切に制御する:時刻のステップ調整はシステム起動直後の最初の3回の更新のみに制限してください。高負荷で稼働中のアプリケーションに対して時刻を過去へ巻き戻すと、分散ロックアルゴリズムやデータベーストランザクションで即座に異常が発生します。
- 最低3〜4つの上位ソースを設定する:Chronyのマルズロアルゴリズム(Marzullo’s algorithm)がクロスチェックを正しく機能させるには、少なくとも3つ以上の独立したソースが必要です。仮に1台のサーバーが異常な時刻を返しても、Chronyは自動的にその異常ソースを計算から除外(隔離)します。

