はじめに
業務では、Github Actionsを利用してIaC化したAzureリソースをデプロイしています。AzureリソースのデプロイはPowershellコマンドのNew-AzDeploymentを使用しており、デプロイ処理をAzure側におまかせしています。我々の使い方として、Github ActionsのランナーはAzureリソースのデプロイ開始を命令してランナーを完了させています。そのため、Azureリソースのデプロイが正常に完了したかどうかはAzure Portalを直接確認する必要があり非常に面倒です。
ユーザーに通知する安易な方法として、Github ActionsのランナーがAzureリソースのデプロイ完了をポーリングすることでGithub側の機能でユーザーに通知することができます。しかし、デプロイするAzureリソースによってはデプロイ時間が長時間になり、その分Github Actionsの課金が発生してしまいます。
本記事では、Azureリソースのデプロイ完了をEventGridでフックしてユーザーに成功/失敗を通知する機構を紹介します。
Azure Event Grid
公式ドキュメントでは、以下のように謳っています。
MQTT および HTTP プロトコルを使用した柔軟なメッセージ消費パターンを提供する、スケーラブルでフル マネージドの 発行/サブスクライブ メッセージ配信サービスです。
このサービスを使用することで、Azureリソースのデプロイに関するイベントをフックすることが可能になります。
Event Gridからフックできるデータ形式の確認
IaCコード
以下のIaCコードは本記事の説明用に簡易的に書いたものです。
-
ディレクトリ構成
|- test | |- main.bicep | |- test.bicep // 適当なAzureリソースをデプロイ | |- deploy.ps1
main.biceptargetScope = 'subscription' resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = { name: 'test_rg' location: deployment().location } module test 'test.bicep' = { scope: rg name: 'test_${deployment().name}' // deployment().nameにはdeploy.ps1のNameに入った値が入ります。 params: { location: deployment().location } }
deploy.ps1$params = @{ Name = 'az_deployment' Location = 'japaneast' TemplateFile = './test/main.bicep' } New-AzDeployment @params
Event Gridの設定
今回Event Gridでフックしたい項目はAzure サブスクリプションになります。
ほぼ既定値で問題ないので、以下の設定でEventGridを作成します。
- システムトピックを「Azure Subscriptions」を選択し、作成する
- サブスクリプショントピックを既定値で作成する
- エンドポイントは smee を指定ことでイベントのデータを確認することができます
EventGrid作成後にIaCコードをデプロイすると、以下のようなjson形式を取得することができます。
{
"payload": [
{
"subject": "/subscriptions/????????-????-????-????-????????????/resourcegroups/test_rg/providers/Microsoft.Resources/deployments/test_az_deployment",
"eventType": "Microsoft.Resources.ResourceWriteSuccess",
"id": "????????-????-????-????-????????????",
"data": {
"authorization": {
...
},
"claims": {
...
},
"correlationId": "????????-????-????-????-????????????",
"httpRequest": {},
"resourceProvider": "Microsoft.Resources",
"resourceUri": "/subscriptions/????????-????-????-????-????????????/resourcegroups/test_rg/providers/Microsoft.Resources/deployments/test_az_deployment",
"operationName": "Microsoft.Resources/deployments/write",
"status": "Succeeded",
"subscriptionId": "????????-????-????-????-????????????",
"tenantId": "????????-????-????-????-????????????"
},
"dataVersion": "2",
"metadataVersion": "1",
"eventTime": "2024-??-??T??:??:??.???????Z",
"topic": "/subscriptions/????????-????-????-????-????????????"
}
]
}
ここで注目したいのは以下のプロパティです。
-
subject
- IaCコードのmodule testのnameに入れた値である
test_az_deployment
が含まれているので、test.bicepをデプロイした際のイベントであることがわかります。
- IaCコードのmodule testのnameに入れた値である
-
status
- このステータスはデプロイの完了ではなく、デプロイ処理開始が完了したことを表しています。
上記より、EventGridのstatus
情報からはAzureリソースのデプロイ完了を判断することが難しそうです。
EventGridからAzureリソースのデプロイ完了を判定する
前述より、EventGridから取れるstatus
からはデプロイ完了を判定ができないことがわかりました。一方で、サブスクリプションの履歴を確認すると、az_deployment
という名称で履歴が残っています。az_deployment
の履歴の詳細を追うと、デプロイ操作とエラーメッセージで記載されているような操作の詳細
を確認することができる。デプロイ完了を判断するためには、操作の詳細
のプロビジョニングの状態を確認する必要があります。
以下のようなコードを用いることで、EventGridの情報からtest.bicepのデプロイ完了結果を取得することができる。このコードは私が自前で用意したものです。
using Azure.ResourceManager.Resources;
public void processEventGrid(string subject)
{
// ... subjectをsplit
// 1. subjectのプロパティからsubscriptionIdとdeploymentNameを取得する
var subscriptionId = "????????-????-????-????-????????????"
var deploymentName = "az_deployment"
var armDeploymentResourceId = ArmDeploymentResource.CreateResourceIdentifier($"/subscriptions/{subscriptionId}", deploymentName);
var armDeployment = armClient.GetArmDeploymentResource(armDeploymentResourceId);
var operations = armDeployment.GetDeploymentOperations(); // 2. サブスクリプションに対するデプロイ操作を全て取得
var operation = operations.FirstOrDefault(operation => operation.Properties.TargetResource?.ResourceName.Contains(deploymentName)); // 3. test.bicepのデプロイ操作を取得
// 4. test.bicepのデプロイ完了結果を表示
System.WriteLine(operation.Properties.ProvisioningState)
// ... 後続処理
}
処理の説明
-
subject
のプロパティからsubscriptionId
とdeploymentName
を取得する -
az_deployment
のサブスクリプションに対するデプロイ操作を全て取得する-
test.bicep
のデプロイ操作はその中に入っている
-
-
test.bicep
のデプロイ操作を取得する -
test.bicep
のデプロイ操作からプロビジョニングの状態を取得する
上記はコードを用いた判定となるため、EventGridのエンドポイント先はAzure Functionsになります。
以上より、EventGridからAzureリソースのデプロイ完了を判定することが可能になりました。
ユーザーに通知する
通知に使用するのはAzure Communication Serviceです。
Azure Communication Serviceとは
公式ドキュメントでは、以下のように謳っています。
Azure Communication Services は、音声、ビデオ、チャット、テキスト メッセージ/SMS、メールなどをすべてのアプリケーションに追加するためのマルチチャネル コミュニケーション API を提供します。
メールの機能以外にも様々なコミュニケーションAPIを提供してくれているようです。
今回はメールの機能を使っていきます。
SDKとREST APIが提供されているので、C#のコード例を以下に載せておきます。
using Azure.Communication.Email;
public void sendEmail(string provisioningState)
{
// メールによる通知
var htmlContent = $"{provisioningState}";
var responseEmail = await emailClient.SendAsync(
WaitUntil.Completed,
senderAddress: "DoNotReply@example.com",
recipientAddress: "myemaile@example.com",
subject: "Deploy Complete",
htmlContent: htmlContent
);
}
システム構成図
完成したシステム構成は以下になります。
- ユーザーがGithub Actionsを実行する
- Github ActionsのランナーがIaCコードを
deploy.ps1
を実行し、AzureリソースのデプロイはAzure Deploymentにおまかせしてランナーは終了します - AzureリソースのデプロイのイベントをEventGridでフックし、Azure Functionsに流します
- EventGridの情報を元に、Azureリソースのデプロイが完了したかどうかを判定する
- 完了していた場合、ユーザーに通知する
このシステム構成により、Github ActionsのランナーはAzureリソースのデプロイを命令処理すると終了するため、課金が抑えられます。尚且つ、Azureリソースのデプロイ完了をユーザーに知らせることができます。
応用編
今回のシステム構成だと、Azureリソースのデプロイ完了後にGithub Actions側が何かしらの処理を実施したい場合には適用できません。
Github Environment
Github EnvironmentにはDeployment Protection Rulesという機能があります。
この機能を使用することで、ジョブの実行を承認があるまで待機させることができます。注意点として、本機能はGithub Enterpriseのライセンスが必要になります。
アプリケーションから承認を実施するにはGithub Appを使用してCustom Deployment Protection Rulesという機能を使います。
この機能を使用した場合のGithub Actionsのyamlは以下になります。
...
jobs:
new-az-deployment:
runs-on: ubuntu-latest
steps:
...
- name: New Az Deployment
shell: pwsh
run: New-AzDeployment deploy.ps1 -RunId ${{ github.run_id }} # Event Gridのsubject値内に埋め込むためにRunIdを引数として取る(次節で説明)
post-process:
needs: new-az-deployment
runs-on: ubuntu-latest
environment: test_environment_protection # Deployment Protection Rulesを適用したGithub Environmentのため、承認がないと処理をしません。
steps:
...
- name: Update Az Resource
shell: pwsh
run: |
// 後続処理
new-az-deployment
はAzureリソースをデプロイのみを実施し、後続に実行するジョブであるpost-process
にはDeployment Protection Rule
を適用したGithub Environmentを指定しています。post-process
は承認がない限り処理されることはありません。
この承認を、Azureリソースのデプロイ完了後にEventGridでフックしAzure Functionsで承認処理を実施することで、後続処理を走らせます。
EventGridからAzureリソースのデプロイ完了を判定し、Deployment Protection Rulesを承認する
公式ドキュメントを確認する限り、Deployment Protection Ruleの承認をする方法として、REST APIが提供されているようです。承認が必要なGithub ActionsのワークフローのIDが必要になります。EventGridでフックした際のワークフローIDを渡すために、deploy.ps1
を以下のように修正します。
[CmdletBinding()]
param (
[Parameter()]
[Alias('RunId')]
[string]$RunId,
)
$params = @{
Name = "az_deployment_{$RunId}"
Location = 'japaneast'
TemplateFile = './test/main.bicep'
}
New-AzDeployment @params
Name箇所にワークフローIDを組み込むことで、EventGridのプロパティであるsubject
から取得することができます。
プロビジョニング状態を元に、Azure FunctionsからREST APIを実行した一部コードを以下になります。コードは上部に記載したコードの続きに記載します。
using Azure.ResourceManager.Resources;
public void processEventGrid(string subject)
{
// ... subjectをsplit
var subscriptionId = "????????-????-????-????-????????????"
var deploymentName = "az_deployment"
var armDeploymentResourceId = ArmDeploymentResource.CreateResourceIdentifier($"/subscriptions/{subscriptionId}", deploymentName);
var armDeployment = armClient.GetArmDeploymentResource(armDeploymentResourceId);
var operations = armDeployment.GetDeploymentOperations();
var operation = operations.FirstOrDefault(operation => operation.Properties.TargetResource?.ResourceName.Contains(deploymentName));
// プロビジョニング状態を元にDeployment Protection Ruleに承認/否認をPOST
var state = operation.Properties.ProvisioningState == "Successed" ? "approved" : "reject";
var runId = "99999999999" // subjectから値を抽出
var url = $"https://api.github.com/repos/test-owner/test-repo/actions/runs/{runId}/deployment_protection_rule";
var response = await httpClient.PostAsJsonAsync(url, new
{
environment_name = "test_environment_protection",
state = state,
comment = "",
});
}
システム構成図
応用編で完成したシステム構成は以下のようになります
- ユーザーがGithub Actionsを実行する
- Github ActionsのランナーがIaCコードを
deploy.ps1
を実行し、AzureリソースのデプロイはAzure Deploymentにおまかせします -
post-process
はDeployment Protection Rulesにより、承認があるまで待ちの状態です - AzureリソースのデプロイのイベントをEventGridでフックし、Azure Functionsに流します
- EventGridの情報を元に、Azureリソースのデプロイが完了したかどうかを判定する
- Azureリソースのデプロイの成功/失敗によって、Deployment Protection Rulesに承認/否認をします
- 承認の場合、
post-porcess
はジョブが開始され、ジョブが完了するとGithub Actionsは成功になります - 否認の場合、
post-porcess
はジョブが開始されず、Github Actionsは失敗となります
- 承認の場合、
- Github Actionsが終了すると、Githubの機能でユーザーに通知します
このシステム構成により、Github ActionsのランナーはAzureリソースのデプロイを命令処理すると終了するため、課金が抑えられます。尚且つ、Azureリソースのデプロイ完了をユーザーに知らせることができます。
まとめ
本記事では、Azureリソースのデプロイ完了をEventGridでフックしてユーザーに成功/失敗を通知する機構を紹介しました。Github ActionsのランナーはAzureリソースのデプロイ開始を命令だけで終了し、Azureリソースのデプロイが完了したらユーザーに通知します。また、EventGridからフック可能であることがわかったため、それを応用した機構も紹介しました。
どちらもGithub ActionsのランナーがAzureリソースのデプロイをポーリングしなくて済み、ユーザーに完了を知らせることができます。
余談ですが、EventGridはドキュメントが少なくAzureリソースのどのイベントが取れる/取れないについて調査/検証が非常に辛かったです。