メモリリークなどの理由から、やむを得ずAzure App Serviceを定期的に再起動したい場合は、再起動のためのPowerShellスクリプトをAutomationアカウントで定期的に実行するように設定すればよい。ただ、他のリソースと比べてちょっと注意すべき点もある。
対象:Azure Automation、App Service、PowerShell
Azureの「App Service」には、異常な状態になったら自動的に再起動して回復を図る機能が備わっている。ただ、一定の時刻になったら再起動するといった機能は用意されていない。
例えば、App Serviceで実行するプログラムにメモリリークなどの不具合があり、かつそれがプログラムの修正などで解消できない場合、一定の周期で再起動せざるを得ないこともあるだろう。
そこで本Tech TIPSでは、同じAzureのサービスである「Automation」のスケジュール機能を利用して、App Serviceを定期的に再起動する方法とその注意点を説明する。App Serviceの再起動もAutomationへのスクリプト登録などもPowerShellで記述している。
Automationなどの操作に必要な「Azure PowerShell」のインストールについては、Tech TIPS「Azure PowerShellをインストールする【Windows 10/11】」を参照していただきたい。
またAutomationを操作する前には、「Set-AzContext」コマンドレットを以下のように実行して、デフォルトのサブスクリプションを適切に選択していただきたい。
Set-AzContext -Subscription <サブスクリプション名>
AutomationでApp Serviceを再起動させるには、それをスクリプトに記述してRunbookに登録する必要がある。以下にPowerShellで記述したApp Service再起動のスクリプトの例を記す(長いので2つに分割している)。
# ファイル名: Restart-AppService.ps1(1/2)
# パラメーターの定義
param(
[Parameter(Mandatory, HelpMessage = "再起動するApp Serviceの名前を指定してください。スロットの場合はApp Service名とスロット名を「/」でつなげてください。")]
[string]$webAppName,
[Parameter(Mandatory, HelpMessage = "再起動するApp Serviceのリソースグループ名を指定してください。")]
[string]$webAppRG
)
$ErrorActionPreference = "Stop" # デフォルトでエラーはスクリプト終了
Write-Output "スクリプトを開始しました。"
# スロットかどうかを判定
$webAppDisplayName = $webAppName # 表示上のApp Service名
$webAppSlotName = $null # スロットなら名前、そうでなければ$null
$webAppNameArray = $webAppName -split "/"
if ($webAppNameArray.Length -eq 2) { # 2つに分割されていたら、
$webAppName = $webAppNameArray[0] # App Service名
$webAppSlotName = $webAppNameArray[1] # スロット名
}
Write-Output "再起動するのは、リソースグループ「$webAppRG」のApp Service「$webAppDisplayName」です。"
# Azureに接続
Write-Output "システム割り当てIDでAzureへ接続します……"
try {
$null = Connect-AzAccount -Identity
} catch {
Write-Error "Azureへの接続時にエラーが発生しました: $($_.Exception.Message)"
}
# Azureコンテキストを確認
Write-Output "Azureコンテキストの状態を取得します……"
try {
($null = Get-AzContext) | Format-List -Property Name, Subscription, Account, Tenant
} catch {
Write-Error "Azureコンテキストの取得時にエラーが発生しました: $($_.Exception.Message)"
}
上記のコードはいわば「準備」の部分で、Azureに接続して各種リソースの状態取得や操作ができるようにしている。
先頭にあるのは入力パラメーター(引数)の定義で、Runbookをスケジュールにひも付ける際に指定することになる。「Parameter()」の第一引数で「Mandatory」とすることで、必須のパラメーターにしている。
「$ErrorActionPreference」はエラー発生時のPowerShellの挙動を決めるための特殊な変数だ。ここでは「Stop」を指定することで、エラー発生時に実行が停止するか、例外が生じるようにしている(デフォルトは「Continue」すなわち実行継続)。例えば、「try」ブロック内でエラーが発生すると、「catch」のブロック内にある「Write-Error」によってエラーメッセージが表された後、実行が停止する。
パラメーター「webAppName」に「<App Service名><スロット名>」のように指定された場合は、デプロイスロットと見なして、本スクリプトの後半ではスロット専用のコマンドレットを実行するようにしている。
その後の「Connect-AzAccount」コマンドレットでは、Automationアカウントのシステム割り当てIDでAzureに接続している。その上で、「Get-AzContext」コマンドレットで接続後のテナントやサブスクリプションを出力している。
以下は残りの後半部分のリストで、実際にApp Serviceまたはそのスロットを再起動している。
# ファイル名: Restart-AppService.ps1(2/2)
# 指定されたApp Serviceの状態を取得
Write-Output "リソースグループ「$webAppRG」のApp Service「$webAppDisplayName」の状態を取得します……"
try {
if ($null -eq $webAppSlotName) {
$webApp = Get-AzWebApp -ResourceGroupName $webAppRG -Name $webAppName
} else {
$webApp = Get-AzWebAppSlot -ResourceGroupName $webAppRG -Name $webAppName -Slot $webAppSlotName
}
} catch {
Write-Error "リソースグループ「$webAppRG」のApp Service「$webAppDisplayName」の状態取得時にエラーが発生しました: $($_.Exception.Message)"
}
$webApp | Format-List -Property Name, Enabled, State
# App Serviceが実行中かどうか確認
if ($webApp.State -ne "Running") {
Write-Output "App Service「$webAppDisplayName」は起動していないので、再起動せずに終了します。"
exit
}
# App Serviceを再起動
Write-Output "App Service「$webAppDisplayName」を再起動します……"
try {
if ($null -eq $webAppSlotName) {
$null = $webApp | Restart-AzWebApp
} else {
$null = $webApp | Restart-AzWebAppSlot
}
} catch {
Write-Error "App Service「$webAppDisplayName」の再起動時にエラーが発生しました: $($_.Exception.Message)"
}
Write-Output "スクリプトは完了しました。" # 正常終了
パラメーターで指定されたApp Service名およびリソースグループ名を基に「Get-AzWebApp」「Get-AzWebAppSlot」コマンドレットを実行して、対象のApp Serviceのオブジェクトを取得している。
ここでは、App Serviceの「State」プロパティを確認して、「Running」すなわち実行中なら「Restart-AzWebApp」「Restart-AzWebAppSlot」コマンドレットでApp Serviceを再起動している。一方、「Stopped」すなわち停止していたら想定外の状態と見なし、再起動せずに終了する。
上記で用意したApp Service再起動用のPowerShellスクリプトは、以下のコードでRunbookに登録できる。
# ファイル: Import-PSScriptToRunbook.ps1
param(
[Parameter(Mandatory, HelpMessage = "Runbookの名前を指定してください。")]
[string]$runbookName,
[Parameter(HelpMessage = "Runbookの説明を記入してください。")]
[string]$runbookDesc = "",
[Parameter(HelpMessage = "Runbookの実行に必要なPowerShellのバージョン(5.1または7.2)を指定してください。")]
[ValidateSet("5.1", "7.2")]
[string]$PSVersion = "5.1",
[Parameter(Mandatory, HelpMessage = "登録するPowerShellスクリプトのパス(ありか)を記入してください。")]
[string]$localPath,
[Parameter(Mandatory, HelpMessage = "Automationアカウントの名前を指定してください。")]
[string]$AAName,
[Parameter(Mandatory, HelpMessage = "Automationアカウントのリソースグループ名を指定してください。")]
[string]$AARG
)
$ErrorActionPreference = "Stop" # エラーが発生したらスクリプトを終了
# Runbook実行に必要なPowerShellのバージョンの表記を変える
$runbookType = switch ($PSVersion) {
"7.2" { "PowerShell72" } # 「S」が小文字だと失敗するので注意
default { "PowerShell" } # PowerShell 5.1
}
# ローカルファイルからPowerShellをインポートし、そのまま発行済みにする
Import-AzAutomationRunbook `
-ResourceGroupName $AARG -AutomationAccountName $AAName `
-Name $runbookName -Description $runbookDesc `
-Type $runbookType `
-Path $localPath `
-Published
Write-Output "スクリプトは完了しました。" # 正常終了
上記リストでちょっと注意したいのは、PowerShell 7.2の場合に変数「$runbookType」に設定する値だ。レファレンスなどに記載されている「Powershell72」だとエラーが発生する。正しくは「s」を大文字にした「PowerShell72」を代入すること。
登録するには以下のコマンドラインを実行する。
.\Import-PSScriptToRunbook.ps1 -runbookName "Restart-AppService" -runbookDesc "App Serviceを再起動する。" -PSVersion "7.2" -localPath "..\Restart-AppService.ps1" -AAName <Automationアカウント名> -AARG <Automationアカウントのリソースグループ名>
再起動に限らずApp Serviceをスクリプトなどで操作する場合、必要な権限(リソースプロバイダ)については少々注意が必要だ。
まず、前述の「Get-AzWebApp」コマンドレットでApp Serviceのオブジェクトを取得するには、「Microsoft.Web/sites/Read」だけではなく「Microsoft.Web/sites/config/Read」も必要だ。
また、デプロイスロットの方を参照/操作するには、「Microsoft.Web/sites/slots/」から始まるアクセス許可も必要である。
具体的なアクセス許可の名称と内容については、Microsoft Learn「Web および Mobile に対する Azure のアクセス許可」を参照していただきたい。
セキュリティポリシーの面で問題がなければ、上記のアクセス許可をまとめてカスタムロールにしておく方が、許可を与える際に手っ取り早くてよいだろう。以下のコードを実行すると、サブスクリプションレベルでApp Serviceとそのスロットの再起動/起動/停止に必要な権限をまとめたカスタムロールが生成できる。
ファイル名: Define-CustomRole.ps1
param(
[Parameter(Mandatory, HelpMessage = "カスタムロールの名前を指定してください。")]
[string]$roleName,
[Parameter(HelpMessage = "カスタムロールの説明を記入してください。")]
[string]$roleDesc = "App Serviceの起動/停止/再起動ができる。"
)
$ErrorActionPreference = "Stop" # エラーが発生したらスクリプトを終了
# 作成先スコープは現在のサブスクリプション
Write-Output "現在のサブスクリプションのGUIDを取得します……"
$subscriptionGUID = (Get-AzContext).Subscription.Id # GUID
# カスタムロールのパラメーターをハッシュテーブルにまとめる
$customeRole = @{
Name = $roleName
Description = $roleDesc
Actions = @(
# このカスタムロールで許可する権限(アクセス許可)を列挙
"Microsoft.Web/sites/Read" # App Serviceの情報の読み取り
"Microsoft.Web/sites/config/Read" # App Serviceの設定情報の読み取り
"Microsoft.Web/sites/restart/Action" # App Serviceの再起動
"Microsoft.Web/sites/start/Action" # App Serviceの起動
"Microsoft.Web/sites/stop/Action" # App Serviceの停止
"Microsoft.Web/sites/slots/Read" # App Serviceスロットの情報の読み取り
"Microsoft.Web/sites/slots/config/Read" # App Serviceスロットの設定情報の読み取り
"Microsoft.Web/sites/slots/restart/Action" # App Serviceスロットの再起動
"Microsoft.Web/sites/slots/start/Action" # App Serviceスロットの起動
"Microsoft.Web/sites/slots/stop/Action" # App Serviceスロットの停止
)
AssignableScopes = @(
# このカスタムロールを作成するスコープのIDを列挙
"/subscriptions/$subscriptionGUID"
)
IsCustom = $true # カスタムロールを指定
}
# JSONファイルに出力
$JSONPath = ".\customrole.json"
$customeRole | ConvertTo-Json -Depth 10 | Out-File $JSONPath
# JSONファイルからカスタムロールを作成
Write-Output "カスタムロールを新たに作成します……"
New-AzRoleDefinition -InputFile $JSONPath
Write-Output "スクリプトは完了しました。" # 正常終了
カスタムロールを適用するスコープは、プロパティ「AssignableScopes」の配列にそのIDを列挙する。本記事ではサブスクリプションスコープに適用することを想定している。そのため、サブスクリプションレベルでカスタムロールを作成するための許可(組み込み済みのロールなら「所有者」など)が与えられているユーザーでログインしてから、上記スクリプトを実行する必要がある。
上記のスクリプトを以下のコマンドラインを実行すると、カスタムロールを作成できる。
.\Define-CustomRole.ps1 -roleName "<カスタムロール名>"
<カスタムロール名>にはスペースが含まれていてもよい。また、説明文を指定したければ、「-roleDesc <説明文>」をコマンドラインに加える。
カスタムロールを作成したら、それを再起動させるApp Serviceまたはそのリソースグループ、サブスクリプションに割り当てることで、Automationアカウントからアクセスできるように許可に与える。
# ファイル名: Assign-CustomRoleToAutomation.ps1
param(
[Parameter(Mandatory, HelpMessage = "割り当てるカスタムロールの名前を指定してください。")]
[string]$roleName,
[Parameter(Mandatory, HelpMessage = "ロール割り当てによってアクセス可能にするリソース/リソースグループ/サブスクリプションのIDを指定してください。")]
[string]$scope,
[Parameter(Mandatory, HelpMessage = "Automationアカウントの名前を指定してください。")]
[string]$AAName,
[Parameter(Mandatory, HelpMessage = "Automationアカウントのリソースグループ名を指定してください。")]
[string]$AARG
)
$ErrorActionPreference = "Stop" # エラーが発生したらスクリプトを終了
# Automationアカウントを取得
Write-Output "リソースグループ「$AARG」のAutomationアカウント「$AAName」のオブジェクトを取得します……"
$automationAccount = Get-AzAutomationAccount -ResourceGroupName $AARG -Name $AAName
if ($null -eq $automationAccount.Identity) {
Write-Output "Automationアカウント「$AAName」のシステム割り当てIDを有効化します……"
$automationAccount | Set-AzAutomationAccount -AssignSystemIdentity
}
# 新たにロールを割り当てる
$managedID = $automationAccount.Identity.PrincipalId # システム割り当てID
Write-Output "指定されたロールを割り当ててています……"
New-AzRoleAssignment -ObjectId $managedID -RoleDefinitionName "$roleName" -Scope $scope
Write-Output "スクリプトは完了しました。" # 正常終了
上記のスクリプトを以下のコマンドラインを実行すると、カスタムロールを割り当てられる。
.\Assign-CustomRoleToAutomation.ps1 -roleName "<カスタムロール名>" -scope "<割り当て先のリソース/リソースグループ/サブスクリプションのID>" -AAName <Automationアカウント名> -AARG <Automationアカウントのリソースグループ名>
例えばサブスクリプションに割り当てるなら、「-scope」に「/subscriptions/<サブスクリプションのGUID>」のように指定する。
最後に、定期的に実行するスケジュールをAutomationアカウントに追加して、インポートして作ったRunbookをひも付ける。
# ファイル: Add-ScheduleToAutomation.ps1
param(
[Parameter(Mandatory, HelpMessage = "再起動させるApp Serviceの名前を指定してください。")]
[string]$webAppName,
[Parameter(Mandatory, HelpMessage = "再起動させるApp Serviceのリソースグループ名を指定してください。")]
[string]$webAppRG,
[Parameter(Mandatory, HelpMessage = "再起動用のRunbookの名前を指定してください。")]
[string]$runbookName,
[Parameter(Mandatory, HelpMessage = "再起動を始める時刻を「HH:mm:ss」のように指定してください。")]
[string]$startTime,
[Parameter(Mandatory, HelpMessage = "Automationアカウントの名前を指定してください。")]
[string]$AAName,
[Parameter(Mandatory, HelpMessage = "Automationアカウントのリソースグループ名を指定してください。")]
[string]$AARG
)
$ErrorActionPreference = "Stop" # デフォルトでエラーはスクリプト終了
$neverExpiry = "9999-12-30T23:59:59.9999999+00:00" # 遠い未来=無期限の意
# スケジュールのパラメーター
$scheduleName = "restart-$webAppName" -replace "/", "-" # スロット指定時の「/」を「-」に変換
$scheduleDesc = "App Service「$webAppName」を毎日${startTime}に再起動する。"
$startTime = (Get-Date $startTime).AddDays(1) # 翌日の指定時刻から始める
$expiryTime = Get-Date $neverExpiry # 無期限
$dayInterval = 1 # 毎日。2日に1回なら「2」
$timeZone = (Get-TimeZone).Id # ローカルのタイムゾーン
# Runbookのパラメーター
$runbookParams = @{
webAppName = $webAppName # 再起動させるApp Serviceの名前
webAppRG = $webAppRG # 再起動させるApp Serviceのリソースグループ名
}
# 先にスケジュールを生成
New-AzAutomationSchedule `
-ResourceGroupName $AARG -AutomationAccountName $AAName `
-Name $scheduleName -Description $scheduleDesc `
-StartTime $startTime -ExpiryTime $expiryTime `
-DayInterval $dayInterval `
-TimeZone $timeZone
# 生成したスケジュールにRunbookをひも付け
Register-AzAutomationScheduledRunbook `
-ResourceGroupName $AARG -AutomationAccountName $AAName `
-ScheduleName $scheduleName `
-Name $runbookName `
-Parameters $runbookParams
Write-Output "スクリプトは完了しました。" # 正常終了
定期的に実行する間隔は、ここでは1日で固定している。例えば3日に1回実行したいのならば、「$dayInterval」に「3」を指定すること。
日ごとではなく、週ごと、あるいは月ごとにスケジュールしたい場合は、「New-AzAutomationSchedule」コマンドレットで、「-DayInterval」パラメーターの代わりに「-WeekInterval」「- MonthInterval」をそれぞれ指定する必要がある。
上記のスクリプトを以下のコマンドラインを実行すると、指定のApp Serviceを1日1回、指定時刻に再起動するスケジュールが作成される。
.\Add-ScheduleToAutomation.ps1 -webAppName <App Service名> -webAppRG <App Serviceのリソースグループ名> -runbookName "Restart-AppService" -startTime <開始時刻> -AAName <Automationアカウント名> -AARG <Automationアカウントのリソースグループ名>
「-startTime」にはApp Serviceの再起動を始める時刻を、例えば「03:00:00」(午前3時ちょうど)のように指定する。
Copyright© Digital Advantage Corp. All Rights Reserved.