仮想化の「時間」は嘘をつく:クラウドエンジニアを悩ませるクロックドリフトと正体
インフラエンジニアとして現場を渡り歩いていると、たまに「なぜかAPIのレスポンスが微妙にズレる」「分散ログのタイムスタンプが前後する」といった不可解な現象に遭遇します。ネットワークの遅延やDBのロックを疑い、パケットをキャプチャし、トレースを追いかけても異常は見当たらない。
そんな時、最後にたどり着く「ラスボス」が、仮想化環境特有の時刻同期問題です。物理サーバーと違い、仮想マシン(VM)はハイパーバイザーという「神」のさじ加減一つで時間を奪われます。今回は、この泥臭いけれど避けては通れない「時間」の話を深掘りしていきましょう。
—
1. なぜ仮想マシンは時間を失うのか?
物理サーバーであれば、OSはハードウェアのタイマー(RTCやTSC)を直接叩けます。しかし、仮想化環境ではOSがハードウェアにアクセスしようとすると、ハイパーバイザーが一度横取りし、VMの命令をエミュレーションして返します。
ここで発生するのがスケジューリング遅延です。ハイパーバイザーが他のVMを優先してCPUリソースを割り当てている間、あなたのVMは「CPUが停止している」状態になります。その間、OSが保持している時計は止まっているため、現実世界の時刻との間に乖離が生じます。これがクロックドリフトの正体です。
—
2. 魔法の杖:kvm-clock とパラメーター設定
Linuxカーネルには、このドリフトを吸収するための仕組みが用意されています。その代表格が kvm-clock です。これは、ハイパーバイザーがゲストOSに対して「今の正確な時間はこれだ」と直接教え込む仕組みです。
実務でまず確認すべきは、現在どのクロックソースが使われているかです。以下のコマンドを叩いてみてください。
# 現在利用可能なクロックソースを確認
cat /sys/devices/system/clocksource/clocksource0/available_clocksource
# 現在アクティブなクロックソースを確認
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
もしここが kvm-clock ではなく acpi_pm や tsc になっている場合、仮想化環境ではパフォーマンスや精度に悪影響が出る可能性があります。特にクラウド(AWSのEC2やGCPのGCE)では、ハイパーバイザー側で最適化された kvm-clock や tsc を利用するのが鉄則です。
設定のヒント:カーネルパラメーター
ブート時に指定する場合は、/etc/default/grub を編集します。
# /etc/default/grub の GRUB_CMDLINE_LINUX に以下を追加
# clocksourceを明示的に指定する場合の例
GRUB_CMDLINE_LINUX="clocksource=kvm-clock"
—
3. NTPかPTPか:高精度を求めるあなたへ
Web APIの設計において、数ミリ秒のズレが致命的になるケース(分散トランザクションやレートリミット判定など)では、NTP(Network Time Protocol)だけでは力不足な場合があります。
- NTP: UDP 123番ポートを使用。インターネット越しの同期に向くが、精度は数ミリ秒〜数10ミリ秒程度。
- PTP (Precision Time Protocol): IEEE 1588で規定。ハードウェアスタンプを利用し、マイクロ秒単位の精度を実現。
クラウド上では chrony を使うのが現代のスタンダードです。ntpd よりもネットワークの変動に強く、仮想化環境特有の急激な時刻変動をスムーズに補正してくれます。
chrony の設定例(/etc/chrony/chrony.conf)
# 公開NTPサーバーを指定し、burstで立ち上がりを高速化
server ntp.nict.jp iburst
# クロックのドリフト量を記録するファイル
driftfile /var/lib/chrony/drift
# 仮想化環境ではシステムクロックを調整しやすくする
rtcsync
—
4. API設計で「時間」を扱う時の注意点
インフラ側でどれだけ努力しても、アプリケーション側での扱い方が雑だと全てが台無しになります。API設計において、以下のルールを徹底してください。
1. タイムスタンプは必ず UTC で保存する: ローカルタイムは論外です。
2. ISO 8601 フォーマットを採用する: 2023-10-27T10:00:00Z のように、末尾に Z をつけることでUTCであることを明示します。
3. クライアントの時刻を信用しない: クライアントから送られてきた日時データは、セキュリティ・整合性の観点から「参考値」として扱ってください。
Pythonで正しいUTC時刻を取得する例です。
from datetime import datetime, timezone
# 常に明示的にUTCを指定する
now_utc = datetime.now(timezone.utc)
# APIレスポンス用フォーマット
print(now_utc.isoformat())
# 出力例: 2023-10-27T01:23:45.678901+00:00
—
最後に:SREからのアドバイス
「時刻がズレる」という障害は、往々にして「たまにしか起きない」「特定のノードでしか起きない」という非常に厄介な性質を持っています。
もしAPIのログで整合性が取れない事態に陥ったら、まずは各ノードの chronyc tracking コマンドで、オフセット(ズレ)が異常に大きくなっていないか確認してください。インフラのトラブルシューティングは、論理的な推論と、OSが吐き出す泥臭いメトリクスの整合性を見比べることから始まります。
皆さんのシステムが、どんな時も正確な時を刻み続けられるよう、この記事が少しでも役立てば幸いです。それでは、また現場でお会いしましょう。
コメント