Azure Container AppsのPrivate Endpoint + Azure Front Doorをbicepで構築してみた(プレビュー)

最近bicepばかり書いていて、コード書けてないのでちょっとストレス溜まっているわたなべです。

昨年11月頃にAzure Container Apps 環境でプライベート エンドポイントを使用するを参考にbicepでPrivate EndpointありのAzure Container Appsをbicepでの構築を試していました。

この時点で構築はできたのですが、以下のような状況を確認して止まっていました。

  • 2024/11時点ではパブリックプレビュー
    • 本番利用非推奨
    • プレビュー中は無料、GA時の料金未定
  • Microsoft.App/managedEnvironments@2024-08-02-previewpublicNetworkAccess が追加になっている
    • @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バージョンで追加になった publicNetworkAccessDisable に指定しして、パブリックインターネットからの接続を無効にしています。

// 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するかはまだ不明ですが、楽しみにして色々実験を進めていきたいと思います。

関連情報