Cloud Service で NLog を使う

先日故合ってNLogのコードを見てたら、Cloud Service のカスタムログ転送で面倒なところが簡単に改善できそうだったので、ローカルストレージの定義を、NLogの設定ファイルに書けるようにしたlayout renderer ${azure-local-resource}NLog.Azureを作ってみました。

face

Azure Diagnostics 1.3 PaaS BUGS」は、無事 1.4で改修されて再び使えるようになりました。これで、普通に使えるようになったのですが、相変わらず面倒がことが2つ。

  1. ローカルストレージからのカスタムログ転送はVSからの設定ができない
  2. ローカルストレージのパスはデプロイ毎に変わる

今回の趣旨は、2のデプロイ毎に変わるパスをNLogの設定ファイルに書くにはどうするかという話です。

ローカル ストレージ リソース については、MSDNの 「ローカル ストレージ リソースを構成する」 を見てください。

1. ローカルストレージからのカスタムログ転送設定

ここは前段です。残念ながら現在のVisual Studioでは、GUIでローカルストレージからのカスタムログ転送設定ができません。ここはdiagnostics.wadcfgxに手で書きます。たいして面倒でもなくDataSources のタグの中が今回追加した記述です。これだけで、ローカルストレージ LogStorage からBlobコンテナdiagnostics-custom-logs への転送を定義になります。

 <?xml version="1.0" encoding="utf-8"?>
 <DiagnosticsConfiguration xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
   <PublicConfig xmlns="http://schemas.microsoft.com/ServiceHosting/2010/10/DiagnosticsConfiguration">
     <WadCfg>
       <DiagnosticMonitorConfiguration overallQuotaInMB="4096">
         <DiagnosticInfrastructureLogs scheduledTransferLogLevelFilter="Error" />
         <Directories scheduledTransferPeriod="PT1M">
           <IISLogs containerName="wad-iis-logfiles" />
           <FailedRequestLogs containerName="wad-failedrequestlogs" />
           <DataSources>
             <DirectoryConfiguration containerName="diagnostics-custom-logs">
               <LocalResource relativePath="." name="LogStorage" />
             </DirectoryConfiguration>
           </DataSources>
         </Directories>

     ... 省略 ...

 </DiagnosticsConfiguration>

2. ローカルストレージのパスはデプロイ毎に変わる

これで、ローカルストレージ LogStorage へログを書けばカスタムログ転送でBlobへ送られます。しかし、Cloud Service だと起動するまでローカルストレージがわかりません。また、デプロイ毎に異なったディレクトリを割り当てられます。WorkerRoleでは、csdef でローカルストレージのパスを環境変数に入れておいて、NLog.config の中で、Environment layout rendererを使ってパスを展開するという技も使えます。しかしWebRoleがFull IISになって以来、csdefが解釈実行されるプロセスとアプリケーションが動くプロセスが別になったので、ローカルストレージのパスを環境変数に入れて、configに渡すという技が使えなくなりました。(WebRoleがFull IIS になったのは、はるか昔のAzure SDK 1.3のことですが)基本的には、Microsoft.WindowsAzure.ServiceRuntime の RoleEnviromentからローカルストレージのパスを取得するのですが、そのためにはNLogの設定をコードでコニョゴニョしないといけません。そこで、NLog の設定ファイルにローカルストレージのパスを展開できるような、layout renderer "${azure-local-resource}NLog.Azureを書きました。(名前がおおざっぱ過ぎたかもしれません)

バイナリがNuGetに上げてあるので、インストールはここからします。微妙な気もするので、まだpreviewにしてあります。

$ Install-Package NLog.Azure -Pre

これでインストールしたら、NLog.configに下記のように書くと実行時にローカルストレージのパスに展開されます。

<variable name="logDirectory" value="${azure-local-resource:name=LogStorage:default=c\:/Logs/}"/>

NLog.configのサンプル

name という引数に ローカルストレージ名を書きます。default は、ServiceRuntime が存在しないとき、あるいはローカルストレージの定義が無い場合に使われるバスを書きます。Azureの本番環境あるいはエミュレータ環境では、ローカルストレージにログを書き、そうでない場合はdefaultで指定された場所にログを書くという風に使うことを想定しています。NLogは結構素直な出来で、ここまではすっきり、さくっとできました。log4net よりいい感じですね。

参考までに、簡単なWebRoleのプロジェクトをGitHubに上げてあります。NLog.Azure.Example

おまけ

実は、Microsoft.WindowsAzure.ServiceRuntime.dll には、再配布可能では無いという大きな問題があります。これはどういうことかというと、プロジェクトでServiceRuntime.dllを参照している場合、必ず Azure SDK を入れないと開発/実行できないということです。Storageなどのライブラリは、Azure SDK無しでもNuGet から落としてきたのだけで動くのに、ASP.NETの普通のプロジェクトとして作っていてもServiceRuntime に依存した瞬間にAzure SDKが必要です。NuGetからも入らないのでなかなかメンドクサイことになります。今回の${azure-local-resource} layout rendererも、ServiceRuntimeを参照してしまうと、Azure SDK の入ってない環境ではアセンブリのロードでエラーになってしまいます。せっかく、defaultパラメータとか作ったのに意味が無い、単純なASP.NETのアプリはそのままでに動くようにしておきたいのに。

こんなとき、.NET では、アセンブリを直接参照しないで自前でロードしてリフレクションで関数を呼び出すという方法があります。今回はServiceRuntimeのアセンブリはGACに登録されているけど物理パスがわからない!、さて困った。そんなときは Fusion API でGACから探し出すのが上等手段です。[1]常套手段と言っても普通に暮らしてるとあまり出番ないですが。Fusion (Unmanaged API Reference)[2]は、GACに登録されているアセンブリを操作する Unmanaged API です、昔はドキュメント無かった気がしますが今は普通に書いてありますね。今回は、GACからアセンブリを探してパスを所得するのだけに使います。CreateAssemblyCache() を呼んで AssemblyInfo を取得し、AssemblyInfo を読むだけです簡単ですね。 コード的には、GetAssemblyPath()のあたり。あとは、リフレクションを駆使して、RoleEnvironment.IsAvailable とか、LocalResource.RootPath を呼んでいますが、ここはコード生成するべきかもしれません。 やっていることは簡単なのにメンドクサイ

最後に

ずいぶん間が空いてしまいましたが、2015/5/12 に、株式会社 kyrt にしました、先月には、2015年7月期、Microsoft Azure のカテゴリで、Microsoft MVP Award を受賞をいただきました。皆さまのおかげです、これらかもよろしくお願いいたします。

File System をバッファーとして使うロギングシステムではパフォーマンスに注意した方が良さそうです。Azure Diagonesticsを使うと、out-process の log collector でよしなにBlobへの転送をしてくれるので、直にBlobやTableに書くより良いとは思いますが、アプリとout-process の間のつなぎがFile System なのは信頼性は良いけどパフォーマンスが疑問です。そんなにログ書くのかという話はあると思いますが。そう考えるとアプリとlog collectorの間はETWが良いような気もします。ついでに、log collector でアーカイブだけじゃなくてリアルタイム系のフィルタリングとかやってくれると便利なんだけど。SLABに期待ですね。

[1]How to programmatically determine if .NET assembly is installed in GAC?
[2]Fusion (Unmanaged API Reference)

tinker 1.5 + sphinx 1.3.1 に updateしました

ゴールデンウェークでちょっと時間が出来たのでblobのbackendを更新しました

bubble by Takekazu Omi

テンプレートを変更してないので、見た目は変わりませんね。

Azure Diagnostics 1.3 PaaS BUGS

更新: 2015/4/17 にリリースされた。Diagnostics Extention 1.4 で下記の問題は修正されました

Azure SDK 2.5 から、Windows Azure Diagnostics (WAD) がVM Extentionになりました。それに伴って、Cloud Service (PaaS)でIISのログ転送と、ローカルストレージのカスタムログ転送が行われないという2つの問題があります。(IaaSでは問題はありません)

Tohaku 2015

※この情報は、2015/4/8 Azure SDK 2.5.1, VM Extention 1.3.1.6 を元にして書いています。

IISのログ転送

デプロイ直後はIISのログ転送が行われず、再起動後に転送が開始されるという問題があります。

原因

WADは起動したときにIIS Management Service経由でIIISログの場所探します。デフォルトでは、この場所は、%SystemDrive%inetpublogsLogFiles に設定されています。PaaSでは WebRoleのIISConfigurator がサービス定義に基いてIISの設定を C:Resourcesdirectory{deploymentid.rolename}.DiagnosticStoreLogFilesWeb へ変更します。その結果、WADの構成が、IISConfigurator の前に実行されると、WADは間違ったディレクトリを監視することになってしまいます。

対策

WADの構成がIISConfiguratorの実行の後に起こればいいので、一度Roleを再起動します。それには、Startup Taskあるいは、Role の OnStartで下記のように処理します。最初の起動の時に、IISConfigurator が行った変更に基いて、2回めの起動でWADが構成を設定します。

{
    // Write a RebootFlag.txt file to the %RoleRoot%\Approot\bin folder to track if this VM has rebooted to fix WAD 1.3 IIS logs issue
    string path = "RebootFlag.txt";
    // If RebootFlag.txt already exists then skip rebooting the VM
    if (!System.IO.File.Exists(path))
    {
        System.IO.File.WriteAllText(path, "Writing RebootFlag at " + DateTime.Now.ToString("O"));
        System.Diagnostics.Trace.WriteLine("Rebooting");
        System.Diagnostics.Process.Start("shutdown", "/r /t 0");
    }

    return base.OnStart();
}

この方法だと、ファイルをアプリケーションのディレクトリに作成しているので、インプレースアップグレードの度に再起動がかかります。そのかわり、アクセス権の問題が無いため、elevated する必要がありません。システムドライブや、Cドライブにファイルを作成する、あるいはレジストリにフラグを作成する方法を使うと、再起動は最小限に抑えられますが、.csdef に <Runtime executionContext=”elevated” /> の設定が必要です。

カスタムログ転送

現在のバージョンでは、従来行っていたローカルストレージからのカスタムログ転送は動作しませんので変わりに絶対ディレクトリからの転送を使います。(ちなみにローカルストレージからのカスタムログ転送はVSからの設定も出来ません)

注意点は2点、

  1. ローカルストレージと違ってquotaが利きません。書き出す量を間違えると、ディスクが溢れて他の動作に影響する可能性があります。
  2. 絶対ディレクトリにはCドライブを指定しますが、指定ディレクトリには転送したいファイルだけが存在するようにします。
  3. 絶対ディレクトリを指定した場合、デプロイID毎に別のディレクトリにローカルストレージの領域が取られますが、絶対パスでは同じディレクトリが使われます。
  4. アクセス権を設定する必要があります。

対策

VSで診断構成のアプリケーションログ転送を開きます。

application log settings

この設定をしたら、IISのログ転送対策と同じように、RoleのOnStartで、下記の処理を入れます。ここでは、ディレクトリの有無を確認していますが、WADが先に起動した場合は、ディレクトリは作成されています。アクセス権が足りないので追加します。必要なIISのアクセス権については、しばやん雑記:IIS 7 以降でのアプリケーションプールと権限について調べたを参考にしています。

if (Directory.Exists(@"c:\AppLogs"))
    Directory.CreateDirectory(@"c:\AppLogs");
System.Diagnostics.Process.Start("icacls", "c:\\AppLogs /grant \"BUILTIN\\IIS_IUSRS:\":(OI)(CI)W");

最後に

最後に上記の問題を合わせたコード例を上げておきます。

var path = @"c:\AppLogs\RebootFlag.txt";
if (!System.IO.File.Exists(path))
{
    if (Directory.Exists(@"c:\AppLogs"))
        Directory.CreateDirectory(@"c:\AppLogs");

    System.Diagnostics.Process.Start("icacls", "c:\\AppLogs /grant BUILTIN\\IIS_IUSRS:(OI)(CI)W");

    System.IO.File.WriteAllText(path, "Writing RebootFlag at " + DateTime.Now.ToString("O"));
    System.Diagnostics.Trace.WriteLine("Rebooting");
    System.Diagnostics.Process.Start("shutdown", "/r /t 0");
}

csdefに、下記のように、 <Runtime executionContext=”elevated” /> を追加します。

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="AzureCloudService1" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition" schemaVersion="2014-06.2.4">
  <WebRole name="WebRole1" vmsize="Small">
    <Runtime executionContext="elevated" />
    <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>
  </WebRole>
</ServiceDefinition>

GoAzure 2015 でAzureの永続化について話をしてきました

GoAzure 2015の2つのセッションで喋ってきました。ひとつでは、Azure Storageの概要からIaaSのDiskについてまでを「Persistence on Azure - Microsoft Azure の永続化」でみっちり53分ほど、もうひとつは、「しばやん」のセッション(GoAzure 2015 でクラウドデザインパターンと Azure Websites について話してきました) にちょっとお邪魔。

しばやんのセッションは、しばやんが気持ちよく喋ってるので間に割り込むのが難しく、しゃべりの修行の必要性を感じましたね。今後の課題です。

Persistence on Azure - Microsoft Azure の永続化

スライドは、SlideShareに公開しています。

Persistence on Azure - Microsoft Azure の永続化

セッション、「Persistence on Azure - Microsoft Azure の永続化」は、資料を作り始めたら80ページ、90ページと順当に増えていって、到底50分に収まりそうもなく非常に難儀し、最終的にIaaSのDisk(Block Device)を落とし所にしてStorageの概要を話す形にしました。Azure Storageの特徴は、データセンターの構成や内部構造が絡んで、冗長構成とパフォーマンス特性に現れてくるので、どうしてそうなっているのかあたりを含めつつ短時間で話をするのはなかなか難しいです。

もう少し、Tableも交えて話すと、Azure Storageという共通の仕組みの上に、NoSQLとBlock Device(VM Disk)が乗ってることの素晴らしさが伝わったんじゃないかと思うと少々残念です。クラウドベンダーとしてのMicrosoftという観点から見ると、Storage共通基盤は投資効果が高く、他のベンダーとの差別化ができるポイントではないかと思うので、さらなる今後の発展に期待しています。

榎本さんのセッション 「GoAzure2015 - オープンソース.NETと Mono / Xamarin の今後について」が同じ時間の開催になってしまって見れなかったのが残念でした。自分で聞きたかったから、「台湾でばかり喋ってないで日本でも話してくれないか」とセッションに誘ったのに・・・ orz

後になって、クラウドデザインパターン色を入れても良かったのかなと思いつつも、イベントを楽しめました。聞きたいセッションが被り過ぎてたのが残念と言えば残念ですが、非常に充実したイベントでした。

謝辞

榎本さん、酒井さん、森島さん、しばやん、セッションお疲れ様でした。

JAZUGの皆さん、Microsoftの皆さん、スポンサーの皆さん、関係者の皆さん本当にありがとうございました。いつも裏方から表方までありがとうございます。

参加者の皆様、ありがとうございました。