Tutorial: Deploy an ASP.NET web application by using Azure Cosmos DB for NoSQL, managed identity, and AKS via Bicep

APPLIES TO: NoSQL

In this tutorial, you'll deploy a reference ASP.NET web application on an Azure Kubernetes Service (AKS) cluster that connects to Azure Cosmos DB for NoSQL.

Azure Cosmos DB is a fully managed distributed database platform for modern application development with NoSQL or relational databases.

AKS is a managed Kubernetes service that helps you quickly deploy and manage clusters.

Important

  • This article requires the latest version of the Azure CLI. For more information, see Install the Azure CLI.
  • This article also requires the latest version of the Bicep CLI within the Azure CLI. For more information, see Install Bicep tools.
  • If you're running the commands in this tutorial locally, ensure that you use an administrator account.

Prerequisites

The following tools are required to compile the ASP.NET web application and create its container image:

Overview

This tutorial uses an infrastructure as code (IaC) approach to deploy the resources to Azure. You'll use Bicep, which is a new declarative language that offers the same capabilities as Azure Resource Manager templates. However, Bicep includes a syntax that's more concise and easier to use.

The Bicep modules will deploy the following Azure resources within the targeted subscription scope:

This tutorial uses the following security best practices with Azure Cosmos DB:

Tip

The steps in this tutorial use Azure Cosmos DB for NoSQL. However, you can apply the same concepts to Azure Cosmos DB for MongoDB.

Download the Bicep modules

Download or clone the Bicep modules from the Bicep folder of the azure-samples/cosmos-aks-samples GitHub repository:

git clone https://github.com/Azure-Samples/cosmos-aks-samples.git

cd Bicep/

Connect to your Azure subscription

Use az login to connect to your default Azure subscription:

az login

Optionally, use az account set with the name or ID of a specific subscription to set the active subscription if you have multiple subscriptions:

az account set \
  --subscription <subscription-id>

Initialize the deployment parameters

Create a param.json file by using the JSON in the following example. Replace the {resource group name}, {Azure Cosmos DB account name}, and {Azure Container Registry instance name} placeholders with your own values.

Important

All resource names that you use in the following code should comply with the naming rules and restrictions for Azure resources. Also ensure that the placeholder values are replaced consistently and match the values in param.json.

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "rgName": {
      "value": "{resource group name}"
    },    
    "cosmosName" :{
      "value": "{Azure Cosmos DB account name}"
    },
    "acrName" :{
      "value": "{Azure Container Registry instance name}"
    }
  }
}

Create a Bicep deployment

Set shell variables by using the following commands. Replace the {deployment name} and {location} placeholders with your own values.

deploymentName='{deployment name}'  # Name of the deployment
location='{location}' # Location for deploying the resources

Within the Bicep folder, use az deployment sub create to deploy the template to the current subscription scope:

az deployment sub create \
  --name $deploymentName \
  --location $location \
  --template-file main.bicep \
  --parameters @param.json

During deployment, the console will output a message indicating that the deployment is still running:

 / Running ..

The deployment could take 20 to 30 minutes. After provisioning is completed, the console will output JSON with Succeeded as the provisioning state:

      }
    ],
    "provisioningState": "Succeeded",
    "templateHash": "0000000000000000",
    "templateLink": null,
    "timestamp": "2022-01-01T00:00:00.000000+00:00",
    "validatedResources": null
  },
  "tags": null,
  "type": "Microsoft.Resources/deployments"
}

You can also see the deployment status in the resource group:

Screenshot of the deployment status for the resource group in the Azure portal.

Note

When you create an AKS cluster, a second resource group is automatically created to store the AKS resources. For more information, see Why are two resource groups created with AKS?.

Use the following commands to link your Azure Container Registry instance with AKS. Replace the {Azure Container Registry instance name} and {resource group name} placeholders with your own values.

acrName='{Azure Container Registry instance name}'
rgName='{resource group name}'
aksName=$rgName'aks'

Run az aks update to attach the existing Azure Container Registry resource with the AKS cluster:

az aks update \
  --resource-group $rgName \
  --name $aksName \
  --attach-acr $acrName

Connect to the AKS cluster

To manage a Kubernetes cluster, you use kubectl, the Kubernetes command-line client. To install kubectl locally, use az aks install-cli:

az aks install-cli

To configure kubectl to connect to your Kubernetes cluster, use az aks get-credentials. This command downloads credentials and configures the Kubernetes CLI to use them.

az aks get-credentials \
  --resource-group $rgName \
  --name $aksName

Connect the AKS pods to Azure Key Vault

Microsoft Entra pod-managed identities use AKS primitives to associate managed identities for Azure resources and identities in Microsoft Entra ID with pods. You'll use these identities to grant access to the Azure Key Vault Provider for Secrets Store CSI Driver.

Use the following command to find the values of the tenant ID (homeTenantId):

az account show

Use the following YAML template to create a secretproviderclass.yml file. Replace the {Tenant Id} and {resource group name} placeholders with your own values. Also ensure that the value for {resource group name} matches the value in param.json.

# This is a SecretProviderClass example that uses aad-pod-identity to access the key vault
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: azure-kvname-podid
spec:
  provider: azure
  parameters:
    usePodIdentity: "true"               
    keyvaultName: "{resource group name}kv"       # Replace resource group name. Bicep generates the key vault name.
    tenantId: "{Tenant Id}"              # The tenant ID of your account. Use the 'homeTenantId' attribute value from  the 'az account show' command output.

Apply the SecretProviderClass to the AKS cluster

Use kubectl apply to install the Secrets Store CSI Driver by using the YAML:

kubectl apply \
  --filename secretproviderclass.yml

Build the ASP.NET web application

Download or clone the web application's source code from the Application folder of the azure-samples/cosmos-aks-samples GitHub repository:

git clone https://github.com/Azure-Samples/cosmos-aks-samples.git

cd Application/

Open the Application folder in Visual Studio Code. Run the application by using either the F5 key or the Debug: Start Debugging command.

Push the Docker container image to Azure Container Registry

  1. To create a container image from the Explorer tab in Visual Studio Code, right-click Dockerfile, and then select Build Image.

    Screenshot of the context menu in Visual Studio Code with the Build Image option selected.

  2. In the prompt that asks for the name and version to tag the image, enter the name todo:latest.

  3. Use the Docker pane to push the built image to Azure Container Registry. You'll find the built image under the Images node. Open the todo node, right-click latest, and then select Push.

    Screenshot of the context menu in Visual Studio Code with the Push option selected.

  4. In the prompts, select your Azure subscription, Azure Container Registry resource, and image tags. The image tag format should be {acrname}.azurecr.cn/todo:latest.

  5. Wait for Visual Studio Code to push the container image to Azure Container Registry.

Prepare the deployment YAML

Use the following YAML template to create an akstododeploy.yml file. Replace the {ACR name}, {Image name}, {Version}, and {resource group name} placeholders with your own values.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: todo
  labels:
    aadpodidbinding: "cosmostodo-apppodidentity"
    app: todo
spec:
  replicas: 2
  selector:
    matchLabels:
      app: todo
  template:
    metadata:
      labels:
        app: todo
        aadpodidbinding: "cosmostodo-apppodidentity"
    spec:
      containers:
      - name: mycontainer
        image: "{ACR name}/{Image name}:{Version}"   # Update per your environment; for example, myacrname.azurecr.cn/todo:latest. Do not add https:// in ACR Name.
        ports:
        - containerPort: 80
        env:
        - name: KeyVaultName
          value: "{resource group name}kv"       # Replace resource group name. Key Vault name is generated by Bicep.
      nodeSelector:
        kubernetes.io/os: linux
      volumes:
        - name: secrets-store01-inline
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: "azure-kvname-podid"       
---
    
kind: Service
apiVersion: v1
metadata:
  name: todo
spec:
  selector:
    app: todo
    aadpodidbinding: "cosmostodo-apppodidentity"    
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

Apply the deployment YAML

Use kubectl apply again to deploy the application pods and expose the pods via a load balancer:

kubectl apply \
  --filename akstododeploy.yml \
  --namespace 'my-app'

Test the application

When the application runs, a Kubernetes service exposes the application's front end to the internet. This process can take a few minutes to complete.

Use kubectl get to view the external IP that the load balancer exposes:

kubectl get services \
  --namespace "my-app"

To access the application, open the IP address that you received as output in a browser.

Clean up the resources

To avoid Azure charges, clean up unneeded resources when you no longer need the cluster. Use az group delete and az deployment sub delete to delete the resource group and subscription deployment, respectively:

az group delete \
  --resource-group $rgName 
  --yes

az deployment sub delete \
  --name $deploymentName

Next steps