Azure で ARR を使って3秒程度でレスポンスを返す

エラーでも良いから数秒以内にレスポンスを返したいときって、ありますよね。

IISだけではクライアントにレスポンスを返すのが15秒以上になってしまいます。そのため、今回はリバースプロキシとしてフロントにARRを使いバックエンドにIIS(ASP.NETアプリケーション)の構成をAzureで構築し、3秒程度でレスポンスを返してみます。

オンプレミスなどでは下記のような構成が多いかと思いますが、

20111211-01-visio1

 

今回はAzureで簡単にスケールアウトできることを考慮し、役割を1ロールに集約した下記のような構成にします。

20111211-02-visio2

 

では、実装してみましょう。今回はユーザーからのリクエストをARRなサイトでポート80で受けて、アプリケーションなサイトに8080でリクエストを転送します。

最初に、マルチサイトな構成のプロジェクトを用意し、エンドポイントとローカルストレージの設定をします。

エンドポイント

20111211-03-endpoints

ローカルストレージ

20111211-04-localstorage

ServiceDefinition.csdef

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="ARR_IIS" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebSite" vmsize="ExtraSmall">
    <Sites>
      <Site name="Web">
        <Bindings>
          <Binding name="Endpoint2" endpointName="InternalIn" />
        </Bindings>
      </Site>
      <Site name="ARRWeb" physicalDirectory="..ARRSite">
        <Bindings>
          <Binding name="Endpoint1" endpointName="InputIn" />
        </Bindings>
      </Site>
    </Sites>
    <Endpoints>
      <InputEndpoint name="InputIn" protocol="http" port="80" />
      <InternalEndpoint name="InternalIn" protocol="http" port="8080" />
    </Endpoints>
    <LocalResources>
      <LocalStorage name="LocalStorage1" cleanOnRoleRecycle="true" />
    </LocalResources>
    <Imports>
      <Import moduleName="Diagnostics" />
    </Imports>
  </WebRole>
</ServiceDefinition>

 

 

ARRのインストール

ARRをインストールします。今回は、WebPIを使ってインストールしますので、下記ページからWebPICmdLineをダウンロードします。

WebPICmd コマンドライン ツールの使用方法
http://msdn.microsoft.com/ja-jp/library/gg433092.aspx

ダウンロードしたファイルを展開します。中にある下記のファイルをプロジェクトにコピーし、「出力ディレクトリにコピー」の値を「常にコピーする」に変更します。※今回は、WebSiteのAssetsフォルダにコピーしています。

WebpiCmdLine.exe
Microsoft.Web.Deployment.dll
Microsoft.Web.PlatformInstaller.dll
Microsoft.Web.PlatformInstaller.UI.dll

 

WebPIを使ってARRをインストールするスタートアップタスクを作成し、「出力ディレクトリにコピー」の値を「常にコピーする」に変更します。

startup.cmd

@echo off

if "%EMULATED%"=="true" goto :EOF
"%~dp0AssetsWebPICmdLine.exe" /AcceptEula /Products: ARRv2_5 /log:ARRv2log.txt

 

ServiceDefinition.csdefを編集して、作成したスタートアップタスクを管理者権限で実行するように追加します。また、次に行うARRの設定でランタイムにも管理者権限が必要ですので追加しておきます。

ServiceDefinition.csdef

    <Runtime executionContext="elevated"></Runtime>
    <Startup>
      <Task executionContext="elevated" taskType="simple" commandLine="startup.cmd">
        <Environment>
          <Variable name="EMULATED">
            <RoleInstanceValue xpath="/RoleEnvironment/Deployment/@emulated" />
          </Variable>
        </Environment>
      </Task>
    </Startup>

 

 

ARRの設定

ARRの設定を行います。参照設定に %windir%system32inetsrv にあるMicrosoft.Web.Administration を追加します。

次にServerManagerクラスを使って設定を編集するコードをWebRole.csに追加します。コードではCacheのドライブの設定を行ってProxyを有効にし、bindingInformationを追加しているだけです。

WebRole.cs

    public class WebRole : RoleEntryPoint
    {
        public override bool OnStart()
        {
            try
            {
                InitializeReverseProxy();
                RoleEnvironment.Changed += RoleEnvironmentChanged;
            }
            catch 
            {
            }

            return base.OnStart();
        }

        private void RoleEnvironmentChanged(object sender, RoleEnvironmentChangedEventArgs e)
        {
            try
            {
                if (e.Changes.Any(chg => chg is RoleEnvironmentConfigurationSettingChange))
                {
                    InitializeReverseProxy();
                }
            }
            catch
            {
            }
        }

        public void InitializeReverseProxy()
        {
            var ls = RoleEnvironment.GetLocalResource("LocalStorage1");
            string cachePath = Path.Combine(ls.RootPath, "cache");
            Directory.CreateDirectory(cachePath);

            string websiteName = RoleEnvironment.CurrentRoleInstance.Id + "_Web";

            using (ServerManager serverManager = new ServerManager())
            {
                // Cacheのドライブの設定
                Configuration config = serverManager.GetApplicationHostConfiguration();
                ConfigurationSection diskCacheSection = config.GetSection("system.webServer/diskCache");
                ConfigurationElementCollection diskCacheCollection = diskCacheSection.GetCollection();
                ConfigurationElement driveLocationElement = diskCacheCollection.CreateElement("driveLocation");
                driveLocationElement["path"] = cachePath;
                driveLocationElement["maxUsage"] = 1;
                diskCacheCollection.Add(driveLocationElement);

                // Proxyを有効にする
                ConfigurationSection proxySection = config.GetSection("system.webServer/proxy");
                proxySection.Attributes["enabled"].Value = true;
                proxySection.Attributes["httpVersion"].Value = "PassThrough";
                proxySection.Attributes["timeout"].Value = TimeSpan.FromSeconds(3);

                // bindingInformationを*を有効にする
                ConfigurationSection sitesSection = config.GetSection("system.applicationHost/sites");
                ConfigurationElementCollection sitesCollection = sitesSection.GetCollection();
                ConfigurationElement siteElement = FindElement(sitesCollection, "site", "name", websiteName);

                ConfigurationElementCollection bindingsCollection = siteElement.GetCollection("bindings");
                ConfigurationElement bindingElement = bindingsCollection.CreateElement("binding");
                bindingElement["protocol"] = @"http";
                bindingElement["bindingInformation"] = @"*:8080:";
                bindingsCollection.Add(bindingElement);

                serverManager.CommitChanges();
            }
        }

        private static ConfigurationElement FindElement(ConfigurationElementCollection collection, string elementTagName, params string[] keyValues)
        {
            foreach (ConfigurationElement element in collection)
            {
                if (String.Equals(element.ElementTagName, elementTagName, StringComparison.OrdinalIgnoreCase))
                {
                    bool matches = true;
                    for (int i = 0; i < keyValues.Length; i += 2)
                    {
                        object o = element.GetAttributeValue(keyValues[i]);
                        string value = null;
                        if (o != null)
                        {
                            value = o.ToString();
                        }
                        if (!String.Equals(value, keyValues[i + 1], StringComparison.OrdinalIgnoreCase))
                        {
                            matches = false;
                            break;
                        }
                    }
                    if (matches)
                    {
                        return element;
                    }
                }
            }
            return null;
        }
    }

 

次にリバースプロキシ(ARRサイト)でURL Rewriteの設定を行います。コードで設定するのが面倒だったのでWeb.configに記載しています。

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <rewrite>
      <rules>
        <rule name="ReverseProxyInboundRule1" stopProcessing="true">
          <match url="(.*)" />
          <action type="Rewrite" url="http://127.0.0.1:8080/{R:1}" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

 

あとはデプロイすれば完了です。ARRなサイトもAzureだと簡単に出来て良いですね。3秒以上かかると下記のような感じで502を返します。

20111211-06-timeout

20111211-05-timeout

ちなみに、今回の最終的なプロジェクトは下記のような感じです。

20111211-07-last

 

参考

TechNet – アプリケーションの既定値
http://technet.microsoft.com/ja-jp/library/ee431548.aspx

Comments are closed.

Post Navigation