Azure Developer CLIをCI/CDに組み込む

# はじめに

[Azure Developer CLI](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/)をCI/CD(Github Actions)に組み込み、開発/本番環境にデプロイさせたいと考えています。

# Azure Developer CLIとは

https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/
```
Azure Developer CLI (azd) は、Azure の使用を開始するのにかかる時間を短縮するオープンソース ツールです。 azd には、ワークフローの主要なステージ (コード、ビルド、デプロイ、監視) にマップする開発者向けコマンドのセットが用意されています。 CLI を使用すると、ターミナル、エディター/統合開発環境、GitHub Actions パイプラインなど、繰り返しが可能な方法で一貫して作業できます。
```

上記のように謳っており、本ツールを使用することで以下の簡単なコマンド操作でインフラ、アプリケーションのデプロイおよび削除を実施することができます。
```bash
$ azd up # インフラ/アプリケーションをデプロイ 
$ azd provision # インフラのみデプロイ
$ azd depoy # アプリケーションのみデプロイ
$ azd down # インフラ/アプリケーションを削除
```

今回はこのコマンドをCI/CDに組み込んでいきます。

# CI/CD

## 公式ドキュメントで紹介されているCI/CD

https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/configure-devops-pipeline?tabs=GitHub

ドキュメントで紹介されている方法では以下のようなGithub Actionsを組み込むことでCI/CDの導入が可能です。

https://github.com/Azure-Samples/todo-csharp-cosmos-sql/blob/main/.github/workflows/azure-dev.yml

```yaml
on:
  workflow_dispatch:
  push:
    # Run when commits are pushed to mainline branch (main or master)
    # Set this to the mainline branch you are using
    branches:
      - main
      - master

# GitHub Actions workflow to deploy to Azure using azd
# To configure required secrets for connecting to Azure, simply run `azd pipeline config`

# Set up permissions for deploying with secretless Azure federated credentials
# https://learn.microsoft.com/en-us/azure/developer/github/connect-from-azure?tabs=azure-portal%2Clinux#set-up-azure-login-with-openid-connect-authentication
permissions:
  id-token: write
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    env:
      AZURE_CLIENT_ID: ${{ vars.AZURE_CLIENT_ID }}
      AZURE_TENANT_ID: ${{ vars.AZURE_TENANT_ID }}
      AZURE_SUBSCRIPTION_ID: ${{ vars.AZURE_SUBSCRIPTION_ID }}
      AZURE_ENV_NAME: ${{ vars.AZURE_ENV_NAME }}
      AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install azd
        uses: Azure/setup-azd@v0.1.0

      - name: Log in with Azure (Federated Credentials)
        if: ${{ env.AZURE_CLIENT_ID != '' }}
        run: |
          azd auth login `
            --client-id "$Env:AZURE_CLIENT_ID" `
            --federated-credential-provider "github" `
            --tenant-id "$Env:AZURE_TENANT_ID"
        shell: pwsh

      - name: Log in with Azure (Client Credentials)
        if: ${{ env.AZURE_CREDENTIALS != '' }}
        run: |
          $info = $Env:AZURE_CREDENTIALS | ConvertFrom-Json -AsHashtable;
          Write-Host "::add-mask::$($info.clientSecret)"

          azd auth login `
            --client-id "$($info.clientId)" `
            --client-secret "$($info.clientSecret)" `
            --tenant-id "$($info.tenantId)"
        shell: pwsh
        env:
          AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Provision Infrastructure
        run: azd provision --no-prompt

      - name: Deploy Application
        run: azd deploy --no-prompt
```

`Log in with Azure (Federated Credentials)`からOIDCが可能なのは嬉しいポイントです。
やりたいことに合うようにこのコードを改造していきます。

## やりたいことを実現するための問題

- やりたいこと
  - インフラとアプリケーションでワークフローを分ける
    - 片方だけ更新がある際にも対応するため
  - 開発/本番等の環境ごとに制御する
    - 各種環境で同じコードを流用したいため


- 問題
  1. インフラとアプリケーションでワークフローを分離した場合、インフラで扱った環境変数をアプリケーションのワークフローでは使えない
     - azdはコマンド実行した際にローカル上の.azure配下にazdコマンドで扱う環境変数群(`.env`)を構築をします。この環境変数はBicepコードや`hooks`のスクリプト等で参照することができます。また、`azd provision`によって新しく環境変数が追加されたります。そのため、インフラとアプリケーションでワークフローを分離した場合、インフラ構築時に値が追加された`.env`をアプリケーションのワークフローでは使用することができません。`.env`を各ワークフローで共有する必要があります。
     - これを解決するために、azdのリモート環境の設定を実施します。
  2. 環境ごとに値の制御ができない
     - 現状は`AZURE_ENV_NAME`でazdのデプロイ先の制御が可能だが、azd auth loginで使用するサービスプリンシパルも環境ごとに変更したいと考えています。
     - これを解決するために、Github Environment等を駆使して`AZURE_CLIENT_ID`等の値を制御します。(本ドキュメントでは特に説明なし)

上記の問題に対する解決策にしたがって、既存コードを改造していきます

# リモート環境

https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/remote-environments-support

この方法を使用することで、azdで構築される環境変数群(`.env`)をAzure Blob Storageに保存することができます。`azure.yaml`に以下を書き込むことで設定完了です。

```yaml
name: azd-project-name
state:
  remote:
    backend: AzureBlobStorage
    config:
      accountName: saazdremotestate
      containerName: myproject
```
上記設定後、以下のコマンドが実行可能になります。
```bash
$ azd env list # ローカル or リモート環境に登録されている環境名を表示
NAME  DEFAULT  LOCAL  REMOTE
dev   true     false  true
prod  true     false  true
$ azd env select dev # 選択したリモート上の環境名をローカル上に構築します
```

リモート設定をCI/CDに組み込むことで、アプリケーションのワークフローでもインフラのワークフローで扱った環境変数を使用することが可能になります。

公式ドキュメントで記載がないことは以下です。
- `azd auth login`でログインするユーザーには指定したAzure Blob Storageへの`ストレージ BLOB データ共同作成者`の権限を与える必要があります。
- ローカル上の`.env`をリモート環境に反映させるには、明確なコマンドはなく、`azd provision`, `azd deploy`といったデプロイコマンドを実行すると自動的にリモートに反映されます。

# 完成したCI/CD

改造して構築したコードは以下になります。

## インフラ
```yaml
name: Provision Infrastructure

on:
  workflow_dispatch:
    inputs:
      environment: 
        description: 'environment'
        required: true
        type: environment
      is_apply:
        description: 'is apply'
        required: true
        default: false
        type: boolean

permissions:
  id-token: write
  contents: read

jobs:
  provision:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }} # Github Environmnet設定
    env:
      AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
      AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      AZURE_ENV_NAME: ${{ inputs.environment }}
      AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install azd
        uses: Azure/setup-azd@v0.1.0

      - name: Log in with Azure (Federated Credentials)
        run: |
          azd auth login `
            --client-id "$Env:AZURE_CLIENT_ID" `
            --federated-credential-provider "github" `
            --tenant-id "$Env:AZURE_TENANT_ID"
        shell: pwsh
      
      - name: Set Remote azd env # リモート環境の環境変数をローカル上に格納
        run: azd env select ${{ inputs.environment }}
      
      - name: Provision Infrastructure Preview
        run: azd provision --preview
      
      - name: Provision Infrastructure
        if: ${{ inputs.is_apply }}
        run: azd provision --no-prompt
```

## アプリケーション

```yaml
name: Deploy Application

on:
  workflow_dispatch:
    inputs:
      environment: 
        description: 'environment'
        required: true
        type: environment

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }} # Github Environmnet設定
    env:
      AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
      AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
      AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
      AZURE_ENV_NAME: ${{ inputs.environment }}
      AZURE_LOCATION: ${{ vars.AZURE_LOCATION }}

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install azd
        uses: Azure/setup-azd@v0.1.0

      - name: Log in with Azure (Federated Credentials)
        run: |
          azd auth login `
            --client-id "$Env:AZURE_CLIENT_ID" `
            --federated-credential-provider "github" `
            --tenant-id "$Env:AZURE_TENANT_ID"
        shell: pwsh
      
      - name: Set Remote azd env # リモート環境の環境変数をローカル上に格納
        run: azd env select ${{ inputs.environment }}
      
      - name: Deploy Application
        run: azd deploy --no-prompt
```

## 補足

- 説明のため`workflow_dispatch`で組んでいます
- Github Environment名とazdの環境名は同名で組んでいます
- Github EnvironmentとAzureのサービスプリンシパルは事前に作成してください
- AzureでのOIDCの設定方法は[こちら](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-azure)を参照してください

# まとめ

[Azure Developer CLI](https://learn.microsoft.com/ja-jp/azure/developer/azure-developer-cli/)をCI/CDに組み込み、開発/本番環境にデプロイする方法を紹介しました。Azure Developer CLIはまだまだ新しいツールではありますが、かなり便利ですのでAzureでシステムを組む際は積極的に使っていきましょう！