Azure Pipelines×Bicep×マネージドDevOpsプールによるAzure自動構築入門
はじめに
Azureでリソースを構築するときには、IaCの選択肢としてはTerraformやBicepがメジャーどころにあります。ノウハウの多さからTerraformを選択することも多かったのですが、最近「マルチクラウドならともかく、AzureだけのシステムならばBicepの方がいいのでは?」と考えるようになりました。
理由としては、Azure純正のIaCであることから「Azureサポートを受けやすい」「Terraformで対応していない痒い所に手が届く」という印象が強いためです。筆者自身、Terraformを使ったCI/CDは実装したことがありますが、Bicepを使った場合のハマりポイントが分からなかったため、整理も含めてアウトプットを行います。
検証観点
- CI/CDパイプラインが正しく実行できること
- マージトリガーや静的解析を含む
- Bicepのモジュール化構成
- Azure Pipelinesの新エージェントの利用
Bicep単位での動作確認
Bicep単体でリソースが構築できることの確認です。Azure CLIを使ってデプロイを実施しました。筆者は検証簡略のために、Azure Portal上のCloud ShellにBicepをアップロードして実施しました。そのため、az login
などの認証は割愛します。
ResourceGroup作成用のBicep
targetScope = 'subscription'
param resourceGroupName string = 'test430-bicep-rg'
param Location string = 'japaneast'
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: resourceGroupName
location: Location
}
az deployment sub create --location japaneast --template-file sample.bicep
CI/CDパイプラインの実装とBicepの組み込み
BicepコードとAzure Pipelines(yaml)は、別のリポジトリで管理することにしています。役割やライフサイクルの違うファイルは別のリポジトリ管理を目的としています。
CI/CDパイプラインの構成としては、Pull Requestの際に実行されるCIパイプライン・マージトリガーで実行されるCDパイプラインの2構成としています。
CIパイプラインでは、Validate
を使ったBicepファイルの構文チェックと、Azure CLIを使ったWhat-if
(ドライラン)を実施しています。validate はコードの構文チェックであり、ドライランではないことに注意してください。
CIパイプライン
trigger:
- none
resources:
repositories:
- repository: iac-test
type: git
name: <ADOのプロジェクト名>/iac-test
ref: refs/heads/feature*
pool:
vmImage: ubuntu-latest
variables:
- name: deploymentDefaultLocation
value: japaneast
jobs:
- job:
steps:
- checkout: iac-test
- task: AzureResourceManagerTemplateDeployment@3
inputs:
deploymentScope: 'Subscription'
azureResourceManagerConnection: 'iac-test-sc'
subscriptionId: '<サブスクリプションID>'
location: '$(deploymentDefaultLocation)'
templateLocation: 'Linked artifact'
csmFile: 'sample.bicep'
deploymentMode: 'Validation'
deploymentName: '$(Build.BuildNumber)'
- task: AzureCLI@2
inputs:
azureSubscription: 'iac-test-sc'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az deployment sub what-if \
--location japaneast \
--template-file sample.bicep
CDパイプライン
trigger: none
pool:
vmImage: ubuntu-latest
resources:
repositories:
- repository: iac-test
type: git
name: <ADOのプロジェクト名>/iac-test
ref: develop
trigger:
branches:
include:
- develop
variables:
- name: deploymentDefaultLocation
value: japaneast
jobs:
- job:
steps:
- checkout: iac-test
- task: AzureResourceManagerTemplateDeployment@3
inputs:
deploymentScope: 'Subscription'
azureResourceManagerConnection: 'iac-test-sc'
subscriptionId: '<サブスクリプションID>'
location: '$(deploymentDefaultLocation)'
templateLocation: 'Linked artifact'
deploymentMode: 'Incremental'
deploymentName: '$(Build.BuildNumber)'
csmFile: sample.bicep
プルリクエストを作成すると、CIパイプラインが実行されます。以下は実行結果の抜粋です。実際に作成されるリソースが事前に表示されていることが分かります(赤枠箇所)。
プルリクエストがマージされると、CDパイプラインが実行されます。以下は実行結果の抜粋です。CIパイプラインで表示されていたWhat-ifは表示されていないことは違いとして見られます。
実際にResourceGroupも作成されていました。
Bicepのモジュール化
簡易なBicepを使ったCI/CDは実施できました。次は、実プロジェクトを意識してBicepのモジュール化に取り組んでみます。
モジュール化では、再利用可能なテンプレートを各Azureサービスに定義します。モジュールを呼び出す際に、環境識別子(prdやstg)といった可変部分のみを渡すことで、実装コストを抑えることができるメリットがあります。
モジュール化やフォルダ構成は正解はありませんが、今回は以下の構成にて実施します。
/root
│
├── modules # 再利用可能なBicepモジュール
│ ├── vnet.bicep # 仮想ネットワークの定義
│
├── param # 環境ごとのパラメータファイル
│ ├── prd # 本番環境
│ │ ├── common.bicepparam # 環境識別子などの共通パラメータ
│ ├── stg # ステージング環境
│ └── dev # 開発環境
│
├── main.bicep # モジュールを読み込んで実行するファイル
├── .gitignore
└── README.md
vnet.bicep
@description('Vnet Bicep File')
param Environment string
param vnetName string = 'vnet-${Environment}-test'
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2024-05-01' = {
name: vnetName
location: resourceGroup().location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'Subnet-1'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
{
name: 'Subnet-2'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
]
}
}
common.bicepparam
using '../../main.bicep'
param Environment = 'prd'
main.bicep
@description('Environment')
param Environment(※1) string
@description('This is Main.Use module file')
module vnet 'modules/vnet.bicep' = {
name: 'Vnet-deploy' //省略可能パラメータ
params:{
Environment(※2):Environment(※1)
}
}
ファイルが分割するため、各ファイルの対応関係を以下に整理しています。特に変数が複数のファイルにまたがるため、そこを中心にマッピングしています。
また、パイプラインの実装も変更する必要がありました。変更後のyamlファイルも掲載します。
CIパイプライン
trigger:
- none
resources:
repositories:
- repository: iac-test
type: git
name: <ADOのプロジェクト名>/iac-test
ref: refs/heads/feature*
pool:
vmImage: ubuntu-latest
variables:
- name: deploymentDefaultLocation
value: japaneast
jobs:
- job:
steps:
- checkout: iac-test
- task: AzureResourceManagerTemplateDeployment@3
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: 'iac-test-sc'
subscriptionId: '<サブスクリプションID>'
action: 'Create Or Update Resource Group'
resourceGroupName: 'test430-bicep-rg'
location: '$(deploymentDefaultLocation)'
templateLocation: 'Linked artifact'
csmFile: 'main.bicep'
csmParametersFile: './param/prd/common.bicepparam'
deploymentMode: 'Validation'
deploymentName: '$(Build.BuildNumber)'
- task: AzureCLI@2
inputs:
azureSubscription: 'iac-test-sc'
scriptType: 'bash'
scriptLocation: 'inlineScript'
useGlobalConfig: false
inlineScript: |
az bicep uninstall
az config set bicep.use_binary_from_path=false
az bicep install
# what-if実行(bicepparam使用可)
az deployment group what-if \
--resource-group test430-bicep-rg \
--template-file main.bicep \
--parameters ./param/prd/common.bicepparam
CDパイプライン
trigger: none
pool:
vmImage: ubuntu-latest
resources:
repositories:
- repository: iac-test
type: git
name: <ADOのプロジェクト名>/iac-test
ref: develop
trigger:
branches:
include:
- develop
variables:
- name: deploymentDefaultLocation
value: japaneast
jobs:
- job:
steps:
- checkout: iac-test
- task: AzureResourceManagerTemplateDeployment@3
inputs:
deploymentScope: 'Resource Group'
azureResourceManagerConnection: 'iac-test-sc'
subscriptionId: '<サブスクリプションID>'
action: 'Create Or Update Resource Group'
resourceGroupName: 'test430-bicep-rg'
location: '$(deploymentDefaultLocation)'
templateLocation: 'Linked artifact'
csmFile: 'main.bicep'
csmParametersFile: './param/prd/common.bicepparam'
deploymentMode: 'Incremental'
deploymentName: '$(Build.BuildNumber)'
パイプラインのモジュール化前後の変更概要は以下の通りです。
No | 変更点 | 変更理由 |
---|---|---|
1 |
deploymentScope をResourceGroup に修正 |
Ventがリソースグループを前提としてリソースのため、最小限のスコープ適用 |
2 |
csmParametersFile: './param/prd/common.bicepparam' を新規で追加 |
パラメータファイル化に伴い |
3 | Azure CLIのオプションをgroup に変更 |
No1と同様の理由 |
4 | Azure CLIのオプションにparameters ./param/prd/common.bicepparam を追加 |
No2と同様の理由 |
CI/CDの流れはモジュール化前と同じです。マージトリガーで実行後にリソースを確認すると、環境識別子(Environment)がprd
になっているリソースが作成されています。
CI/CDの実行基盤のマネージドDevOpsプール化
メジャーなCI/CDエージェントとしては、MSホステッドエージェントやSelf-Hostedエージェントが存在します。
それぞれ、Vnet内のリソースへの操作が困難・実行基盤(VMやVMSS)の維持が必要というデメリットが存在しました。それを解消してくれるのが、マネージド DevOps プール(MDP)というサービスです。
ここまでの検証では、MSホステッドエージェントで進めてきました。これを、MDPに置き換えてたいと思います。
MDPのリソースをAzure Portalから作成する必要があります。具体的な手順は公式ドキュメントに従えば実施できるため割愛します。
リソースの作成後は、引き続き公式ドキュメントに従いyamlで指定するエージェントプールを修正するだけです。結果は割愛しますが、サブネットのCIDRの修正がMDPで実施できました。
MDPを使った感想
-
起動時間について
エージェントの起動にはMSホステッドよりも時間がかかりました(特にCIパイプラインが顕著でした)。理由は、MDPの作成時に「Fresh agent every time」という設定にしているためです。予め利用時間が分かっているならばキャッシュ状態での起動をした方が良いです。4回しかMDPエージェントを起動していませんが、かかる時間はまちまちでした。MS側での割り当て次第なのでしょうか。 -
エージェントの状態の可視化が容易に
エージェントの状態(いつ起動したのか・終了したのかなど)は、Azureポータルのメトリックから確認ができます。
まとめ
BicepをAzure Pipelinesに統合し、新たなCI/CDエージェントとしてマネージドDevOpsプールを利用しました。Terraformで実施していた時と細かな違いはありつつも、最低限実現したいことは実施できそうです。また、MDPの利用も思ったより簡単であったため、今後はCI/CDエージェントの候補として浮上してくると感じしました。
以下は、今回の検証を通じた次回以降の整理ポイントです。また時間を見つけて整理したいと思います。
1. Bicepでデプロイスコープをサブスクリプションにしたままでも、リソースグループ以外のリソースの作成は可能なのか?
→ (UPDATE) 結論、可能です。ただし、この時にはBicepの書き方を工夫する必要があります。
-
注意点1:スコープ定義
リソースグループはSubscriptionがスコープになり、Vnetなどのリソースグループ配下に作るリソースはResourceGroupがスコープになるため、スコープの定義を工夫する必要があります。
今回、リソースグループとVnetの作成を実装し、かつ共にモジュール化を図りました。
BicepではデフォルトでスコープがResourceGroupのため、リソースグループ作成のBicepコードではSubscriptionスコープを明示する必要があります。
また、モジュール化することによって、モジュール側はスコープを再定義することができます。これによって、Vnet側はResourceGroupスコープでのデプロイが可能になります。参考 -
注意点2:リソース名の受け渡し
リソースグループのモジュールのouputsで宣言したリソース名を、Vnet側に渡すという初期実装を行いましたが、Bicepのエラーが出ました。
Bicepでスコープを指定する際、デプロイ開始時点で確定している値を使用する必要があります。outputsの出力値は、厳密にはデプロイ開始後に決まる値です。そのため、スコープの制約にかかってしまいました。
スコープに渡したかったリソースグループ名を、bicepparam側に記載する(モジュール側に記載しない)ことで解決しました。
修正したコードも記載します。参考にしてください。
resourcegroup.bicep
targetScope = 'subscription'
param resourceGroupName string
param Location string = 'japaneast'
resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: resourceGroupName
location: Location
}
common.bicepparam
using '../../main.bicep'
param Environment = 'prd'
param RgName = 'bicep-${Environment}-rg'
main.bicep
targetScope = 'subscription'
@description('Environment')
param Environment string
param RgName string
@description('RG')
module rg 'modules/resorcegroup.bicep' = {
name: 'RG-deploy'
scope: subscription()
params:{
resourceGroupName:RgName
}
}
@description('This is Main.Use module file')
module vnet 'modules/vnet.bicep' = {
name: 'Vnet-deploy' //省略可能パラメータ
scope: resourceGroup(RgName)
params:{
Environment:Environment
}
dependsOn: [
rg
]
}
2. .bicepparamは2つ以上存在することが可能なのか?(1つのparamの中で別のparamを呼び出すしかない?)
→ (UPDATE)Azure PipelinesでAzureResourceManagerTemplateDeployment@3
を利用する場合には、1つのbicepparamしか指定できなさそうです。そのため、ファイルの肥大化がリスクにありますが、1つのパラメータファイルを追記していく方法しかなさそうです。
一方で、以下の回避策も取ることができます。
- bicepparamではなく、bicepファイルにパラメータを記載する。
パラメータファイルも1つのモジュールとして扱い、その中にパラメータ定義(param)とアウトプット(outputs)を記載します。
パラメータを利用したいファイルからは、該当するパラメータファイル(bicep)を参照します。
これによって、パラメータファイルの分割が可能です。また、AzureResourceManagerTemplateDeployment@3
上では明確にパラメータファイルとして引数に渡す必要もありません。 -
AzureResourceManagerTemplateDeployment@3
タスクではなく、インラインスクリプトでaz deployment
コマンドを実施する
az deployment
のオプションである--parameters
に、複数のbicepparamを渡すことができます。これによって実現は可能です。
inlineScript: |
az deployment group create \
--resource-group $(resourceGroupName) \
--template-file main.bicep \
--parameters ./param/prd/common.bicepparam ./param/prd/additional.bicepparam
- bicepの静的解析にはどのようなことができるのか?
-
MDPに元々インストールされていないソフトウェアを扱うためにはどうすればいいか?
→ (UPDATE)Pipelinesのyamlの中で、インストールスクリプトを使ってインストールするのが一番早そう。
Discussion