Why use Staging Slots with Azure App Services?
Making use of staging slots within Azure App Services is a recommended best-practice by Microsoft for all Production applications. Utilising deployment slots enables:
- Zero downtime updates to your application’s code
- Configuration changes applied and tested prior to swapping the slots
- Better perceived performance by application users, who don’t have to witness application warmup
To make use of deployment slots, your App Service has to be on the appropriate level of App Service Plan, which at the time of writing is the “Standard” plan level and above. See Microsoft’s App Service Limits documentation for details.
Creating and configuring an Azure App Service Staging Slot:
Creating the deployment slot to deploy to is a one-line command with the Azure CLI:
1az webapp deployment slot create -n [YOUR_APP_SERVICE_NAME] -g [YOUR_RESOURCE_GROUP] -s [YOUR_SLOT_NAME]
You may want to configure the health check endpoint for the App Service, so the load balancer can detect whether an instance is healthy. This will be transferred across to the Production slot when the slots are swapped:
1az webapp config set -g [YOUR_RESOURCE_GROUP] -n [YOUR_APP_SERVICE_NAME] -s [YOUR_SLOT_NAME] --generic-configurations "{\`"healthCheckPath\`": \`"[YOUR_HEALTH_CHECK_RELATIVE_URL]\`"}"
You may also want to disable ARR Affinity (Sticky Sessions), so that users aren’t stuck on an unhealthy instance. This is dependent on your application either being stateless, or maintaining state in a centralised store, such as SQL Server or Redis.
The command for disabling Arr Affinity (Sticky Sessions) is:
1az webapp update -n [YOUR_APP_SERVICE_NAME] -g [YOUR_RESOURCE_GROUP] -s [YOUR_SLOT_NAME] --client-affinity-enabled false
Deploying to an App Service Slot with Azure DevOps
Create an Azure Classic Service Connection
Firstly you will need to create an Azure Service Connection within your Azure DevOps instance that can be referenced within the pipelines. This will make use of a service principle account, which will need to have the required permissions within your Azure estate to provision and administrate resources.
Firstly Navigate to the Service Connections page within your Azure DevOps project (you will need to be a Project Administrator to access these settings). Select New Service Connection.
Next Select “Azure Classic” as the type of Service Connection that you want to create.
Finally, input the details of your Azure Subscription and the Service Principle Credentials that are required to access Azure and create/manage resources:
Ensure on the final step that you make sure to enable “Grant access permissions to all pipelines”, which will enable the Service Connection to be used in pipelines created within your project.
Create and configure a deployment slot in an Azure DevOps pipeline
Making use of the Azure CLI Task available within Azure DevOps enables you to call the necessary Azure CLI commands to create and configure a staging deployment slot for your app service. The YAML for the task is shown below:
1- task: AzureCLI@22 displayName: 'Create and Configure Slot'3 inputs:4 azureSubscription: '[YOUR_AZURE_SERVICE_CONNECTION_NAME - SEE ABOVE]'5 scriptType: ps6 scriptLocation: inlineScript7 inlineScript: |8 az webapp deployment slot create -n [YOUR_APP_SERVICE_NAME] -g [YOUR_RESOURCE_GROUP] -s [YOUR_SLOT_NAME]9 az webapp update -n [YOUR_APP_SERVICE_NAME] -g [YOUR_RESOURCE_GROUP] -s [YOUR_SLOT_NAME] --client-affinity-enabled true10 az webapp config set -g [YOUR_RESOURCE_GROUP] -n [YOUR_APP_SERVICE_NAME] -s [YOUR_SLOT_NAME] --use-32bit-worker-process false11 az webapp config set -g [YOUR_RESOURCE_GROUP] -n [YOUR_APP_SERVICE_NAME] -s [YOUR_SLOT_NAME] --generic-configurations "{\`"healthCheckPath\`": \`"/api/health/\`"}"
Having created and configured the slot, you can now have a second task in your pipeline that will deploy an artefact to that slot:
1- task: AzureRmWebAppDeployment@42 displayName: 'Deploy Candidate to slot'3 inputs:4 azureSubscription: '[YOUR_AZURE_SERVICE_CONNECTION_NAME - SEE ABOVE]'5 WebAppName: '[YOUR_APP_SERVICE_NAME]'6 deployToSlotOrASE: true7 ResourceGroupName: '[YOUR_RESOURCE_GROUP]'8 SlotName: [YOUR_SLOT_NAME]9 packageForLinux: '$(System.DefaultWorkingDirectory)/**/*[YOUR_BUILD_ARTEFACT_NAME].zip'10 enableCustomDeployment: true11 RemoveAdditionalFilesFlag: true12 ExcludeFilesFromAppDataFlag: false13 enableXmlTransform: true14 enableXmlVariableSubstitution: false
Finally, having either run automated tests, or manually verified that the application is running correctly, you can now swap the slots, with the new slot replacing the production slot for your App Service. This results in a seamless transition to the latest version of your application:
1- task: AzureAppServiceManage@02 displayName: 'Swap Deployment Slots:'3 inputs:4 azureSubscription: '[YOUR_AZURE_SERVICE_CONNECTION_NAME - SEE ABOVE]'5 WebAppName: '[YOUR_APP_SERVICE_NAME]'6 ResourceGroupName: '[YOUR_RESOURCE_GROUP]'7 SourceSlot: [YOUR_SLOT_NAME]8 PreserveVnet: true
Further considerations
Having worked with deployment slots, and the App Service Manage task referenced above in production scenarios, I have witnessed occasional disparities between when the task reports that the slots have swapped and when Azure actually completes the process.
Having raised a support case with Microsoft on the matter, the advice received was to query the App Service logs to ascertain if the slow swap had completed. Wanting to ensure that this could be automated, the following powershell script was produced, which would poll the logs until receiving the correct “Succeeded” state and wrapped in a DevOps powershell task:
1- task: AzurePowerShell@52 displayName: 'Query logs to ensure swap complete'3 inputs:4 azureSubscription: '[YOUR_AZURE_SERVICE_CONNECTION_NAME - SEE ABOVE]'5 ScriptType: InlineScript6 Inline: |7 $azureSubscriptionId = "[YOUR_SUBSCRIPTION_ID]"8 $azureResourceGroup = "[YOUR_RESOURCE_GROUP]"9 $azureAppServiceName = "[YOUR_APP_SERVICE_NAME]"1011 Write-Output "Query Logs to confirm swap completion"1213 $date = Get-Date14 $date = $date.AddMinutes(-30)1516 [bool] $swapComplete = 017 $logCheckCount = 01819 while(!$swapComplete){20 $logCheckCount++2122 $logs = Get-AzLog -ResourceId "/subscriptions/$azureSubscriptionId/resourceGroups/$azureResourceGroup/providers/Microsoft.Web/sites/$azureAppServiceName/slots/staging" -StartTime $date2324 $logs | where { $_.operationName.value -match 'Microsoft.Web/sites/slots/slotsswap/action' -or $_.operationName.value -match 'Microsoft.Web/sites/slots/SlotSwap/action' } | Sort-Object eventTimestamp | ForEach-Object {25 Write-Host $_.eventTimestamp $_.status.value $_.description26 }2728 Write-Output $logs[0]29 $LastLogObject = $logs[0]30 $LastLogStatus = $LastLogObject.Status.Value3132 Write-Output "Last log entry status: $LastLogStatus"3334 if($LastLogObject.Status.Value -eq "Succeeded"){35 $swapComplete = 136 break37 }3839 Write-Output "Checked logs $logCheckCount times"4041 if($logCheckCount -eq 20){42 throw "Retry count exceeded"43 }4445 Write-Output "Waiting 30 seconds before checking again"4647 Start-Sleep -s 3048 }49 azurePowerShellVersion: LatestVersion
This felt an unfortunate necessity at the time. Hopefully the App Service Manage task within DevOps will be enhanced in the future to correctly identify the point in time at which the swap has completed, before allowing the pipeline to continue.
Summary
I would consider making use of deployment slots within Azure App Services an essential, within a production application. Without utilising deployment slots, users of the application will witness application restarts and warmups, causing a loss of credibility of your application.
Further to this, adding the ability to conduct quick automated smoke tests against your application running in the production environment, along with any configuration changes, is likely to reduce everyone’s stress levels. So why not get started with deployment slots today?