Quickstart: Scaling services deployed with the azd Python web templates using Bicep
The Python web azd
templates allow you to quickly create a new web application and deploy it to Azure. The azd
templates were designed to use low-cost Azure service options. Undoubtedly, you'll want to adjust the service levels (or skus) for each of the services defined in the template for your scenario.
In this Quickstart, you'll update the appropriate bicep template files to scale up existing services and add new services to your deployment. Then, you'll run the azd provision
command and view the change you made to the Azure deployment.
Prerequisites
An Azure subscription - Create one for free
You must have the following installed on your local computer:
- Azure Developer CLI
- Docker Desktop
- Visual Studio Code
- Dev Container Extension
- Visual Studio Code Bicep This extension helps you author Bicep syntax.
Deploy a template
To begin, you need a working azd
deployment. Once you have that in place, you're able to modify the Bicep files generated by the azd
template.
Follow steps 1 through 7 in the Quickstart article. In step 2, use the
azure-django-postgres-flexible-appservice
template. For your convenience, here's the entire sequence of commands to issue from the command line:mkdir azdtest cd azdtest azd init --template azure-django-postgres-flexible-appservice azd auth login azd up
Once
azd up
finishes, open the Azure portal, navigate to the Azure App Service that was deployed in your new Resource Group and take note of the App Service pricing plan (see the App Service plan's Overview page, Essentials section, "Pricing plan" value).In step 1 of the Quickstart article, you were instructed to create the azdtest folder. Open that folder in Visual Studio Code.
In the Explorer pane, navigate to the infra folder. Observe the subfolders and files in the infra folder.
The main.bicep file orchestrates the creation of all the services deployed when performing an
azd up
orazd provision
. It calls into other files, like db.bicep and web.bicep, which in turn call into files contained in the \core subfolder.The \core subfolder is a deeply nested folder structure containing bicep templates for many Azure services. Some of the files in the \core subfolder are referenced by the three top level bicep files (main.bicep, db.bicep and web.bicep) and some aren't used at all in this project.
Scale a service by modifying its Bicep properties
You can scale an existing resource in your deployment by changing its SKU. To demonstrate this, you'll change the App Service plan from the "Basic Service plan" (which is designed for apps with lower traffic requirements and don't need advanced auto scale and traffic management features) to the "Standard Service plan", which is designed for running production workloads.
Note
Not all SKU changes can be made after the fact. Some research may be necessary to better understand your scaling options.
Open the web.bicep file and locate the
appService
module definition. In particular, look for the property setting:sku: { name: 'B1' }
Change the value from
B1
toS1
as follows:sku: { name: 'S1' }
Important
As a result of this change, the price per hour will increase slightly. Details about the different service plans and their associated costs can be found on the App Service pricing page.
Assuming you already have the application deployed in Azure, use the following command to deploy changes to the infrastructure while not redeploying the application code itself.
azd provision
You shouldn't be prompted for a location and subscription. Those values are saved in the .azure<environment-name>.env file where
<environment-name>
is the environment name you provided duringazd init
.When
azd provision
is complete, confirm your web application still works. Also find the App Service Plan for your Resource Group and confirm that the Pricing Plan is set to the Standard Service Plan (S1).
Add a new service definition with Bicep
You can add a new resource to your deployment by making larger changes to the Bicep in the project. To demonstrate this, you'll add an instance of Azure Cache for Redis to your existing deployment in preparation for a fictitious new feature you plan to add at some future date.
Important
As a result of this change, you will be paying for an instance of Azure Cache for Redis until you delete the resource in the Azure portal or using azd down
. Details about the different service plans and their associated costs can be found on the Azure Cache for Redis pricing page.
Create a new file in the infra folder named redis.bicep. Copy and paste the following code into the new file:
param name string param location string = resourceGroup().location param tags object = {} param keyVaultName string param connStrKeyName string param passwordKeyName string param primaryKeyKeyName string @allowed([ 'Enabled' 'Disabled' ]) param publicNetworkAccess string = 'Enabled' @allowed([ 'C' 'P' ]) param skuFamily string = 'C' @allowed([ 0 1 2 3 4 5 6 ]) param skuCapacity int = 1 @allowed([ 'Basic' 'Standard' 'Premium' ]) param skuName string = 'Standard' param saveKeysToVault bool = true resource redis 'Microsoft.Cache/redis@2020-12-01' = { name: name location: location properties: { sku: { capacity: skuCapacity family: skuFamily name: skuName } publicNetworkAccess: publicNetworkAccess enableNonSslPort: true } tags: tags } resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' existing = { name: keyVaultName } resource redisKey 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = if (saveKeysToVault) { name: primaryKeyKeyName parent: keyVault properties: { value: redis.listKeys().primaryKey } } resource redisConnStr 'Microsoft.KeyVault/vaults/secrets@2018-02-14' = if (saveKeysToVault) { name: connStrKeyName parent: keyVault properties: { value: '${name}.redis.cache.windows.net,abortConnect=false,ssl=true,password=${redis.listKeys().primaryKey}' } } resource redisPassword 'Microsoft.KeyVault/vaults/secrets@2018-02-14' = if (saveKeysToVault) { name: passwordKeyName parent: keyVault properties: { value: redis.listKeys().primaryKey } } output REDIS_ID string = redis.id output REDIS_HOST string = redis.properties.hostName
Modify the main.bicep file to create an instance of the "redis" resource.
In the main.bicep file, add the following code below the ending curly braces associated with the Web frontend section and above the secrets section.
// Caching server module redis 'redis.bicep' = { name: 'redis' scope: resourceGroup params: { name: replace('${take(prefix, 19)}-rds', '--', '-') location: location tags: tags keyVaultName: keyVault.outputs.name connStrKeyName: 'RedisConnectionString' passwordKeyName: 'RedisPassword' primaryKeyKeyName: 'RedisPrimaryKey' publicNetworkAccess: 'Enabled' skuFamily: 'C' skuCapacity: 1 skuName: 'Standard' saveKeysToVault: true } }
Add output values to the bottom of the file:
output REDIS_ID string = redis.outputs.REDIS_ID output REDIS_HOST string = redis.outputs.REDIS_HOST
Confirm that the entire main.bicep file is identical to the following code:
targetScope = 'subscription' @minLength(1) @maxLength(64) @description('Name which is used to generate a short unique hash for each resource') param name string @minLength(1) @description('Primary location for all resources') param location string @secure() @description('DBServer administrator password') param dbserverPassword string @secure() @description('Secret Key') param secretKey string @description('Id of the user or app to assign application roles') param principalId string = '' var resourceToken = toLower(uniqueString(subscription().id, name, location)) var prefix = '${name}-${resourceToken}' var tags = { 'azd-env-name': name } resource resourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = { name: '${name}-rg' location: location tags: tags } // Store secrets in a keyvault module keyVault './core/security/keyvault.bicep' = { name: 'keyvault' scope: resourceGroup params: { name: '${take(replace(prefix, '-', ''), 17)}-vault' location: location tags: tags principalId: principalId } } module db 'db.bicep' = { name: 'db' scope: resourceGroup params: { name: 'dbserver' location: location tags: tags prefix: prefix dbserverDatabaseName: 'relecloud' dbserverPassword: dbserverPassword } } // Monitor application with Azure Monitor module monitoring 'core/monitor/monitoring.bicep' = { name: 'monitoring' scope: resourceGroup params: { location: location tags: tags applicationInsightsDashboardName: '${prefix}-appinsights-dashboard' applicationInsightsName: '${prefix}-appinsights' logAnalyticsName: '${take(prefix, 50)}-loganalytics' // Max 63 chars } } // Web frontend module web 'web.bicep' = { name: 'web' scope: resourceGroup params: { name: replace('${take(prefix, 19)}-appsvc', '--', '-') location: location tags: tags applicationInsightsName: monitoring.outputs.applicationInsightsName keyVaultName: keyVault.outputs.name appCommandLine: 'entrypoint.sh' pythonVersion: '3.12' dbserverDomainName: db.outputs.dbserverDomainName dbserverUser: db.outputs.dbserverUser dbserverDatabaseName: db.outputs.dbserverDatabaseName } } // Caching server module redis 'redis.bicep' = { name: 'redis' scope: resourceGroup params: { name: replace('${take(prefix, 19)}-rds', '--', '-') location: location tags: tags keyVaultName: keyVault.outputs.name connStrKeyName: 'RedisConnectionString' passwordKeyName: 'RedisPassword' primaryKeyKeyName: 'RedisPrimaryKey' publicNetworkAccess: 'Enabled' skuFamily: 'C' skuCapacity: 1 skuName: 'Standard' saveKeysToVault: true } } var secrets = [ { name: 'DBSERVERPASSWORD' value: dbserverPassword } { name: 'SECRETKEY' value: secretKey } ] @batchSize(1) module keyVaultSecrets './core/security/keyvault-secret.bicep' = [for secret in secrets: { name: 'keyvault-secret-${secret.name}' scope: resourceGroup params: { keyVaultName: keyVault.outputs.name name: secret.name secretValue: secret.value } }] output AZURE_LOCATION string = location output AZURE_KEY_VAULT_ENDPOINT string = keyVault.outputs.endpoint output AZURE_KEY_VAULT_NAME string = keyVault.outputs.name output APPLICATIONINSIGHTS_NAME string = monitoring.outputs.applicationInsightsName output BACKEND_URI string = web.outputs.uri output REDIS_ID string = redis.outputs.REDIS_ID output REDIS_HOST string = redis.outputs.REDIS_HOST
Make sure all your changes are saved, then use the following command to update your provisioned resources on Azure:
azd provision
Note
Depending on many factors, adding an instance of Azure Cache for Redis to the existing deployment could take a long time. In testing, we experienced execution times in excess of 20 minutes. As long as you do not see any errors, allow the process to continue until complete.
When
azd provision
completes, open the Azure portal, navigate to the Resource Group for your deployment, and in the list of services confirm you now have an instance of Azure Cache for Redis.
This concludes the Quickstart, however there are many Azure services that can help you build more scalable and production-ready applications. A great place to start would be to learn about Azure API Management, Azure Front Door, Azure CDN, and Azure Virtual Network, to name a few.
Clean up resources
Clean up the resources created by the template by running the azd down command.
azd down
The azd down
command deletes the Azure resources and the GitHub Actions workflow. When prompted, agree to deleting all resources associated with the resource group.
You can also delete the azdtest folder, or use it as the basis for your own application by modifying the files of the project.
Related Content
- Learn more about the Python web
azd
templates - Learn more about the
azd
commands. - Learn what each of the folders and files in the project do and what you can edit or delete?
- Update the Bicep templates to add or remove Azure services. Don't know Bicep? Try this Learning Path: Fundamentals of Bicep
- Use
azd
to set up a GitHub Actions CI/CD pipeline to redeploy on merge to main branch - Set up monitoring so that you can Monitor your app using the Azure Developer CLI