With Azure Container Apps (ACA), Microsofts latest serverless runtime for containerized workloads in Azure, we can customize certain aspects of traffic routing on the ingress level. This article will look at traffic split and configure a relatively simple API to leverage a tailored traffic split configuration in Azure Container Apps. If you are new to Azure Container Apps, consider reading my article “Introduction to Azure Container Apps” first.

What is traffic split

First, let’s quickly revisit what traffic split is. Traffic split is a mechanism that routes configurable percentages of incoming requests (traffic) to various downstream services. See the following picture, which shows the relation between ACA ingress controller, traffic split configuration, and downstream services (or revisions in the case of ACA):

Traffic Split in Azure Container Apps (ACA)

Traffic Split in Azure Container Apps (ACA)

The sample application

To demonstrate traffic split, we will take a small API written in Go. The API responds to HTTP calls being issued to the root route (/). It returns a simple JSON structure so we can see which version of the API handled the request. The sample repository contains two dedicated versions of that API. See the following snippet demonstrating requests to both versions of the API while running locally:

# query version 1
curl --silent http://127.0.0.1:8080/ | jq
{
    "version": "v1.0.0"
}

# query version 2
curl --silent http://127.0.0.1:8081/ | jq
{
    "version": "v2.0.0"
}

Create the core infrastructure

First, let’s create the necessary core infrastructure in Azure. Although I use Azure CLI, you can also create the resource group and the Azure Container Registry (ACR) using the Azure Portal.

rgName="rg-blog-sample"
acrName="blogsample"
location="northeurope"
# create a resource group
az group create -n $rgName -l $location

# create an Azure Container Registry
az acr create -n $acrName -g $rgName -l $location --sku Basic --admin-enabled true

In the snippet above, an ACR instance was created with admin-enabled set to true. In a real-world scenario, you should definitely use ACR tokens instead of enabling the admin account. However, this requires an ACR Premium SKU.

Publishing Containers to Azure Container Registry

Now that we have an ACR instance in place, we can build and push our API container images.

# build container images
docker build ./v1 -t $acrName.azurecr.io/sample-api:1.0.0
docker build ./v2 -t $acrName.azurecr.io/sample-api:2.0.0

# login to ACR 
az acr login -n $acrName
# push container images
docker push $acrName.azurecr.io/sample-api:1.0.0
docker push $acrName.azurecr.io/sample-api:2.0.0

You should now have both container images stored securely in ACR. Next, we have to grab the password for the administrative account:

# Grab Admin Password from ACR
acrPassword=$(az acr credential show -n $acrName --query "passwords[0].value" -o tsv)

We will hand over this password later to Azure Container Apps (ACA), because the service must authenticate against ACR.

Deploy revisions to Azure Container Apps

To configure traffic split in ACA, we need at least two active revisions of our API. The first revision will use the container image sample-api:1.0.0. For the second revision, we will deploy the container image sample-api:2.0.0.

By default, ACA will only have one active revision per container app. However, we can customize this behavior by setting activeRevisionsMode to multiple on our container app. This is done in ./bicep/container-app.bicep:26:

// omitted 
properties: {
  kubeEnvironmentId: containerAppEnvironmentId
  configuration: {
    activeRevisionsMode: 'multiple'
// omitted

The repository contains all necessary Bicep manifests to deploy a container app to ACA (including downstream Log Analytics Workspace) and the required ACA environment. If you want to learn more about the deployment, read my post on “Deploying Azure Container Apps with Bicep”.

Deploy the initial revision

We can issue a deployment using Azure CLI on the scope of the previously created resource group:

# Deploy initial revision
az deployment group create -n golang-api -g $rgName \
  --template-file ./bicep/main.bicep \
  -p containerImage=$acrName.azurecr.io/sample-api:1.0.0 \
     containerPort=5000 \
     registry=$acrName.azurecr.io \
     registryUsername=$acrName \
     registryPassword=$acrPassword

Deploy the preview revision

Deploying the second revision is pretty much identical to deploying the initial revision; the only difference is that we provide a different container image tag as a parameter:

# Deploy initial revision
az deployment group create -n golang-api -g $rgName \
  --template-file ./bicep/main.bicep \
  -p containerImage=$acrName.azurecr.io/sample-api:2.0.0 \
     containerPort=5000 \
     registry=$acrName.azurecr.io \
     registryUsername=$acrName \
     registryPassword=$acrPassword

Verify revisions in Azure Container Apps

We can use Azure CLI to grab a list of all revisions for a particular container app in ACA, as shown here:

az containerapp revision list -n golang-api \
  -g $rgName -o table

Name                 CreatedTime                Active    Replicas
-------------------  -------------------------  --------  ----------
golang-api--s25y6d3  2021-11-04T17:16:02+00:00  True      1
golang-api--hjctnnw  2021-11-04T17:18:25+00:00  True      1

Note down the revision names from the table. We need those to configure traffic split.

Traffic split in Azure Container Apps

Modifying traffic split configuration and re-deploying a container app to ACA does not create a new revision. So we can easily update the Bicep manifest and provide a tailored traffic split configuration to route 80% of all requests to version 1.0.0 and 20% to version 2.0.0 of our API.

Please keep in mind that the sum of all splits must be 100. Otherwise, Azure will reject the deployment.

Traffic split is configured on the ingress controller used to route requests to our containers. That said, update ./bicep/container-app.bicep and provide the following traffic split configuration. (Note: Remember to replace the revision names with yours)

ingress: {
  external: useExternalIngress
  targetPort: containerPort
  traffic: [
    {
      revisionName: 'golang-api--s25y6d3' // sample-api:1.0.0
      weight: 80
    }
    {
      revisionName: 'golang-api--hjctnnw' // sample-api:2.0.0
      weight: 20
    }
  ]
}  

Apply traffic split in Azure Container Apps

With your custom traffic split configuration in place, we can issue another deployment using Azure CLI. Using the same values containerImage and containerPort as we used to deploy the second revision is important. Updating just the configuration of a container app (speaking of ingress, registries, secrets, and activeRevisionsMode) is considered as an application-scope change. Application-scope changes won’t result in ACA creating a new revision.

# Redeploy bicep manifest
az deployment group create -n golang-api -g $rgName \
  --template-file ./bicep/main.bicep \
  -p containerImage=$acrName.azurecr.io/sample-api:2.0.0 \
     containerPort=5000 \
     registry=$acrName \
     registryUsername=$acrName \
     registryPassword=$acrPassword

Test traffic split behavior

To test traffic split behavior, we have to get the FQDN associated with our container app:

# query FQDN
fqdn=$(az deployment group show -g $rgName --query properties.outputs.fqdn.value \
  -n golang-api -o tsv)

You can test traffic split in the browser by just hitting the FQDN a couple of times. However, you can also use curl. The repository contains a simple script that will issue 10 requests to the URL specified as arg. Let’s do that to verify traffic split works as expected:

# Let's issue 10 requests to see traffic split in action
./scripts/test.sh $fqdn

{"version":"v1.0.0"}
{"version":"v2.0.0"}
{"version":"v2.0.0"}
{"version":"v1.0.0"}
{"version":"v1.0.0"}
{"version":"v1.0.0"}
{"version":"v1.0.0"}
{"version":"v1.0.0"}
{"version":"v1.0.0"}
{"version":"v1.0.0"}

Excellent. As you can see from the output of test.sh, our traffic split configuration works as expected, and ACA routes requests different revisions according to our specification.

Conclusion

Being able to configure traffic split in Azure Container Apps is great. Multi-Revision mode is something you can active also after the container app has been deployed to ACA, so there is no need to re-create an already running instance of your container app. Once that’s the case, we can route requests randomly to different revisions of an application running in ACA.

The current implementation of traffic split in ACA is straightforward. We can weight traffic between multiple downstream revisions. However, what I need is the possibility to create HTTPRouteGroup instances according to the SMI Spec.