UNIX/Linux目線のWindows Azure入門ガイド 6ページ

ロール起動時の処理を記述する

 永続的ストレージ(Azure Drive)のマウントについては、WebRole.cs内のOnStartメソッド内にC#で記述する。先にも述べたとおり、OnStartはロール起動時に実行されるメソッドだ。ここでは、下記の処理を記述する。

  • 永続的ストレージ(Azure Drive)へのアクセスに必要なIDの取得
  • Azure Driveの作成とマウント
  • 各種サービスの設定に必要な情報の取得
  • サービス設定の変更

 Azure Driveを利用するためのコンポーネントは標準ではプロジェクトに含まれていないので、まずコンポーネントの参照設定を変更してそれらを利用できるようにしておく必要がある。これはソリューションエクスプローラーでWebロールの「参照設定」を右クリックして表示される「参照の追加」で行う(図32)。

図32 「参照設定」を右クリックし、メニューから「参照の追加」を選択する
図32 「参照設定」を右クリックし、メニューから「参照の追加」を選択する

 「参照の追加」ウィンドウが表示されるので「参照」タブをクリックし、「c:\Program Files\Windows Azure SDK\v1.6\ref\Microsoft.WindowsAzure.CloudDrive.dll」(Windows Azure SDKのインストール先がC:ドライブの場合)というファイルを選択して「OK」をクリックする(図33)。これでAzure DriveをWebロールから利用できるようになる。

図33 「参照の追加」ウィンドウの「参照」タブで「c:\Program Files\Windows Azure SDK\v1.6\ref\Microsoft.WindowsAzure.CloudDrive.dll」というファイルを指定してOKをクリックする
図33 「参照の追加」ウィンドウの「参照」タブで「c:\Program Files\Windows Azure SDK\v1.6\ref\Microsoft.WindowsAzure.CloudDrive.dll」というファイルを指定してOKをクリックする

永続的ストレージの作成とマウント

 続いて、Azureドライブをマウントするためのコードを作成する。このコードはWebRole.csのOnStart()メソッド内に記述する。

 まず、Webロールのプロパティ内で「設定」として保存しておいたWindows Azureのストレージアカウント情報を取得するコードを記述する(リスト4)。

リスト4 ストレージへのアクセスに必要なIDを取得するコード

CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
{
  configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
  RoleEnvironment.Changed += (sender, arg) =>
  {
    if (arg.Changes.OfType<RoleEnvironmentConfigurationSettingChange>()
.Any((change) => (change.ConfigurationSettingName == configName)))
    {
      if (!configSetter(RoleEnvironment.GetConfigurationSettingValue(configName)))
      {
        RoleEnvironment.RequestRecycle();
      }
    }
  };
});

 続いて、Azure Driveの作成とマウントを行うコードを記述する(リスト5)。

リスト5 Azureドライブを作成してマウントするコード

// WebRole.csの先頭付近に下記を追加する
using Microsoft.WindowsAzure.StorageClient; // CloudDriveの利用に必要
 :
 :
//
// Windows Azure Drive用のblob割り当て
//
int DRIVE_SIZE = 1024; // ドライブのサイズは1024MB(=1GB)とする

// Webロールのプロパティで「StorageAccount」として設定しておいたアカウント情報を読み出す
CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("StorageAccount");

// blob操作用のクライアントを作成
CloudBlobClient client = account.CreateCloudBlobClient();

// ストレージコンテナを作成
CloudBlobContainer container = client.GetContainerReference("ftp-storage");

// 成功するまでリトライする
bool is_created = false;
while (!is_created)
{
  try
  {
    container.CreateIfNotExist();
    is_created = true;
  }
  catch (StorageClientException e)
  {
    // エラーコードをチェックしてサービス側のエラーであればリトライする
    switch (e.ErrorCode)
    {
      case StorageErrorCode.ServiceInternalError:
      case StorageErrorCode.ServiceTimeout:
      case StorageErrorCode.ServiceBadResponse:
      case StorageErrorCode.ServiceIntegrityCheckFailed:
      case StorageErrorCode.TransportError:
        break;
      case StorageErrorCode.ContainerAlreadyExists:
        is_created = true;
        break;
      // そのほかのエラーはとりあえず放置
      default:
        is_created = true;
        break;
    }
  }
}

// blobの参照を取得
CloudPageBlob blob = container.GetPageBlobReference("storageblob");

// ローカルストレージを取得
LocalResource localCache = RoleEnvironment.GetLocalResource("DriveCache01");

// キャッシュとして利用するパスと容量を指定
int cacheSize = localCache.MaximumSizeInMegabytes;
CloudDrive.InitializeCache(localCache.RootPath, cacheSize);

// AzureDriveの作成
drive = new CloudDrive(blob.Uri, account.Credentials);
string drive_path = "";
try
{
  // ドライブを作成
  // CloudPageBlob側でcreateを実行してしまうとエラーとなるので注意
  drive.CreateIfNotExist(DRIVE_SIZE);
  // ドライブをマウント
  drive_path = drive.Mount(cacheSize, DriveMountOptions.None);
  // adrive_pathは末尾に「\」がついているのでこれを除く
  drive_path = drive_path.Replace(@"\", "");
}
catch (CloudDriveException)
{
  return base.OnStart();
}

 詳細はコード内のコメントを参照してほしいが、ここでは「ftp-storage」という名称のBlobストレージを作成し、そこに「storageblob」というBlobを作成してAzureドライブとしてマウントしている。Blobストレージについては本連載第3回で説明する予定なので詳細はそちらで解説する予定だ。

各種サービスの設定に必要な情報の取得

 WebRole.cs内では、インスタンスのIPアドレスやディレクトリ構成といったインスタンスの実行時情報を.NETライブラリ経由で取得できる。各種サービスの設定変更などにこれらの情報が必要な場合は、ここであらかじめ情報を取得しておくと便利だ。たとえば、WebロールにインターネットからアクセスするためのURLやIPアドレスは次のようにして取得できる(リスト6)。

リスト6 WebロールにインターネットからアクセスするためのURLやIPアドレスの取得

// WebRole.csの先頭付近に下記を追加する
using System.Net;                           // DNSアクセスに利用
 :
 :
//
// インターネット側のIPアドレスを取得
//
string dep_id = RoleEnvironment.DeploymentId;
string hostname = dep_id + @".cloudapp.net"; // stagingの場合、ホスト名はこれになる
string ip_addr;
try
{
  IPHostEntry ip_info = Dns.GetHostEntry(hostname);
  ip_addr = ip_info.AddressList[0].ToString();
}
catch (System.Net.Sockets.SocketException)
{
  ip_addr = "";
}
if (ip_addr == "") // productionの場合、作成時に指定したDNSプレフィックスが付加される
{
  try
  {
    hostname = "<デプロイ時に指定したDNSプレフィックス>" + ".cloudapp.net";
    IPHostEntry ip_info = Dns.GetHostEntry(hostname);
    ip_addr = ip_info.AddressList[0].ToString();
  }
  catch (System.Net.Sockets.SocketException) // それでも失敗したらlocalhostを指定
  {
    hostname = "localhost";
    ip_addr = "127.0.0.1";
  }
}
// ip_addrがIPアドレス、hostnameがホスト名となる

 また、Webサーバーのホスト名は次のようにして取得できる。

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

サービス設定の変更

 .NETライブラリにはWindowsの設定・管理用モジュールも多数含まれており、これらを用いてWebRole.cs内にシステムやサービスの設定変更処理を記述できる。また、任意のコマンドを実行することも可能だ。今回は、C#内からバッチファイルを呼び出して設定変更を行うことにする(リスト7)。

 ここでは、プロセスを実行するProcessクラスを作成し、実行するコマンドとして「cmd.exe」を指定、その引数にバッチファイル「onstart.cmd」を指定している。また、バッチファイルにインターネットのIPアドレスやAzure Driveのパス、FTPで使用するSSL証明書などの情報を渡すための環境変数とその値も定義している。

リスト7 OnStart()でバッチファイルを呼び出す

// WebRole.csの先頭付近に下記を追加する
using System.Diagnostics;                   // Processクラスの利用に必要
 :
 :
// onstart.cmdを実行
Process p = new Process();
string role_root = Environment.GetEnvironmentVariable("ROLEROOT");
p.StartInfo.FileName = @"cmd.exe";
p.StartInfo.Arguments = @"/c """"" + role_root + @"\approot\bin\onstart.cmd""""";
p.StartInfo.UseShellExecute = false;

// 環境変数を定義
p.StartInfo.EnvironmentVariables.Add("INET_IP_ADDR", ip_addr);

if (RoleEnvironment.IsEmulated)
{
  // エミュレータ環境の場合
  p.StartInfo.EnvironmentVariables.Add("AZUREDRIVE_ROOT", @"C:\temp");
}
else
{
  // 実環境の場合
  p.StartInfo.EnvironmentVariables.Add("AZUREDRIVE_ROOT", drive_path);
}

string ssl_cert = "<利用するSSL証明書のハッシュ>";
p.StartInfo.EnvironmentVariables.Add("SSL_CERT", ssl_cert);

// 実行
p.Start();

 なお、IIS 7.5のFTP機能ではSSLによる暗号化通信が必須となる。SSLで利用する証明書は本来であれば証明書発行機関によって発行されたものを利用するのが好ましいが、テスト目的であればリモートデスクトップ接続用に自動的に生成される自己署名証明書を利用することもできる。証明書はロールのプロパティ内「証明書」項目で確認できるので、コード内の「<SSLで利用する証明書のサムプリント>」部分にはここに表示されているサムプリントを記述しておく(図34)。

図34 ロールのプロパティ内「証明書」で登録されているSSL証明書を確認できる
図34 ロールのプロパティ内「証明書」で登録されているSSL証明書を確認できる

 実行する「onstart.cmd」バッチファイルの内容はリスト8のとおりだ。このファイルもstartup.cmdと同様「日本語(シフトJIS)- コードページ932」エンコードで保存し、プロパティの「出力ディレクトリにコピー」は「新しい場合はコピーする」に変更しておく。

リスト8 Webロールの開始時に実行される「onstart.cmd」ファイル


REM FTP/Webサーバーの設定
setlocal
set ftp_sitename=FTP Site
set ftp_path=%AZUREDRIVE_ROOT%
set ftp_cert=%SSL_CERT%
set web_site=%ROLEINSTANCEID%_Web
set web_root=%AZUREDRIVE_ROOT%\WWWRoot

REM IISの設定変更
cd /d %windir%\system32\inetsrv

REM すでにFTPサイト設定が作成されている場合はいったん削除
appcmd delete site "%ftp_sitename%"

REM FTPサイト設定の作成
appcmd add site /name:"%ftp_sitename%" /bindings:ftp/*:21: /physicalPath:"%ftp_path%"

REM SSL証明書の追加
appcmd set site "%ftp_sitename%" /ftpServer.security.ssl.serverCertHash:%ftp_cert%

REM 基本認証の有効化
appcmd set site "%ftp_sitename%" /ftpServer.security.authentication.basicAuthentication.enabled:true

REM 承認規則の追加
appcmd set config "%ftp_sitename%" /section:system.ftpServer/security/authorization /+"[accessType='Allow',users='*',permissions='Read, Write']" /commit:apphost

REM FTPでPASV向けに使用するポートを指定する
appcmd set config /section:system.ftpServer/firewallSupport /lowDataChannelPort:"5000" /commit:apphost
appcmd set config /section:system.ftpServer/firewallSupport /highDataChannelPort:"5000" /commit:apphost

REM ロードバランサのインターネット側IPアドレスを指定
appcmd set config /section:system.applicationHost/sites /siteDefaults.ftpServer.firewallSupport.externalIp4Address:%INET_IP_ADDR% /commit:apphost

REM ファイアウォールの設定
netsh advfirewall firewall add rule name="FTP" action=allow protocol=TCP dir=in localport=21
netsh advfirewall firewall add rule name="FTP-Data" action=allow protocol=TCP dir=in localport=5000

REM FTPサーバーを再起動する
net stop ftpsvc
net start ftpsvc

REM Webサーバーの設定
REM ルートディレクトリを作成する
mkdir %web_root%

REM 公開するルートディレクトリを変更する
appcmd set vdir "%web_site%/" /physicalPath:"%web_root%"

endlocal
exit /b 0

 onstart.cmdではIISのコマンドライン設定ツール「appcmd.exe」を利用してIISのパラメータを変更している。

 Windows Azure環境で特有なのは、PASVモードの受付ポートとして5000番を指定している点だ。Windows Azure環境では各インスタンスがロードバランサ経由でインターネットに接続されるのだが、Windows Azureのロードバランサではどのポートを利用するかを事前に指定しておく必要がある。そのため今回はPASVモードの受付ポートを5000番に固定している。

 そのほか、FTPサーバーのルートディレクトリはマウントしたAzure Driveのルートディレクトリを、WebサーバーのルートディレクトリはAzure Driveのルートディレクトリ直下の「WWWRoot」に設定している。

永続的ストレージのアンマウント

 Webロールの終了時には、マウントしたAzure Driveを適切にアンマウントする必要がある。WebRoleの停止時にはWebRoleクラス内のOnStopメソッドが実行されるので、このメソッドを作成してマウントしたドライブをアンマウントする処理を記述しておく(リスト9)。

リスト9 マウントしたAzure DriveはOnStop()でアンマウントする

public override void OnStop()
{
  try
  {
    drive.Unmount();
  }
  catch (CloudDriveException e) { }
  base.OnStop();
}

 以上でWebRole.csの変更は終了だ。記事末に今回作成したWebRole.cs全文を掲載しているので、コード全体はそちらを参照してほしい。

WebRole.csの実行権限を変更する

 最後に、WebRole.csが管理者権限で実行されるよう設定しておく。これは、スタートアップタスクの設定でも用いた「ServiceDefinition.csdef」ファイルで設定できる。具体的には、ファイル内の「<WebRole>」要素内に「<Runtime executionContext="elevated"></Runtime>」という要素を追加する(リスト10)。スタートアップタスクを設定した際と同様、「</WebRole>」の直前に要素を追加すれば良い。

リスト10 ServiceDefinition.cscfgファイルにリスト中赤字で記されている要素を追加する

<?xml version="1.0" encoding="utf-8"?>
<ServiceDefinition name="AzureWithFTP" xmlns="http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">
  <WebRole name="WebRole1" vmsize="Small">
    <Sites>
      <Site name="Web">
 :
 :
    <Runtime executionContext="elevated"></Runtime>
  </WebRole>
</ServiceDefinition>

 以上で今回作成するWindows Azureアプリケーションの設定は完了だ。続いてビルドおよびデバッグを行い、問題がなければWindows Azure環境へのデプロイを行うという流れとなる。