VMwareの時刻あわせ 2008年冬版

VMwareではWindowsホスト上のLinuxゲストの時計が大きくずれるという問題が昔からあって、調べるといろいろ回避策が見つかります。しかし古い情報が多くなってきており、今となってはあまり適切でない方法になってしまっているものもあります。
そこで、2008年冬時点での時刻あわせ手法についてまとめておきます。環境は以下のとおりです。

  • VMware Server 1.0.8
  • 物理マシン:Core Duo T2300 (1.66GHz) EIST有効
  • ホストOS:Windows XP Professional SP3 32bit
  • ゲストOS:CentOS 5.2 32bit、仮想CPU数 2

目次です。

  1. Windows XPのSNTPサーバを構成する
  2. VMware Serverの設定ファイルを修正する
  3. カーネル再構築は必要ない
  4. CentOS 5.2のカーネルパラメータを変更する
  5. adjtimexで時計の進みを調整する
  6. VMware Toolsの時刻同期機能は使わない
  7. CentOS 5.2のntpdを構成する
  8. おわりに

1. Windows XPのSNTPサーバを構成する

今回の方針ではゲストOSはntpを利用してホストOSとの同期を取ります。そのため、まずはホストOSが正しい時刻を刻んでいる必要があります。
設定は@ITNTPでネットワーク全体のマシンの時刻を合わせる(2)という記事に詳しく記載されていますので、そちらをご参照ください。同期先サーバはISPが提供しているサーバがあればそれを指定しますが、私の利用しているISPはNTPサービスを提供していないためntp.nict.jpを使わせていただいています。また、デフォルトではポーリング間隔が1週間になっているところを1時間に変更しています。あまり間隔を短くするとネットワークや相手のサーバに負荷をあたえてしまいますが、Linuxのntpdにおける最大ポーリング間隔が1,024秒であることを考慮すると、1時間であれば特に問題ないと思います。

2. VMware Serverの設定ファイルを修正する

物理マシンにおいてSpeedStepEIST(Enhanced Intel SpeedStep Technology)などの可変クロック機能が有効化されている場合、ゲストOSの時刻ずれがひどくなります。可変クロック機能を無効化するというのも一つの案ですが、省電力にできるものはなるべく省電力にしたいものです。
可変クロックマシンへの対処方法はネットワールド社のFAQに記載されていますので、これを参考に設定ファイルを変更します。VMware Serverを普通にインストールした場合、設定ファイルはC:\Documents and Settings\All Users\Application Data\VMware\VMware Server\config.iniにあります。

host.cpukHz = 1660000
host.noTSC = TRUE
ptsc.noTSC = TRUE

FAQの後半に記載されている「仮想マシンとホストOS 間の時刻同期化」は、実施しません。

3. カーネル再構築は必要ない

Linuxでは毎秒一定回数のタイマー割り込みが発生し、それに応じてKernelがさまざまな仕事をするとともに時刻を進めるようになっています。この割り込み回数がKernel 2.4では毎秒100回、Kernel 2.6では毎秒1,000回となっており、その後Kernel 2.6.13で250回に変更されています。ちなみにCentOS 5.2はKernel 2.6.18ですが、割り込み回数は毎秒1,000回のままです。
Windowsホスト+Linuxゲストにおいては、この毎秒1,000回の割り込みを取りこぼしてしまうことが時刻ずれの大きな要因になっています。そのため割り込み頻度が毎秒100回のカーネルを再構築するという回避策があり、手順がVMware 上の CentOS の時間が狂う ( θ_Jθ)コマッタモンダなどで紹介されています。(CentOS 4を使っていたころは大変役に立ちました。ありがとうございました)
しかしカーネル再構築はかなり時間がかかりますし、CentOSならまだしもRed Hat Enterprise Linuxでは自前ビルドしたカーネルはサポート対象外になってしまいます。エラータが出るたびに再構築をするのもかなりの手間です。CentOS 4.7以降、あるいはCentOS 5.1以降では次に述べるように、カーネルを再構築せずに割り込み頻度を変更する方法が用意されていますので、今後はこれを利用します。

4. CentOS 5.2のカーネルパラメータを変更する

以下が現在利用している/boot/grub/grub.confの内容です。

default=0
timeout=5
splashimage=(hd0,0)/grub/splash.xpm.gz
hiddenmenu
title CentOS (2.6.18-92.1.18.el5)
  root (hd0,0)
  kernel /vmlinuz-2.6.18-92.1.18.el5 ro root=LABEL=/ rhgb quiet divider=10 clocksource=acpi_pm
  initrd /initrd-2.6.18-92.1.18.el5.img

このように、パラメータを2つ追加します。

  • divider=10
    これが前述した割り込み頻度を変更するパラメータです。Red Hat Enterprise Linux 5.1のリリースノートに説明があります。今回は割り込み頻度を毎秒100回にするために、divider=10と設定しました。
  • clocksource=acpi_pm
    Kernelがシステム時刻を補正するために使う時刻ソースとして、ACPI Power Management Timerを用います。clocksourceについてはMicrosoftサポートオンラインのArticle ID: 918461が参考になるでしょう。CentOS 5.2におけるデフォルトはtsc(Time Stamp Counter)ですが、tscはクロック変動の影響を受けるため可変クロック環境との相性が悪く、仮想環境においてはクロック変動分の補正をしようとしてかえってひどいことになります。そのためVMwareなどの仮想環境では、よりシンプルなpit(Programmable Intervel Timer)の利用が推奨されています。
    ではなぜpitではなくacpi_pmを使うかというと、CentOS 5.2にはdividerとpitを組み合わせるとブート時にハングする不具合があるためです(発言0006636以降の流れを確認してください)。そのため次善の策としてacpi_pmを利用します。acpi_pmはクロック変動の影響を受けませんが、若干CPU負荷が高くなるデメリットがあります。

現在ではVMwareのKnowledge Base 1006427ディストリビューションごとの適切なカーネルパラメータの組み合わせが掲載されています。また、こうした時刻あわせの課題はRed Hat社も認識しており、Red Hat Enterprise Linux 5.4に向けて改善作業が進められています。
割り込み頻度が変更されたかどうかは/proc/interruptsで確認できますが、vmstat 1の方がより簡単です。

# vmstat 1
procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  0     64  57408 170548 553308    0    0     1     8   29    6  0  0 99  0  0
 0  0     64  57408 170548 553308    0    0     0     0  104   46  0  0 100  0  0
 0  0     64  57408 170548 553308    0    0     0     0  123   60  0  0 100  0  0
 0  0     64  57408 170548 553332    0    0     0     0  103   42  0  0 100  0  0

無負荷時にvmstatを取ると、in(interrupts)の値がほぼ100前後で推移していることが確認できます。
clocksourceは以下のようにして確認します。

# cat /sys/devices/system/clocksource/clocksource0/current_clocksource
acpi_pm

また、現時点でもウェブで検索すると「nosmp noapic nolapic」といったカーネルパラメータを指定する事例が数多く見つかりますが、これは古い方法です。目的が達成できれば特に問題はないのですが、nosmpは仮想マシンのCPU数を1個に制限してしまうことに注意してください。現在は上記の設定をすれば仮想CPU 2個の構成でも時刻のずれを抑えることができます。

5. adjtimexで時計の進みを調整する

カーネルパラメータの構成を行うと、最初は10秒あたり1秒ずれていたようなひどい状況が、数分あたり1秒程度まで抑制されます。この後はntpdにまかせてしまうというのもありですが、その前にもうすこし精度を上げることができます。
システム時刻は割り込みタイマーによって進められ、divider=10の場合は1回割り込みが入るごとに0.01秒進むことになります。この進み具合をadjtimexコマンドで調整することができます。

# adjtimex -p
         mode: 0
       offset: 101823
    frequency: 2331347
     maxerror: 336584
     esterror: 36532
       status: 1
time_constant: 6
    precision: 1
    tolerance: 33554432
         tick: 9983
     raw time:  1228648399s 366798us = 1228648399.366798

tickとfrequencyというパラメータが時計の進め方に関する補正値になっています。tickは10,000を基準値として、1増えるごとに0.01%時計の進みが速くなります。これは1日あたりにすると8.64秒ということになります。frequencyはtickの小数点以下の値を指定するもので、6,553,600freqsが1tickに相当します。
つまりゲストOSにおける時刻のずれ方が安定している場合は、tick値を変更することで1日あたりのずれを±4.32秒以内に抑えることができるということになります。時刻のずれは最初に構築したホストOSのSNTPサーバを使って、以下のようにして調べられます。

# ntpdate -b nx6320; sleep 180; ntpdate -b nx6320
 7 Dec 20:25:41 ntpdate[11852]: step time server 192.168.1.2 offset 0.076665 sec
 7 Dec 20:28:41 ntpdate[11895]: step time server 192.168.1.2 offset -0.304926 sec

この例だと3分間で時計が0.304926秒進んでしまっている、つまり0.17%進みが速いということになります。ですから、tickに9983を指定してその分時計がゆっくり進むようにします。

# adjtimex -t 9983
# ntpdate -b nx6320; sleep 180; ntpdate -b nx6320
 7 Dec 20:35:59 ntpdate[12016]: step time server 192.168.1.2 offset -0.028609 sec
 7 Dec 20:38:59 ntpdate[12059]: step time server 192.168.1.2 offset 0.025056 sec

このように、時刻のずれを0.01%まで抑えることができました。実際には3分ではなく、10分や15分などある程度長い時間をかけて計測するとよいでしょう。
ここで求めた設定値は、仮想マシンの起動時に反映されるようにしておきます。例えば/etc/rc.localの末尾に以下のような行を追加します。

/sbin/adjtimex -t 9983 -f 0

この時点ではfrequencyは0にしておきます。frequencyはこの後ntpdの節で扱います。

6. VMware Toolsの時刻同期機能は使わない

VMware ToolsにはゲストOSの時刻をホストOSにあわせる機能がついており、これを用いて時刻あわせをしている環境も多いことと思います。しかし実はこれは古い方法です。
VMware Toolsによる時刻あわせには重大な欠点が一つあります。それは、時刻を進める方向には補正ができるが、時刻を戻す方向には補正ができないということです。これはマニュアル(PDF)にも明記されています。
adjtimexによる補正をかけた段階で時刻のずれはごくわずかになっており、ここからどちらにずれるのかは環境やシステム負荷に依存します。わざと遅れる方向に調整してVMware Toolsで補正しながら運用するという案もあるにはありますが、それよりはきちんとtick値を調整しきって最後はntpdにまかせたほうが良いでしょう。

7. CentOS 5.2のntpdを構成する

ここまで、カーネルパラメータで大きなずれを修正し、adjtimexでやや細かい調整を行いました。最後にntpdで残ったずれを補正します。以下に/etc/ntp.confの内容を示します。

restrict default kod nomodify notrap nopeer noquery
restrict 127.0.0.1
server nx6320
driftfile /var/lib/ntp/drift
keys /etc/ntp/keys

ゲストOSは時刻を提供する側にはならないので、デフォルトのntp.confから変更が必要なパラメータはserverのみです。設定をしたらデーモンを起動し、数分待ってからntpq -pで同期状況を確認します。

# service ntpd start
ntpd を起動中:                                             [  OK  ]
(数分待つ)
# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
*nx6320          133.243.238.243  2 u    5   64   37    0.360   -2.869  19.854

remote列のサーバ名に「*」がつけば、同期ができていることになります。同期ができない場合、adjtimexのtick値がずれすぎていないか、iptablesがntpのパケットを止めていないかなどの点を確認してください。また、最初の段階であまりずれすぎているとntpdが時刻あわせを断念してしまうので、あらかじめntpdateで時刻を同期してからデーモンを起動してみてください。
同期ができたらしばらく運用してみて、時々adjtimex -pを確認します。

# adjtimex -p
         mode: 0
       offset: 28915
    frequency: 3867184
     maxerror: 663263
     esterror: 12562
       status: 1
time_constant: 2
    precision: 1
    tolerance: 33554432
         tick: 9984
     raw time:  1228654414s 784146us = 1228654414.784146

このように、frequencyの値がntpdによって自動的に調整されます。ここでfrequencyの値が3,276,800を超えたり-3,276,800を下回っている場合は、最適なtick値から1以上離れているということになります。気になるようでしたらadjtimexでtick値を微調整していってください。今回の例では最初9,983に設定してみたのですが、実際には9,984が最適値でした。
ntpdは、時刻あわせとして二種類の方法を使い分けています。一つはfrequencyを調節することで徐々に時刻をあわせる方法(slewモード)、もう一つは強制的に現在時刻にあわせる方法(stepモード)です。ntpdはなるべくslewモードで時刻の同期を取ろうとしますが、ずれが0.128秒以上になるとstepモードで時刻をあわせます。以下はここまでの設定を行ったゲストOSのsyslogです。

# grep ntp /var/log/messages
Dec  4 03:49:15 centos5 ntpd[5335]: time reset +0.167550 s
Dec  4 03:53:46 centos5 ntpd[5335]: synchronized to 192.168.1.2, stratum 2
Dec  5 05:30:40 centos5 ntpd[5335]: time reset -1.348708 s
Dec  5 05:34:59 centos5 ntpd[5335]: synchronized to 192.168.1.2, stratum 2
Dec  5 23:49:17 centos5 ntpd[5335]: time reset -0.580365 s
Dec  5 23:53:52 centos5 ntpd[5335]: synchronized to 192.168.1.2, stratum 2
Dec  6 14:14:18 centos5 ntpd[5335]: time reset -0.268466 s
Dec  6 14:19:00 centos5 ntpd[5335]: synchronized to 192.168.1.2, stratum 2
Dec  6 15:10:29 centos5 ntpd[5335]: time reset -0.211380 s
Dec  6 15:15:00 centos5 ntpd[5335]: synchronized to 192.168.1.2, stratum 2
Dec  6 20:33:22 centos5 ntpd[5335]: time reset +0.195034 s
Dec  6 20:37:41 centos5 ntpd[5335]: synchronized to 192.168.1.2, stratum 2

ログをみると、一日に数回程度stepモードが発動してしまっていることが分かります。とはいえずれは最大でも1.35秒であり、一日の大半は時刻のずれが0.128秒以内に収まっていることも確認できます。10秒あたり1秒もずれていた最初の状況からはずいぶんと進歩したものです。
stepモードは強制的に現在時刻にあわせてしまうため、時刻の巻き戻りを引き起こすことがあります。データベースなど業務上重要な役割を担っているサーバの場合は、時刻の巻き戻りが業務に与える影響を事前に確認しておく必要があります。
一応、ntpdの設定でstepモードによる時刻あわせを止めることは可能です。しかしその場合slewモードで補正しきれないとどんどんずれが蓄積していってしまい、最終的にはntpdが止まってしまいます。そのためあまりおすすめはできません。実際にはstepモードの発動を100%阻止することは狙わず、ntpdの最大ポーリング間隔を短くすることで大きなずれを早めに補正するようにすれば良いでしょう。そのためには/etc/ntp.confのserver指定にmaxpollパラメータを追加します。

server nx6320 maxpoll 6

maxpollは2の階乗の値をその指数で記述します。デフォルトは10で、2^10=1,024秒です。つまり1,024秒の間にずれが0.128秒蓄積されてしまうと、次回の同期の際にstepモードが発動することになります。この例のように6にすれば2^6=64秒ですから、デフォルトの設定よりも16倍ずれにくくなるということになります。同期先は同じ物理サーバのホストOSですので、ポーリング間隔をいくら短くしても外部に迷惑はかかりません。

8. おわりに

VMwareにおいてWindowsホストでLinuxゲストの場合、時刻あわせをあきらめていたり、毎秒ntpdateを打つといった豪快な対処をされる方が多いようでしたので、この文書をまとめてみることにしました。VMware使いの方の一助になれば幸いです。
最近の傾向としてはVMware ServerのようなホストOSタイプは下火になって、VMware ESXiやXenなどのハイパーバイザタイプが主流になってきているようですね。ハイパーバイザタイプはまだ使ったことがないのですが、このような時刻あわせの問題は解決されているのでしょうか。Red Hat Enterprise LinuxXenカーネルはわざわざ割り込み頻度が毎秒250回になっているようなので、やはりある程度は課題があるように思います。
VMwareは今のところ趣味で使っているだけなのですが、来年度以降仕事でも何らかの仮想化への取り組みは避けられない状況です。時刻あわせの問題をみるだけでも正直全然枯れていないし、まだまだ過渡期だと思います。開発環境ならともかく、本番環境への投入はちょっと心配です。
以上です。最後まで読んでいただき、ありがとうございました。