🦐
【Azure/Bicep】AI Foundry+PrivateEndpointをBicepからデプロイする方法
記事の内容
下記の構成でAI Foundryをデプロイする。
※下記のBicepでは、SubnetとNSGを余分に作成しています。用途に合わせて削ってください。
ディレクトリ構成
bicep-project/
├── deployments/
│ ├── main.bicep
│ └── rg.bicep
└── modules/
├── resourceGroup.bicep
├── vnet.bicep
├── NSG.bicep
├── aifoundry.bicep
└── privateEndpoint.bicep
Bicep
rg.bicep/resourceGroup.bicep
rg.bicep
targetScope = 'subscription'
param rgName string = 'myProjectRG'
param location string = 'japaneast'
var tags = {
Environment: 'dev'
}
module rgModule '../modules/resourceGroup.bicep' = {
name: 'resourceGroup'
params: {
rgName: rgName
location: location
tags: tags
}
}
resourceGroup.bicep
targetScope = 'subscription'
param rgName string
param location string
param tags object
resource RG 'Microsoft.Resources/resourceGroups@2022-09-01' = {
name: rgName
location: location
tags: tags
}
output rgName string = RG.name
vnet.bicep
vnet.bicep
param vnetName string
param location string
param tags object
param vnetAddressPrefix string
param subnets array
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
name: vnetName
location: location
tags: tags
properties: {
addressSpace: {
addressPrefixes: [
vnetAddressPrefix
]
}
subnets: [
for (subnet, index) in subnets: {
name: subnet.name
properties: union(
{
addressPrefix: subnet.addressPrefix
privateEndpointNetworkPolicies: 'Enabled'
networkSecurityGroup: {
id: resourceId('Microsoft.Network/networkSecurityGroups', subnet.nsgName)
}
},
index == 2
? {
delegations: [
{
name: 'delegation'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}
]
}
: {}
)
}
]
}
}
output vnetId string = vnet.id
output vnetName string = vnet.name
output agwsubnetId01 string = vnet.properties.subnets[0].id
output pesubnetId01 string = vnet.properties.subnets[1].id
output vnetIntegrationSubnetId string = vnet.properties.subnets[2].id
output pesubnetId02 string = vnet.properties.subnets[3].id
NSG.bicep
NSG.bicep
param location string = 'japaneast'
param tags object = {
environment: 'sample'
}
param subnets array = [
{
name: 'subnet-app'
nsgName: 'nsg-app'
}
{
name: 'subnet-pe'
nsgName: 'nsg-pe'
}
]
var nsgRules = {
'${subnets[0].nsgName}': [
{
name: 'AllowAppSubnetInbound'
priority: 100
direction: 'Inbound'
access: 'Allow'
protocol: '*'
sourcePortRange: '*'
destinationPortRange: '*'
sourceAddressPrefix: '10.1.0.0/24' // sample private range
destinationAddressPrefix: '*'
}
{
name: 'DenyAllOutbound'
priority: 4096
direction: 'Outbound'
access: 'Deny'
protocol: '*'
sourcePortRange: '*'
destinationPortRange: '*'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
}
]
'${subnets[1].nsgName}': [
{
name: 'AllowHttpsOutbound'
priority: 100
direction: 'Outbound'
access: 'Allow'
protocol: 'Tcp'
sourcePortRange: '*'
destinationPortRange: '443'
sourceAddressPrefix: '*'
destinationAddressPrefix: 'Internet'
}
{
name: 'DenyAllOutbound'
priority: 4096
direction: 'Outbound'
access: 'Deny'
protocol: '*'
sourcePortRange: '*'
destinationPortRange: '*'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
}
]
}
resource nsgs 'Microsoft.Network/networkSecurityGroups@2023-05-01' = [
for subnet in subnets: {
name: subnet.nsgName
location: location
tags: tags
properties: {
securityRules: [
for rule in nsgRules[subnet.nsgName]: {
name: rule.name
properties: {
priority: rule.priority
direction: rule.direction
access: rule.access
protocol: rule.protocol
sourcePortRange: rule.sourcePortRange
destinationPortRange: rule.destinationPortRange
sourceAddressPrefix: rule.sourceAddressPrefix
destinationAddressPrefix: rule.destinationAddressPrefix
}
}
]
}
}
]
output appNsgId string = nsgs[0].id
output peNsgId string = nsgs[1].id
main.bicep
// 共通パラメータ (sample)
targetScope = 'subscription'
param rgName string = 'sampleRG'
param location string = 'japaneast'
param environment string = 'dev'
param projectCode string = 'sample'
param tags object = {
Environment: 'dev'
}
// ネットワーク関連 (sample ranges)
var subnets = [
{
name: 'sample-agw-subnet01'
addressPrefix: '10.1.0.0/24'
nsgName: 'sample-agw-nsg01'
}
{
name: 'sample-pe-subnet01'
addressPrefix: '10.1.1.0/24'
nsgName: 'sample-pe-nsg01'
}
{
name: 'sample-integration-subnet01'
addressPrefix: '10.1.2.0/24'
nsgName: 'sample-integration-nsg01'
}
{
name: 'sample-pe-subnet02'
addressPrefix: '10.1.3.0/24'
nsgName: 'sample-pe-nsg02'
}
]
module nsgModule '../modules/NSG.bicep' = {
name: 'NetworkSecurityGroup'
scope: resourceGroup(rgName)
params: {
location: location
tags: tags
subnets: subnets
}
}
module vnetModule '../modules/vnet.bicep' = {
name: 'VirtualNetwork'
scope: resourceGroup(rgName)
params: {
vnetName: 'sampleVnet01'
location: location
tags: tags
vnetAddressPrefix: '10.1.0.0/16'
subnets: subnets
}
dependsOn: [
nsgModule
]
}
// AI Foundry (sample)
param aiFoundryName string = 'sample-ai-foundry'
param aiProjectName string = '${aiFoundryName}-project'
module aiFoundryModule '../modules/aifoundry.bicep' = {
name: 'ai-foundry'
scope: resourceGroup(rgName)
params: {
aiFoundryName: aiFoundryName
aiProjectName: aiProjectName
location: location
}
}
// AI Foundry PrivateEndpoint (sample)
module aifoundryPrivateEndpoint '../modules/privateEndpoint.bicep' = {
name: 'aifoundry-pe'
scope: resourceGroup(rgName)
params: {
privateEndpointName: 'sample-aifoundry-pe01'
location: location
tags: tags
subnetId: vnetModule.outputs.peSubnetId02
targetResourceId: aiFoundryModule.outputs.aiFoundryId
groupIds: ['account']
privateDnsZoneNames: [
'privatelink.cognitiveservices.azure.com'
'privatelink.openai.azure.com'
'privatelink.services.ai.azure.com'
]
vnetId: vnetModule.outputs.vnetId
}
dependsOn: [
vnetModule
aiFoundryModule
]
}
aifoundry.bicep
param aiFoundryName string
param aiProjectName string
param location string
// AI Foundry アカウントの作成
resource aiFoundry 'Microsoft.CognitiveServices/accounts@2025-04-01-preview' = {
name: aiFoundryName
location: location
identity: {
type: 'SystemAssigned'
}
sku: {
name: 'S0'
}
kind: 'AIServices'
properties: {
// AI Foundry での動作に必要な設定
allowProjectManagement: true // プロジェクト管理機能を有効化
// 開発者API エンドポイントのサブドメイン名を定義
customSubDomainName: aiFoundryName // カスタムサブドメイン(例:uniquename.cognitiveservices.azure.com)
disableLocalAuth: false // ローカル認証を無効化(Azure AD認証のみ使用)
publicNetworkAccess: 'Enabled' // パブリックネットワークアクセスを無効化
networkAcls: {
defaultAction: 'Allow' // デフォルトでアクセス拒否
bypass: 'AzureServices' // Azureサービスは許可
}
}
}
// AI プロジェクトの作成
resource aiProject 'Microsoft.CognitiveServices/accounts/projects@2025-04-01-preview' = {
name: aiProjectName
parent: aiFoundry // 親の AI Foundry アカウントに紐づけ
location: location
identity: {
type: 'SystemAssigned' // プロジェクト専用のマネージドIDを作成
}
properties: {} // 現在は追加プロパティなし
}
// モデルデプロイメントの作成(オプション)
resource modelDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-10-01' = {
parent: aiFoundry // AI Foundry アカウントの子リソースとして作成
name: 'gpt-4o' // デプロイメント名
sku: {
capacity: 1 // デプロイ容量(TPM:Tokens Per Minute の単位)
name: 'GlobalStandard' // グローバル標準SKU(複数リージョンで負荷分散)
}
properties: {
model: {
name: 'gpt-4o' // デプロイするモデル名
format: 'OpenAI' // OpenAI互換フォーマット
}
}
}
privateEndpoint.bicep
param privateEndpointName string
param location string
param tags object
param subnetId string
param targetResourceId string
param groupIds array
param privateDnsZoneNames array
param vnetId string
resource privateDnsZones 'Microsoft.Network/privateDnsZones@2020-06-01' = [
for zoneName in privateDnsZoneNames: {
name: zoneName
location: 'global'
tags: tags
}
]
resource privateDnsZoneLinks 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = [
for (zoneName, index) in privateDnsZoneNames: {
parent: privateDnsZones[index]
name: 'vnet-link-${index}'
location: 'global'
tags: tags
properties: {
registrationEnabled: false
virtualNetwork: {
id: vnetId
}
}
}
]
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-05-01' = {
name: privateEndpointName
location: location
tags: tags
properties: {
subnet: {
id: subnetId
}
privateLinkServiceConnections: [
{
name: '${privateEndpointName}-connection'
properties: {
privateLinkServiceId: targetResourceId
groupIds: groupIds
}
}
]
}
}
resource privateDnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-05-01' = {
parent: privateEndpoint
name: 'default'
properties: {
privateDnsZoneConfigs: [
for (zoneName, index) in privateDnsZoneNames: {
name: replace(zoneName, '.', '-')
properties: {
privateDnsZoneId: privateDnsZones[index].id
}
}
]
}
dependsOn: [
privateDnsZoneLinks
]
}
output privateEndpointId string = privateEndpoint.id
output privateEndpointName string = privateEndpoint.name
output privateDnsZoneIds array = [for (zoneName, index) in privateDnsZoneNames: privateDnsZones[index].id]
デプロイ手順
1. Azure CLIへのログイン
az login
2. リソースグループのデプロイ
az deployment sub create `
--location japaneast `
--template-file .\deployments\rg.bicep
3. メインリソースのデプロイ
az deployment sub create `
--location japaneast `
--template-file .\deployments\main.bicep
デプロイ時に起きた課題と解決案
-
AI Foundryのリソース不足
- デプロイリージョン(location)を変更する(japaneast, eastusなど)
-
リージョンの不一致
- AI Foundryをデプロイするリージョンとネットワーク関連(VNet,Subnet,NSG,PE)のリージョンは統一する
-
AI Foundryの名前被り
- 一意の名前に変更してからデプロイする
-
プロビジョニングの問題
- PrivateEndpointを別モジュールに作成+depends onで対応
// AI Foundry PrivateEndpoint (sample)
module aifoundryPrivateEndpoint '../modules/privateEndpoint.bicep' = {
name: 'aifoundry-pe'
scope: resourceGroup(rgName)
params: {
privateEndpointName: 'sample-aifoundry-pe01'
location: location
tags: tags
subnetId: vnetModule.outputs.peSubnetId02
targetResourceId: aiFoundryModule.outputs.aiFoundryId
groupIds: ['account']
privateDnsZoneNames: [
'privatelink.cognitiveservices.azure.com'
'privatelink.openai.azure.com'
'privatelink.services.ai.azure.com'
]
vnetId: vnetModule.outputs.vnetId
}
dependsOn: [
vnetModule
aiFoundryModule
]
}
参考
Discussion