Azure App Service allows launching your apps quickly. You can run app as a code if you are using one of the supported languages, or run a docker container (or even a docker-compose).
Modern applications should be configurable by using some external data. Settings should not be stored in code repository. Storing configuration in repo and building specialized packages requires doing a release every time there is a need to change a single setting. Injecting configuration during startup allows using a single code base for any of the environments. A need to prepare a dedicated package for each environment is no longer required. That approach greatly improves the CI/CD process and lowers the risk of deploying applications with the wrong configuration.
There are multiple ways to configure settings, starting from setting environmental variables up to using dedicated service like App Configuration. Let's focus on App Settings today.
Managing App Settings
When it comes to configuring App Service, you can use App Settings. App settings in Azure App Service are key-value pairs used to store configuration data for your application. They allow you to manage the configuration of your app without changing code or redeploying the application. App settings can (but not always should) store various types of information such as database connection strings, API keys, or feature flags.
If you are using any Infrastructure as a Code (IaaC) tool (e.g. Terraform) app settings can be configured there.
resource "azurerm_app_service" "cloudoing_app_service" {
name = "cloudoing"
location = azurerm_resource_group.example.location
resource_group_name = azurerm_resource_group.example.name
app_service_plan_id = azurerm_app_service_plan.example.id
app_settings = {
"key" = "value"
"environment" = "demo"
"color" = "azure"
"address" = "cloudoing.com"
}
}
That is definitely a great way to standardize the setup when you are starting working with IaaC. It streamlines configuration management, configuration is versioned and is linked to the deployed environment. Sounds great, right?
The challenges
After a while, you realize that every time, there is a need to update an app setting, you have to deploy the infrastructure code. On a small scale, that approach does work well, but when the infra grows things became more complicated.
Imagine there is a common set of variables that have to be deployed to 1, 5, 100 App services. Your IaaC can be well written, so it might be just a change of a single variable. But it might not… :)
Another challenge is a team's and responsibilities. It is still very common that roles and responsibilities are separated between Infra & Apps teams. Especially in big organizations. The process of enabling a single team to make changes in every single place required to deliver a solution is challenging.
Decoupling Infrastructure settings from App Settings
As always, there are many ways to configure things. There is no single approach that covers all scenarios. Decoupling App Settings from infrastructure management is one of them. My experience shows that it is one of the easiest to start with. It doesn't require many changes, can be implemented quickly, and pays back fast by improving general app delivery agility.
Managing settings by pipelines
The common way to manage settings is to update them on release. That approach decouples application changes from infrastructure and grants more control over the scope of the changes.
Azure DevOps currently offers two ways to manage releases. Modern – using pipelines and YAML definitions. It generally offers a better experience as the pipelines can be templated and stored as a code. And the classic way – releases, allowing more user-friendly release management.
Classic release pipelines
Create a new release and add the “Deploy Azure App Service” task.
By clicking on the three dots, you can enter edit form. There, you can specify key-value pairs that will be set as app settings. The same can be done for Configuration settings.
YAML Pipelines
Similar can be achieved by using YAML pipelines variables.
Choose tasks AzureRMAppDeployment@4. If you are using Azure DevOps editor, use the tasks creation helper.
OK, environment variables (app settings) are decoupled from infra settings. But is there a better way to manage them now?
Yes – let's explore DevOps libraries.
Variables Library
Moving app settings configuration did not resolve the issue of storing the same configuration values in multiple places. Azure DevOps variables library might help you there.
Let's create our first variable group and add variables we previously included in pipelines.
Using variables stored in the Azure DevOps library
First, let's set it up for classic releases
Edit the previously created release and select the variables tab.
Link variable group
It's worth highlighting that a variable group can target a single or multiply stages or the whole release pipeline. If you are using a single classic release pipeline to deploy to multiple environments, stages targeting might be useful to inject the proper set of variables.
Once linked, it's time to tell Azure DevOps to pull values from the Azure DevOps library during run.
To do so, replace values with ADO reference to variable - $(variableName)
Once this is done, Azure DevOps will replace variables with the values during the release.
Using Azure DevOps library in YAML Pipeline
Firstly, a variable group needs to be linked to the YAML pipeline
The next step is to replace variables values with references to variable group elements. This is done the same way as for the classic release pipelines.
variables:
- group: demo
trigger:
- None
pool:
vmImage: ubuntu-latest
steps:
- task: AzureRmWebAppDeployment@4
inputs:
ConnectionType: 'AzureRM'
appType: 'webApp'
WebAppName: cloudoing.com
packageForLinux: '$(System.DefaultWorkingDirectory)/**/*.zip'
AppSettings: |
-key $(key)
-environment $(environment)
-color $(color)
-address $(address)
Summary
App configuration management can be challenging when done manually or when it is too depended on processes that are not strictly linked to Application release process.
The demonstrated example is one of the ways to manage environmental variables. It is worth to be highlighted because it is a recommended step to move away from storing variables with a code. This approach helps to simplify the CI/CD process. The built package is always the same, regardless of what the target environment is, and variables will be injected at a later stage.