手动创建Azure Functions正常,Bicep部署遇存储403错误求助
问题排查:Azure Functions部署时存储访问403错误
问题场景
通过Bicep文件+Azure DevOps流水线构建环境,核心服务包含2个App Services、SQL Server、Storage、Azure Functions、Virtual Network。为禁用公网访问,所有资源均纳入VNet,Bicep可正常创建所有资源,但部署Azure Functions时出现以下错误:
Creation of storage file share failed with: 'The remote server returned an error: (403) Forbidden.'. Please check if the storage account is accessible.
手动创建Function App的成功步骤:
- 创建Function App
- 关联Bicep创建的S1标准层App Service Plan
- 使用Bicep创建的现有存储账户
- 禁用公网访问
- 启用网络注入
- 选择Bicep创建的现有虚拟网络
- 选择专用于Functions的Bicep创建子网作为出站访问
- 不使用专用端点
相关Bicep代码
vnet.bicep
param nameBase string param location string resource vnet 'Microsoft.Network/virtualNetworks@2022-09-01' = { name: '${nameBase}-vnet' location: location properties: { addressSpace: { addressPrefixes: [ '10.0.0.0/16' ] } subnets: [ { name: 'deafult' properties: { addressPrefix: '10.0.0.0/24' } } { name: 'api' properties: { addressPrefix: '10.0.1.0/24' delegations: [ { name: 'Microsoft.Web/serverFarms' properties: { serviceName: 'Microsoft.Web/serverFarms' } } ] } } { name: 'ui' properties: { addressPrefix: '10.0.2.0/24' delegations: [ { name: 'Microsoft.Web/serverFarms' properties: { serviceName: 'Microsoft.Web/serverFarms' } } ] } } { name: 'functions' properties: { addressPrefix: '10.0.3.0/24' delegations: [ { name: 'Microsoft.Web/serverFarms' properties: { serviceName: 'Microsoft.Web/serverFarms' } } ] serviceEndpoints: [ { service: 'Microsoft.Storage' } ] } } { name: 'storage' properties: { addressPrefix: '10.0.4.0/24' serviceEndpoints: [ { service: 'Microsoft.Storage' } ] } } ] } } output subnetId string = vnet.properties.subnets[0].id output subnetIdApi string = vnet.properties.subnets[1].id output subnetIdUi string = vnet.properties.subnets[2].id output subnetIdFunctions string = vnet.properties.subnets[3].id output subnetIdStorage string = vnet.properties.subnets[4].id
functions.bicep
param name string param client string param env string param location string param subnetIdFunctions string param subnetIdStorage string resource storage 'Microsoft.Storage/storageAccounts@2022-05-01' = { name: 'storage${client}${env}' location: location sku: { name: 'Standard_LRS' } kind: 'StorageV2' properties: { accessTier: 'Hot' isHnsEnabled: true isSftpEnabled: true minimumTlsVersion: 'TLS1_2' publicNetworkAccess: 'Enabled' allowBlobPublicAccess: false supportsHttpsTrafficOnly: true allowSharedKeyAccess: true encryption: { keySource: 'Microsoft.Storage' requireInfrastructureEncryption: false services: { blob: { enabled: true keyType: 'Account' } file: { enabled: true keyType: 'Account' } queue: { enabled: true keyType: 'Service' } table: { enabled: true keyType: 'Service' } } } keyPolicy: { keyExpirationPeriodInDays: 7 } networkAcls: { bypass: 'AzureServices' defaultAction: 'Deny' virtualNetworkRules: [ { id: subnetIdStorage action: 'Allow' } ] } } } output storageAccountName string = storage.name resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = { name: '${name}-functions-insights' location: location kind: 'web' properties: { Application_Type: 'web' Request_Source: 'rest' } } resource serviceplan 'Microsoft.Web/serverfarms@2021-03-01' = { name: '${name}-functions-serviceplan' location: location sku: { name: 'S1' tier: 'Standard' } } var functionAppName = '${name}-functions' resource functionApp 'Microsoft.Web/sites@2021-03-01' = { name: functionAppName location: location kind: 'functionapp' identity: { type: 'SystemAssigned' } properties: { virtualNetworkSubnetId: subnetIdFunctions serverFarmId: serviceplan.id siteConfig: { appSettings: [ { name: 'AzureWebJobsStorage' value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storage.listKeys().keys[0].value}' } { name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING' value: 'DefaultEndpointsProtocol=https;AccountName=${storage.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storage.listKeys().keys[0].value}' } { name: 'WEBSITE_CONTENTSHARE' value: toLower(functionAppName) } { name: 'FUNCTIONS_EXTENSION_VERSION' value: '~2' } { name: 'WEBSITE_NODE_DEFAULT_VERSION' value: '~10' } { name: 'APPINSIGHTS_INSTRUMENTATIONKEY' value: applicationInsights.properties.InstrumentationKey } { name: 'FUNCTIONS_WORKER_RUNTIME' value: 'dotnet' } ] ftpsState: 'FtpsOnly' minTlsVersion: '1.2' } httpsOnly: true } }
问题排查与修复
核心问题
存储账户的网络ACL仅允许storage子网访问,但Function App部署在functions子网,导致创建文件共享时被存储账户拒绝访问。同时缺少强制VNet路由的配置,部分流量可能未走VNet通道。
修复步骤
- 更新存储账户的网络ACL规则:在functions.bicep的storage资源中,将
functions子网添加到virtualNetworkRules列表:
networkAcls: { bypass: 'AzureServices' defaultAction: 'Deny' virtualNetworkRules: [ { id: subnetIdStorage action: 'Allow' } { id: subnetIdFunctions action: 'Allow' } ] }
- 添加VNet路由强制配置:在Function App的appSettings中加入以下配置,确保所有出站流量通过VNet路由:
{ name: 'WEBSITE_VNET_ROUTE_ALL' value: '1' }
- 确认子网配置有效性:当前vnet.bicep中
functions子网已正确配置Microsoft.Web/serverFarms委托和Microsoft.Storage服务端点,无需修改。
补充说明
手动创建成功是因为配置网络注入时,隐式完成了子网访问权限的关联;而Bicep部署需要显式声明存储ACL规则和VNet路由配置,否则会出现403访问拒绝错误。
内容的提问来源于stack exchange,提问作者kjubersson
相关产品推荐
相关产品推荐

