手書きBicepをAVMに乗り換えたらPrivate Endpoint周りがこんなにシンプルになった
はじめに
以前、私はAzureのPrivate EndpointまわりのBicepを書くたびに「なんでこんなに長くなるんだろう」と感じていました。
VNetの定義、サブネット、Private Endpointリソース、DNS Zoneの作成、VNetリンク、DNSゾーングループ……それぞれ別々に書いて、IDを繋いで、ようやく動く。コードは正確でも、読み返したときに全体像が見えにくくなっていました。
Azure Verified Modules(AVM) に乗り換えてから、その悩みがかなり解消されました。この記事では実際に書き換えたコードを比較しながら、AVMのどこが便利なのかを紹介します。
Azure Verified Modules(AVM)とは
AVMはMicrosoftが提供するBicep/Terraformの公式モジュール集です。Bicep Public Registryに登録されているモジュールは現在AVM準拠のものだけとなっており、Microsoftの「One IaC」戦略の中核を担っています。
モジュールは以下の3種類があります。
| 種類 | 概要 | 例 |
|---|---|---|
| Resource Module | 単一のAzureリソース | VNet、Storage Account など |
| Pattern Module | 複数リソースのまとまり | Hub-Spoke構成 など |
| Utility Module | 型定義やヘルパー | 共通型 など |
今回使うのは Resource Module です。
今回作るアーキテクチャ(使用バージョン)
今回使うモジュールのバージョンです(2026年2月時点)。
| モジュール | バージョン | GitHubリポジトリ |
|---|---|---|
avm/res/network/virtual-network |
0.7.0 | リンク |
avm/res/network/private-dns-zone |
0.7.0 | リンク |
avm/res/storage/storage-account |
0.19.0 | リンク |
今回作るアーキテクチャ
シンプルな構成でAVMの恩恵を実感します。
使うAVMモジュールはこの3つです。
br/public:avm/res/network/virtual-network:0.7.0br/public:avm/res/network/private-dns-zone:0.7.0br/public:avm/res/storage/storage-account:0.19.0
手書きBicepとの比較
まずPrivate Endpointを手書きで定義するとどうなるか振り返ります。
手書きBicepの場合(抜粋)
// (1) VNet
resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = {
name: vnetName
location: location
properties: {
addressSpace: { addressPrefixes: [vnetAddressPrefix] }
subnets: [
{ name: 'snet-general', properties: { addressPrefix: '10.0.0.0/24' } }
{ name: 'snet-pe', properties: { addressPrefix: '10.0.1.0/24', privateEndpointNetworkPolicies: 'Disabled' } }
]
}
}
// (2) Private DNS Zone
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.blob.core.windows.net'
location: 'global'
}
// (3) VNet リンク(DNSゾーンとVNetを紐付け)
resource vnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
name: '${privateDnsZone.name}/link-${vnetName}'
location: 'global'
properties: {
virtualNetwork: { id: vnet.id }
registrationEnabled: false
}
}
// (4) Storage Account
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
properties: {
publicNetworkAccess: 'Disabled'
networkAcls: { bypass: 'AzureServices', defaultAction: 'Deny' }
}
}
// (5) Private Endpoint
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = {
name: 'pep-${storageAccountName}-blob'
location: location
properties: {
subnet: { id: '${vnet.id}/subnets/snet-pe' }
privateLinkServiceConnections: [
{
name: 'blob-connection'
properties: {
privateLinkServiceId: storageAccount.id
groupIds: ['blob']
}
}
]
}
}
// (6) DNS Zone Group(PEとDNSゾーンを紐付け)
resource dnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-11-01' = {
name: '${privateEndpoint.name}/blob-dns-zone-group'
properties: {
privateDnsZoneConfigs: [
{
name: 'blob-config'
properties: { privateDnsZoneId: privateDnsZone.id }
}
]
}
}
リソースが 6つ 。それぞれのIDを正確に繋いでいく必要があり、書いている最中に「あれ、これどのIDを渡すんだっけ」となりがちです。
AVMに乗り換えた場合
// (1) VNet
module vnet 'br/public:avm/res/network/virtual-network:0.7.0' = {
name: 'vnet-deployment'
params: {
name: vnetName
location: location
addressPrefixes: [vnetAddressPrefix]
subnets: [
{ name: 'snet-general', addressPrefix: '10.0.0.0/24' }
{ name: 'snet-pe', addressPrefix: '10.0.1.0/24', privateEndpointNetworkPolicies: 'Disabled' }
]
tags: tags
enableTelemetry: false
}
}
// (2) Private DNS Zone(VNetリンクも一緒に定義できる!)
module privateDnsZone 'br/public:avm/res/network/private-dns-zone:0.7.0' = {
name: 'private-dns-zone-deployment'
params: {
name: 'privatelink.blob.${environment().suffixes.storage}'
virtualNetworkLinks: [
{
name: '${vnetName}-link'
virtualNetworkResourceId: vnet.outputs.resourceId
registrationEnabled: false
}
]
tags: tags
enableTelemetry: false
}
}
// (3) Storage Account(Private Endpoint + DNS 登録まで全部ここで完結!)
module storageAccount 'br/public:avm/res/storage/storage-account:0.19.0' = {
name: 'storage-account-deployment'
params: {
name: storageAccountName
location: location
skuName: 'Standard_LRS'
kind: 'StorageV2'
publicNetworkAccess: 'Disabled'
networkAcls: { bypass: 'AzureServices', defaultAction: 'Deny' }
blobServices: {
containers: [{ name: 'data', publicAccess: 'None' }]
}
// ここが最大のポイント!
// privateEndpoints を書くだけで、PEの作成・サブネット配置・DNS登録まで自動
privateEndpoints: [
{
name: 'pep-${storageAccountName}-blob'
service: 'blob'
subnetResourceId: '${vnet.outputs.resourceId}/subnets/snet-pe'
privateDnsZoneGroup: {
privateDnsZoneGroupConfigs: [
{ privateDnsZoneResourceId: privateDnsZone.outputs.resourceId }
]
}
}
]
tags: tags
enableTelemetry: false
}
}
モジュールが 3つ になりました。特にStorage AccountのAVMモジュールがすごくて、privateEndpoints パラメータを1ブロック書くだけで、Private Endpointの作成・サブネットへの配置・DNS Zoneへの登録まで全部やってくれます。手書きで6ステップかかっていた処理が、この1ブロックに収まります。
完全なコード
ファイル構成はこうなります。
.
├── main.bicep # メインテンプレート
└── main.bicepparam # パラメータファイル(dev環境用)
main.bicep(全文)
// ============================================================
// main.bicep
// AVM を使った Private Endpoint 付きアーキテクチャのサンプル
// ============================================================
targetScope = 'resourceGroup'
@description('リソースを作成するAzureリージョン')
param location string = resourceGroup().location
@description('リソース名のプレフィックス (例: myapp)')
param prefix string = 'myapp'
@description('VNet のアドレス空間')
param vnetAddressPrefix string = '10.0.0.0/16'
@description('汎用サブネットのアドレス範囲')
param generalSubnetPrefix string = '10.0.0.0/24'
@description('Private Endpoint 専用サブネットのアドレス範囲')
param peSubnetPrefix string = '10.0.1.0/24'
@description('AVMテレメトリを有効にするか')
param enableTelemetry bool = false
var vnetName = '${prefix}-vnet'
var storageAccountName = toLower('${prefix}st${uniqueString(resourceGroup().id)}')
var privateDnsZoneName = 'privatelink.blob.${environment().suffixes.storage}'
var tags = {
Environment: 'dev'
ManagedBy: 'Bicep-AVM'
}
module vnet 'br/public:avm/res/network/virtual-network:0.7.0' = {
name: 'vnet-deployment'
params: {
name: vnetName
location: location
addressPrefixes: [vnetAddressPrefix]
subnets: [
{ name: 'snet-general', addressPrefix: generalSubnetPrefix }
{ name: 'snet-pe', addressPrefix: peSubnetPrefix, privateEndpointNetworkPolicies: 'Disabled' }
]
tags: tags
enableTelemetry: enableTelemetry
}
}
module privateDnsZone 'br/public:avm/res/network/private-dns-zone:0.7.0' = {
name: 'private-dns-zone-deployment'
params: {
name: privateDnsZoneName
virtualNetworkLinks: [
{
name: '${vnetName}-link'
virtualNetworkResourceId: vnet.outputs.resourceId
registrationEnabled: false
}
]
tags: tags
enableTelemetry: enableTelemetry
}
}
module storageAccount 'br/public:avm/res/storage/storage-account:0.19.0' = {
name: 'storage-account-deployment'
params: {
name: storageAccountName
location: location
skuName: 'Standard_LRS'
kind: 'StorageV2'
publicNetworkAccess: 'Disabled'
networkAcls: {
bypass: 'AzureServices'
defaultAction: 'Deny'
}
blobServices: {
containers: [{ name: 'data', publicAccess: 'None' }]
}
privateEndpoints: [
{
name: 'pep-${storageAccountName}-blob'
service: 'blob'
subnetResourceId: '${vnet.outputs.resourceId}/subnets/snet-pe'
privateDnsZoneGroup: {
privateDnsZoneGroupConfigs: [
{ privateDnsZoneResourceId: privateDnsZone.outputs.resourceId }
]
}
}
]
tags: tags
enableTelemetry: enableTelemetry
}
}
output vnetResourceId string = vnet.outputs.resourceId
output storageAccountResourceId string = storageAccount.outputs.resourceId
output storageAccountName string = storageAccount.outputs.name
output privateDnsZoneResourceId string = privateDnsZone.outputs.resourceId
main.bicepparam
using './main.bicep'
param location = 'japaneast'
param prefix = 'myapp'
param vnetAddressPrefix = '10.0.0.0/16'
param generalSubnetPrefix = '10.0.0.0/24'
param peSubnetPrefix = '10.0.1.0/24'
param enableTelemetry = false
デプロイ方法
# リソースグループを作成
az group create --name rg-avm-sample --location japaneast
# デプロイ(パラメータファイルを使う場合)
az deployment group create \
--resource-group rg-avm-sample \
--template-file main.bicep \
--parameters main.bicepparam
# デプロイ(パラメータを直接渡す場合)
az deployment group create \
--resource-group rg-avm-sample \
--template-file main.bicep \
--parameters prefix=myapp location=japaneast
デプロイが完了すると、以下のリソースが作成されます。
- VNet(サブネット2つ)
- Private DNS Zone(
privatelink.blob.core.windows.net)+ VNetリンク - Storage Account(パブリックアクセス無効)
- Blob用Private Endpoint(
snet-peサブネットに配置) - DNS Aレコード(Private EndpointのIPを自動登録)
知っておくと便利なこと
バージョンは固定する
AVMモジュールは頻繁に更新されます。0.7.0 のように バージョンを固定して使う ことを強くおすすめします。latest の指定はできないので、使用前にGitHubやAzAdvertizerで最新バージョンを確認してください(記事末尾の参考リンクを参照)。
enableTelemetry について
AVMモジュールにはデフォルトで enableTelemetry: true が設定されており、匿名の利用統計がMicrosoftに送信されます。不要な場合は enableTelemetry: false を指定してください。
params: {
// ...
enableTelemetry: false // テレメトリを無効化
}
VS Code の補完が効く
Bicep拡張機能を入れていれば、br/public: のエイリアスに対して補完が効きます。モジュールのパラメータ名や型を確認しながら書けるので、ドキュメントを別タブで開かなくても作業できます(記事末尾の参考リンクを参照)。
まとめ
AVMに乗り換えてよかったポイントをまとめます。
| ポイント | 内容 |
|---|---|
| コード量が減る | Private Endpoint関連で6リソース → 3モジュールに |
| 設定漏れが減る | privateEndpointNetworkPolicies など推奨設定が組み込み済み |
| 可読性が上がる | 各モジュールが「何をしているか」明確 |
| 更新が楽 | Microsoftがモジュールをメンテしてくれる |
特に「Storage AccountモジュールのparamにPrivate Endpoint情報を書くだけでDNS登録まで完結する」という体験は最初かなり感動しました。
今回はStorageを例にしましたが、Key VaultやCognitive Services(Azure OpenAI)でも同じパターンが使えます。ぜひ試してみてください。
参考
- Azure Verified Modules 公式サイト
- AVM Bicep Resource Modules 一覧
- GitHub - bicep-registry-modules
- AzAdvertizer - Bicep Modules(バージョン確認に便利)
- Bicep 拡張機能 for VS Code
Discussion