【Azure/Bicep】Logic Apps × PrivateEndpointをBicepからデプロイする方法

下記の構成でLogic Appsをデプロイする。
※下記のBicepでは、SubnetとNSGを余分に作成しています。用途に合わせて削ってください。

※StorageAccountは診断設定に用いられるものですが、今回は診断設定は除いてデプロイします。
↓ 本来の想定

Logic Appsについて


今回はStandardプランのWS1でデプロイします。PrivateEndpointを付けてデプロイするにはLogic AppsはStandardプラン(ワークフロー or App Service Environment)である必要があります。

また、Logic AppsとPrivateEndpointを組み合わせる場合、AppServicePlanのデプロイも必要です。従量課金プランではMicrosoftがホストするため、AppServicePlanを作成する必要はありません。Standardプランでは固定の支払いとなり、ユーザーがホストする必要があるため、AppServicePlanの構築が必要です。

https://zenn.dev/headwaters/articles/2aa981ac24fd0a

ディレクトリ構成

bicep-project/
├── deployments/
│   ├── main.bicep
│   └── rg.bicep
└── modules/
     ├── resourceGroup.bicep
     ├── vnet.bicep
     ├── NSG.bicep
     ├── logicapps.bicep
     ├── appserviceplan.bicep
     ├── blobstorage.bicep
     └── privateEndpoint.bicep

Bicep

rg.bicep/resourceGroup.bicep

rg.bicep

targetScope = 'subscription'
param rgName string = 'myProjectRG'
param location string = 'japanwest'

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 = 'japanwest'
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' 
      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


targetScope = 'subscription'
param rgName string = 'sampleRG'
param location string = 'japanwest'
param environment string = 'dev'
param projectCode string = 'sample'

param tags object = {
  Environment: 'dev'
}


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
  ]
}


param appPlanSku string = 'WS1'
param appPlanTier string = 'WorkflowStandard'

module appServicePlanModule '../modules/appserviceplan.bicep' = {
  name: 'AppsServicePlan'
  scope: resourceGroup(rgName)
  params: {
    appServicePlanName: '${development}-${case}appplan001'
    location: location
    tags: tags
    appPlanSku: appPlanSku
    appPlanTier: appPlanTier

    
    
    
  }
  dependsOn: [
    
  ]
}



param logicAppStorageName string = '${toLower(development)}${toLower(case)}logicst01'

module logicAppStorage '../modules/blobstorage.bicep' = {
  name: 'logicAppStorage'
  scope: resourceGroup(rgName)
  params: {
    storageAccountName: logicAppStorageName
    location: location
    tags: tags
    sku: 'Standard_LRS'
    kind: 'StorageV2'
    accessTier: 'Hot'
    publicNetworkAccess: 'Enabled'
    supportsHttpsTrafficOnly: true
    minimumTlsVersion: 'TLS1_2'
  }
}


param logicAppName string = 'son-logicapp001'

module logicAppsModule '../modules/logicapps.bicep' = {
  name: 'logicApps'
  scope: resourceGroup(rgName)
  params: {
    location: location
    tags: tags
    logicAppName: logicAppName
    appServicePlanId: appServicePlanModule.outputs.appServicePlanId
    storageAccountName: logicAppStorage.outputs.storageAccountName
    storageAccountId: logicAppStorage.outputs.storageAccountId
    vnetIntegrationSubnetId: vnetModule.outputs.vnetIntegrationSubnetId
  }
  dependsOn: [
    appServicePlanModule
    vnetModule
  ]
}

module logicAppsPrivateEndpoint '../modules/privateEndpoint.bicep' = {
  name: 'logicapps-pe'
  scope: resourceGroup(rgName)
  params: {
    privateEndpointName: '${development}-${case}-logicapps-pe01'
    location: location
    tags: tags
    subnetId: vnetModule.outputs.pesubnetId01
    targetResourceId: logicAppsModule.outputs.logicAppsId
    groupIds: ['sites']
    privateDnsZoneNames: [
      'privatelink.azurewebsites.net'
    ]
    vnetId: vnetModule.outputs.vnetId
  }
  dependsOn: [
    vnetModule
    logicAppsModule
  ]
}

logicapps.bicep

param location string
param tags object
param logicAppName string
param appServicePlanId string
param storageAccountName string
param storageAccountId string
param vnetIntegrationSubnetId string


resource logicAppStandard 'Microsoft.Web/sites@2023-01-01' = {
  name: logicAppName
  location: location
  tags: tags
  kind: 'functionapp,workflowapp'
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    serverFarmId: appServicePlanId
    httpsOnly: true
    virtualNetworkSubnetId: vnetIntegrationSubnetId
    siteConfig: {
      netFrameworkVersion: 'v6.0'
      use32BitWorkerProcess: false
      alwaysOn: true
      vnetRouteAllEnabled: true
      appSettings: [
        {
          name: 'AzureWebJobsStorage'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${listKeys(storageAccountId, '2023-01-01').keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
        }
        {
          name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
          value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};AccountKey=${listKeys(storageAccountId, '2023-01-01').keys[0].value};EndpointSuffix=${environment().suffixes.storage}'
        }
        {
          name: 'WEBSITE_CONTENTSHARE'
          value: toLower(logicAppName)
        }
        {
          name: 'FUNCTIONS_EXTENSION_VERSION'
          value: '~4'
        }
        {
          name: 'FUNCTIONS_WORKER_RUNTIME'
          value: 'node'
        }
        {
          name: 'APP_KIND'
          value: 'workflowApp'
        }
      ]
    }
  }
}

output logicAppsId string = logicAppStandard.id
output logicAppsName string = logicAppStandard.name
output principalId string = logicAppStandard.identity.principalId

appserviceplan.bicep

param location string
param tags object
param appServicePlanName string
param appPlanSku string
param appPlanTier string



resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
  name: appServicePlanName
  location: location
  tags: tags
  sku: {
    name: appPlanSku
    tier: appPlanTier
  }
  kind: 'linux'
  properties: {
    reserved: true
    perSiteScaling: false
    maximumElasticWorkerCount: 1
  }
}






















output appServicePlanId string = appServicePlan.id
output appServicePlanName string = appServicePlan.name

blobstorage.bicep

param storageAccountName string
param location string
param tags object
param sku string
param kind string
param accessTier string
param publicNetworkAccess string
param supportsHttpsTrafficOnly bool
param minimumTlsVersion string


resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  tags: tags
  sku: {
    name: sku
  }
  kind: kind
  identity: {
    type: 'SystemAssigned'
  }
  properties: {
    accessTier: accessTier
    publicNetworkAccess: publicNetworkAccess
    supportsHttpsTrafficOnly: supportsHttpsTrafficOnly
    minimumTlsVersion: minimumTlsVersion
    allowBlobPublicAccess: false
    allowSharedKeyAccess: true
    networkAcls: {
      defaultAction: publicNetworkAccess == 'Enabled' ? 'Allow' : 'Deny'
      bypass: 'AzureServices'
      ipRules: []
      virtualNetworkRules: []
    }
    encryption: {
      services: {
        blob: {
          enabled: true
        }
        file: {
          enabled: true
        }
      }
      keySource: 'Microsoft.Storage'
    }
  }
}


resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-01-01' = {
  parent: storageAccount
  name: 'default'
  properties: {
    cors: {
      corsRules: []
    }
    deleteRetentionPolicy: {
      enabled: true
      days: 7
    }
    containerDeleteRetentionPolicy: {
      enabled: true
      days: 7
    }
    changeFeed: {
      enabled: false
    }
    restorePolicy: {
      enabled: false
    }
    isVersioningEnabled: false
  }
}


resource defaultContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-01-01' = {
  parent: blobService
  name: 'default-container'
  properties: {
    publicAccess: 'None'
  }
}


output storageAccountId string = storageAccount.id
output storageAccountName string = storageAccount.name
output blobEndpoint string = storageAccount.properties.primaryEndpoints.blob
output storageAccountPrincipalId string = storageAccount.identity.principalId

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へのログイン

2. リソースグループのデプロイ

az deployment sub create `
  --location japanwest `
  --template-file .\deployments\rg.bicep

3. メインリソースのデプロイ

az deployment sub create `
  --location japanwest `
  --template-file .\deployments\main.bicep

デプロイ時に起きた課題と解決案

  • AppServicePlanの価格(SKU)に関して
    • P0V3でデプロイしようとしたところ失敗
    • 正しくは、WS1, WS2, WS3のグレードで設定可能

main.bicep


param appPlanSku string = 'WS1'
param appPlanTier string = 'WorkflowStandard'

module appServicePlanModule '../modules/appserviceplan.bicep' = {
  name: 'AppsServicePlan'
  scope: resourceGroup(rgName)
  params: {
    appServicePlanName: '${development}-${case}appplan001'
    location: location
    tags: tags
    appPlanSku: appPlanSku
    appPlanTier: appPlanTier

    
    
    
  }
  dependsOn: [
    
  ]
}

Source link

関連記事

コメント

この記事へのコメントはありません。