【Azure】Azure Automation でジョブ失敗時のエラー通知を受け取る

# はじめに

以前作成した、Azure Automation を利用して VM を自動起動/停止するシステムを構成しました。
今回は、これにエラー通知を実装するする形で紹介しようと思います。

:::note info
以前の記事
[【Azure】Azure Automation を利用して VM を自動起動 / 停止する](https://qiita.com/nownaka/items/03f61be1e6e0baa669c1)
:::

# **環境**

- macOS: `15.0.1`
- PowerShell: `7.4.0`

# 実装方法について検討する

ドキュメントなどを確認すると、2種類ほどありそうです。

（他にもありそうですが。）

ざっくりとした手順は下記の通りです。

## 1. メトリックアラートを利用する方法

1. Automation Account 上の「警告」からアラートルールを作成。
2. シグナルに`TotalJobs`選択する。
3. ディメンションに`Status = Failed`を設定する。
4. その他の必要なパラメータを入力する。

https://learn.microsoft.com/ja-jp/azure/automation/automation-alert-metric

## **2. 診断ログを利用する方法**

1. Automation Account に診断設定を構成する。
2. カテゴリ `JobLogs` を選択し、Log Analytics ワークスペースにログを送信する。
3.  ログから`ResultStatus = Failed` のジョブを抽出するクエリを実行する
4. その結果を元にアラートルールを作成する

https://learn.microsoft.com/ja-jp/azure/automation/automation-manage-send-joblogs-log-analytics

今回は「**2. 診断ログを利用する方法**」で実装してみたいと思います。

:::note warn
注意
Azure サービスを利用するため、コストが発生する場合があります。
コストに関しては自己責任でお願いします。
:::

# 診断設定を構成する

Automation アカウントは作成済として話を進めます。
Log Analytics ワークスペースの作成と診断設定の構成は Bicep を利用して行いたいます。
（この量なら Azure Portal でポチポチしたほうが速そうですが、、、）

## Bicep テンプレート

```bicep: main.bicep
//
// Bicep template to configure diagnostic settings for Azure Automation.
// Created by nownaka.
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Resource deployment region.')
param location string = resourceGroup().location

@description('Resource name of Azure Automation Account.')
param automationAccountName string
@description('Resource name of Log Analytics Workspace')
param workspaceName string

// --------------------------------------------------------------------------------
// Modules
// --------------------------------------------------------------------------------
/* Azure Automation */
// diagnosticSetting
@description('The confings for diagnosticSetting.')
var _diagnosticSettingConfigs = [{
  diagnosticSettingName: 'JobLogs'
  logs: [{
    category: 'JobLogs'
    enabled: true
  }]
}]

@description('Diagnostic Setting.')
module diagnosticSettings './modules/diagnosticSettingsWithinAutomationAccount.bicep' = [ for config in _diagnosticSettingConfigs: {
  name: 'Deploy-DiagnosticSettings-${config.diagnosticSettingName}'
  params: {
    scopeResourceName: automationAccountName
    diagnosticSettingName: config.diagnosticSettingName
    logs: config.logs
    workspaceId: workspace.outputs.resourceId
  }
}]

/* Log Analytics Workspace */
module workspace './modules/workspaces.bicep' = {
  name: 'Deploy-LogAnalyticsWorkspace'
  params: {
    workspaceLocation: location
    workspaceName: workspaceName
  }
}

```

```bicep: modules/workspaces.bicep
//
// https://learn.microsoft.com/ja-jp/azure/templates/microsoft.operationalinsights/workspaces?pivots=deployment-language-bicep
// Created by nownaka.
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Resource name of Log Analytics Workspace.')
param workspaceName string
@description('Resource deployment region.')
param workspaceLocation string
@description('Resource tags.')
param tags object = {}

@description('The SKU of the workspace.')
@allowed([
  'CapacityReservation'
  'LACluster'
  'PerGB2018'
  'PerNode'
  'Premium'
  'Standalone'
  'Standard'])
param workspaceSkuName string = 'PerGB2018'

// --------------------------------------------------------------------------------
// Resources
// --------------------------------------------------------------------------------
@description('Log Analytics Workspace.')
resource workspace 'Microsoft.OperationalInsights/workspaces@2023-09-01' = {
  name: workspaceName
  location: workspaceLocation
  tags: tags
  properties: {
    sku: {
      name: workspaceSkuName
    }
  }
}

// --------------------------------------------------------------------------------
// Outputs
// --------------------------------------------------------------------------------
@description('Resource name of Log Analytics Workspace.')
output workspaceName string = workspace.name
@description('Resource Id of Log Analytics Workspace.')
output resourceId string = workspace.id

```

```bicep: modules/diagnosticSettingsWithinAutomationAccount.bicep
//
// Reference: https://learn.microsoft.com/ja-jp/azure/templates/microsoft.insights/diagnosticsettings?pivots=deployment-language-bicep#metricsettings-1
// Created by nownaka.
//

// --------------------------------------------------------------------------------
// Params
// --------------------------------------------------------------------------------
@description('Name of the scoped resource')
param scopeResourceName string
@description('')
param diagnosticSettingName string
@description('The resource Id for the event hub authorization rule.')
param eventHubAuthorizationRuleId string?
@description('The name of the event hub. If none is specified, the default event hub will be selected.')
param eventHubName string?
@description('A string indicating whether the export to Log Analytics should use the default destination type.')
@allowed(['Dedicated'])
param logAnalyticsDestinationType string?
@description('The list of logs settings.')
param logs {
  category: string?
  categoryGroup: string?
  enabled: bool
  retentionPolicy: {
    days: int
    enabled: bool
  }?
}[] = []
@description('The full ARM resource ID of the Marketplace resource to which you would like to send Diagnostic Logs.')
param marketplacePartnerId string?
@description('The list of metric settings.')
param metrics {
  category: 'string'
  enabled: bool
  retentionPolicy: {
    days: int
    enabled: bool
  }
  timeGrain: 'string'
}[] = []
@description('The service bus rule Id of the diagnostic setting. This is here to maintain backwards compatibility.')
param serviceBusRuleId string?
@description('The resource ID of the storage account to which you would like to send Diagnostic Logs.')
param storageAccountId string?
@description('The full ARM resource ID of the Log Analytics workspace to which you would like to send Diagnostic Logs. ')
param workspaceId string?

// --------------------------------------------------------------------------------
// Resources
// --------------------------------------------------------------------------------
// Replace {resource type} according to the scoped resource.
@description('Scoped resource')
resource scope 'Microsoft.Automation/automationAccounts@2023-11-01' existing = {
  name: scopeResourceName
}

@description('diagnosticSettings.')
resource diagnosticSettings 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
  name: diagnosticSettingName
  scope: scope
  properties: {
    eventHubAuthorizationRuleId: eventHubAuthorizationRuleId
    eventHubName: eventHubName
    logAnalyticsDestinationType: logAnalyticsDestinationType
    logs: logs
    marketplacePartnerId: marketplacePartnerId
    metrics: metrics
    serviceBusRuleId: serviceBusRuleId
    storageAccountId: storageAccountId
    workspaceId: workspaceId
  }
}

// --------------------------------------------------------------------------------
// Outputs
// --------------------------------------------------------------------------------
output scope string = scope.id

```

### 依存関係：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/96326fe5-27bc-7c9c-4260-a0e62f8a5a6b.png)

## パラメータの設定

値は適宜青木換えて利用します。

```bicepparam: main.bicepparam
using './main.bicep'

param automationAccountName = '{your automation account name}'
param workspaceName = 'your new automation account name'
```

## リソースデプロイ

下記コマンドを実行してリソースをデプロイします。
コマンド実行は各種ファイルが存在するルートディレクトリで行ってください。

### 変数の設定：

```bash
# 変数の設定
resourceGroupName={your resource group name} # デプロイするリソースグループの名前
```

### Azure ログイン：

```bash
# ログイン
az login
```

### デプロイ：

```bash
# デプロイ
az deployment group create \
--name 'Deploy_VMAutoStartStop' \
--resource-group $resourceGroupName \
--template-file './main.bicep' \
--parameters './main.bicepparam'
```

### デプロイ結果：

下記のようになっていればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/0f0bc523-aff1-61ed-5430-8ae624a8a70d.png)

# アラートの構成

アラートルールも Bicep でできそうでしたが、力尽きたのでこれは Azure Portal から設定していきます。

## ログ検索を実行する

まずは失敗したログを抽出するクエリを実行します。
（この時点では何も表示されなくてもOKです。）

### クエリ：

```KQL: KQL
AzureDiagnostics
| where Category == 'JobLogs'
| where ResultType == 'Failed'
```

[監視] > [ログ] で実行できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/3eadfc18-8653-b9ea-9de0-e36c3312dca3.png)

右上の [新しいアラートルール] からアラートルールを作成していきます。

## アラートルール作成

[条件] にさきほどのクエリが反映されています。
今回はしきい値は「0」に設定します。
アクショングループなど通知先の設定は適宜行ってください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/1e328c30-b37f-47b0-c5b0-3a5353fc1d82.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/894b71a7-c71b-43d2-10f3-d0a21447af3c.png)

以上で実装は完了です！

# 実際に試してみる

## テスト用 Runbook の作成

ローカルに `Test-Alert.ps1` を作成し、Automation Account にインポートします。

### スクリプト：

```powershell: Test-Alert.ps1
# ============================================================================
# アラート通知設定を確認するためスクリプト
# Created by nownaka.
# ============================================================================

# エラーを発生させるかどうかのフラグ
param(
    [Parameter(Mandatory=$true)]
    [Boolean]$isError
)

Write-Output "Start!"

if($isError -eq $true) {
    throw "Error!"
}

Write-Output "Successful!"

```

### インポート手順：

[プロセスオートメーション] > [Runbook] > [Runbook のインポート]

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/2f406541-e3a2-e0a2-3d1c-45f16e5440e9.png)


インポートが完了したら、発行(公開)します。

## テスト用 Runbook の実行

先ほど作成した Runbook を実行します。

エラーを吐かせるため、入力パラメータは `True` を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/d6ce048b-7d97-e755-daf5-938f8ad05385.png)

## 通知確認

下記のようなメールが通知さればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2513223/6d96fb1b-be08-4dbd-4bf2-2ca24012d8d9.png)

# さいごに

Azure Automation でジョブ失敗時に通知を受け取る方法を紹介しました。
Bicep を使ってかえって時間がかかってしまったような気がしますがやりたいことはできたので良しとします。

最後まで見ていただきありがとうございました。
誰かの役に立てば幸いです。

# 参考

https://learn.microsoft.com/ja-jp/azure/automation/automation-alert-metric

https://learn.microsoft.com/ja-jp/azure/automation/automation-manage-send-joblogs-log-analytics

https://learn.microsoft.com/ja-jp/azure/templates/microsoft.insights/diagnosticsettings?pivots=deployment-language-bicep

https://learn.microsoft.com/ja-jp/azure/templates/microsoft.operationalinsights/workspaces?pivots=deployment-language-bicep