プライベート エンドポイント経由で Bicep デプロイ スクリプトをプライベートに実行する

Microsoft.Resources/deploymentScripts リソース API バージョン2023-08-01を使うと、Azure コンテナー インスタンス (ACI) 内でデプロイ スクリプトをプライベートに実行できます。

環境を構成する

このセットアップでは、デプロイ スクリプトによって作成された ACI は仮想ネットワーク内で実行されて、プライベート IP アドレスを取得します。 その後、プライベート エンドポイントを介して、新規または既存のストレージ アカウントへの接続を確立します。 containerSettings/subnetIds プロパティでは、仮想ネットワークのサブネットにデプロイする必要がある ACI を指定します。

デプロイ スクリプトをプライベートに実行するためにインフラストラクチャがどのように接続されているかを示すアーキテクチャの概要のスクリーンショット。

デプロイ スクリプトをプライベートに実行するには、アーキテクチャの図で示されているように、次のインフラストラクチャが必要です。

  • 2 つのサブネットから成る仮想ネットワークを作成します。
    • プライベート エンドポイントのサブネット。
    • ACI のサブネット。このサブネットには Microsoft.ContainerInstance/containerGroups デリゲートが必要です。
  • パブリック ネットワーク アクセスなしでストレージ アカウントを作成します。
  • ストレージ アカウントの file サブリソースで構成された仮想ネットワーク内に、プライベート エンドポイントを作成します。
  • プライベート DNS ゾーン privatelink.file.core.windows.net を作成し、プライベート エンドポイントの IP アドレスを A レコードとして登録します。 作成した仮想ネットワークにプライベート DNS ゾーンをリンクします。
  • ストレージ アカウントで Storage File Data Privileged Contributor アクセス許可を持つユーザー割り当てマネージド ID を作成し、デプロイ スクリプト リソースの identity プロパティでそれを指定します。 ID を割り当てるには、Identity を参照してください。
  • ACI リソースは、デプロイ スクリプト リソースによって自動的に作成されます。

次の Bicep ファイルでは、デプロイ スクリプトをプライベートに実行するために必要なインフラストラクチャが構成されます。

@maxLength(10) // Required maximum length, because the storage account has a maximum of 26 characters
param namePrefix string
param location string = resourceGroup().location
param userAssignedIdentityName string = '${namePrefix}Identity'
param storageAccountName string = '${namePrefix}stg${uniqueString(resourceGroup().id)}'
param vnetName string = '${namePrefix}Vnet'
param deploymentScriptName string = '${namePrefix}ds'

var roleNameStorageFileDataPrivilegedContributor = '69566ab7-960f-475b-8e7c-b3118f30c6bd'
var vnetAddressPrefix = '192.168.4.0/23'
var subnetEndpointAddressPrefix = '192.168.4.0/24'
var subnetACIAddressPrefix = '192.168.5.0/24'

resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
  name: userAssignedIdentityName
  location: location
}

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-04-01' = {
  name: storageAccountName
  kind: 'StorageV2'
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  properties: {
    publicNetworkAccess: 'Disabled'
    networkAcls: {
      defaultAction: 'Deny'
      bypass: 'AzureServices'
    }
  }
}

resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-11-01' = {
   name: storageAccount.name
   location: location
   properties: {
    privateLinkServiceConnections: [
      {
        name: storageAccount.name
        properties: {
          privateLinkServiceId: storageAccount.id
          groupIds: [
            'file'
          ]
        }
      }
    ]
    customNetworkInterfaceName: '${storageAccount.name}-nic'
    subnet: {
      id: virtualNetwork::privateEndpointSubnet.id
    }
   }
}

resource storageFileDataPrivilegedContributorReference 'Microsoft.Authorization/roleDefinitions@2022-04-01' existing = {
  name: roleNameStorageFileDataPrivilegedContributor
  scope: tenant()
}

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(storageFileDataPrivilegedContributorReference.id, managedIdentity.id, storageAccount.id)
  scope: storageAccount
  properties: {
    principalId: managedIdentity.properties.principalId
    roleDefinitionId: storageFileDataPrivilegedContributorReference.id
    principalType: 'ServicePrincipal'
  }
}

resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: 'privatelink.file.core.windows.net'
  location: 'global'

  resource virtualNetworkLink 'virtualNetworkLinks' = {
    name: uniqueString(virtualNetwork.name)
    location: 'global'
    properties: {
      registrationEnabled: false
      virtualNetwork: {
        id: virtualNetwork.id
      }
    }
  }

  resource resRecord 'A' = {
    name: storageAccount.name
    properties: {
      ttl: 10
      aRecords: [
        {
          ipv4Address: first(first(privateEndpoint.properties.customDnsConfigs)!.ipAddresses)
        }
      ]
    }
  }
}

resource virtualNetwork 'Microsoft.Network/virtualNetworks@2023-11-01' = {
  name: vnetName
  location: location
  properties:{
    addressSpace: {
      addressPrefixes: [
        vnetAddressPrefix
      ]
    }
  }

  resource privateEndpointSubnet 'subnets' = {
    name: 'PrivateEndpointSubnet'
    properties: {
      addressPrefixes: [
        subnetEndpointAddressPrefix
      ]
    }
  }

  resource containerInstanceSubnet 'subnets' = {
    name: 'ContainerInstanceSubnet'
    properties: {
      addressPrefix: subnetACIAddressPrefix
      delegations: [
        {
          name: 'containerDelegation'
          properties: {
            serviceName: 'Microsoft.ContainerInstance/containerGroups'
          }
        }
      ]
    }
  }
}

resource privateDeploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
  name: deploymentScriptName
  dependsOn: [
    privateEndpoint
    privateDnsZone::virtualNetworkLink
  ]
  location: location
  kind: 'AzurePowerShell'
  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${managedIdentity.id}' : {}
    }
  }
  properties: {
    storageAccountSettings: {
      storageAccountName: storageAccount.name
    }
    containerSettings: {
      subnetIds: [
        {
          id: virtualNetwork::containerInstanceSubnet.id
        }
      ]
    }
    azPowerShellVersion: '9.0'
    retentionInterval: 'P1D'
    scriptContent: 'Write-Host "Hello World!"'
  }
}

ACI は、Microsoft Container Registry からコンテナー イメージをダウンロードします。 ファイアウォールを使う場合は、イメージをダウンロードするための URL mcr.microsoft.com を許可リストに載せます。 コンテナー イメージのダウンロードが失敗すると、ACI は waiting 状態になり、最終的にタイムアウト エラーが発生します。

次のステップ

この記事では、プライベート エンドポイント経由でデプロイ スクリプトを実行する方法について説明しました。 詳細については、以下を参照してください。