SE の雑記

SQL Server の情報をメインに Microsoft 製品の勉強内容を日々投稿

VM Role 起動時に Windows Azure Drive を自動でマウント

leave a comment

前回の投稿では、VM Role で Windows Azure Drive をマウントする方法をまとめてみました。
PowerShell を手動で実行しているので VM Role のインスタンスを再起動するたびに手動でマウントしないといけません…。
これはちょっと非効率ですよね。

今回の投稿では前回作成したPowerShellのスクリプトをタスクスケジューラでシステム起動時 (サーバーの起動時) に実行するように設定して、自動でマウントできるように設定をしてみたいと思います。
Web / Worker ロールであれば、StartUpTask や OnStart に起動時の処理を書けばよいと思うのですが、VM Role はそのあたりの仕組みは自前で実装しないといけないかと。

処理としてはこのような流れですね。
image

 

 

■タスクスケジューラを設定


それでは、タスクスケジューラを設定したいと思います。
今回の作業は VM Role のインスタンス上でがりがりと作業をしています。
本来は、VHD をアップする前のオンプレミス環境で実施しておくのですけども、まぁ勉強ですので。

ベースとなるマウント用のスクリプトは [C:ToolsMountDrive.ps1] に保存をしています。
# VM Role でいろいろ遊んでいるので、私の VM Role のインスタンスの中身はごちゃごちゃしています (汗)
image

今回は以下のタスクスケジューラーを設定しています。

<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.3" xmlns="
http://schemas.microsoft.com/windows/2004/02/mit/task">
  <RegistrationInfo>
    <Date>2011-01-25T15:18:25.7720892</Date>
    <Author>WIN-4U4J5U33UM5user01</Author>
  </RegistrationInfo>
  <Triggers>
    <BootTrigger>
      <Enabled>true</Enabled>
    </BootTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>S-1-5-18</UserId>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
    <StopIfGoingOnBatteries>true</StopIfGoingOnBatteries>
    <AllowHardTerminate>true</AllowHardTerminate>
    <StartWhenAvailable>false</StartWhenAvailable>
    <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
    <IdleSettings>
      <StopOnIdleEnd>true</StopOnIdleEnd>
      <RestartOnIdle>false</RestartOnIdle>
    </IdleSettings>
    <AllowStartOnDemand>true</AllowStartOnDemand>
    <Enabled>true</Enabled>
    <Hidden>false</Hidden>
    <RunOnlyIfIdle>false</RunOnlyIfIdle>
    <DisallowStartOnRemoteAppSession>false</DisallowStartOnRemoteAppSession>
    <UseUnifiedSchedulingEngine>false</UseUnifiedSchedulingEngine>
    <WakeToRun>false</WakeToRun>
    <ExecutionTimeLimit>P3D</ExecutionTimeLimit>
    <Priority>7</Priority>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>cmd.exe</Command>
      <Arguments>/C "powershell -file c:ToolsMountDrive.ps1" &gt;&gt; c:ToolsMountDrive.log</Arguments>
    </Exec>
  </Actions>
</Task>

上記の XML はタスクスケジューラーの内容をエクスポートしたものになります。
これをコピーして XML ファイルとして保存をしてタスクスケジューラにインポートしてみます。

  1. [スタート] → [管理ツール] → [タスク スケジューラ] を実行します。
    image
  2. [タスク スケジューラ ライブラリ] を選択して、中央のペインで右クリック→ [タスクのインポート] をクリックします。
    image
  3. 先ほど作成した XML を選択して、[開く] をクリックします。
    image
  4. タスクの作成が表示されますので [OK] をクリックします。
    image

そうすると [システム起動時] に実行されるタスクが追加されます。
image

このタスクの中身ですが、[cmd.exe] 経由で PowerShell を呼び出して標準出力の結果をファイルにリダイレクトしています。
はい、私が PowerShell の中でエラートラップとログ出力を書かなかったのでこういう呼び出し方法になっています…。
image

また、権限としては [最上位の特権で実行する] を付与しています。
管理者として実行しないと権限の関係でスクリプトが異常終了してしまうので…。
image

 

 

■タスクスケジューラーの実行確認


それでは、サーバーを再起動して挙動を確認してみたいと思います。
Management Portal からではなく、リモートデスクトップから再起動を実施します。
image

Management Portal の [Reboot] だと、たまに [Reimage] 相当の挙動となって、VHD がオリジナルになってしまうのですよね…。

こちらが再起動後のディスクの管理の状態になります。
image

タスクスケジューラでサーバー起動時に設定したタスクが自動で実行されますので、スクリプトを手動で実行しないでも Azure Drive として自動でマウントされた状態となります。

 

 

■Sysprep 後の実行確認


VM Role のインスタンスは Sysprep から開始されます。
# Azure の SLA では、複数インスタンスで起動することを前提としており、コンピューター名とコンピューターアカウントの SID 重複を防ぐために Sysprep をした状態の VHD を使用して VM Role のインスタンスを作成する必要があります。

それでは、VM Role 上で Sysprep を実行してその後にマウントされるかを確認してみたいと思います。
# VM Role の OS の認証は KMS で実施されているので、ライセンス認証クロックのリセット回数はさほど気にしないでも良いかと。
image

Sysprep 後に起動してきた直後の状態がこちらになります。
image

Azure Drive がマウントされていないですね…。
標準出力に出力していたログを確認してみたいと思います。

"1" 個の引数を指定して "GetConfigurationSettingValue" を呼び出し中に例外が発生しました: "the role environment is not available"
発生場所 C:ToolsMountDrive.ps1:7 文字:152
+ $connection = [Microsoft.WindowsAzure.CloudStorageAccount]::Parse([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetConfigurationSettingValue <<<<
("StorageConnectionString"))   
+ CategoryInfo          : NotSpecified: (:) []、MethodInvocationException   
+ FullyQualifiedErrorId : DotNetMethodException New-Object :

"2" 個の引数を指定して ".ctor" を呼び出し中に例外が発生しました: "オブジェクト参照がオブジェクト インスタンスに設定されていません。"
発生場所 C:ToolsMountDrive.ps1:10 文字:22
+ $account = New-Object <<<<  Microsoft.WindowsAzure.CloudStorageAccount( $connection.Credentials, $false)
+ CategoryInfo          : InvalidOperation: (:) [New-Object]、MethodInvocat  ionException   
+ FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.Powe Shell.Commands.NewObjectCommand
 

"1" 個の引数を指定して "GetLocalResource" を呼び出し中に例外が発生しました: "the role environment is not available"
発生場所 C:ToolsMountDrive.ps1:14 文字:90
+ $localstorage = [Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource <<<< ("AzureLocalStorage")   
+ CategoryInfo          : NotSpecified: (:) []、MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
 

"2" 個の引数を指定して "InitializeCache" を呼び出し中に例外が発生しました: "Expected a non-zero positive value.パラメータ名: totalCacheSize"
発生場所 C:ToolsMountDrive.ps1:15 文字:67
+ [Microsoft.WindowsAzure.StorageClient.CloudDrive]::InitializeCache <<<< ($localstorage.RootPath + "cache", $localstorage.MaximumSizeInMegabytes)   
+ CategoryInfo          : NotSpecified: (:) []、MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
 

"2" 個の引数を指定して "CreateCloudDrive" を呼び出し中に例外が発生しました: "CloudStorageAccount is null"
発生場所 C:ToolsMountDrive.ps1:18 文字:111
+ $clouddrive = [Microsoft.WindowsAzure.StorageClient.CloudStorageAccountCloudDriveExtensions]::CreateCloudDrive <<<< ($account, "$($account.BlobEndpoint)persistent/PersistentDisk.vhd")
+ CategoryInfo          : NotSpecified: (:) []、MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
 

null 値の式ではメソッドを呼び出せません。
発生場所 C:ToolsMountDrive.ps1:19 文字:33
+ $driveletter = $clouddrive.Mount <<<< (100, [Microsoft.WindowsAzure.StorageClient.DriveMountOptions]::None)   
+ CategoryInfo          : InvalidOperation: (Mount:String) []、RuntimeException
+ FullyQualifiedErrorId : InvokeMethodOnNull
 

"2" 個の引数を指定して "InitializeCache" を呼び出し中に例外が発生しました: "ERROR_UNSUPPORTED_OS"
発生場所 C:ToolsMountDrive.ps1:15 文字:67
+ [Microsoft.WindowsAzure.StorageClient.CloudDrive]::InitializeCache <<<< ($localstorage.RootPath + "cache", $localstorage.MaximumSizeInMegabytes)  
+ CategoryInfo          : NotSpecified: (:) []、MethodInvocationException   
+ FullyQualifiedErrorId : DotNetMethodException
 

"2" 個の引数を指定して "Mount" を呼び出し中に例外が発生しました: "ERROR_UNSUPPORTED_OS"
発生場所 C:ToolsMountDrive.ps1:19 文字:33
+ $driveletter = $clouddrive.Mount <<<< (100, [Microsoft.WindowsAzure.StorageClient.DriveMountOptions]::None)   
+ CategoryInfo          : NotSpecified: (:) []、MethodInvocationException   
+ FullyQualifiedErrorId : DotNetMethodException

エラーとしては大きく分けて、、

  • the role environment is not available
    Windows Azure 上でロールとしての準備が整う前に、[GetConfigurationSettingValue] を実行して、構成情報の取得を行おうとしている、
  • ERROR_UNSUPPORTED_OS
    [Windows Azure Cloud Driver Provider] サービスが起動する前に StorageClient 関連の処理を実行しようとしている。
    image

の 2 種類になります。

タスク スケジューラの [システム起動時] に実行したタスクが Azure に関連するサービスが起動する前に実行されてしまっているためにこのようなエラーとなっているようです。

[ERROR_UNSUPPORTED_OS] に関しては、[Windows Azure Cloud Driver Provider] が起動するまで待つように PowerShell に処理を記載すると回避できます。
# このエラーに関しては再起動をした場合でもサービスの起動状況によっては発生する可能性があります。

 

[the role environment is not available] については厳密な回避方法がいまいちわかっていないのですよね…。
[Windows Azure Integration Components] と [Windows Azure Remote Forwader Service] サービスが起動するまで待つようにするとある程度安定した挙動になったように思えるのですが。
サービスの起動状態の確認と、[GetConfigurationSettingValue] で構成情報の取得ができるまでループさせるようにしてとりあえずしのいでいます。

Twitter で Windows Azure Startup Tasks: Tips, Tricks, and Gotchas を教えていただいたので、[Using the Service Runtime from PowerShell] の対応もしてみたのですが、VM Role の場合は、統合コンポーネントをインストールしたタイミングで PowerShell のスナップインは登録がされているようで、この回避策はうまくいかなかったのですよね。

 

前回作成したスクリプトを以下のように変更して、Sysprep 後の挙動を確認してみました。

# 必要となるアセンブリの参照
Add-Type -Path "C:Program FilesWindows Azure Integration Componentsv1.3refMicrosoft.WindowsAzure.StorageClient.dll"
Add-Type -Path "C:Program FilesWindows Azure Integration Componentsv1.3refMicrosoft.WindowsAzure.CloudDrive.dll"
Add-Type -Path "C:Program FilesWindows Azure Integration Componentsv1.3refMicrosoft.WindowsAzure.ServiceRuntime.dll"

# サービスの起動が完了するまで待機
while((Get-Service -Name "Windows Azure Cloud Drive Provider").Status -ne "Running"){
    Start-Sleep -s 10
}

while((Get-Service -Name "Windows Azure Integration Components").Status -ne "Running"){
    Start-Sleep -s 10
}

while((Get-Service -Name "Windows Azure Remote Forwader Service").Status -ne "Running"){
    Start-Sleep -s 10
}

# Azure Storag に接続するための接続文字列をサービスモデルで定義した設定から取得
$connection = [Microsoft.WindowsAzure.CloudStorageAccount]::Parse([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetConfigurationSettingValue("StorageConnectionString"))
while (!$?) {
    $connection = [Microsoft.WindowsAzure.CloudStorageAccount]::Parse([Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetConfigurationSettingValue("StorageConnectionString"))
}

# Azure Storage に接続
$account = New-Object Microsoft.WindowsAzure.CloudStorageAccount( $connection.Credentials, $false)

# ローカルストレージに Azure Drive 用のキャッシュディレクトリを作成
# GetLocalResource で指定しているのは、サービス定義ファイル (csdef) で指定した LocalStorage の設定値
$localstorage = [Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment]::GetLocalResource("AzureLocalStorage")
[Microsoft.WindowsAzure.StorageClient.CloudDrive]::InitializeCache($localstorage.RootPath + "cache", $localstorage.MaximumSizeInMegabytes)

# Azure Drive として、VHD をマウント
$clouddrive = [Microsoft.WindowsAzure.StorageClient.CloudStorageAccountCloudDriveExtensions]::CreateCloudDrive($account, "$($account.BlobEndpoint)persistent/PersistentDisk.vhd")
$driveletter = $clouddrive.Mount(100, [Microsoft.WindowsAzure.StorageClient.DriveMountOptions]::None)

Windows Azure Managed Library Reference をもっときちんと読めば、何かしら便利なクラス見つかりそうなのですが、いかんせん開発苦手なものでして…・

このスクリプトをタスクスケジューラで実行するようにすると Sysprep 後でも自動でマウントできるようになりました。
image

 

最終的な処理の流れとしてこのようになるかと思います。
こんな風かな~という感覚で書いているので厳密には違うのかもしれませんが。
image

Azure Drive の領域を使用するサービスがある場合には、手動起動にしておいてマウントスクリプト内でサービスを開始することで永続化された領域を使用することができるようになると思います。

ファイル名がかなりおざなりですが、今回作成した PowerShell とタスクスケジューラのスクリプトについては以下のディレクトリに保存しておきましたので使用される方はご利用いただければと思います。
Azure Drive サンプルスクリプト

今回の設定ですが、VM Role 上のインスタンスで検証をしていますが、本来はオンプレミスの Hyper-V 上のゲスト OS で設定を行い、設定後の VHD をアップする必要があるということはご留意いただければと思います。
# VHD の再アップが大変なので今回は VM Role 上で検証をしてしまいました。

また、今回は 2 インスタンスで検証をしていますので、VHD のパスは固定でも動作しますが、2 インスタンス以上になった場合は、各インスタンスで使用する VHD を変更するようにスクリプトを組まないといけませんので、この辺は現時点の課題ですね。

データ永続化の最初の一歩として検証したことをまとめてみました。
# 分かりにくい文章を長々と書いてしまったので、私以外の方には読みにくいのが大変恐縮なのですが…。

Written by masayuki.ozawa

1月 29th, 2011 at 4:17 pm

Posted in Windows Azure

Tagged with ,

No Responses to 'VM Role 起動時に Windows Azure Drive を自動でマウント'

Subscribe to comments with RSS or TrackBack to 'VM Role 起動時に Windows Azure Drive を自動でマウント'.

  1. […] Role で Windows Azure Drive を使用 VM Role 起動時に Windows Azure Drive を自動でマウント 同一スクリプトで VM Role […]

Leave a Reply

*