다음을 통해 공유


프라이빗 엔드포인트를 통해 Bicep 배포 스크립트를 비공개로 실행

Microsoft.Resources/deploymentScripts 리소스 API 버전 2023-08-01을 사용하면 ACI(Azure Container Instance) 내에서 배포 스크립트를 비공개로 실행할 수 있습니다.

환경 구성

이 설정에서는 배포 스크립트로 만들어진 ACI가 가상 네트워크 내에서 실행되고 개인 IP 주소를 가져옵니다. 그런 다음 프라이빗 엔드포인트를 통해 새 스토리지 계정이나 기존 스토리지 계정에 대한 연결을 설정합니다. containerSettings/subnetIds 속성은 가상 네트워크의 서브넷에 배포되어야 하는 ACI를 지정합니다.

배포 스크립트를 비공개로 실행하기 위해 인프라가 어떻게 연결되어 있는지 보여 주는 개략적인 아키텍처의 스크린샷.

배포 스크립트를 비공개로 실행하려면 아키텍처 다이어그램에서 볼 수 있는 다음과 같은 인프라가 필요합니다.

  • 두 서브넷이 있는 가상 네트워크 만들기:
    • 프라이빗 엔드포인트용 서브넷
    • ACI에 대한 서브넷, 이 서브넷에는 Microsoft.ContainerInstance/containerGroups 위임이 필요합니다.
  • 공용 네트워크에 액세스하지 않고 스토리지 계정을 만듭니다.
  • 스토리지 계정의 file 하위 리소스로 구성된 가상 네트워크 내에 프라이빗 엔드포인트를 만듭니다.
  • 프라이빗 DNS 영역 privatelink.file.core.windows.net을 만들고 프라이빗 엔드포인트 IP 주소를 A 레코드로 등록합니다. 만들어진 가상 네트워크에 프라이빗 DNS 영역을 연결합니다.
  • 스토리지 계정에 Storage File Data Privileged Contributor 권한이 있는 사용자가 할당한 관리 ID를 만들고 배포 스크립트 리소스의 identity 속성에 지정합니다. ID를 할당하려면 ID를 참조하세요.
  • 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 상태로 전환되고 결국 시간 제한 오류가 발생합니다.

다음 단계

이 문서에서는 프라이빗 엔드포인트에서 배포 스크립트를 실행하는 방법을 알아보았습니다. 자세히 알아보려면 다음을 수행합니다.