Lately I’ve been playing around with both Azure App Service and Azure Kubernetes Service along with Terraform - hopefully I’ll throw together a blog post or two about my experiences with that later. To sum up some of my experiences working with this: There has to be an easier way.

Azure Container Apps announced

Now Microsoft has announced a new general-purpose serverless container platform: Azure Container Apps. Container Apps is what seems like a fully managed platform for microservice applications that runs on top of Kubernetes and open-source technologies like KEDA and Dapr.

The new service supports a broad range of usage scenarios, including

  • Microservices over HTTP or gRPC
  • HTTP APIs and websites
  • Event processing workers
  • Long-running background jobs

Azure Container Apps: Example scenarios

Having played around with AKS, which in itself is a managed platform, there is still quite a lot infrastructure-related things to take care of. Now, I haven’t worked long enough with AKS or other Kubernetes-offerings to be able now exactly what we loose when it comes to flexibility. But the threshold of running containers with the ability to autoscale, run different revisions of containers, enable HTTPS ingress without worrying about other Azure infrastructure or splitting traffic across different versions of your application for Blue/Green deployment or A/B-testing is a lot lower.

Create a simple Container App

Checking out Azure Container apps seemed like a good time to try out Bicep. My reasoning was that was that we actually just need a resource group, a log analytics workspace, container app environment and the container app itself - so not that many resources.

The resources need to be put into a resource group, so you need to create that:

az group create -n containerAppRg -l northeurope

At the time of writing the Azure Container App service is only available in canadacentral and northeurope.

Log analytics workspace: create la-workspace.bicep

param location string
param name string

resource la-workspace 'Microsoft.OperationalInsights/workspaces@2020-03-01-preview' = {
  name: name
  location: location
  properties: any({
    retentionInDays: 30
    features: {
      searchVersion: 1
    }
    sku: {
      name: 'PerGB2018'
    }
  })
}
output clientId string = la-workspace.properties.customerId
output clientSecret string = la-workspace.listKeys().primarySharedKey

Container App Environment: create containerenv.bicep

param name string
param location string
param logAnalyticsClientId string
param logAnalyticsClientSecret string

resource containerappenv 'Microsoft.Web/kubeEnvironments@2021-02-01' = {
  name: name
  location: location
  properties: {
    type: 'managed'
    internalLoadBalancerEnabled: false
    appLogsConfiguration: {
      destination: 'log-analytics'
      logAnalyticsConfiguration: {
        customerId: logAnalyticsClientId
        sharedKey: logAnalyticsClientSecret
      }
    }
  }
}
output id string = containerappenv.id

Container App Environment: create containerapp.bicep

param location string
param name string
param containerEnvId string
param containerImage string
param useExternalIngress bool = false
param containerPort int

resource containerApp 'Microsoft.Web/containerApps@2021-03-01' = {
  name: name
  kind: 'containerapp'
  location: location
  properties: {
    kubeEnvironmentId: containerEnvId
    configuration: {
        ingress: {
        external: useExternalIngress
        targetPort: containerPort
      }
    }
    template: {
      containers: [
        {
          image: containerImage
          name: name
          env: envVars
        }
      ]
      scale: {
        minReplicas: 0
      }
    }
  }
}

Wrap it up with main.bicep

param location string = resourceGroup().location
param envName string = 'containerApp-test'


module law 'la-workspace.bicep' = {
    name: 'log-analytics-workspace'
    params: {
      location: location
      name: 'law-${envName}'
    }
}

module containerAppEnv 'containerenv.bicep' = {
  name: 'containerAppEnv'
  params: {
    name: envName
    location: location
    lawClientId: law.outputs.clientId
    lawClientSecret: law.outputs.clientSecret
  }
}

module containerApp 'containerapp.bicep' = {
  name: 'containerapptest'
  params: {
    name: 'containerapptest'
    location: location
    containerEnvId: containerAppEnv.outputs.id
    containerImage: 'mcr.microsoft.com/azuredocs/aks-helloworld:v1'
    containerPort: 80
    envVars: [
        {
        name: 'TITLE'
        value: 'Welcome to your new Container App!'
        }
    ]
    useExternalIngress: true
  }
}

Now deploy the solution

az deployment group create -n container-app -g containerAppRg --template-file ./main.bicep

When this has gone through go check your resources in the portal. From the Container App that was created find the “Application Url” in the overview blade and test the url. If everything went well you should see at simple web page with the text “Welcome to your new Container App!”

Finishing thoughts

Having done a bit of work with Terraform lately I was surprised how easy using Bicep felt. I will definitively look into using Bicep more as I am not doing anything else than work on Azure anyways. Having done pretty much the same drill getting the container in the example above with Terraform and AKS it is a lot simpler with Container Apps. The ability to enable the HTTPS Ingress in particular is a real time saver - this cost me a lot of time when trying to set this up correctly in AKS with cert-manager, NGINX and Let’s Encrypt.

I’ll hopefully be back with more on my experiences trying out AKS and Terraform at a later date!