Skip to content
This repository was archived by the owner on Mar 31, 2026. It is now read-only.

Commit 87a54e7

Browse files
committed
Enable deployment to Linux Azure compute
Change NuGet.Insights config section to NuGetInsights ("." becomes "_" on Linux app service). Change ":" in config keys to "__" since that is Linux friendly. Use Join-Path and GetFullPath in PowerShell for cleaner, consistent paths. Use ZIP URL deploy on Linux, ZipDeploy ARM extension breaks for some reason. Add PowerShell dynamicparam for config name parameters.
1 parent e23f707 commit 87a54e7

21 files changed

Lines changed: 303 additions & 201 deletions

deploy/bicep/function-worker.bicep

Lines changed: 88 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ param location string
55

66
param planName string
77
param sku string
8+
param isLinux bool
89

910
param autoscaleName string
1011
param minInstances int
@@ -28,12 +29,20 @@ resource userManagedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2
2829
var sakConnectionString = 'AccountName=${storageAccountName};AccountKey=${storageAccount.listkeys().keys[0].value};DefaultEndpointsProtocol=https;EndpointSuffix=${environment().suffixes.storage}'
2930
var isConsumptionPlan = sku == 'Y1'
3031

32+
// See: https://learn.microsoft.com/en-us/azure/azure-functions/run-functions-from-deployment-package#using-website_run_from_package--url
33+
// Also, I've see weird deployment timeouts or "Central directory corrupt" errors when using ZipDeploy on Linux.
34+
var runFromZipUrl = isLinux
35+
3136
resource workerPlan 'Microsoft.Web/serverfarms@2020-09-01' = {
3237
name: planName
3338
location: location
39+
kind: 'functionapp'
3440
sku: {
3541
name: sku
3642
}
43+
properties: {
44+
reserved: isLinux
45+
}
3746
}
3847

3948
resource workerPlanAutoScale 'Microsoft.Insights/autoscalesettings@2015-04-01' = if (!isConsumptionPlan) {
@@ -96,17 +105,17 @@ resource workerPlanAutoScale 'Microsoft.Insights/autoscalesettings@2015-04-01' =
96105
}
97106

98107
var workerConfigWithStorage = concat(isConsumptionPlan ? [
99-
{
100-
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
101-
// SAS-based connection strings don't work for this property
102-
value: sakConnectionString
103-
}
104-
] : [], config)
108+
{
109+
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
110+
// SAS-based connection strings don't work for this property
111+
value: sakConnectionString
112+
}
113+
] : [], config)
105114

106-
resource worker 'Microsoft.Web/sites@2020-09-01' = {
115+
resource worker 'Microsoft.Web/sites@2022-09-01' = {
107116
name: name
108117
location: location
109-
kind: 'FunctionApp'
118+
kind: isLinux ? 'functionapp,linux' : 'functionapp'
110119
identity: {
111120
type: 'UserAssigned'
112121
userAssignedIdentities: {
@@ -117,61 +126,79 @@ resource worker 'Microsoft.Web/sites@2020-09-01' = {
117126
serverFarmId: workerPlan.id
118127
clientAffinityEnabled: false
119128
httpsOnly: true
120-
siteConfig: {
121-
minTlsVersion: '1.2'
122-
alwaysOn: !isConsumptionPlan
123-
use32BitWorkerProcess: false
124-
appSettings: concat([
125-
{
126-
name: 'AzureFunctionsJobHost__logging__LogLevel__Default'
127-
value: logLevel
128-
}
129-
{
130-
name: 'AzureFunctionsWebHost__hostId'
131-
value: hostId
132-
}
133-
{
134-
name: 'AzureWebJobsStorage__accountName'
135-
value: storageAccountName
136-
}
137-
{
138-
name: 'AzureWebJobsStorage__credential'
139-
value: 'managedidentity'
140-
}
141-
{
142-
name: 'AzureWebJobsStorage__clientId'
143-
value: userManagedIdentity.properties.clientId
144-
}
145-
{
146-
name: 'FUNCTIONS_EXTENSION_VERSION'
147-
value: '~4'
148-
}
149-
{
150-
name: 'FUNCTIONS_WORKER_RUNTIME'
151-
value: 'dotnet'
152-
}
153-
{
154-
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
155-
value: 'false'
156-
}
157-
{
158-
name: 'QueueTriggerConnection__queueServiceUri'
159-
value: storageAccount.properties.primaryEndpoints.queue
160-
}
161-
{
162-
name: 'QueueTriggerConnection__credential'
163-
value: 'managedidentity'
164-
}
165-
{
166-
name: 'QueueTriggerConnection__clientId'
167-
value: userManagedIdentity.properties.clientId
168-
}
169-
], workerConfigWithStorage)
170-
}
129+
siteConfig: union({
130+
minTlsVersion: '1.2'
131+
alwaysOn: !isConsumptionPlan
132+
use32BitWorkerProcess: false
133+
appSettings: concat([
134+
{
135+
name: 'AzureFunctionsJobHost__logging__LogLevel__Default'
136+
value: logLevel
137+
}
138+
{
139+
name: 'AzureFunctionsWebHost__hostId'
140+
value: hostId
141+
}
142+
{
143+
name: 'AzureWebJobsStorage__accountName'
144+
value: storageAccountName
145+
}
146+
{
147+
name: 'AzureWebJobsStorage__credential'
148+
value: 'managedidentity'
149+
}
150+
{
151+
name: 'AzureWebJobsStorage__clientId'
152+
value: userManagedIdentity.properties.clientId
153+
}
154+
{
155+
name: 'FUNCTIONS_EXTENSION_VERSION'
156+
value: '~4'
157+
}
158+
{
159+
name: 'FUNCTIONS_WORKER_RUNTIME'
160+
value: 'dotnet'
161+
}
162+
{
163+
name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
164+
value: 'false'
165+
}
166+
{
167+
name: 'QueueTriggerConnection__queueServiceUri'
168+
value: storageAccount.properties.primaryEndpoints.queue
169+
}
170+
{
171+
name: 'QueueTriggerConnection__credential'
172+
value: 'managedidentity'
173+
}
174+
{
175+
name: 'QueueTriggerConnection__clientId'
176+
value: userManagedIdentity.properties.clientId
177+
}
178+
{
179+
// See: https://github.com/projectkudu/kudu/wiki/Configurable-settings#ensure-update-site-and-update-siteconfig-to-take-effect-synchronously
180+
name: 'WEBSITE_ENABLE_SYNC_UPDATE_SITE'
181+
value: '1'
182+
}
183+
{
184+
name: 'WEBSITE_RUN_FROM_PACKAGE'
185+
value: runFromZipUrl ? split(zipUrl, '?')[0] : '1'
186+
}
187+
], runFromZipUrl ? [
188+
{
189+
name: 'WEBSITE_RUN_FROM_PACKAGE_BLOB_MI_RESOURCE_ID'
190+
value: userManagedIdentity.id
191+
}
192+
] : [], workerConfigWithStorage)
193+
}, isLinux ? {
194+
linuxFxVersion: 'DOTNET|6.0'
195+
} : {
196+
netFrameworkVersion: 'v6.0'
197+
})
171198
}
172199

173-
resource workerDeployments 'extensions@2020-09-01' = {
174-
name: any('ZipDeploy')
200+
resource workerDeploy 'extensions' = if (!runFromZipUrl) {
201+
name: any('ZipDeploy') // Workaround per: https://github.com/Azure/bicep/issues/784#issuecomment-817260643
175202
properties: {
176203
packageUri: zipUrl
177204
}

deploy/bicep/function-workers.bicep

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ param planCount int
88
@minValue(1)
99
param countPerPlan int
1010
param sku string
11+
param isLinux bool
1112

1213
param autoscaleNamePrefix string
1314
param minInstances int
@@ -35,6 +36,7 @@ module workers './function-worker.bicep' = [for i in range(0, workerCount): {
3536
location: planLocations[(i / countPerPlan) % length(planLocations)]
3637
planName: '${planNamePrefix}${i / countPerPlan}'
3738
sku: sku
39+
isLinux: isLinux
3840
autoscaleName: '${autoscaleNamePrefix}${i / countPerPlan}'
3941
minInstances: minInstances
4042
maxInstances: maxInstances

deploy/bicep/main.bicep

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ param alertPrefix string
1414

1515
param websitePlanId string = 'new'
1616
param websitePlanName string = 'default'
17+
param websiteIsLinux bool
1718
param websiteName string
1819
@secure()
1920
param websiteZipUrl string
@@ -27,6 +28,7 @@ param workerPlanCount int
2728
@minValue(1)
2829
param workerCountPerPlan int
2930
param workerSku string = 'Y1'
31+
param workerIsLinux bool
3032
param workerAutoscaleNamePrefix string
3133
param workerMinInstances int
3234
param workerMaxInstances int
@@ -87,30 +89,21 @@ var sharedConfig = [
8789
value: '~2'
8890
}
8991
{
90-
name: 'NuGet.Insights:DeploymentLabel'
92+
name: 'NuGetInsights__DeploymentLabel'
9193
value: deploymentLabel
9294
}
9395
{
94-
name: 'NuGet.Insights:LeaseContainerName'
96+
name: 'NuGetInsights__LeaseContainerName'
9597
value: leaseContainerName
9698
}
9799
{
98-
name: 'NuGet.Insights:StorageAccountName'
100+
name: 'NuGetInsights__StorageAccountName'
99101
value: storageAccountName
100102
}
101103
{
102-
name: 'NuGet.Insights:UserManagedIdentityClientId'
104+
name: 'NuGetInsights__UserManagedIdentityClientId'
103105
value: userManagedIdentity.properties.clientId
104106
}
105-
{
106-
// See: https://github.com/projectkudu/kudu/wiki/Configurable-settings#ensure-update-site-and-update-siteconfig-to-take-effect-synchronously
107-
name: 'WEBSITE_ENABLE_SYNC_UPDATE_SITE'
108-
value: '1'
109-
}
110-
{
111-
name: 'WEBSITE_RUN_FROM_PACKAGE'
112-
value: '1'
113-
}
114107
]
115108

116109
// Storage and Key Vault
@@ -154,6 +147,7 @@ module website './website.bicep' = {
154147
location: location
155148
planId: websitePlanId
156149
planName: websitePlanName
150+
isLinux: websiteIsLinux
157151
name: websiteName
158152
zipUrl: websiteZipUrl
159153
aadClientId: websiteAadClientId
@@ -209,6 +203,7 @@ module workers './function-workers.bicep' = {
209203
planCount: workerPlanCount
210204
countPerPlan: workerCountPerPlan
211205
sku: workerSku
206+
isLinux: workerIsLinux
212207
autoscaleNamePrefix: workerAutoscaleNamePrefix
213208
minInstances: workerMinInstances
214209
maxInstances: workerMaxInstances

deploy/bicep/spot-worker.bicep

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -242,45 +242,45 @@ resource autoscale 'Microsoft.Insights/autoscalesettings@2015-04-01' = {
242242
maximum: string(maxInstances)
243243
}
244244
rules: concat(eventCounterRules, [
245-
{
246-
metricTrigger: {
247-
metricName: 'Percentage CPU'
248-
metricNamespace: 'microsoft.compute/virtualmachinescalesets'
249-
metricResourceUri: vmss.id
250-
timeGrain: 'PT1M'
251-
statistic: 'Average'
252-
timeWindow: 'PT10M'
253-
timeAggregation: 'Average'
254-
operator: 'GreaterThan'
255-
threshold: 25
256-
}
257-
scaleAction: {
258-
direction: 'Increase'
259-
type: 'ChangeCount'
260-
cooldown: 'PT1M'
261-
value: '5'
262-
}
263-
}
264-
{
265-
metricTrigger: {
266-
metricName: 'Percentage CPU'
267-
metricNamespace: 'microsoft.compute/virtualmachinescalesets'
268-
metricResourceUri: vmss.id
269-
timeGrain: 'PT1M'
270-
statistic: 'Average'
271-
timeWindow: 'PT10M'
272-
timeAggregation: 'Average'
273-
operator: 'LessThan'
274-
threshold: 15
245+
{
246+
metricTrigger: {
247+
metricName: 'Percentage CPU'
248+
metricNamespace: 'microsoft.compute/virtualmachinescalesets'
249+
metricResourceUri: vmss.id
250+
timeGrain: 'PT1M'
251+
statistic: 'Average'
252+
timeWindow: 'PT10M'
253+
timeAggregation: 'Average'
254+
operator: 'GreaterThan'
255+
threshold: 25
256+
}
257+
scaleAction: {
258+
direction: 'Increase'
259+
type: 'ChangeCount'
260+
cooldown: 'PT1M'
261+
value: '5'
262+
}
275263
}
276-
scaleAction: {
277-
direction: 'Decrease'
278-
type: 'ChangeCount'
279-
cooldown: 'PT2M'
280-
value: '10'
264+
{
265+
metricTrigger: {
266+
metricName: 'Percentage CPU'
267+
metricNamespace: 'microsoft.compute/virtualmachinescalesets'
268+
metricResourceUri: vmss.id
269+
timeGrain: 'PT1M'
270+
statistic: 'Average'
271+
timeWindow: 'PT10M'
272+
timeAggregation: 'Average'
273+
operator: 'LessThan'
274+
threshold: 15
275+
}
276+
scaleAction: {
277+
direction: 'Decrease'
278+
type: 'ChangeCount'
279+
cooldown: 'PT2M'
280+
value: '10'
281+
}
281282
}
282-
}
283-
])
283+
])
284284
}
285285
]
286286
}

deploy/bicep/spot-workers.bicep

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ resource uploadBlobs 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
4444
arguments: '-ManagedIdentityClientId \'${userManagedIdentity.properties.clientId}\' -DeploymentLabel \'${deploymentLabel}\' -StorageAccountName \'${storageAccountName}\' -SpotWorkerDeploymentContainerName \'${spotWorkerDeploymentContainerName}\''
4545
primaryScriptUri: uploadScriptUrl
4646
supportingScriptUris: concat(deploymentUrls.files, [
47-
'https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1'
48-
])
47+
'https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1'
48+
])
4949
cleanupPreference: 'Always'
5050
retentionInterval: 'PT1H'
5151
}

0 commit comments

Comments
 (0)