In the Bicep infrastructure article I demonstrated how to write Bicep files and deploy them on Azure each time our pipeline runs. This enabled us to have our resources defined in code and in a later article we easily deployed a new environment using the same Bicep files.
Our QA environment was a mirror image of our production environment and that's okay for now, but this isn't always the case in real-life applications.
As you can see, we have a staging slot in our QA resource group, but do we really need it?
To Slot or Not to Slot on QA:
The immediate consequence of omitting the staging slot for our QA environment is a minor downtime during deployments. However, considering it's the QA environment, a brief pause is acceptable. Why? Let's break it down:
Cost Efficiency: By doing away with the staging slot, we can downgrade from the S1 tier to the B1 tier for our app service plan specifically for the QA environment. This transition alone saves approximately 50 EUR/month.
Resource Optimization: By aligning resources more appropriately to their intended purpose (QA vs. Production), we ensure that we're not over-provisioning for a testing environment.
Now that we decided to remove it, you may be tempted to just go into Azure and delete the deployment slot, then manually downgrade the tier of the App Service Plan. However, if you do that, then on the next pipeline run the slot will be created again and our App Service Plan will get back up to the S1 tier.
At first glance, it might seem tedious to modify Bicep files for every infrastructure change. However, consider this: using Bicep ensures consistent, automated, and error-free updates. And, realistically, how frequently do you actually alter your infrastructure?
To optimize our QA environment, we'll adapt our Bicep configurations to exclude the staging slot and shift the app service plan from S1 to B1. This approach is practical, cost-efficient, and tailored to our specific deployment needs. Let's do this in 3 steps:
Introducing a parameter within our configuration files.
Incorporating this parameter into our Bicep definitions
Facilitating deployments contingent on the established parameter value.
Creating a parameter in configuration files
We introduce a hasStagingSlot
parameter in our configuration files to determine whether a staging slot should be deployed.
For the Production environment, hasStagingSlot
is set to true
:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"value": "westeurope"
},
"appName": {
"value": "bogdan-todo"
},
"appSku": {
"value": "S1"
},
"hasStagingSlot": {
"value": true // on prod environment we want the slot to be deployed
}
}
}
For the QA environment, hasStagingSlot
is set to false
:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"value": "westeurope"
},
"appName": {
"value": "bogdan-todo-qa"
},
"appSku": {
"value": "B1"
},
"hasStagingSlot": {
"value": false // on qa environment we don't want the slot to be deployed
}
}
}
We also change the appSku
value to B1.
Integrating with Bicep
The main.bicep
file receives the hasStagingSlot
variable and passes it onto app.bicep
as a parameter.
...
param hasStagingSlot bool = false // we can add a default value if we want
module app 'app.bicep' = {
name: 'bogdan-todo-app'
params:{
location: location
appName: appName
appSku: appSku
hasStagingSlot: hasStagingSlot // we send this parameter to our app.bicep module
}
}
Conditional deployment based on the parameter value
In app.bicep
, the deployment of the staging slot depends on the state of hasStagingSlot
app.bicep
...
param hasStagingSlot bool // this is our variable which decides if we're creating the slot or not
...
// using the if statement we will deploy the slot
// only if hasStagingSlot is true
resource webAppSlot 'Microsoft.Web/sites/slots@2022-03-01' = if (hasStagingSlot) {
name: 'staging'
location: location
kind: 'app,linux'
parent: webApp
properties:{
enabled: true
httpsOnly: true
siteConfig: {
httpLoggingEnabled: true
linuxFxVersion: 'DOTNETCORE|7.0'
}
}
}
This segment essentially states that the staging slot will only be deployed if hasStagingSlot
is true
.
There's one more thing we have to do, we need to remove the slot deployment from our qa.yaml
stage:
...
- job: Deploy
dependsOn: UpdateAzureResources
steps:
- task: DownloadPipelineArtifact@2
displayName: 'Download pipeline artifact'
inputs:
buildType: 'current'
artifactName: 'drop'
targetPath: '$(Pipeline.Workspace)/drop'
- task: AzureWebApp@1
displayName: 'Deploy to QA'
inputs:
azureSubscription: 'AzureConnection'
appType: 'webAppLinux'
appName: 'bogdan-todo-qa-app'
package: '$(Pipeline.Workspace)/drop'
resourceGroupName: $(resourceGroupName)
runtimeStack: 'DOTNETCORE|7.0'
startUpCommand: 'dotnet ToDoApp.Server.dll'
Now let's deploy the changes and see what happens!
Our pipeline was deployed successfully, but when we look at our QA environment we still see the staging slot:
When a resource is missing from a Bicep file but exists in Azure, Bicep doesn't automatically delete it. Here's why:
Safety: Deleting resources can be destructive. Imagine accidentally removing a database with valuable data. By not auto-deleting, Bicep ensures safety.
State Management: Bicep and ARM templates are declarative. This means you declare your desired state, and Azure ensures that the actual state matches. However, they don't maintain a history of previous states. So, when a resource is omitted from the Bicep file, Bicep doesn't necessarily recognize it as a directive to delete; it just sees it as "not part of the current directive."
Explicitness: The philosophy behind tools like Bicep is that operations, especially destructive ones, should be explicit. Removing a resource from a Bicep file is implicit. If you want to delete a resource, it's generally better to do it explicitly, ensuring you're aware of the ramifications.
This basically means we have to go do it ourselves manually. Now that we updated our Bicep files, the staging slot will only be used in the production environment.
Takeaways
When to Use Conditional Deployment:
You need to maintain different configurations for dev/test vs. production.
There are cost constraints and you want to deploy certain resources only when absolutely necessary.
How to do conditional deployments:
Use configuration files with different values per environment
Use the if statement for conditional deployments based on your conditions
Conclusion:
Conditional deployments provide precise control over our infrastructure configuration. They optimize resource usage and can significantly minimize deployment errors. However, like all tools, they have their appropriate applications. Assess your needs, understand the circumstances, and employ them wisely.
The full source code can be downloaded from here: https://dev.azure.com/bujdea/_git/AzureDevopsYamlPipeline
What's next
In the next article, I will demonstrate how to add application settings using Bicep. Feel free to subscribe to the newsletter below or follow me on Twitter if you'd like to be notified as soon as possible!