しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Azure Web Apps 上で JRuby を使って Redmine 3.0.3 を動かす

昨日の記事で Azure Web Apps でも JRuby をインストールして Rails 4 を動かすとこまで出来たので、今日は Redmine 3.0.3 をインストールしてみました。

以前に Redmine 2.5 をインストールしたことはありましたが、デプロイスクリプトの実行に失敗したり不安定な面がありました。原因は DevKit 周りだったので、JRuby を使うことで解消できるという目論見です。

インストールの流れは Redmine 2.5 の時と変わりませんが、以下の記事を参考にしながら行いました。

DB として前回は ClearDB を使いましたが、今回は記事の通り SQLite を使うことにします。

ファイルをダウンロード

Redmine 公式サイトから 3.0.3 をダウンロードして、適当に展開しておきます。

Download - Redmine

今回もカスタムデプロイスクリプトを使って JRuby のインストールと、bundle install の実行を行うので必要なファイルをリポジトリからコピーしておきます。

見たところ Gemfile.lock が Redmine のファイルに含まれていなかったので、deploy.cmd の該当部分を修正して Gemfile.lock を使わないようにします。

:: 2. Exec Bundler
IF EXIST "%DEPLOYMENT_TARGET%\Gemfile" (
  echo Executing bundle install

  PUSHD "%DEPLOYMENT_TARGET%"
  
  %JRUBY_EXE% -S "%JRUBY_BUNDLER_CMD%" install --without development:test --path vendor/bundle --binstubs vendor/bundle/bin -j4
  IF !ERRORLEVEL! NEQ 0 goto error
  
  POPD
)

これで Azure Web Apps に必要な設定は終わったので、Redmine 自体の設定を行います。

Redmine の設定を変更する

サーバーとして今回も puma を使うので、Gemfile に追記しておく必要があります。

gem "puma"

当然ながら忘れると、最後の最後で動かなくなるので注意です。

config\database.yml ファイルも必要なので、サンプルをリネームして SQLite を使うように書き換えます。

production:
  adapter: jdbcsqlite3
  database: db/redmine.db
  encoding: utf8

これで準備が出来たので、Git リポジトリを作成して Azure Web Apps へデプロイする準備を行いますが、database.yml は .gitignore に書かれているので、そのままだとコミットされない点だけ注意したいです。

Web Apps へデプロイ

このままデプロイすることで JRuby と必要な gem がインストールされますが、更に少しコマンドを叩く必要があるので Kudu の Debug Console から作業を行います。

と言っても、ドキュメント通りに rake を実行するだけです。

set PATH=D:\home\site\wwwroot\bin\jruby-1.7.20\bin\;%PATH%
set RAILS_ENV=production

jruby -S bundle exec rake generate_secret_token
jruby -S bundle exec rake db:migrate
jruby -S bundle exec rake redmine:load_default_data

全て実行に成功すると、ブラウザからのアクセスで Redmine が立ち上がるようになります。

初回起動には時間がかかりますが、一度起動してしまえばサクサク動くようになります。以前に 2.5 を動かした時よりも快適かもしれません。

メモリを結構必要とするので、B1 以上のインスタンスで Always On を有効にして使うのが現実的かなと思いました。少し弄ったぐらいですが、実用出来るレベルだと感じました。

大きなはまりどころもなく、デプロイスクリプトを使って理想的な形でデプロイ可能なので、Azure Web Apps で Ruby を動かす場合は JRuby が一番良いかなと思いました。

標準でインストールされるとパフォーマンスが改善されると思うので、少しぐらい期待してみたいです。

JRuby を使うと Azure Web Apps でも Ruby on Rails 4 をデプロイするだけで動かすことが出来た

これまで何回か Azure Web Apps 上で Rails を動かしてきましたが、簡単に使えるデプロイ可能にしようとすると bundle install でエラーになる謎現象に阻まれて諦め気味でした。*1

ふと JRuby でも動くことを思い出したのと、以前に Scott Hanselman がブログで書いていたので Web Apps にデプロイするカスタムデプロイスクリプトを作成しました。

Announcing: Running Ruby on Rails on IIS8 (or anything else, really) with the new HttpPlatformHandler - Scott Hanselman's Blog

結果としては、必要なファイルをコピーして少し修正するぐらいで Web Apps へ Rails 4 アプリをデプロイ出来るようになりました。GitHub にサンプルを上げてあります。

リポジトリ内にある必要なファイルは以下の 5 つになります。

  • .deployment
  • deploy.cmd
    • Web Apps のカスタムデプロイスクリプト
  • Procfile
  • startup.cmd
    • 以前に作った foreman っぽく使うスクリプト*2
  • Web.config
    • HttpPlatformHandler を使うための設定

JRuby のバージョンは決め打ちにしてあります。細かい説明は後にして、とりあえず空っぽの Web App を用意してデプロイしてみましょう。

このリポジトリをフォークするなどして Azure Web Apps にデプロイするだけで、自動的に JRuby のインストールを行い、その後 bundle install を実行して必要な gem を全てインストールするようになっています。

JRuby 1.7.20 をインストールしていることが確認出来ます。その後に bundler も一緒にインストールします。

wwwroot 以下に KuduSync.NET でファイルをコピーした後には、bundle install を実行し必要な gem のインストールを行います。これも少し時間がかかります。

暫く待つとデプロイが完了するので、ブラウザからサイトへアクセスしてみます。ちなみに私が B1 インスタンスで試したところ、立ち上がるのに 2 分近くかかりました。

気長に待つとプロセスが立ち上がるので、その後はサクサク動作するようになります。

環境情報にちゃんと JRuby で動作していることが表示されています。

実用的かと言われると、起動の遅さは Azure Web Apps のストレージパフォーマンスに問題があると思っているので、このあたりが改善されるまでは Always On で誤魔化すぐらいしか出来ないかなと思ってます。

この後はテンプレートに行った修正をまとめます。

Gemfile に puma を追加する

RubyInstaller の時には thin を使いましたが、JRuby では puma が使えるらしいのでこっちを使いました。

gem 'puma'

Gemfile に上の 1 行を追記するだけです。

sqlite3 のインストールが失敗する

標準で Gemfile に追加されている sqlite3 のインストールに失敗したので、以下のページを参考にして Gemfile を修正しました。

JRuby on RailsでSQLite3を使うための覚え書き - NAT’s Programming Champloo

sqlite3 となっている部分を、以下のように修正するだけでした。

gem 'activerecord-jdbcsqlite3-adapter'

これだけで依存関係で色んなものが入ってくれるみたいです。

tzinfo-data が存在しないエラーになる

puma が起動して、最後の最後に tzinfo-data のデータソースが読み込めないというエラーが発生しました。少し検索すると簡単に解決策が見つかりました。

Resolving TZInfo::DataSourceNotFound Errors · tzinfo/tzinfo Wiki · GitHub

元々が Windows で作った Gemfile なので、指定が抜けていただけのようです。

gem 'tzinfo-data', platforms: [:mingw, :mswin, :jruby]

この修正で、問題なくデプロイから実行までスムーズに行くようになりました。

追記:スタートアップ時間について

JRuby の Wiki にスタートアップ時間を改善する方法がいくつか書いてありました。

Improving startup time · jruby/jruby Wiki · GitHub

いくつかを試してみましたが、どれも効果はないように感じました。やはりストレージ周りかもしれません。

クライアント VM を使う方法に関しては、Azure Web Apps の制約があるのかわかりませんが JAVA_HOME を変更できず、常に 64bit 版の Java 1.7.0_51 が使われてしまうみたいでした。

Azure Web Apps を使うと WordPress の脆弱性を教えてくれる

今朝、Azure から Security Alert を伝えるメールが届きました。今まで受け取ったことが無かったので少し驚きましたが、中身を読むと Azure の監視体制がしっかりしていることが理解出来ました。

要するに、Azure Web Apps にデプロイしている WordPress が脆弱性のあるバージョンだから、早くアップデートしなさいと書いてあります。

WordPress なんてデプロイしていたかなと思いましたが、daruyanagi.com が WordPress だったのを思い出しましたので、管理者でログインしてみると確かにアップデートの通知が来ていました。

daruyanagi.com は半分死んでいるので、アップデートのことなんて完全に忘れていました。

アップデート自体はとても簡単で、ワンクリックで WordPress 4.2.2 へのアップデートが完了しました。

管理しているサイトが多くなると、バージョン管理に関しては放置気味になってしまいがちですが、脆弱性の修正を含む重要なアップデートに関しては個別に通知をしてくれるのはとても便利ですね。

かなり前から Web Apps は有名な OSS アプリケーションに関しては、デプロイされているかと言った情報を収集しているみたいでしたし、WordPress 以外でも同じように通知がされるかもしれません。

Azure Web Apps で HttpPlatformHandler を foreman っぽく使えるバッチファイルを作った

Azure Web Apps で Hubot を動かした時に、HttpPlatformHandler が実行するアプリケーションを Procfile から読み込むようにしましたが、もうちょっと汎用的に作れる気がしたので修正してみました。

foreman のパチモンなので pseudo-foreman という名前でリポジトリ作っておきました。中には Hubot のサンプルも入れてあるので、Azure Web Apps で動かす時の参考にどうぞ。

Web.config で HttpPlatformHandler の設定を行い、実行される startup.cmd が Procfile を読み込んで、アプリケーションを実行するだけのシンプルなものです。

リポジトリにも 2 つのファイルしか置いてありません。

利用方法は startup.cmd と Web.config をコピーして、Azure Web Apps にデプロイするだけです。

ポート番号は HTTP_PLATFORM_PORT と PORT 両方の環境変数から取れますが、Procfile 内で参照は出来ない仕様になってます。このあたりは何とかしたいです。

苦労した点と HttpPlatformHandler の制約

そもそも HttpPlatformHandler を使う上での問題点が、起動されるプロセスのカレントディレクトリが D:\Windows\System32 になることです。基本的にドキュメントルートをカレントディレクトリとして動かす前提のアプリケーションが多いので、この挙動をまずは何とかする必要があります。

回避方法は、単純に cd でカレントディレクトリを移動させることです。これで大体は解決します。

SET WWWROOT_DIR=%~dp0

cd /d %WWWROOT_DIR%

そして、Azure Web Apps としての制約として環境変数 PATH を変更できないというのがあります。これはシステムにインストールされることが前提となる Strawberry Perl を使う場合などに問題となります。

システムの環境変数は弄れなくてもプロセス単位なら弄れるので、以下のようにして PATH_EXTEND が設定されているときに PATH に追加するようにしておきます。

IF DEFINED PATH_EXTEND (
  SET "PATH=%PATH_EXTEND%;%PATH%"
)

Hubot の時には coffee-script をグローバルでインストール出来なかったので node_modules\.bin へパスを通すコードを書いておきましたが、PATH_EXTEND という名前でポータルから設定すればパスが通ります。

これで HttpPlatformHandler で何かを動かすことが少し楽になりました。カスタムデプロイスクリプトと組み合わせることで、新しいプラットフォームにも比較的楽に対応出来ると思います。

Azure Web Apps で Hubot を Git でデプロイして動かす方法

何となく Hubot を使って @statemachine2 を書き換えようかと思ったので、まずは Azure Web Apps で Hubot をデプロイして動かすところまで試しました。

https://hubot.github.com/
https://hubot.github.com/docs/

Node.js で動くので、当然ながら Windows 向けの Node.js をインストールしておきます。

テンプレートを作成

テンプレートの作成自体は Yeoman を使って行いますが、ドキュメント通りで問題なかったです。

npm install -g yo generator-hubot

mkdir statemachinebot & cd statemachinebot
yo hubot

Yeoman で Hubot のテンプレート作成中にいろいろ聞かれるので、ボット名や作者など適当に答えます。重要なのは adapter ですが、今回はシンプルに Twitter を使うようにしました。

これで基本的な Bot のテンプレートが完成しましたが、このままだと Web Apps で実行出来ないので、テンプレートのコードを少しだけ修正していきます。

テンプレートを修正

デフォルトでは bin\hubot.cmd に npm install コマンドが入っていますが、Azure Web Apps へ Git デプロイした場合には自動的にインストールされるので、予め削除しておきます。

@echo off

node_modules\.bin\hubot.cmd --name "statemachinebot" %* 

Hubot は実行に Coffee Script が必要なので、以下のコマンドを実行して package.json に追加しておきます。

npm install coffee-script --save

これでデプロイ時に Coffee Script がインストールされるようになります。

更にデフォルトでは hubot-heroku-keepalive が package.json と external-scripts.json に含まれていますが、このままだとエラーになるので削除しておきます。Azure Web Apps では Always On を有効にするだけです。

起動スクリプトを追加

Azure Web Apps で Node.js アプリを動かす場合には iisnode が使われますが、今回は HttpPlatformHandler を使うので startup.cmd として以下のようなバッチファイルを作成しておきます。

処理としては Procfile を読み込んで実行しているだけです。

@echo off

SET DOCUMENT_ROOT=%~dp0
SET PROCFILE=%DOCUMENT_ROOT%Procfile

SET PATH=%DOCUMENT_ROOT%node_modules\.bin\;%PATH%

cd /d %DOCUMENT_ROOT%

IF NOT EXIST %PROCFILE% (
  exit 1
)

FOR /f "delims=: tokens=1,2" %%i IN (%PROCFILE%) DO (
  IF "%%i" == "web" (
    call %%j
  )
)

Procfile の web で指定されているコマンドを実行するようになってます。

そして startup.cmd を実行するために Web.config を作成し、HttpPlatformHandler の設定を追加します。

<?xml version="1.0"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
    </handlers>
    <httpPlatform processPath="D:\home\site\wwwroot\startup.cmd" startupTimeLimit="120">
      <environmentVariables>
        <environmentVariable name="PORT" value="%HTTP_PLATFORM_PORT%" />
      </environmentVariables>
    </httpPlatform>
  </system.webServer>
</configuration>

Procfile を読み込むように作ったので、多少の汎用性があります。これで特に startup.cmd と Web.config は都度変更する必要は無くなったかと思います。

Azure Web Apps へデプロイ

デプロイ自体は Git を使ってこれまで通りに行うだけなので割愛します。設定に関してはポータルから Twitter のアクセストークンを追加するだけです。

これでブラウザでアクセスすると Node.js が起動するので、Bot 自体も同時に起動します。

簡単にテストとして ping を投げると返事を返してきました。

Azure Web Apps の特性として一定時間アクセスが無い場合サイトが落ちるので、常時立ち上げておく必要がある場合には Always On を有効にしておけば問題無いです。

Azure Web Apps が Go で書かれたアプリケーションのデプロイに標準で対応してました

前回見た時は Go の x64 ランタイムがインストールされていただけでしたが、最近 Kudu がアップデートされて Go で書かれたアプリケーションをデプロイして実行することが出来るようになっていました。

既に Go を何回か使ったことがありますが、Kudu が標準で対応したので同じコードを Git でデプロイしてみました。デプロイスクリプトでの対応なので、SCM を使わないとダメです。

単純なアプリケーションをデプロイ

以前に試した文字列を返すだけの単純なアプリケーションをデプロイしてみました。

適当に Web App を作って git push してデプロイしてみたところ、特に問題なくデプロイは完了しました。

ブラウザでアクセスするとあっさりと動作しました。バージョンは 1.4.2 なのもわかります。

デプロイスクリプトの実行ログを確認すると、ソースコードを実行時にコンパイルするのではなくデプロイのタイミングで実行ファイルにコンパイルして、それを wwwroot にコピーするようになっています。リポジトリに Web.config を用意する必要もありません。

実行ログをまるっと貼り付けておきます。

Handling Go deployment.
Prepare workspace
GOROOT D:\Program Files\go\1.4.2
Creating GOPATH\bin D:\local\Temp\a7f1898d-a920-4b54-a4ae-5c840c91e440\gopath\bin
Creating GOPATH\pkg D:\local\Temp\a7f1898d-a920-4b54-a4ae-5c840c91e440\gopath\pkg
Creating GOPATH\src D:\local\Temp\a7f1898d-a920-4b54-a4ae-5c840c91e440\gopath\src
Creating D:\local\Temp\a7f1898d-a920-4b54-a4ae-5c840c91e440\gopath\src\azureapp
Copy source code to Go workspace

-------------------------------------------------------------------------------
   ROBOCOPY     ::     Robust File Copy for Windows
-------------------------------------------------------------------------------

  Started : Friday, May 1, 2015 8:30:08 AM
   Source : D:\home\site\repository\
     Dest : D:\local\Temp\a7f1898d-a920-4b54-a4ae-5c840c91e440\gopath\src\azureapp\

    Files : *.*

Exc Files : .deployment
            deploy.cmd

 Exc Dirs : .git
            .hg

  Options : *.* /NDL /NFL /S /E /DCOPY:DA /COPY:DAT /NP /R:1000000 /W:30

------------------------------------------------------------------------------


------------------------------------------------------------------------------

               Total    Copied   Skipped  Mismatch    FAILED    Extras
    Dirs :         2         0         1         0         0         0
   Files :         1         1         0         0         0         0
   Bytes :       316       316         0         0         0         0
   Times :   0:00:00   0:00:00                       0:00:00   0:00:00
   Ended : Friday, May 1, 2015 8:30:08 AM

Resolving dependencies
..
Building Go app to produce exe file
Copy files for deployment
KuduSync.NET from: 'D:\home\site\repository' to: 'D:\home\site\wwwroot'
Deleting file: 'hostingstart.html'
Copying file: 'azureapp.exe'
Copy web.config
        1 file(s) copied.
Finished successfully.
Deployment successful.

Go アプリも ASP.NET と同様に Kudu がビルドするので、スタートアップのパフォーマンスが改善されていると思います。デフォルトで CI 対応は良い感じですね。

Martini アプリケーションをデプロイ

単純なアプリケーションだけではなく、Martini を使ったアプリケーションも動くか確認してみました。ライブラリのインストールが必要なはずですが、どうなるのか気になったので試します。

特に考えずに git push すると、import に書いてある依存関係を自動的に解決してくれるみたいです。デプロイの履歴は以下のように、前回のコードを丸っと書き換える形にしました。

設定ファイルなど必要なく、ソースのデプロイだけで問題なく動作しました。テンポラリで依存関係の解決とビルドを行うので、これまでの環境に依存しないのがとても良いです。

HttpPlatformHandler のポート番号を取得する部分だけ注意したいです。久しぶりになる Web Apps での新しいプラットフォーム対応なので喜ばしいですね。

Azure App Service Environment がプレビュー公開されました

Build 2015 の Keynote では発表されなかった気がしますが、Azure App Service Environment がプレビューとして公開されました。プレビューなので料金は多分半額です。

Preview: Azure App Service Environment | Azure updates | Microsoft Azure
http://azure.microsoft.com/ja-jp/documentation/articles/app-service-web-how-to-create-an-app-service-environment/

既にドキュメントが色々と公開されているので、少しだけ引用してきました。

An App Service Environment is a Premium service plan option of Azure App Service that provides a fully isolated and dedicated environment for securely running all of your apps. This includes Web Apps, Mobile Apps, Api Apps and Logic Apps with expanded scaling options.

ASE v1 の概要 - Azure App Service Environment | Microsoft Learn

完全に分離された環境、リソースで App Service を利用したいという要望は多かったようです。特に仮想ネットワークに関しては Web Apps でも対応していますが、Point to Site の参加になるという制約があります。

簡単に要約すると App Service の実行環境ごと作る機能と言ったところでしょうか。

Azure App Service は簡単に使えますが、アーキテクチャは複雑になっていて制約も少なからず存在します。

App Service で Basic / Standard インスタンスを選んだ場合、Web Worker だけを占有する形になりますが App Service Environment では恐らく IIS ARR まで占有する形になります。全てのインスタンスを占有する形になるので、仮想ネットワークへの参加が行えるようになるわけですね。

既に新ポータルからは作れるようになっているので、すぐに試すことが出来ます。しかし、あくまでも App Service Environment は Premium 以上のスケールを必要とする場合や、仮想ネットワークへの完全な参加が必要な場合に限定と考えておいた方が良いでしょう。

何故なら、必要となるインスタンスは最低でも Frontend に P2 が 2 つ、Web Worker として P1 が 2 つ必要となるからです。日本円に換算すると月に 4.5 万ぐらいで、GA になれば倍になるので 10 万近くになりますね。

作成完了までに 2 時間ほどかかるのと、課金が恐ろしかったので試してはいませんが、設定は調べました。ちなみに仮想ネットワークへの参加は必須です。

App Service Environment を使うと *.p.azurewebsites.net というドメインになるみたいです。

コンピューティングリソースは 50 まで選択可能で、Worker Pool 毎に数を設定可能です。通常の App Service と異なり、App Service Environment を使った場合には Worker Pool 単位でスケールを設定するようです。

VIP も 10 個まで確保できるので、IP SSL も簡単に使えるようになっているみたいです。

個人的にはこれでクラウドサービスを使う必要が無くなったと思います。デフォルトで HA 構成になってますし、仮想ネットワークに参加出来るようになったのは大きいです。

Azure WebJobs でログを出力する方法をまとめる

ちょっと前に Azure WebJobs のログ周りで少し躓いたので、少し挙動を調べてみました。

Console.WriteLine を使う

単純なコンソールアプリケーションを WebJobs として実行する場合には、Console.WriteLine を使うだけでランタイムがログとして保存してくれます。

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("log message");
    }
}

ただし、単純に Console.WriteLine を使っただけでは、日本語を書きだした時に化けます。

普通にコンソールで文字列出力するとエンコーディングが UTF-8 以外になっているのが原因なので、回避するために OutputEncoding で UTF-8 を明示的に指定してあげます。

class Program
{
    static void Main(string[] args)
    {
        // UTF-8 を出力エンコーディングに指定しないと化ける
        Console.OutputEncoding = Encoding.UTF8;

        Console.WriteLine("テストメッセージ");
    }
}

これで再度実行すると、日本語がちゃんと出力されているのが分かります。

入力値をログで書きだす場合には、マルチバイト文字が混ざってくる可能性があるので常に設定しておきたいですが、開発中に表示がおかしくなるので Release ビルドの時のみ有効にしておいた方が良いでしょう。

TextWriter をバインドする

Azure WebJobs SDK を使う場合には TextWriter を logger という名前で引数に追加しておけば、ランタイム側がいい感じにログとして扱ってくれるようになります。

メソッド内では WriteLine などを使って書き込むだけになります。

public class SampleJobs
{
    public static void SampleFunc([QueueTrigger("queue")] string message, TextWriter logger)
    {
        logger.WriteLine("キュー受信:" + message);
    }
}

メソッドと実行履歴単位で確認出来るので、WebJobs SDK を使う場合には logger を使った方が便利です。

Console.WriteLine で書きだした場合には、メソッド単位にならないのでそこだけ注意ですね。

Redis Desktop Manager を使って Azure Redis Cache の管理を行ってみた

RESP.app (formerly Redis Desktop Manager) - GUI for Redis ® available on Windows, macOS, iPad and Linux.

これまでは redis-cli を使って Azure Redis Cache の操作を行っていましたが、Redis Desktop Manager というクロスプラットフォームなアプリがあるようなので、実際にインストールして試してみました。

Redis Desktop Manager をインストールして起動すると、シンプルな画面が立ち上がります。

この後は Add Connection を選んで Azure Redis Cache への接続設定を追加するんですが、Redis Desktop Manager は SSL を使った接続に対応していないみたいなので、Azure Redis Cache の設定から非 SSL ポートを有効にしておきます。

非 SSL ポートを有効にしてから Add Connection ダイアログに接続情報を入力します。

これで接続設定に問題が無ければ DB の一覧が表示されます。既に RedisSessionStateProvider を使って多少のキーを入れておいたので、データも表示されています。

JSON が値として追加されている場合、べたなテキストではなくインデントを付けた形で表示することが出来るのは、結構便利な気がします。ちゃんと UTF-8 な文字列も表示されますし。

Azure Redis Cache はデフォルトで SSL のみ通信可能になっているのに対して、Redis Desktop Manager は SSL に対応していないという点だけ注意したいところです。

人生で初めて Azure Cloud Services の RoleEntryPoint で IIS の設定を弄った話

Azure Cloud Services の Web Role を使って IIS の設定を変更してみたかったので、田口社長のブログを検索して、その中の 1 つを参考にしつつ試しました。

http://blogs.gine.jp/taka/archives/2846

さすが安心と信頼の田口社長、クラウドサービスは全て田口社長で安心。と思っていたら田口社長は海外出張中なので、半泣きになりながら自分で調べました。

ちなみに題材としては前に書いた IIS 8.5 のカスタムログの設定を自動化してみます。

必要な設定は URL Rewrite の allowedServerVariables への追加と、ログにサーバー変数を書きだす設定の追加となります。簡単そうに見えますね。

Startup Task として書く方法と RoleEntryPoint の OnStart で書く方法があるみたいですが、IIS の設定を変える場合には OnStart で実行した方が良いみたいでした。

スタートアップ処理のスタートアップ タスク段階では IIS が完全に構成されていない可能性があります。そのため、ロール固有のデータが使用できない場合があります。ロール固有のデータが必要なスタートアップ タスクは Microsoft.WindowsAzure.ServiceRuntime.RoleEntryPoint.OnStart を使用してください。

https://msdn.microsoft.com/ja-jp/library/azure/hh180155.aspx

なので RoleEntryPoint を使うことにしました。

Microsoft.Web.Administration.dll を参照に追加

IIS の設定を弄るのに必要な Microsoft.Web.Administration.dll は IIS がインストールされているディレクトリと同じ場所にあります。*1

注意点としては IIS のバージョンと Microsoft.Web.Administration.dll のバージョンは対応しているみたいなので、このあたりだけ注意したいところです。

更にローカルマシンの IIS によって古いバージョンだったり、HintPath の都合で GAC を参照したりするみたいなので、プロジェクトにコピーしておくのが無難かもしれません。

RoleEntryPoint を追加する

IIS の設定を弄るためには権限を与えておかないといけないので、ServiceDefinition.csdef に以下のような設定を追加しておきます。

<Runtime executionContext="elevated"></Runtime>

これで RoleEntryPoint の OnStart で IIS の設定を弄れるようになります。

実際に書いた WebRole のコードは以下のような感じです。

public class WebRole : RoleEntryPoint
{
    public override bool OnStart()
    {
        using (var serverManager = new ServerManager())
        {
            // CustomLogFields
            var site = serverManager.Sites[RoleEnvironment.CurrentRoleInstance.Id + "_Web"];

            site.LogFile.CustomLogFields.Clear();
            site.LogFile.CustomLogFields.Add("SESSION_VALUE", "SESSION_VALUE", CustomLogFieldSourceType.ServerVariable);

            // allowedServerVariables
            var config = serverManager.GetApplicationHostConfiguration();

            var allowedServerVariablesSection = config.GetSection("system.webServer/rewrite/allowedServerVariables");
            var allowedServerVariablesCollection = allowedServerVariablesSection.GetCollection();

            var addAllowedServerVariablesElement = allowedServerVariablesCollection.CreateElement("add");

            addAllowedServerVariablesElement["name"] = @"SESSION_VALUE";

            allowedServerVariablesCollection.Clear();
            allowedServerVariablesCollection.Add(addAllowedServerVariablesElement);

            serverManager.CommitChanges();
        }

        return base.OnStart();
    }
}

注意点としてはインプレース更新の場合、既に反映済みの applicationHost.config に対して処理を行ってしまうので、設定を削除してから追加するようにしました。本来なら該当する項目だけ削除するのが良いんでしょうが、今回はめんどくさいので全部消します。

これでデプロイすると applicationHost.config に設定が追加されていることが確認出来ます。

Azure Web Apps チームがどのように大量の VM を管理しているのか、事例を聞いてみたいです。

*1:つまり C:\Windows\System32\inetsrv とか