Azure リソースの構成変更を通知する仕組みを作る

## はじめに

クラウド管理者の方々にとって、Azure のリソースの構成が変更された際、変更が通知されるような仕組みがあると嬉しいときってありますよね。

本記事では、 Azure Resource Graph と Logic Apps を使用して、 Azure リソースの構成変更があった際に通知を行う仕組みを構築する手順をご紹介します。

※ 2021 年 9 月時点でプレビューの機能です。

## 構成図

今回は、１時間ごとに仮想マシンに対する構成変更があったかどうかを検知する方針で実装を行います。

仮想マシンに対する構成変更があった場合は、Teams に通知を飛ばすようにしています。

<b>※なお、通知は、Teams のみならず、Slack やメールでも行うことが可能です。</b>

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/3ab0bf6a-c6ed-05a3-daf6-b3ac19330acb.png)


## 手順

### 0. 変更検知対象リソースの作成

変更検知の対象とする Azure リソースの作成は行われている前提で以下の手順を説明します。

今回は、構成図にあるように、 1 台の仮想マシンだけを作成しています。

### 1. Logic Apps の作成

Azure リソースの変更検知を行うための Logic Apps を作成します。タイプは「消費」にて作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/223898f8-194c-2365-be0d-fe53d1a9eed8.png)


### 2. マネージド ID の割り当て

Logic Apps から Azure の各リソースに対する操作を行うために、Logic Apps に対してマネージド ID を割り当てます。今回は、リソースの構成変更を検知するだけなので、リソースグループに対する「閲覧者」権限を割り当てます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/b23b4c5d-7222-382d-ebcf-66cfb2c4f7fb.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/c000a474-a09a-28d1-ead7-a51d07fb869b.png)



### 3. Logic Apps の構成

以下の要領で Logic Apps を構成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/5390e0a2-f490-de75-6527-a2be5d0f3ef5.png)


#### 3-1. トリガーの設定

<b>「繰り返し（Recurrence）」</b>を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/dfbdc738-0f30-a0be-b48f-42ae62b069c6.png)

今回は、１時間ごとに仮想マシンに対する構成変更の確認を行うため、下図の通り、繰り返しの間隔を１時間としています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/864eac9e-b620-66b6-8225-f80f85078c73.png)

#### 3-2. HTTP メソッドの定義

Azure Resource の変更を検知するやり取りは、<b>resourceChanges REST エンドポイント</b> を介して行われます。そのため、Logic Apps は、この <b>resourceChanges REST エンドポイント</b> に対して、<b>変更検知対象のリソース等、いくつかの条件を指定して</b>変更の有無を確認する、といった方針で実装を行います。

<b>resourceChanges REST エンドポイント</b>の URI は、`https://management.azure.com/providers/Microsoft.ResourceGraph/resourceChanges?api-version=2018-09-01-preview` です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/2a6001dc-ee18-5312-444f-ae5d8b3d21e1.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/62a61f8f-2611-2872-e304-0cd274e7bce5.png)


ここで指定する「いくつかの条件」を下表にまとめます。ちなみに Docs は[こちら](https://docs.microsoft.com/ja-jp/azure/governance/resource-graph/how-to/get-resource-changes#find-detected-change-events-and-view-change-details)です。

| パラメータ | 説明 | 必須か否か |
| :--- | :--- | :--- |
| resourceId | 変更を検索する対象の Azure リソース | 必須 |
| interval | 変更イベントを確認する期間の開始日付、終了日付 | 必須 |
| fetchPropertyChanges | HTTP レスポンスに `"propertyChanges"` を含めるか否か | 省略可能 |  


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/bcd83a66-5da6-2468-d226-dbfdd2ff84d5.png)

今回は、対象の仮想マシン「VM-001」に対して、１時間ごとに構成変更の確認をします。そのため、`"interval"` プロパティの設定が肝になってきます。

ここで、それぞれ Logic Apps 組み込み式関数を使用します。関数の使い方は、各式の Docs に遷移できるようリンクを貼っていますので、適宜ご確認ください。


- <b>現在時刻より１時間前 -> [getPastTime()](https://docs.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#getpasttime) を使用
- <b>現在時刻 -> [utcNow()](https://docs.microsoft.com/ja-jp/azure/logic-apps/workflow-definition-language-functions-reference#utcnow) を使用</b>

とすることで、「１時間前から現在まで」という期間を `"interval"` として指定することができます。

この作戦をとると、Logic Apps のコードデザイナーからは、下図のように HTTP リクエストボディを設定することになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/49d463f2-b806-ede3-876e-50219770a1b7.png)

※補足として、実際のコードは、下記のように指定されます。コードデザイナーから Logic Apps を作成していくと、裏側では以下のような JSON ファイルが作成されていきます。今回の手順では、明示的に下記を指定する必要はありません。

```json:sample_body.json
{
  "fetchPropertyChanges": true,
  "interval": {
    "end": @{utcNow()},
    "start": @{getPastTime(1, 'Hour')}
  },
  "resourceId": "/subscriptions/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx/resourceGroups/Qiita-DetectChanges/providers/Microsoft.Compute/virtualMachines/VM-001"
}
```

HTTP メソッド（POST）実行時に必要な認証は、手順２. にて作成したマネージド ID を使用して行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/8e99ead0-b932-21c3-0a18-c0163b48d206.png)

よって、Logic Apps で使用する HTTP メソッドは、コードデザイナーからは最終的に下記のように見えます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/69c82ea6-3583-587d-6b1f-a4f994366b0f.png)

#### 3-3. JSON 解析

HTTP メソッド（POST）実行後、Teams に通知する際のメッセージに入れるプロパティを抽出するために、レスポンスを解析します。

レスポンスの解析は、<b>「JSON の解析」</b>を使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/31427de5-85e9-6ec7-7d08-3b90607180a5.png)

上記の「サンプルのペイロードを使用してスキーマを生成する」をクリックし、 [Docs](https://docs.microsoft.com/ja-jp/azure/governance/resource-graph/how-to/get-resource-changes#find-detected-change-events-and-view-change-details) に公開されている下記のサンプルペイロードを入力します。

```json
{
    "changes": [
        {
            "changeId": "{\"beforeId\":\"3262e382-9f73-4866-a2e9-9d9dbee6a796\",\"beforeTime\":\"2019-09-28T00:45:35.012Z\",\"afterId\":\"6178968e-981e-4dac-ac37-340ee73eb577\",\"afterTime\":\"2019-09-28T00:52:53.371Z\"}",
            "beforeSnapshot": {
                "snapshotId": "3262e382-9f73-4866-a2e9-9d9dbee6a796",
                "timestamp": "2019-09-28T00:45:35.012Z"
            },
            "afterSnapshot": {
                "snapshotId": "6178968e-981e-4dac-ac37-340ee73eb577",
                "timestamp": "2019-09-28T00:52:53.371Z"
            },
            "changeType": "Create"
        },
        {
            "changeId": "{\"beforeId\":\"a00f5dac-86a1-4d86-a1c5-a9f7c8147b7c\",\"beforeTime\":\"2019-09-28T00:43:38.366Z\",\"afterId\":\"3262e382-9f73-4866-a2e9-9d9dbee6a796\",\"afterTime\":\"2019-09-28T00:45:35.012Z\"}",
            "beforeSnapshot": {
                "snapshotId": "a00f5dac-86a1-4d86-a1c5-a9f7c8147b7c",
                "timestamp": "2019-09-28T00:43:38.366Z"
            },
            "afterSnapshot": {
                "snapshotId": "3262e382-9f73-4866-a2e9-9d9dbee6a796",
                "timestamp": "2019-09-28T00:45:35.012Z"
            },
            "changeType": "Delete"
        },
        {
            "changeId": "{\"beforeId\":\"b37a90d1-7ebf-41cd-8766-eb95e7ee4f1c\",\"beforeTime\":\"2019-09-28T00:43:15.518Z\",\"afterId\":\"a00f5dac-86a1-4d86-a1c5-a9f7c8147b7c\",\"afterTime\":\"2019-09-28T00:43:38.366Z\"}",
            "beforeSnapshot": {
                "snapshotId": "b37a90d1-7ebf-41cd-8766-eb95e7ee4f1c",
                "timestamp": "2019-09-28T00:43:15.518Z"
            },
            "afterSnapshot": {
                "snapshotId": "a00f5dac-86a1-4d86-a1c5-a9f7c8147b7c",
                "timestamp": "2019-09-28T00:43:38.366Z"
            },
            "propertyChanges": [
                {
                    "propertyName": "tags.org",
                    "afterValue": "compute",
                    "changeCategory": "User",
                    "changeType": "Insert"
                },
                {
                    "propertyName": "tags.team",
                    "afterValue": "ARG",
                    "changeCategory": "User",
                    "changeType": "Insert"
                }
            ],
            "changeType": "Update"
        },
        {
            "changeId": "{\"beforeId\":\"19d12ab1-6ac6-4cd7-a2fe-d453a8e5b268\",\"beforeTime\":\"2019-09-28T00:42:46.839Z\",\"afterId\":\"b37a90d1-7ebf-41cd-8766-eb95e7ee4f1c\",\"afterTime\":\"2019-09-28T00:43:15.518Z\"}",
            "beforeSnapshot": {
                "snapshotId": "19d12ab1-6ac6-4cd7-a2fe-d453a8e5b268",
                "timestamp": "2019-09-28T00:42:46.839Z"
            },
            "afterSnapshot": {
                "snapshotId": "b37a90d1-7ebf-41cd-8766-eb95e7ee4f1c",
                "timestamp": "2019-09-28T00:43:15.518Z"
            },
            "propertyChanges": [{
                "propertyName": "tags.cgtest",
                "afterValue": "hello",
                "changeCategory": "User",
                "changeType": "Insert"
            }],
            "changeType": "Update"
        }
    ]
}
```

すると、内部的にサンプルペイロードを解析し、下記のペイロードが自動的に生成されます。便利ですね。

```json
{
    "properties": {
        "changes": {
            "items": {
                "properties": {
                    "afterSnapshot": {
                        "properties": {
                            "snapshotId": {
                                "type": "string"
                            },
                            "timestamp": {
                                "type": "string"
                            }
                        },
                        "type": "object"
                    },
                    "beforeSnapshot": {
                        "properties": {
                            "snapshotId": {
                                "type": "string"
                            },
                            "timestamp": {
                                "type": "string"
                            }
                        },
                        "type": "object"
                    },
                    "changeId": {
                        "type": "string"
                    },
                    "changeType": {
                        "type": "string"
                    },
                    "propertyChanges": {
                        "items": {
                            "properties": {
                                "afterValue": {
                                    "type": "string"
                                },
                                "changeCategory": {
                                    "type": "string"
                                },
                                "changeType": {
                                    "type": "string"
                                },
                                "propertyName": {
                                    "type": "string"
                                }
                            },
                            "type": "object"
                        },
                        "type": "array"
                    }
                },
                "type": "object"
            },
            "type": "array"
        }
    },
    "type": "object"
}
```

#### 3-4. Teams へメッセージを送信

Teams へ送信するメッセージの中で、`3-3. JSON 解析` で解析した各プロパティを指定できます。

今回は、下図のように設定しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/44004732-0614-c31b-a452-24e87095e72b.png)

### 4. リソース変更の検知

実際に仮想マシンのサイズを `DS1_v2` に変えた際に Teams に通知されたメッセージは下図の通りです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/248756/40dce74f-59a7-8cd8-d879-2849fabc932e.png)

ちゃんと Teams に通知が届いています！

## まとめ

今回は、 Azure リソースの構成変更を通知する仕組みを作るために、 Azure Resource Graph と Logic Apps を使用しました。

今回は仮想マシンの構成変更を通知しましたが、Storage 等 Azure の他のリソースについても同様の仕組みを構築できます。

非常に便利な機能ですので、是非ともこういった仕組みを取り入れてみてはいかがでしょうか？