In this post, we’ll show you a process for automating the deployment of a Container Apps workload along with the Azure resources required for its environment.
We’ll be using the Azure CLI in a bash shell. If you’re on Windows, you can follow along with the Windows Subsystem for Linux.
You’ll need the following tools installed into your shell.
The source code is available here:
https://github.com/playtimesolutions/ContainerAppsDemo
Set your working account with your subscription id.
SUBSCRIPTION_ID=""
az login
az account set --subscription $SUBSCRIPTION_ID
Note: Your user account will need to have the Contributor role assignment for the subscription.
The following variables are required to parameterise the deployment.
ENVIRONMENT_NAME="container-apps-demo"
LOCATION="australiaeast"
RESOURCE_GROUP="container-apps-demo-rg"
KEYVAULT_NAME="${ENVIRONMENT_NAME}-kv"
DEPLOYMENT_NAME="${ENVIRONMENT_NAME}-deployment"
The container registry name is globally unique so we need to set up a pseudo-random name – the following bash generates a URL based on the subscription and resource group name.
UNIQUENESS=$(echo -n "${SUBSCRIPTION_ID}-${RESOURCE_GROUP}" | sha256sum | head -c 6)
CONTAINER_REGISTRY_NAME="${ENVIRONMENT_NAME//-/}${UNIQUENESS}"
CONTAINER_REGISTRY_URL="${CONTAINER_REGISTRY_NAME}.azurecr.io"
Echo the variables so you can see how each resource will be named in your deployment.
echo "Environment Name: $ENVIRONMENT_NAME"
echo "Location: $LOCATION"
echo "Resource Group: $RESOURCE_GROUP"
echo "Keyvault Name: $KEYVAULT_NAME"
echo "Deployment Name $DEPLOYMENT_NAME"
echo "Container Registry: $CONTAINER_REGISTRY_URL"
The following resource will contain all the resources we’ll create in this demo. You can remove everything created when you’re done by simply deleting the resource group.
az group create \
--location $LOCATION \
--name $RESOURCE_GROUP
This step will apply a set of bicep templates to create the environment. Use the Azure CLI to create a deployment.
az deployment group create \
--resource-group $RESOURCE_GROUP \
--name $DEPLOYMENT_NAME \
--template-file environment.bicep \
--parameters "containerRegistryName=$CONTAINER_REGISTRY_NAME" \
--parameters "environmentName=$ENVIRONMENT_NAME" \
--parameters "keyVaultName=$KEYVAULT_NAME"
This will take a few minutes. Once completed, your environment is ready to deploy your first container app. Before we proceed, let’s examine the bicep in detail.
environment.bicep
The following bicep template is the entry point to deploy each Azure resource in the environment.
You’ll notice each param maps to the parameters provided on the CLI in step 2.2.
// Input parameters from the CLI
param containerRegistryName string
param environmentName string
param keyVaultName string
param location string = resourceGroup().location
// Keyvault
module keyVault './modules/keyvault.bicep' = {
name: '${environmentName}-kv-deployment'
params: {
keyVaultName: keyVaultName
location: location
}
}
// Container Registry
module containerRegistry './modules/container-registry.bicep' = {
name: '${environmentName}-acr-deployment'
dependsOn: [keyVault]
params: {
containerRegistryName: containerRegistryName
keyVaultName: keyVaultName
location: location
}
}
// Container App Environment
module containerAppEnvironment './modules/container-app-environment.bicep' = {
name: '${environmentName}-env-deployment'
dependsOn: [containerRegistry]
params: {
environmentName: environmentName
keyVaultName: keyVaultName
location: location
}
}
For this simple environment, we only have a few resources.
Key Vault
We need somewhere secure to store the container registry access keys. In future, we can use the vault to store many other secrets, including connection strings, API keys, certificates, encryption keys, etc.
modules/keyvault.bicep
This template deploys the key vault.
Note:
param keyVaultName string
param location string = resourceGroup().location
resource keyVault 'Microsoft.KeyVault/vaults@2021-11-01-preview' = {
name: keyVaultName
location: location
properties: {
sku: {
family: 'A'
name: 'standard'
}
accessPolicies: []
enabledForTemplateDeployment: true
tenantId: subscription().tenantId
}
}
Container Registry
The container registry will store the container images used at runtime by each application in the environment. We can use any registry, but since we’re using Azure it makes sense to use an Azure Container Registry resource.
modules/container-registry.bicep
This template deploys the container registry.
Note:
param containerRegistryName string
param keyVaultName string
param location string = resourceGroup().location
resource containerRegistry 'Microsoft.ContainerRegistry/registries@2022-02-01-preview' = {
name: containerRegistryName
location: location
sku: {
name: 'Standard'
}
properties: {
adminUserEnabled: true
publicNetworkAccess: 'Enabled'
}
}
resource keyVault 'Microsoft.KeyVault/vaults@2021-11-01-preview' existing = {
name: keyVaultName
}
resource acrAccessKey 'Microsoft.KeyVault/vaults/secrets@2021-11-01-preview' = {
name: 'acr-accesskey'
parent: keyVault
properties: {
attributes: {
enabled: true
}
value: containerRegistry.listCredentials().passwords[0].value
}
}
Container App Environment
This is the resource in which our container apps will reside. It is analogous to a Kubernetes cluster and is fully managed by Microsoft, providing a limited subset of what Kubernetes offers.
modules/container-registry.bicep
This template deploys the Container App Environment and the associated Log Analytics Workspace.
param environmentName string
param keyVaultName string
param location string = resourceGroup().location
resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' = {
name: '${environmentName}-env'
location: location
properties:{
appLogsConfiguration: {
destination: 'log-analytics'
logAnalyticsConfiguration: {
customerId: reference(logAnalyticsWorkspace.id, '2021-06-01').customerId
sharedKey: listKeys(logAnalyticsWorkspace.id, '2021-06-01').primarySharedKey
}
}
}
}
resource logAnalyticsWorkspace'Microsoft.OperationalInsights/workspaces@2021-06-01' = {
name: '${environmentName}-law'
location: location
properties: {
retentionInDays: 30
sku: {
name: 'PerGB2018'
}
}
}
The environment is now ready to accept a container app. Let’s look at what we have so far in the Azure portal.
To keep things simple we will deploy the Microsoft ASP.NET sample container image.
We’ll use the current date and time to make our image name unique. Typically a build pipeline job would use the build number or some other identifier for traceability and uniqueness of container images in the registry.
TAG=$(date '+%Y%m%d%H%M%S')
IMAGE_NAME="sample-app:$TAG"
CONTAINER_IMAGE="$CONTAINER_REGISTRY_URL/$IMAGE_NAME"
docker pull mcr.microsoft.com/dotnet/samples:aspnetapp
docker tag mcr.microsoft.com/dotnet/samples:aspnetapp $CONTAINER_IMAGE
Note: We can use our user’s principal to login to the container registry – this allows us to interact with the registry without exposing the access keys.
az acr login --name $CONTAINER_REGISTRY_NAME
docker push $CONTAINER_IMAGE
az deployment group create \
--resource-group $RESOURCE_GROUP \
--name $DEPLOYMENT_NAME \
--template-file src/sample-app/deploy.bicep \
--parameters "containerRegistryName=$CONTAINER_REGISTRY_NAME" \
--parameters "environmentName=$ENVIRONMENT_NAME" \
--parameters "imageName=$IMAGE_NAME" \
--query properties.outputs.fullyQualifiedDomainName.value
Once the command completes, the sample app will be running in a container app.
Let’s dive into the bicep.
./src/sample-app/deploy.bicep
The following bicep template is the entry point to deploy the container app.
Note:
param containerRegistryName string
param environmentName string
param imageName string
param location string = resourceGroup().location
resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' existing = {
name: '${environmentName}-kv'
}
module sampleApp './../../modules/container-app.bicep' = {
name: '${environmentName}-sample-app-container-app-deployment'
params: {
acrAccessKey: keyVault.getSecret('acr-accesskey')
acrUsername: containerRegistryName
containerAppName: 'sample-app'
containerRegistry: '${containerRegistryName}.azurecr.io'
environmentName: '${environmentName}-env'
imageName: imageName
location: location
}
}
output fullyQualifiedDomainName string = sampleApp.outputs.fullyQualifiedDomainName
modules/container-app.bicep
This module has the definition for a container app workload.
We can re-use this module across many workloads. However, in practice, you might want to consider a single-purpose template for each workload to provide an additional level of customisation, as the specific details of each template could differ, e.g. scaling rules, sidecar containers, etc.
Note:
@secure()
param acrAccessKey string
param acrUsername string
param containerAppName string
param containerRegistry string
param cpu string = '0.5'
param environmentName string
param imageName string
param location string
param memory string = '1.0Gi'
param maxReplicas int = 10
param minReplicas int = 0
param targetPort int = 80
resource containerAppEnvironment 'Microsoft.App/managedEnvironments@2022-03-01' existing = {
name: environmentName
}
resource containerApp 'Microsoft.App/containerApps@2022-03-01' = {
name: containerAppName
location: location
properties: {
managedEnvironmentId: containerAppEnvironment.id
configuration: {
secrets: [
{
name: 'acr-accesskey'
value: acrAccessKey
}
]
registries: [
{
server: containerRegistry
username: acrUsername
passwordSecretRef: 'acr-accesskey'
}
]
ingress: {
external: true
targetPort: targetPort
transport: 'auto'
}
}
template: {
containers: [
{
image: '${containerRegistry}/${imageName}'
name: containerAppName
env: [ ]
resources: {
cpu: json(cpu)
memory: memory
}
}
]
scale: {
minReplicas: minReplicas
maxReplicas: maxReplicas
rules: [
{
name: 'http-rule'
custom: {
type: 'http'
metadata: {
concurrentRequests: '3'
}
}
}
]
}
}
}
}
output fullyQualifiedDomainName string = '${containerAppName}.${containerAppEnvironment.properties.defaultDomain}'
The application is now ready to handle requests.
Cleanup
To remove all resources, run the following commands.
az group delete --name $RESOURCE_GROUP
az keyvault purge --name $KEYVAULT_NAME
In this post, we demonstrated how to deploy the environment and resources required to run a simple container app workload.
We used the Azure CLI and bicep templates to reliably and repeatably create Azure resources. It is worth taking this automated approach instead of doing things manually in the portal – even for small applications, as the time investment will pay off manyfold over the project’s life.
This demo is not intended to be an example of best-practice security. In a follow-up post, we will explore how we can increase the security posture of the environment with the following approaches:
Stay tuned for the next post on configuration and secrets management.
A 23 page best-practice checklist, leveraging Playtime Solutions’ hands-on experience in designing, developing and delivering enterprise-grade application. This guide assists IT and DevOps professionals in creating an enterprise-grade Kubernetes environment in Microsoft AKS.