【Azure】 Bicepで複数のデプロイスコープにリソースをデプロイする

# はじめに
みずほリサーチ&テクノロジーズ株式会社の田代(@k_tashiro)です。

本記事では、[Azure Bicep](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/overview?tabs=bicep)を用いて、複数のデプロイスコープへのリソースをデプロイする方法をご紹介致します。

## 本記事の背景・概要
弊社では、開発・運用など様々なフェーズでパブリッククラウドを安心して使えるように、様々な取り組みを進めています。

例えば、Azure上の開発においては、Azure Policyを使ったガードレールにより危険な設定を防止しつつ開発を進められるような仕組みや、サブスクリプションの初期セットアップとしてガードレールや監視の設定を自動化し、Azureに精通していない人でも漏れなく実装できるような仕組みを整備しています。

初期セットアップではARM テンプレートを活用していますが、「サブスクリプションに対する設定」と、「ログ保管などの必要なリソースの作成」を1つのテンプレート(json)で実現しようとすると、json内で複雑な表現が必要となりなかなか実装がうまくいかず、テンプレートを分割して対応していました。

そこで、新しく登場したBicepでトライしたところ1つのテンプレートで実装できたため、本記事ではその内容についてご紹介致します。

- Azure リソースは階層構造で表現されており、デプロイスコープ毎にデプロイ可能なリソースが決まっている
- ARM テンプレートでは、複数のデプロイスコープにリソースを1度のデプロイする方法がサポートされているが、jsonでの実装は難しい
- Bicepを使うと、異なるデプロイスコープ間でも依存関係を意識したデプロイが簡単に表現できる

## 検証環境
Azure CloudShellを使用しました。
azure-cliのバージョンは2.48.1です。

# Azureリソースのデプロイスコープを理解する

普段Azureを使って開発される方は、サブスクリプションの**リソースグループに**リソースを作ることが多いと思います。
Azureを管理される方は、**サブスクリプション**にロールやPolicyを作ることが多いと思います。

このように、作成するリソースに応じて操作する対象が異なりますが、これはAzureリソースが階層構造で管理されているためです。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2993657/05b978cb-ded4-b8a8-60a2-102f3ad21ef5.png">

> 参考元：[MS Learn Training - デプロイのスコープの概要](https://learn.microsoft.com/ja-jp/training/modules/deploy-resources-scopes-bicep/2-understand-deployment-scopes)

Azure Portal上でリソース作成・操作する際は少し意識し辛いものですが、ARM テンプレートを利用したリソースデプロイではこのスコープの理解が重要です。

スコープ毎にデプロイ可能なリソースは[スコープのドキュメント](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/deploy-to-subscription?tabs=azure-cli#create-resource-group-and-resources)に明記されているので、デプロイしたいリソースがどのスコープで有効であるかを確認しましょう。


# Bicepについて
Bicepは、ARM テンプレートのドメイン固有言語 (DSL)であり、従来の記述方法であるjson形式に比べて**書きやすさやモジュール化のしやすさが向上**しており、より便利になっています。

本記事では、Bicepの基本的な構文や利用方法などの解説は割愛致しますが、[MS LearnのBicep初級コース](https://learn.microsoft.com/ja-jp/training/paths/fundamentals-bicep/)が理解しやすい内容となっておりますので、ご参考ください。

## Moduleを活用して複数のスコープにリソースをデプロイする
Bicepでは[デプロイスコープ](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/deploy-to-subscription?tabs=azure-cli#create-resource-group-and-resources)を下記の通りファイルの冒頭に設定します。
```bicep
targetScope = 'subscription'
```
上記の例では、ファイル内に宣言されたリソースは**Subscriptionスコープ**でデプロイするものと認識されます。

しかし、この状態ではSubscriptionスコープでサポートされないStorage等のリソースは、デプロイすることができません。

`targetScope`に指定したスコープとは別のスコープでリソースをデプロイするためには、**下記の通り[Module](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/modules)を宣言します。**
```bicep
targetScope = 'subscription'

// リソースグループの作成
resource newRG 'Microsoft.Resources/resourceGroups@2021-01-01' = {
  name: resourceGroupName
  location: resourceGroupLocation
}

// LogAnalyticsWorkspaceの作成
module newLogAnalyticsWorkspace 'newLogAnalytics.bicep' = {
  name: 'loganalyticsworkspace'
  // ターゲットスコープをリソースグループに設定
  scope: newRG
  params: {
    location: location
    workspaceName: workspaceName
    sku: sku
    retentionInDays: retentionInDays
    resourcePermissions: resourcePermissions
  }
}
```

Moduleは、メインとなるBicepファイルとは別のBicepファイルに定義したリソースや処理を呼び出す機能で、リソース・デプロイの管理性の向上に役立ちます。
Moduleの呼び出しにおいて、`scope`の指定がサポートされているため、呼び出し元と異なるスコープでリソースをデプロイすることができるようになります。

上記例ではSubscriptionスコープでリソースグループの作成を行い、Moduleの`newLogAnalytics.bicep`では、ResourceGroupスコープでLogAnalytics Workspaceをデプロイしています。


## Bicepでは暗黙的な依存関係が作成され、Moduleの参照も簡単
ARM テンプレートでは、リソースのデプロイに依存関係を定義することができ、あるリソースがデプロイされた後の結果を参照して他方のリソースをデプロイする、という順序付けが可能です。

Bicepでは、下記のようにあるリソースが同じファイル内の別のリソースのデプロイ結果を参照している場合、暗黙的な依存関係が作成されます。
```bicep
resource exampleDnsZone 'Microsoft.Network/dnszones@2018-05-01' = {
  name: 'myZone'
  location: 'global'
}

resource otherResource 'Microsoft.Example/examples@2020-06-01' = {
  name: 'exampleResource'
  properties: {
    // get read-only DNS zone property
    nameServers: exampleDnsZone.properties.nameServers
  }
}
```
> 参考元：[MS Learm - Bicep 暗黙の依存関係](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/resource-dependencies#implicit-dependency)

ResourceではないModule内のデプロイ結果を参照するためには、**Module内で[Output](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/outputs)を宣言**して情報を出力し、呼び出し元でもOutputを指定して情報を取得する必要があります。（このModule参照の場合も、暗黙的な依存関係が作成されます。）

```main.bicep
// LogAnalyticsWorkspaceの作成
module newLogAnalyticsWorkspace 'module/newLogAnalytics.bicep' = {
  name: 'loganalyticsworkspace'
  // ターゲットスコープをリソースグループに設定
  scope: newRG
  params: {
    location: location
    workspaceName: workspaceName
    sku: sku
    retentionInDays: retentionInDays
    resourcePermissions: resourcePermissions
  }
}

resource setting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
  name: settingName
  properties: {
    // モジュール：newLogAnalyticsWorkspaceのリソース作成時に出力したワークスペースIDを設定
    workspaceId: newLogAnalyticsWorkspace.outputs.results.workSpaceId
  }
  // 以下割愛
}
```

```module/newLogAnalytics.bicep
resource workspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = {
  name: workspaceName
  location: location
  properties: {
    sku: {
      name: sku
    }
    retentionInDays: retentionInDays
    features: {
      enableLogAccessUsingOnlyResourcePermissions: resourcePermissions
    }
  }
}

// Outputを宣言し、ワークスペースIDを出力
output results object = {
  workSpaceId: workspace.id
}
```

従来のjson形式の場合、`dependson`を宣言して明示的な依存関係としてリソースIDを指定する必要がありました。

複数スコープでデプロイする場合は、（自身の仕様の理解不足である可能性もありますが）リソースIDを指定するための式が複雑となってしまっていたため、テンプレートを分割して対応していましたが、BicepではModuleのScope指定とOutputを活用することで、**少ないコードで無理なく1ファイル内に実装**することができます。

# Bicepで「サブスクリプションのアクティビティログの診断設定」と「保管先となるLog Analytics Workspace」を設定する
ここまでの情報で、Bicepで複数のデプロイスコープに対して依存関係のあるリソースのデプロイを実現することができそうなことが分かりました。

今回は、サブスクリプションの初期セットアップをユースケースとして、スコープが異なるリソースである
- 「サブスクリプションのアクティビティログの診断設定、ResourceGroupの作成」
- 「アクティビティログの保管先となるLogAnalytics Workspaceの作成」

について、Bicepのデプロイを試します。

デプロイを成功させるためには、以下のような依存関係が必要です。
1. Subscriptionスコープ：ResourceGroupの作成
2. ResourceGroupスコープ：ResourceGroupを参照してLogAnalyticsWorkspaceの作成
3. Subscriptionスコープ：LogAnalyticsWorkspaceを参照してアクティビティログを診断設定

よって、targetScopeはSubscriptionを設定し、LogAnalyticsはModule内でデプロイします。

各リソースの作成においては、[アクティビティ ログの診断設定のBicepサンプル](https://learn.microsoft.com/ja-jp/azure/azure-monitor/essentials/resource-manager-diagnostic-settings?tabs=bicep#diagnostic-setting-for-an-activity-log)と、[Log Analytics WorkspaceのBicepサンプル](https://learn.microsoft.com/ja-jp/azure/azure-monitor/logs/resource-manager-workspace?tabs=bicep#create-a-log-analytics-workspace)が用意されているので、こちらを参考にしました。

## Bicepの実装とデプロイ結果
実装したBicepは、`main.bicep`と、`module/newLogAnalyticsWorkspace.bicep`です。
※Parameterはハードコードされていますが、Parameterファイルへ切り出すことが推奨されます

```main.bicep
targetScope = 'subscription'

@description('The name of the ResourceGroup.')
param resourceGroupName string = 'rg-XXX'

@description('Specify the location for the ResourceGroup.')
param resourceGroupLocation string = 'japaneast'

@description('The name of the diagnostic setting.')
param settingName string = 'send-to-loganalytics'

@description('Specify the name of the workspace.')
param workspaceName string = 'log-XXX'

@description('Specify the location for the workspace.')
param location string = 'japaneast'

@description('Specify the pricing tier: PerGB2018 or legacy tiers (Free, Standalone, PerNode, Standard or Premium) which are not available to all customers.')
param sku string = 'PerGB2018'

@description('Specify the number of days to retain data.')
param retentionInDays int = 120

@description('Specify true to use resource or workspace permissions, or false to require workspace permissions.')
param resourcePermissions bool = true

// ResourceGroupの作成
resource newRG 'Microsoft.Resources/resourceGroups@2021-01-01' = {
  name: resourceGroupName
  location: resourceGroupLocation
}

// LogAnalyticsWorkspaceの作成
module newLogAnalyticsWorkspace 'module/newLogAnalyticsWorkspace.bicep' = {
  name: 'loganalyticsworkspace'
  // ターゲットスコープをリソースグループ:newRGに設定
  scope: newRG
  params: {
    location: location
    workspaceName: workspaceName
    sku: sku
    retentionInDays: retentionInDays
    resourcePermissions: resourcePermissions
  }
}

// アクティビティログの診断設定を作成
resource setting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
  name: settingName
  properties: {
    // モジュール：newLogAnalyticsWorkspaceのリソース作成時に出力したワークスペースIDを設定
    workspaceId: newLogAnalyticsWorkspace.outputs.results.workSpaceId
    logs: [
      {
        category: 'Administrative'
        enabled: true
      }
      {
        category: 'Security'
        enabled: true
      }
      {
        category: 'ServiceHealth'
        enabled: true
      }
      {
        category: 'Alert'
        enabled: true
      }
      {
        category: 'Recommendation'
        enabled: true
      }
      {
        category: 'Policy'
        enabled: true
      }
      {
        category: 'Autoscale'
        enabled: true
      }
      {
        category: 'ResourceHealth'
        enabled: true
      }
    ]
  }
}
```

```module/newLogAnalytics.bicep
@description('Specify the name of the workspace.')
param workspaceName string

@description('Specify the location for the workspace.')
param location string

@description('Specify the pricing tier: PerGB2018 or legacy tiers (Free, Standalone, PerNode, Standard or Premium) which are not available to all customers.')
@allowed([
  'CapacityReservation'
  'Free'
  'LACluster'
  'PerGB2018'
  'PerNode'
  'Premium'
  'Standalone'
  'Standard'
])
param sku string

@description('Specify the number of days to retain data.')
param retentionInDays int

@description('Specify true to use resource or workspace permissions, or false to require workspace permissions.')
param resourcePermissions bool

// LogAnalyticsWorkspaceの作成
resource workspace 'Microsoft.OperationalInsights/workspaces@2021-12-01-preview' = {
  name: workspaceName
  location: location
  properties: {
    sku: {
      name: sku
    }
    retentionInDays: retentionInDays
    features: {
      enableLogAccessUsingOnlyResourcePermissions: resourcePermissions
    }
  }
}

// Outputを宣言し、ワークスペースIDを出力
output results object = {
  workSpaceId: workspace.id
}

```

[VS CodeのBicep拡張機能](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep)でVisualizeすると、シンプルですが想定通りの依存関係が作成されています。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2993657/cd3c5f24-949b-822f-f145-1a282694604a.png">

上記のBicepファイルをzure CLIのコマンドでデプロイします。
```shell
az deployment sub create --location 'japaneast' --template-file main.bicep
```

結果、下記の通りデプロイ成功が確認でき、LogAnalytics Workspaceの作成と、診断設定の保管先がLogAnalytics Workspaceに設定されていることを確認しました。


<img width="700" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2993657/be64cd51-b2c4-ef76-d4db-e358002fd6bf.png">

# まとめ

今回のBicepの実装で、1回のデプロイで複数スコープへのリソースデプロイが実現できました。

統制されたAzureサブスクリプションを払い出す場合は、セキュリティ設定や監視設定など様々なスコープでリソース作成が必要になると思いますので、そのようなケースでBicepの見通しの良さ、管理しやすさは有効だと考えます。
今後もスキルアップと改善・強化に取り組み、[Azure LandingZone](https://learn.microsoft.com/ja-jp/azure/cloud-adoption-framework/ready/landing-zone/)の活用方法なども検討していきたいと思います。

また、MS LearnのBicepコースの解説が分かりやすく、スキルアップにとても役立ちました。今後も積極的にMS Learnを活用していこうと思います。

最後までご覧いただきありがとうございます。
本記事がどなたかのお役に立てれば幸いです。


# 参考ドキュメント

- [MS Learn - モジュールのスコープを設定する](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/modules#set-module-scope)

- [MS Learn - Bicep ファイルを使用したサブスクリプションのデプロイ](https://learn.microsoft.com/ja-jp/azure/azure-resource-manager/bicep/deploy-to-subscription?tabs=azure-cli#create-resource-group-and-resources)

- [MS Learn Training - Bicep を使用してサブスクリプション、管理グループ、テナントにリソースをデプロイする ](https://learn.microsoft.com/ja-jp/training/modules/deploy-resources-scopes-bicep/)