最近bicepばかり書いていて、コード書けてないのでちょっとストレス溜まっているわたなべです。
昨年11月頃にAzure Container Apps 環境でプライベート エンドポイントを使用するを参考にbicepでPrivate EndpointありのAzure Container Appsをbicepでの構築を試していました。
この時点で構築はできたのですが、以下のような状況を確認して止まっていました。
- 2024/11時点ではパブリックプレビュー
- 本番利用非推奨
- プレビュー中は無料、GA時の料金未定
Microsoft.App/managedEnvironments@2024-08-02-previewでpublicNetworkAccessが追加になっている@2024-03-01で実行するとporalで指定したpublicNetworkAccessが更新されてしまう- まだドキュメントは更新されていない
- zoneRedundantを指定するにはVnet統合を有効にしないといけないので、現状指定できなかった(対応してほしいなぁ...)
年明け後のバタバタが落ち着いて、久々に調べてみたのですが、新しいバージョンのMicrosoft.App/managedEnvironments@2024-10-02-previewが公開されていたり、そもそもやりたかったFront Door経由で利用するサンプルが、Azure Front Door を使用して Azure Container App へのプライベート リンクを作成する (プレビュー)として公開されていました。ということで、このサンプルを参考にbicepでの構築を試してみました。
2025/2時点ではパブリックプレビューなので実験用途以外では利用しないようにしてください
構築
構築の流れ
今回の構築の流れはざっくり以下のようになります
- パブリックインターネットからの接続を無効なContainer Apps Environmentを作成
- Azure Front Doorを作成
- Front Door Endpointを作成
Origin Groupを作成
Originを追加
originの接続先に、Container Appsを指定する(この時点では接続がリクエストされた状態)
routesを作成
- プライベートエンドポイント接続を承認する
実際には関連するリソースなどいくつかありますが、今回のメインであるContainer Apps EnvironmentとFront Door Endpointをメインに解説を進めます。
Container Apps Environmentの構築
環境に関連する情報を受け取って、Container Apps Environmentを生成するbicepファイル(./modules/ca.bicep)を以下のような形で呼び出します。指定しているのは作成するリソース名と、内部で利用するログ関連のリソース名です。
var caeName = 'cae-kaz29-labo'
var logAnalyticsWorkspaceName = 'log-kaz29-labo'
var applicationInsightsName = 'appinsights-kaz29-labo'
//...
module cae './modules/cae.bicep' = {
name: 'provision-cae'
params: {
caeName: caeName
logAnalyticsWorkspaceName: logAnalyticsWorkspaceName
applicationInsightsName: applicationInsightsName
}
dependsOn: [
log
]
}
//...
modules/ca.bicep は以下のようになります。新しいAPIバージョンで追加になった publicNetworkAccess を Disable に指定しして、パブリックインターネットからの接続を無効にしています。
// modules/ca.bicep
param location string = resourceGroup().location
param caeName string
param logAnalyticsWorkspaceName string
param applicationInsightsName string
resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2020-08-01' existing = {
name: logAnalyticsWorkspaceName
}
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' existing = {
name: applicationInsightsName
}
resource environment 'Microsoft.App/managedEnvironments@2024-10-02-preview' = {
name: caeName
location: location
properties: {
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: logAnalyticsWorkspace.properties.customerId
sharedKey: logAnalyticsWorkspace.listKeys().primarySharedKey
}
}
daprAIInstrumentationKey: applicationInsights.properties.InstrumentationKey
zoneRedundant: false
peerAuthentication: {
mtls: {
enabled: false
}
}
workloadProfiles: [{
name: 'Consumption'
workloadProfileType: 'Consumption'
}]
peerTrafficConfiguration: {
encryption: {
enabled: false
}
}
publicNetworkAccess: 'Disabled' // 追加になっているプロパティ
}
}
output managedEnvironmentsId string = environment.id
Front Doorの構築
Front Doorの構築はとても簡単で、リソース名とSkuを指定して、./modules/afd.bicepを呼び出します。
//...
module afd './modules/afd.bicep' = {
name: 'provision-afd'
params: {
frontDoorProfileName: frontDoorProfileName
frontDoorSkuName: frontDoorSkuName
}
dependsOn: [
ca
]
}
module/afd.bicep はこんな感じ。
//
param location string = 'global'
param frontDoorProfileName string
@allowed([
'Standard_AzureFrontDoor'
'Premium_AzureFrontDoor'
])
param frontDoorSkuName string
resource frontDoorProfile 'Microsoft.Cdn/profiles@2024-06-01-preview' = {
name: frontDoorProfileName
location: location
sku: {
name: frontDoorSkuName
}
}
Front Door Endpointの構築
次にFront Door Endpointの作成です。下記では、リソース名、前の手順で作成した情報をいくつか渡しています。
module fde './modules/fde.bicep' = {
name: 'provision-fde'
params: {
frontDoorProfileName: frontDoorProfileName
prefix: 'test-app'
originHostName: ca.outputs.fqdn
originHostHeader: ca.outputs.fqdn
managedEnvironmentsId: cae.outputs.managedEnvironmentsId
}
dependsOn: [
afd
]
}
modules/fde.bicepではendpoint/originGroup/origin/routesなどを定義しています。今回の肝はオリジン作成時に sharedPrivateLinkResource を指定し、Container Appsに接続をリクエストする箇所(*1)です。
// modules/fde.bicep
param location string = 'global'
param frontDoorProfileName string
param prefix string
param originHostName string
param originHostHeader string
param probePath string = '/'
param managedEnvironmentsId string
var endpointName = '${prefix}-endpoint'
var routeName = '${prefix}-route'
var originGroup = {
name: '${prefix}-origingroup'
probePath: probePath
}
var origins = [{
name: '${prefix}-origin'
hostName: originHostName
originHostHeader: originHostHeader
priority: 1
weight: 1000
// -- (*1)ここでContainer Appsに接続をリクエストする --
sharedPrivateLinkResource: {
privateLink: {
id: managedEnvironmentsId
}
groupId: 'managedEnvironments'
privateLinkLocation: 'japaneast'
requestMessage: 'AFD Private Link Request'
}
// -----------------------------------------------
}]
resource frontDoorProfile 'Microsoft.Cdn/profiles@2024-09-01' existing = {
name: frontDoorProfileName
}
resource endpoint 'Microsoft.Cdn/profiles/afdEndpoints@2024-09-01' = {
parent: frontDoorProfile
name: endpointName
location: location
properties: {
enabledState: 'Enabled'
}
}
resource originGroupResource 'Microsoft.Cdn/profiles/originGroups@2024-09-01' = {
parent: frontDoorProfile
name: originGroup.name
properties: {
loadBalancingSettings: {
sampleSize: 4
successfulSamplesRequired: 3
}
healthProbeSettings: {
probePath: originGroup.probePath
probeRequestType: 'HEAD'
probeProtocol: 'Https'
probeIntervalInSeconds: 100
}
}
}
resource originResources 'Microsoft.Cdn/profiles/originGroups/origins@2024-09-01' = [for (origin, index) in origins: {
parent: originGroupResource
name: origin.name
properties: {
hostName: origin.hostName
httpsPort: 443
originHostHeader: origin.originHostHeader
priority: origin.priority
weight: origin.weight
sharedPrivateLinkResource: origin.sharedPrivateLinkResource
enforceCertificateNameCheck: true
}
dependsOn: index == 0 ? [] : [origins[index - 1]]
}]
resource routeResource 'Microsoft.Cdn/profiles/afdEndpoints/routes@2024-09-01' = {
parent: endpoint
name: routeName
dependsOn: [
originResources
]
properties: {
originGroup: {
id: originGroupResource.id
}
supportedProtocols: [
'Https'
]
patternsToMatch: [
'/*'
]
forwardingProtocol: 'HttpsOnly'
linkToDefaultDomain: 'Enabled'
httpsRedirect: 'Enabled'
}
}
output endpointId string = endpoint.id
プロビジョニング
Azure CLIでログインしている状態で、以下のコマンドを実行し、プロビジョニングします。 今回はリソースグループは事前に作成しておきました。
$ az deployment group create \ --template-file ./bicep/provision.bicep \ --name "provision-from-local" \ --mode Complete \ --resource-group rg-labo
実行の状況はリソースグループの、デプロイセクションで確認することができます。何か問題があってエラーが発生した場合にもここでエラーの詳細を確認できます。

プライベートエンドポイント接続の承認
現状この承認をbicepで自動で処理する方法がわからないため、手動で実行する方法を紹介します。 Front Doorからのプライベートリンク接続は作成しましたが、Pending状態なので以下の手順で承認します。
まず、以下のコマンドでプライベートエンドポイント接続の一覧を取得します。
$ az network private-endpoint-connection list \
--name 'cae-kaz29-labo' \
--resource-group 'rg-labo' \
--type Microsoft.App/managedEnvironments \
--query "[].{id:id, status: properties.privateLinkServiceConnectionState.status}"
[
{
"id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-labo/providers/Microsoft.App/managedEnvironments/cae-kaz29-labo/privateEndpointConnections/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"status": "Approved"
}
]
上記コマンドで表示される id を使用して以下のコマンドを実行しプライベートエンドポイント接続を承認します。
az network private-endpoint-connection approve --id $ID
これで下記に表示されるURLを開くと、いつものnginxの初期画面が表示されるはずです。
現状把握している問題点
- 承認作業をbicepで自動化する方法が未確立
- プライベートエンドポイントが複数作成される(これは今後解決する?)
まとめ
いかがでしたか?プライベートエンドポイント接続は、待望の機能だったのでGAに向けた準備として実験をしてみました。 いつGAするかはまだ不明ですが、楽しみにして色々実験を進めていきたいと思います。