AZ-400：Azure Pipelines 承認とゲートの使用

この記事は、Designing and Implementing Microsoft DevOps Solutions（AZ-400）の受験勉強の中で、自分が間違えやすい、覚えにくいと思ったポイントの一つをまとめたものです。同じく資格取得に向けて勉強されている方の参考になれば幸いです。

### リリースパイプライン

Azure Pipelines には、下記 2 種類の構築方法があります。

- YAML 構文の使用（最新の方法とされているのはこちら）
- クラシックインターフェイス（GUI）

後者のクラシックインターフェイスでは、コードをビルド、テストしてから成果物を発行するまでのビルドパイプラインと、成果物を使用してデプロイするリリースパイプラインとに分かれています。

リリースパイプラインでは、承認、ゲートという機能を使って特定の条件を満たさなければデプロイしないような制御を行うことができます。

- 承認: 承認者が承認しなければデプロイしない
- ゲート: Azure Monitor や外部サービスなどから情報収集し、条件を満たさなければデプロイしない

これらはリリースパイプラインを構成するステージの前、または後に追加することが可能です。

### Azure Functions を使った検証

簡単な Azure Functions のプログラムを作成して Azure Pipelines を使ってデプロイし、デプロイ時のリリースパイプラインで承認、ゲートを設定してみます。
VSCode の Azure Functions 拡張機能を使って HTTP Trigger の Node.js 関数を作成してみます。
下記のように簡単な文字列を返すだけの関数にします。

```
const httpTrigger: AzureFunction = async function (
  context: Context,
  req: HttpRequest
): Promise<void> {
  const responseMessage = "Ver.1.0.0";
  context.res = {
    body: responseMessage,
  };
};
```

Azure Functions 拡張機能や Azure ポータルなどから Azure 上にデプロイ先となる Azure Functions を作成します。
Azure Functions には「deploy」という名称の slot を作成しておきます。デプロイ時は、まず deploy slot にデプロイし、その後、production slot と swap を実施する形式でリリースパイプラインを構成します。

### Azure DevOps プロジェクトの作成

Azure DevOps 向けに検証用のプロジェクトを作成します。
ここではプロジェクト名は「FuncDeployTest202302」としています。
コードはこのプロジェクトの Repos に格納します（手順の詳細は割愛しますが、プロジェクトの Repos メニューにある git コマンドで origin 設定して push しておきます）。

### ビルドパイプラインの作成

プロジェクトの Pipelines -> Pipelines メニューを開き、Create Pipeline を選択、ソースは Azure Repos のリポジトリ、タスクには「Node.js Function App to Linux on Azure」 を選択します。

![スクリーンショット_20230212_222834.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/26008212-9042-760b-f166-c342b376f624.png)


AAD ログインし、作成した関数アプリの存在するサブスクリプション、関数アプリを選択します。
タスクには下記のような Deploy ステージも存在しますが、リリースパイプラインは別に作成するため、ここでは削除して保存します。

```
- stage: Deploy
  displayName: Deploy stage
  dependsOn: Build
  condition: succeeded()
  jobs:
    ...
```

### リリースパイプラインの作成

プロジェクトの Pipelines -> Releases メニューを開き、New Pipeline を選択します。
「Add a stage」を選択し、テンプレートとして 「Deploy a function app to Azure Functions with slot」を使用します。
ステージ名は「Deploy to slot」としておきます。

![スクリーンショット_20230212_235530.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/6bd2f226-c338-54fd-5467-cff9c63ae11c.png)

作成したステージの job リンクを選択してタスクを設定します。
Azure のサブスクリプション、リソースグループ、関数アプリ、slot を入力します。
選択したテンプレートでは slot の swap を行うタスクが含まれていますが、今回の検証では swap 前に承認、ゲート設定を行うため、swap は別ステージで実施するものとしてここではタスクを削除しておきます。



Pipeline タブに戻り、「Deploy to slot」の下の「+ Add」ボタンを押してステージを追加します。
テンプレートは「Empty job」を選択し、ステージ名は「Swap」とします。
作成したステージの job リンクを選択してタスクを設定します。
タスクの追加ボタン「+」を押し、「Azure App Service manage」を選択します。
Azure のサブスクリプション、リソースグループ、関数アプリ、slot を入力します。

![スクリーンショット_20230212_225625.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/aa15414b-0958-5b50-a469-22250f63bcbe.png)

Pipeline タブに戻り、「Add an artifact」を選択します。
Source に「FuncDeployTest202302」を指定して Artifact を追加します。

下図のようなリリースパイプラインが完成します。

![スクリーンショット_20230213_084523.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/fac80021-638f-987b-10e0-a8132b17f7f9.png)


### デプロイの検証

ひとまずビルドパイプライン、リリースパイプラインが作成できたので動作検証します。
ビルドパイプラインで「Run pipeline」を実行します。
パイプラインが正しく設定できていれば、ビルドパイプライン後にリリースパイプラインが動作し、デプロイが完了します。

![スクリーンショット_20230213_085226.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/301f4a0c-689a-e78e-9f51-c011c8d8d34c.png)

下記のような関数アプリのエンドポイントにブラウザや curl コマンドなどでアクセスすると、「Ver.1.0.0」が返ってきます。

```
https://{関数アプリ名}.azurewebsites.net/api/{トリガー名}
```

### 承認の設定

承認者の承認者がなければ swap しないようにリリースパイプラインを修正します。
「Swap」ステージ前のボタンを選択し、「Pre-deployment approvals」を有効にし、承認者を設定後、パイプラインを保存します。

![スクリーンショット_20230213_090154.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/49c0840c-ac1b-2626-14ee-7c140e2cdc0a.png)
![スクリーンショット_20230213_090253.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/8a7941b9-ed8f-cdf9-6999-035db4420fc4.png)

コードを変更して動作検証してみます。
下記のように返却するメッセージを「Ver.1.0.0」->「Ver.1.1.0」に変更し、修正を commit、Repos に push します。

```
const responseMessage = "Ver.1.1.0";
```

リリースパイプラインが承認前で中断されていることが確認できます。

![スクリーンショット_20230213_092443.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/5f7e2250-7d03-c265-97fd-b4ae1f5b3750.png)

下記 production のエンドポイントにアクセスすると、返るメッセージは「Ver.1.0.0」のままです。

```
https://{関数アプリ名}.azurewebsites.net/api/{トリガー名}
```

下記 deploy slot のエンドポイントにアクセスすると、返るメッセージは「Ver.1.1.0」となっています。
deploy slot のみデプロイされていることがわかります。

```
https://{関数アプリ名}-deploy.azurewebsites.net/api/{トリガー名}
```

「Approve」ボタンから承認を行うと、「Swap」ステージのタスクが実行されます。
デプロイ完了後、production のエンドポイントにアクセスすると、返るメッセージが「Ver.1.1.0」となりました。

### ゲートの設定

「Swap」ステージ前の条件設定を行うボタンを再度押し、今度は「Gates」を有効にします。

![スクリーンショット_20230213_102621.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/f0324bfd-8fed-f2ec-f9ac-01dfbfaa92b4.png)

「Deployment gates」の「+ Add」ボタンから、条件を追加します。
下記のような条件を追加できます。

- Invoke Azure function: Azure Function を呼び出し、成功することを確認します。
- Query Azure monitor alerts: Azure Monitor で特定のアラートが発生していないかを確認します。
- Invoke REST API: REST API を呼び出し、成功することを確認します。
- Query Work items: クエリから返る作業項目の数がしきい値内にあることを確認します。
- Security and compliance assessment: Azure Policy コンプライアンスを確認します。

ここでは、「Invoke Azure function」を選択し、「Azure function URL」には、単純に deploy slot のエンドポイントを設定します。「Display name」を「call endpoint of deploy slot」とし、「Function key」にキーを入力、「Method」を「GET」にして条件を保存します。

Evaluation options を開き、ここではゲートの評価期間を下記のように短い時間に設定します。

- The time between re-evaluation of gates: 5 minutes
- The timeout after which gates fail: 6 minutes

また、承認はゲートの評価後に実施するように設定を変更します。規定では承認をゲートの評価前に実施するフローとなっています。

- Gates and approvals: On successful gates, ask for approvals

コードを変更して動作検証してみます。
下記のように返却するメッセージを「Ver.1.1.0」->「Ver.1.2.0」に変更し、不正参照が発生するようなコードを追加します。

```
  const responseMessage = "Ver.1.2.0";
  context.log(req.params.undefinedkey.trim()); // cause an exception.
```

修正を commit、Repos に push します。

リリースパイプラインが承認待ちで止まりますので承認を行うと、ゲートでの評価が開始されます。
評価状況のサマリはリリースパイプラインのステージに表示されます。

![スクリーンショット_20230213_114047.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/1f59b175-5e26-179d-edb3-293d651a51cd.png)

不正参照のため、deploy slot の関数アプリからは HTTP 500 エラー応答が返り、評価は失敗します。
「The time between re-evaluation of gates」の設定に従い、5 分後に再評価が行われます。
「The timeout after which gates fail」を 6 分に設定していたため、6 分で評価が中断されるかと思いましたが、再評価が繰り返される動作となりました。
このままではいつまでも評価が完了しないように思われるため、ステージの「Cancel」を実行したところ、Cancel は失敗し、ゲートの認証が失敗する形でデプロイが中断されました。結果的には Cancel ができたことにはなりますが、やや不可解な動作です。

![スクリーンショット_20230213_115034.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/d53fb053-bd27-95cf-fb15-5ad64ba86330.png)

今度は、不正参照を発生させる下記のコードを削除して commit、Repos に push してみます。

```
  context.log(req.params.undefinedkey.trim()); // cause an exception.
```

今回はゲートの評価が成功、5 分後に再評価の後、6 分後に評価完了して承認待ちとなる動作が確認できました。
ゲートの評価結果やログは承認時の画面からタブを切り替えることで確認できます。

![スクリーンショット_20230213_120140.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104149/c0706983-bcc5-7278-037c-c7e98eeb6caf.png)

承認を実施してデプロイ完了後、production のエンドポイントにアクセスすると、返るメッセージが「Ver.1.2.0」となりました。