Windows AzureのCloud ServicesではWindows Server 2012ベースのGuest OS 3.xを使用してPaaSなWebやWorkerを作れます。
さてさてこのGuest OS 3.xなCloud Servicesですが、意外な嵌りどころがあるようなので注意が必要です。結構クリティカル。※2013年2月現在。
対象
Windows Azure Cloud Services の Guest OS 3.x
問題点
Windows Server のNTFSにはファイルの更新を記録するための機能があります。USN(更新シーケンス番号)ジャーナルというのがその機能ですが、今回の問題はこの機能に起因して発生します。
通常、Cloud ServicesのアプリケーションルートはEまたはFドライブになり、1GBしか容量が割り当てられません。
10GB以上容量のあるCドライブやDドライブだと問題が顕在化しませんが、1GBしか割り当てられない(アプリケーションが配置される)EまたはFドライブだと、初期状態で400MB程度このUSNジャーナルの為に確保され、ログがリフレッシュされる前にディスク容量を食いつぶしてしまいます。
※USNジャーナルは循環ログなので一定以上の容量でログがクリアされる
USNジャーナルはファイルの更新履歴を保持するので、エクスプローラーで見えるファイルの容量が変わっていなくても(たとえばファイルの追加・削除を繰り返す等)更新が多いとこの領域が使用され、履歴が増えていきます。
例えばAppRoot以下に安易にテンポラリファイルを作っては消しするとか、そういう操作をたくさん行っているとそのうちE/Fドライブの空き容量が無くなり作り方によってはアプリが停止してしまう事態になるということです。
※iisnodeとかだと何か定期的に更新してるみたいで、じわじわと容量が減るみたいですね…謎。
※期間によってリフレッシュされるかどうかまでは検証できていません。
問題の再現
以下のようなアプリを実行し、ファイルの作成・削除を行います。
using System; namespace ConsoleApplication1 { class Program { static void Main(string[] args) { long i = 0; var utf8 = new System.Text.UTF8Encoding(false); while (true) { var fileName = Guid.NewGuid().ToString(); using (var sw = new System.IO.StreamWriter(fileName, false, utf8)) { sw.Write("small text"); } System.IO.File.Delete(fileName); Console.WriteLine(string.Format("{0:#,0}",i)); i++; } } } }
※コードは手抜きです。4k以上のファイル操作したら例外でるかもという話ですがまぁ。
※実際に見ればわかりますが、初期状態で20MB程度しかエクスプローラー上ではファイルが無いのに空きディスク容量が420MB程度(=USNジャーナルで420MB近くとっている)です。
上記アプリをWindows Azure上でEまたはFドライブで実行します。すぐに容量が減っていくことが確認できると思います。
だいたい85万回ぐらい(テストアプリは読み書き1セットだから170万回ぐらい)ファイルの変更等すると空き容量が無くなる=500MBぐらいログが肥大化するようです。
以下はUSNジャーナルの様子。
※ちなみに、テストアプリをそのまま放置すると(なぜか更新できるので)USNジャーナルのログが循環して450MBぐらいに空き容量が復活してその後は400~450MBあたりの空き容量を推移します。(意味がない)
一度肥大化したUSNジャーナルは以下のコマンドを実行することで削除できます。
fsutil usn deletejournal /D E:
※E: または F:を指定します。
削除した瞬間は空き容量が950MB程度になります。※すぐにUSNジャーナル用に確保されてしまいますが。
対策
Windows Server 2008 R2(Guest OS 2.x)だとこういった現象にはならなかったので(ジャーナルが無効といわれた)、問題ないようです。つまりWindows Server 2012で同じ設定になるか、不具合?を修正するかしてもらえればいいのですが今のところ不明なので暫定として、定期的にUSNジャーナルを削除する方法をとりたいと思います。
スタートアップタスクでジャーナルの削除を行うようにタスクスケジューラーに登録してその場しのぎをすることにします。
以下のバッチファイルを作っておきます。
@echo off fsutil usn deletejournal /D e: fsutil usn deletejournal /D f:
次に上記バッチを定期実行するようにタスクスケジューラーに登録するスタートアップタスク用バッチファイルを作成します。
@echo off copy deletejournal.bat C:\ schtasks /create /tn DeleteJournal /tr c:\deletejournal.bat /sc HOURLY /ru System
どちらもAzure用のプロジェクトに追加して出力ディレクトリにコピーされるようにしておきます。
後は管理者権限で起動するようにスタートアップタスクをサービス定義ファイルに指定します。
<?xml version="1.0" encoding="utf-8"?> <ServiceDefinition name="WindowsAzure5" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2012-10.1.8"> <WebRole name="MvcWebRole1" vmsize="Small"> <Sites> <Site name="Web"> <Bindings> <Binding name="Endpoint1" endpointName="Endpoint1" /> </Bindings> </Site> </Sites> <Endpoints> <InputEndpoint name="Endpoint1" protocol="http" port="80" /> </Endpoints> <Imports> <Import moduleName="RemoteAccess" /> <Import moduleName="RemoteForwarder" /> </Imports> <Startup> <Task commandLine="createtask.bat" executionContext="elevated" taskType="simple" /> </Startup> </WebRole> </ServiceDefinition>
まとめ
ディスク空き容量には気を付けよう。※ローカルストレージを有効に使おうね☆
※USNジャーナルのMAXサイズを変えたりとか、他にも手はありそうですが根本的にAzure側で対応してくれたら済む話だと思うのでそこまでしないことにしました。
最後に、いろいろ検証手伝っていただいた @takekazuomi さん、ありがとうございました。