.NET · Architecture · Azure · Azure Container Registry · AzureContainerApps · Bicep · CI-CD · GitHub-Actions · Infrastructure As Code · managed-identity

Demystifying Azure Container Apps & Dapr – Part 4

Read Part 1 here.

Read Part 2 here.

Read Part 3 here.

Last time, I wrote the backend service (JobListener) which gets triggered by the Dapr pubsub whenever an image is uploaded into the designated blob container in the storage account. It then uses Computer Vision APIs to do the image recognition. Today we will write a front-end application which will allow users to upload images from the user interface and store it into the blob container. Also, the backend will use SignalR to notify the user at his/her browser about the image recognition outcome.

Creating the Azure SignalR

Here’s the bicep component that provisions the SignalR resource.

@description('Name of the Azure SignalR service')
param signalRName string
param location string = resourceGroup().location
param keyVaultName string 
param signalRKeyName string 

resource keyVault 'Microsoft.KeyVault/vaults@2021-11-01-preview' existing = {
  name: keyVaultName
}
resource signalR 'Microsoft.SignalRService/SignalR@2018-03-01-preview' = {
  name: signalRName
  location: location  
  sku: {
    name: 'Free_F1'
    tier: 'Free'
    size: 'F1'
    capacity: 1
  }
  properties: {

  }
}
module signalRSecret 'kvSecret.bicep' = {
  name: '${signalRName}-deploy-secrets'
  params: {
    keyVaultName: keyVault.name
    secretName: signalRKeyName
    secretValue: signalR.listKeys().primaryConnectionString
  }
}

With SignalR provisioned, I can send the image recognition result near-real-time to the end user. You can see the code for the front-end in GitHub.

Continuous Deployment

Now that the application is functional, I wanted to write the GitHub actions to continuously deploy the code to the cloud. I decided to create one designated action that will only provision the infrastructure on Azure. And a separate action that will build the container images and deploy the code.

Let’s start with the first action:

Workload Identity

GitHub action need to talk to Azure and it requires authentication. I will be using Workload federated identity -which is a very neat way to deploy code from GitHub without any secrets spread outside of Azure.

First, we need to configure a trust relationship between your app in Azure AD and a GitHub repo in the Azure portal or using Microsoft Graph. Then configure a GitHub Actions workflow to get an access token from.

Azure AD app registration page

With this trust relation configured, we only need to configure Tenant ID, client ID (the app ID) and Subscription ID. But no client secrets or certificates are needed. That is very neat.

Secrets in GitHub settings

With this, we are ready to create the action workflows.

Infrastructure workflow

name: Infrastructure
on:
  push:
     branches: [ main ]  
permissions:
      id-token: write
      contents: read
    
env:
  AZURE_RESOURCE_GROUP: xeniel
  AZURE_LOCATION: westeurope
  AZURE_MANAGED_BY: "moim.hossain@microsoft.com"

jobs:
  build:    
    runs-on: ubuntu-latest    
    steps:      
      - uses: actions/checkout@v2
      
      - name: OIDC Login to Azure 
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} 
          enable-AzPSSession: false

      - name: Provision Resource Group
        uses: Azure/cli@v1
        with:
          inlineScript: az group create --name ${{ env.AZURE_RESOURCE_GROUP }} --location ${{ env.AZURE_LOCATION }} --tags Purpose=Demo Production=NO --managed-by ${{ env.AZURE_MANAGED_BY }}

      - name: Deploy Bicep Template
        uses: Azure/arm-deploy@main
        with:
          scope: resourcegroup
          subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
          resourceGroupName: ${{ env.AZURE_RESOURCE_GROUP }}
          template: ./Infrastructure/main.bicep
      - name: Azure logout
        run: az logout

Nice, this gives us the required infrastructure provisioned and ready for our container image deployment.

Container workflow

Let’s create a pipeline that builds and deploys the images to the Azure Container App environment.

name: Deployment
on:
  push:
     branches: [ main ]
permissions:
      id-token: write
      contents: read
    
env:
  AZURE_RESOURCE_GROUP: xeniel
  AZURE_LOCATION: westeurope
  AZURE_MANAGED_BY: "moim.hossain@microsoft.com"

jobs:
  build-container-images:    
    runs-on: ubuntu-latest    
    steps:      
      - uses: actions/checkout@v2
      - uses: tenhaus/get-release-or-tag@v2
        id: tag      
      - name: OIDC Login to Azure 
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} 
          enable-AzPSSession: false
      - name: Building container images
        run: ${PWD}/CognitiveDemo/build.sh $ImageTag $RegistryName
        env:
          ImageTag: ${{ steps.tag.outputs.tag }}
          RegistryName: "xenielscontainerregistry.azurecr.io"
      - name: Azure logout
        run: az logout
  deploy-containers: 
    needs: build-container-images   
    runs-on: ubuntu-latest    
    steps:      
      - uses: actions/checkout@v2
      - uses: tenhaus/get-release-or-tag@v2
        id: tag      
      - name: OIDC Login to Azure 
        uses: azure/login@v1
        with:
          client-id: ${{ secrets.AZURE_CLIENT_ID }}
          tenant-id: ${{ secrets.AZURE_TENANT_ID }}
          subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} 
          enable-AzPSSession: false
      - name: Deploy Bicep Template
        uses: Azure/arm-deploy@main
        with:
          scope: resourcegroup
          subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
          resourceGroupName: ${{ env.AZURE_RESOURCE_GROUP }}
          template: ./Infrastructure/application.bicep
          parameters: 'tagName=${{ steps.tag.outputs.tag }}'
      - name: Azure logout
        run: az logout        

This gives us a multi-stage pipeline where the first stage builds the images and pushes to the container registry and the second stage does the deployment to Azure container apps.

A quick demo of the user interface

That’s it for today, it gives us the application URI that we can use from browser and do image recognition.

To be continued

Next, I would like to use the revision capability of Azure container apps to gradually release a new version and use traffic splitting feature.

The entire source code can be found in GitHub – if you want to take a look.

Thanks for reading.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s