BicepとAzure DevOpsを使ったAzure Static Web AppsへのReactのサンプルアプリのデプロイ

# はじめに
`ARM(Azure Resource Manager)`テンプレートの存在は以前から知っていたのですが、最近になってARMテンプレートをさらに見やすく・扱いやすくしたBicepの存在を知りました。Bicepは最近アツくなってきているらしく、興味を持ったので勉強したことを本記事にまとめたいと思います。本記事の内容がより実用的な内容になるように、Bicepを使ってAzureリソースをデプロイするだけではなく、ReactのサンプルWebアプリを`Azure DevOps`を使って自動デプロイするところまでまとめました。

# アーキテクチャ
本記事で構築するアーキテクチャは以下の図になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/75b337e7-683f-98de-ab60-eabf655e7be2.png)

# Azure Static Web Appsとは
本記事のメインであるBicepを紹介する前に、[Azure Static Web Appsとは何か](https://learn.microsoft.com/ja-jp/azure/static-web-apps/overview)について先に説明をしたいと思います。`Azure Static Web Apps`は`SPA(Single Page Application)`用のAzureリソースであり、静的サイトホスティング環境です。Angular、React、Svelte、Vue、Blazor など、サーバー側のレンダリングが不要なライブラリとWebフレームワークを使用することができます。GitHubや`Azure DevOps`との連携が容易であり、簡単な設定によりCI/CDを組むことが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/af317250-5a47-b7a0-c2da-53011b0deb7a.png)
`Azure Static Web Apps`自体は静的Webアプリのホスティングサービスですが、`Azure Functions`と組み合わせることで簡単に動的Webアプリを構築することも可能です。`Azure Static Web Apps`と`Azure Functions`を組み合わせて動的なWebアプリを作りたい方は、以下の記事をご覧ください。

https://qiita.com/y__saito/items/7c78893eaf2af6ecd301

# Bicepとは
本記事のメインコンテンツであるBicepについて説明したいと思います。[Bicepとは](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/overview?tabs=bicep)、Azureリソースをデプロイするための、宣言型構文のドメイン固有言語(DSL)です。 要するに、Azure用のコードとしてのインフラストラクチャ(IaC: `Infrastructure as Code`)になります。

:::note info
**コードとしてのインフラストラクチャ(IaC)とは？**
ネットワーク、仮想マシン、ロードバランサー、接続トポロジ、DevOpsの手法とバージョン管理などのインフラストラクチャを定義してデプロイするためのコードであり、デプロイするたびに同じ環境を生成します。
https://learn.microsoft.com/ja-jp/devops/deliver/what-is-infrastructure-as-code
:::


## BicepとARMテンプレートの違い
Bicepと似たものとして、ARMテンプレートがあります。[ARMテンプレートとは](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/templates/overview)、プロジェクトのインフラストラクチャと構成が定義されているJSONファイルです。ARMテンプレートのJSONは冗長になることがありますが、Bicepではその複雑さが軽減されます。同じストレージアカウントをデプロイするためのBicepファイルとARMテンプレートを以下の実際のコードで比較しましょう。
```typescript:Bicep
param location string = resourceGroup().location
param storageAccountName string = 'toylaunch${uniqueString(resourceGroup().id)}'

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
  }
}
```
```json:JSON
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    },
    "storageAccountName": {
      "type": "string",
      "defaultValue": "[format('toylaunch{0}', uniqueString(resourceGroup().id))]"
    }
  },
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2021-06-01",
      "name": "[parameters('storageAccountName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard_LRS"
      },
      "kind": "StorageV2",
      "properties": {
        "accessTier": "Hot"
      }
    }
  ]
}
```
Bicepファイルの方がARMテンプレートよりも、簡潔にインフラ構成を記述できていることが確認できたと思います。

:::note warn
Bicepでは同等のJSONと比較して、より簡単で簡潔な構文を提供できるため、MicrosoftはARMテンプレートよりも、改良版言語であるBicepの使用を推奨しています。
:::

## BicepとARMテンプレートの関係性
BicepをコンパイルするとARMテンプレートになり、ARMテンプレートをデコンパイルするとBicepになります。この関係性を図式化すると以下になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/2a9ae0a7-e752-a880-ab31-a1c6d715d527.png)

## IaCと非テンプレートIaCの違い
Azureのリソースを作成するために例えば、以下のようにAzure CLIを使うことも可能です。
```bash:Azure CLI
az staticwebapp create \
    --name my-first-static-web-app \
    --resource-group my-swa-group \
    --source https://github.com/$GITHUB_USER_NAME/my-first-static-web-app \
    --location "eastus2" \
    --branch main \
    --app-location "src" \
    --login-with-github
```
`Azure CLI`での命令をシェルスクリプト化(非テンプレートIaC)して手続き的にリソースをデプロイすることも可能ですが、通常のIaCを使用する方が様々な理由からメリットが大きいです。そのうちの1つの理由として挙げられるのが、通常のIaCであれば命令コマンドの回数を1回で済ませられることです。下の図では、IaC(図ではARMテンプレート)と非テンプレートIaCの命令コマンドの回数違いが示されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/3d4d2a37-ca2a-f7d9-7c43-0d6de9a1183c.png)
ARMテンプレートのようなIaCでは、相互に依存するリソースのデプロイが調整され、正しい順序で作成されるので複雑な順序付けのことを心配する必要はなく、1つの命令コマンドでデプロイは完了する並列デプロイになります。一方で非テンプレートIaCでは、順番を考慮した上で複数の命令コマンドを行う必要性があるため、直列デプロイになります。並列デプロイは直列デプロイに比べ、短時間でデプロイが完了する点でも優れているデプロイ方法になります。

# Bicepファイルの作成方法
ここでは、VSCodeを使ってBicepファイルを作成する方法を紹介します。

## Bicepの拡張機能をインストール
まずはVSCodeにBicep拡張機能をインストールしましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/afdcad16-4c3e-3d7b-eede-94ca5b52dc34.png)

### Bicep拡張機能の機能
Bicepの拡張機能には主に以下のような機能があります。Bicepファイルの作成においては、スニペットが特に強力な機能になります。
- バリデーション
- スニペット
- コードナビゲーション
    - 定義
    - 定義のピーク
- ハイライト
- リファクター
- フォーマッティング

## リソースの作成
Bicep拡張機能をインストールしたら、VSCodeで任意のファイル名のBicepファイルを展開してください。そこで、作成したいリソースを入力してください。以下のスクリーンショットの例では、`storage`と入力しました。するとリストが表示されるので、`res-storage`を選択して`Enter`キーを入力しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/973a02c2-976d-9370-e391-28c9ad69033a.png)
すると、一瞬で次のコードが追加されます。このように、Bicep拡張機能のスニペット機能を使用することで、一瞬で作成したいリソースを含むBicepファイルが作成できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/b0cec191-fa2f-490e-5996-43239c27eebc.png)

:::note warn
今回紹介したBicep拡張機能のスニペット機能は強力ですが、全てのリソースに対応しているわけではありません。`Azure Static Web Apps`のように登録されていないソースコードは公式ドキュメントを参照して確認しましょう。今回は[こちらの記事](https://learn.microsoft.com/ja-jp/azure/templates/microsoft.web/staticsites?pivots=deployment-language-bicep)でまとめられている`Azure Static Web Apps`のBicepテンプレートを参考にしました。
:::

Bicepファイルの作成について、詳しくは以下の公式ドキュメントが参考になります。

https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/quickstart-create-bicep-use-visual-studio-code?tabs=CLI

# サンプルアプリをデプロイして実行するまでの流れ
ここから、次の流れでサンプルアプリをAzure上で実行するところまでの手順をまとめたいと思います。
1. [アプリ開発](#1-アプリ開発)
1. [ローカルでの実行](#2-ローカルでの実行)
1. [Azureリソースのデプロイ](#3-azureリソースのデプロイ)
1. [アプリのデプロイ](#4-アプリのデプロイ)
1. [クラウドでの実行](#5-クラウドでの実行)

# 1. アプリ開発
まず最初の工程としてWebアプリを開発しましょう。アプリ開発は本記事の目的ではないため、以下のリポジトリに保存されているReactのWebアプリを使用させていただきました。このWebアプリは、絵文字検索ができるアプリになります。

https://github.com/salehaparveen/React-emoji-search

# 2. ローカルでの実行
先ほどご紹介したサンプルアプリをローカルで実行するために、お好きなシェルを開いて`npm start`を実行します。その後、ブラウザで`http://localhost:3000`にアクセスすれば以下のスクリーンショットのような画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/a4e76a02-f9ad-c2d6-9117-65fb8e4b1059.png)
このようにローカルでは、適切にアプリが動くことが確認できました。

# 3. Azureリソースのデプロイ
ローカルで動いているWebアプリをデプロイするために、Azureリソースのデプロイを事前に行いましょう。

## Bicepファイルの作成
本記事のタイトルに含まれているBicepをここから使っていきます。Bicepを使うことで、簡単にAzureリソースのデプロイを行うことができます。今回は以下のBicepファイルを使用しました。`repositoryUrl`は適宜自分のリポジトリを指定してください。
```ts:main.bicep
param appName string = 'bicep-practice-app'
param location string = 'eastasia'
param skuName string = 'Free'
param skuTier string = 'Free'
param provider string = 'DevOps'
param repositoryUrl string = 'https://github.com/yus04/React-emoji-search'
param branch string = 'main'

resource staticWebApp 'Microsoft.Web/staticSites@2022-03-01' = {
  name: appName
  location: location
  sku: {
    name: skuName
    tier: skuTier
  }
  properties: {
    provider: provider
    repositoryUrl: repositoryUrl
    branch: branch
  }
}
```

## リソースグループの作成
Azureポータルから任意のリソースグループ名、リージョンでリソースグループを作成します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/5898d17c-99d9-1323-6499-989a1cee5673.png)

## Azure Static Web Appsのデプロイ
先ほど作成したリソースグループの中で、Bicepと`Azure CLI`を使用して`Azure Static Web Apps`をデプロイします。`"provisioningState": "Succeeded"`と返ってきたらデプロイは成功です。
```bash: Azure CLI
az deployment group create \
  --name ExampleDeployment \
  --resource-group ExampleGroup \
  --template-file <path-to-bicep> \
```

:::note info
Bicepファイルをデプロイするときに、`--name`でデプロイに名前を付けることができます。 
:::

Bicepと`Azure CLI`を使用したリソースのデプロイについては、以下の記事が参考になります。

https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/deploy-cli#deploy-local-bicep-file

Azureポータルを開き、静的Webアプリが作られていることが確認できます。次のスクリーンショットの赤枠で囲まれた部分がWebアプリのURLになっているので、コピーしてブラウザでアクセスしてみましょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/1cdbaca5-faaa-9f2f-c544-4fc03793afbf.png)
ブラウザで次のような画面が表示されれば、`Azure Static Web Apps`のデプロイは成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/e64b2015-2f30-af63-c9a9-8834648d32b6.png)

# 4. アプリのデプロイ
次はローカルで動いているWebアプリを`Azure Static Web Apps`にデプロイしていきましょう。本記事では`Azure DevOps`を使って、WebアプリのソースコードをGitHubリポジトリのmainブランチへプッシュした時に自動ビルド＆自動デプロイするCI/CD環境を構築します。

## パイプラインの定義
`Azure DevOps`のPipelinesを使います。以下の`azure-pipelines.yaml`ファイルを使用しました。
```yaml: azure-pipelines.yaml
trigger:
  - main
pool:
  vmImage: ubuntu-latest
steps:
  - task: AzureStaticWebApp@0
    name: DeployStaticWebApp
    displayName: Deploy Static Web App
    inputs:
      app_location: /
      output_location: build
    env:
      azure_static_web_apps_api_token: $(deployment_token)
```
yamlファイルの`steps.task`で使用されている`AzureStaticWebApp@0`は`Azure Static Web Apps`アプリをビルド＆デプロイするタスクになります。このタスクについては以下の公式ドキュメントで詳しく説明されています。

https://learn.microsoft.com/ja-jp/azure/devops/pipelines/tasks/reference/azure-static-web-app-v0?view=azure-pipelines

## パッケージファイルの準備
以下のjsonファイルを使用しました。
```json:package.json
{
  "name": "emoji-search",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.10",
    "@testing-library/react": "^11.2.6",
    "@testing-library/user-event": "^12.8.3",
    "react": "^17.0.2",
    "react-copy-to-clipboard": "^5.0.3",
    "react-dom": "^17.0.2",
    "web-vitals": "^1.1.1"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "devDependencies": {
    "prettier": "^2.2.1",
    "react-scripts": "^4.0.3"
  }
}
```
先ほどの`azure-pipelines.yaml`の`steps.task`に記述されている`AzureStaticWebApp@0`が実行されることで、`scripts.build`に記述されている`react-scripts build`が実行されます。

## GitHubリポジトリの作成
GitHubリポジトリを作成し、ソースコードをプッシュします。`main.bicep`と`azure-pipelines.yaml`を作成し、最終的なフォルダ構成は以下になりました。

```:フォルダ構成
.
├─ build
│  └─ ...
├─ node_modules
│  └─ ...
├─ public
│  └ ...
├─ src
│  └ ...
├─ .gitignore
├─ azure-pipelines.yaml
├─ main.bicep
├─ package-lock.json
├─ package.json
└─ README.md
```
それぞれのディレクトリやファイルの解説は次の通りになります。
- `build/`
    - `npm run build`を実行したときに生成される本番向けのビルド済みアプリを格納するディレクトリ
- `node_modules/`
    - Node.jsで使用するパッケージをインストールするディレクトリ
    - `package.json`に記述されたパッケージ名とバージョンを元に、`npm install`コマンドでインストールされる
- `public/`
    - 公開される静的ファイルを格納するディレクトリ
- `src/`
    - アプリケーションのソースコードを格納するディレクトリ
- `.gitignore`
    - Gitで管理するファイルのうち、バージョン管理しないファイルを指定するためのファイル
- `azure-pipelines.yaml`
    - `Azure Pipelines`でビルドやデプロイを行うための設定ファイル
- `main.bicep`
    - Azureリソースを定義するためのBicepファイル
- `package-lock.json`
    - `node_modules/`の構造や`package.json`が修正または生成される際の依存性を正確かつ具体的な情報を記載するファイル
    - `npm install`コマンドでインストールされたパッケージのバージョンを固定するために使用
- `package.json`
    - Node.jsにおいてインストールするパッケージが記述されたJSONファイル
    - パッケージ管理の設計書なので、異なる環境でも読み込むことで同じパッケージをインストール可能
- `README.md`
    - プロジェクトの説明や使い方などが記述されたファイル

:::note info
`build`及び`node_modules`フォルダとその中身、および`package-lock.json`フォルダをリポジトリに保存したくないため、以下の通りに`.gitignore`ファイルに記載しました。
```:.gitignore
build/
node_modules/
package-lock.json
```
:::

## パイプラインの作成
次に、GitHubリポジトリに保存されている`azure-pipelines.yaml`を使って、`Azure DevOps`のPipelinesを作成します。まず、`Azure DevOps`のPipelines画面を開いて、「Create Pipeline」を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/628652e0-9440-d7c0-a855-4c65b8a85eac.png)
ソースコードの保存元として「GitHub」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/68f6af7b-1479-53c7-05cb-4da244fb6c90.png)
リポジトリを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/ec130ac3-dcfb-c76f-ce1b-25f81213bb9b.png)
GitHubに保存されている`azure-pipelines.yaml`を使ってパイプラインを定義したいので、「Existing Azure Pipelines YAML file」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/21692080-8da0-2ed4-5c90-2a164d7ae5ac.png)
ブランチを指定した後で、パスとして「/azure-pipelines.yaml」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/91288ff9-e07d-c8cd-1274-405fff500287.png)
以上で、パイプラインの作成ができました。

## 環境変数の登録
`azure-pipelines.yaml`の`env`で使われている環境変数`deployment_token`の設定を`Azure DevOps`で行います。まず、次のスクリーンショットの通りに「Edit」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/3da3e630-7bd5-35c2-09ac-a628160d3fd7.png)
「Variables」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/054f492f-657e-e47f-522f-2c33ea0d7425.png)
「New Variable」を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/3aa7036d-271e-9fb4-c167-08df162eedad.png)
Nameを`deployment_token`、ValueをAzureポータルでコピーしたデプロイトークンにします。デプロイトークンの取得方法は次に紹介します。NameとValueを入力と「Keep this value secret」のチェックをしたら「OK」ボタンを押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/7a819418-3067-e248-58df-75c6d4da6e7f.png)

## デプロイトークンの取得
Azureポータルを開いて、Bicepでデプロイ済みの静的Webアプリの概要画面を開きます。次のスクリーンショットの赤枠で囲まれている「デプロイトークンの管理」を押下します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/c8fb796d-6c43-1957-6c5f-f8f2cdc8aca6.png)
すると、デプロイトークンが表示されるので右のアイコンを押下してクリップボードにコピーすることで、デプロイトークンを取得できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/a0369ad5-b415-1ece-e158-ccb637759135.png)

## アプリのビルドとデプロイの実行
以上でアプリのデプロイの準備が完了しました。アプリのビルドとデプロイを行っていきましょう。Pipelinesの画面右上にある、「Run pipeline」を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/09d439d7-be11-7025-7ccf-27d2f5354b92.png)
「Run」を押します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/321dd6b5-58f0-016e-2bcc-cf5482c86dc0.png)
Pipelinesの画面では、パイプラインで定義したタスクが実行されているのをリアルタイムで確認することができます。次のスクリーンショットのように、Jobに緑色のチェックマークがつけば、エラーなくアプリのビルドとデプロイが完了したことを意味しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/3770e26a-d51d-d369-06aa-1aab81c0c86e.png)

# 5. クラウドでの実行
Azureポータルの`Azure Static Web Apps`の概要画面に表示されているURLにブラウザでアクセスしてみて、次のスクリーンショットのような画面が表示されれば、無事にReactのサンプルアプリが`Azure Static Web Apps`にデプロイできました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2452857/29235caa-9dda-2027-7efa-873f6557063b.png)

:::note info
不要になったリソースは、Azure CLIを使ってリソースグループごとクリーンアップしましょう。
```bash:Azure CLI
az group delete --name exampleRG
```
:::

# おわりに
Bicepと`Azure DevOps`を使って簡単に`Azure Static Web Apps`にReactのサンプルアプリをデプロイすることができました。もちろん、Azureポータルを使ってリソースを作成することは簡単なのですが、VSCodeのBicep拡張機能を使うことで簡単にBicepファイルを作成することができるため、Bicepによるリソース作成も容易であることに感動しました。初めはとっつきにくかったBicepでしたが、今後は積極的に利用していきたいと思います。