Microsoft Azure provides an all-encompassing service that allows you to host Docker containers on the Azure Container Registry (ACR), deploy to a production-ready Kubernetes cluster via the Azure Kubernetes Service (AKS), and more. Using CircleCI, you can automatically deploy updates to your application, providing a safer and more efficient CI/CD process for managing your software. This article shows you how to automate deployments for a .Net application to Azure Kubernetes.

A sample application has been created for this tutorial. Once you set it up, it will run in your local environment.

2023-05-26-weather-api

Prerequisites

  • Familiarity with Docker and Docker Desktop installed on your local machine. You can follow Docker’s tutorial for Windows or macOS.
  • GitHub account
  • CircleCI account
  • Azure account with an active subscription
  • Basic knowledge of building applications with ASP.NET Core framework
  • Azure CLI installed on your workstation

Setting up the development environment

To help understand the concepts in this article, a simple .NET application has been created — a weather API that gives a forecast for the week. Use Git {: target=”_blank”} to clone the sample application to your development environment.

git clone https://github.com/CIRCLECI-GWP/docker-dotnet-app-aks

Change into the cloned directory:

cd docker-dotnet-app-aks

Authenticating with Azure

To run commands using the Azure CLI, you will need to be authenticated. Use this command:

az login

This opens a new window in your browser. Provide your email address and password to complete the authentication process. When the authentication process is complete, the subscription details will be printed out in your terminal. Make a note of the id key; you will need it when creating a service principal.

Azure CLI successful login this command:

az ad sp create-for-rbac --name <service_principal_name> --scopes /subscriptions/<subscription_id> --role owner

In the command, service_principal_name can be any name you choose, and subscription_id is the value of the id key in the terminal output of the successful login. On successful completion, the service principal information will be printed on the terminal. The information displayed includes the appId and password; both will be used to log in to the container registry.

Service credential created

Setting up the Container Registry

Create a new resource group using this command:

az group create --name AzureRG --location eastus

Next, create a new container registry using this command:

az acr create --resource-group AzureRG --name dotnetaksdemo --sku Basic

NOTE: The registry name must be unique. If your name is already in use, you will get an error prompting you to choose a different, unique name. You can use the Registries API to check for available names. In this tutorial, we will use the registry name dotnetaksdemo.

With a registry successfully set up, you can build the Docker image and push it to the registry.

Building the Docker image

The application contains a Dockerfile containing instructions for building and serving the API. When building the application, specify the registry DNS name:

docker build -t dotnetaksdemo.azurecr.io/dotnetapi-aks-app:latest .

Note: Replace *dotnetaksdemo.azurecr.io* and *dotnetapi-aks-app* with the names you chose for the registry URL and login server details.

You can run the Docker image locally using this command:

docker run -d -p 5001:80 dotnetaksdemo.azurecr.io/dotnetapi-aks-app

The application will run on port 5001.

Pushing the Docker image to ACR

Once the build process is complete, you can push the image to the container registry. You will need to be authenticated on the container registry first. Use this command:

echo $AZURE_SP_PASSWORD | docker login dotnetaksdemo.azurecr.io -u $AZURE_SP --password-stdin

Replace $AZURE_SP_PASSWORD and $AZURE_SP in the command with the service principal password and appId you created.

Push to the container registry:

docker push dotnetaksdemo.azurecr.io/dotnetapi-aks-app

To confirm if the image has been deployed, you can run this command:

az acr repository list --name dotnetaksdemo --output table

You will get this output:

Result
-----------------
dotnetapi-aks-app

Creating a cluster

Create an AKS cluster using az aks create. The example in this section of the tutorial creates a cluster named myAKSCluster in the resource group named myResourceGroup. This resource group was created in the previous tutorial in the eastus region. The AKS cluster will also be created in the eastus region.

For more information about AKS resource limits and region availability, review quotas, virtual machine size restrictions, and region availability in AKS.

To allow an AKS cluster to interact with other Azure resources, a cluster identity is automatically created. In this example, the cluster identity is granted the right to pull images from the ACR instance you created in the previous tutorial. To execute the command successfully, you’re required to have an Owner or Azure account administrator role for your Azure subscription.

To avoid needing an Owner or Azure account administrator role, you can also manually configure a service principal to pull images from ACR. Use ACR authentication with service principals or authenticate from Kubernetes with a pull secret. Alternatively, you can use a managed identity instead of a service principal for easier management.

To create the AKS cluster, the Azure CLI should be connected to your Azure account.

Launch an new AKS cluster named DotnetCluster with a two-node cluster in the resource group AzureRG using the Azure CLI. Use this command:

az aks create --resource-group AzureRG --name DotnetCluster \
   --node-count 2 --enable-addons http_application_routing \
   --generate-ssh-keys --service-principal <SERVICE_PRINCIPAL_ID> \
   --client-secret <SERVICE_PRINCIPAL_PASSWORD> \
   --attach-acr dotnetaksdemo

Note: Update the SERVICE_PRINCIPAL_ID and SERVICE_PRINCIPAL_PASSWORD accordingly.

This command includes the details of the service principal and attaches the Azure container registry created earlier to the AKS cluster.

Configuring Kubernetes manifests

The next step is to set up Kubernetes manifests for deployment. At the root of the project, create a new folder named manifests. This folder will contain all the Kubernetes YAML configuration.

First up is the Deployment configuration. In the manifests folder, create a new file named deployment.yaml. Add this to it:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: dotnetapi
  namespace: dotnetapi
  labels:
    app: dotnetapi
spec:
  replicas: 3
  selector:
    matchLabels:
      app: dotnetapi
  template:
    metadata:
      labels:
        app: dotnetapi
    spec:
      nodeSelector:
        "beta.kubernetes.io/os": windows
      containers:
        - name: dotnetapi-aks-app
          image: dotnetapi-aks-app
          ports:
            - name: http
              containerPort: 80

Next, add the Namespace configuration. Create a file named ` namespace.yaml` and add:

apiVersion: v1
kind: Namespace
metadata:
  name: dotnetapi
  labels:
    name: dotnetapi

Next, add the Service configuration. Create a new file named service.yaml and add:

apiVersion: v1
kind: Service
metadata:
  name: dotnetapi
  namespace: dotnetapi
  labels:
    app: dotnetapi
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: dotnetapi

Next, add the configuration for Kustomize. Create a new file named kustomization.yaml and add:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
  - namespace.yaml
namespace: dotnetapi
images:
  - name: dotnetapi-aks-app
    newName: <ACR_SERVER_NAME>
    newTag: v1

Configuring CircleCI

The last piece of the puzzle is the CircleCI pipeline, which manages the entire process of deploying updates. In the root of the project, create a new folder named .circleci and in it a new file named config.yml. Add this:

version: 2.1
orbs:
  azure-aks: circleci/azure-aks@0.3.0
  kubernetes: circleci/kubernetes@1.3.1
jobs:
  build-docker-image:
    docker:
      - image: cimg/base:2023.05
    steps:
      - checkout
      - setup_remote_docker:
          docker_layer_caching: true
      - run:
          name: Build and push Docker image
          command: |
            docker build -t dotnetaksdemo.azurecr.io/dotnetapi-aks-app:$CIRCLE_SHA1 .
            echo $AZURE_SP_PASSWORD | docker login dotnetaksdemo.azurecr.io -u $AZURE_SP --password-stdin
            docker push dotnetaksdemo.azurecr.io/dotnetapi-aks-app:$CIRCLE_SHA1
  aks-deploy:
    docker:
      - image: cimg/base:2023.05
    parameters:
      cluster-name:
        description: |
          Name of the AKS cluster
        type: string
      resource-group:
        description: |
          Resource group that the cluster is in
        type: string
    steps:
      - checkout
      - run:
          name: Pull Updated code from repo
          command: |
            git pull origin $CIRCLE_BRANCH
      - azure-aks/update-kubeconfig-with-credentials:
          cluster-name: << parameters.cluster-name >>
          install-kubectl: true
          perform-login: true
          resource-group: << parameters.resource-group >>
      - kubernetes/create-or-update-resource:
          resource-file-path: manifests/$APP_NAME.yaml
          resource-name: kustomization/$APP_NAME
  bump-docker-tag-kustomize:
    docker:
      - image: cimg/base:2023.05
    steps:
      - run:
          name: Install kustomize
          command: |
            URL=https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/v4.5.2/kustomize_v4.5.2_linux_amd64.tar.gz
            curl -L $URL | tar zx
            [ -w /usr/local/bin ] && SUDO="" || SUDO=sudo
            $SUDO chmod +x ./kustomize
            $SUDO mv ./kustomize /usr/local/bin
      - checkout
      - run:
          name: Bump Docker Tag.
          command: |
            cd manifests
            kustomize edit set image $APP_NAME=$ACR_LOGIN_SERVER/$APP_NAME:$CIRCLE_SHA1
            kustomize build . > $APP_NAME.yaml
      - add_ssh_keys:
          fingerprints:
            - "$SSH_FINGERPRINT"
      - run:
          name: Commit & Push to GitHub
          command: |
            git config user.email "$GITHUB_EMAIL"
            git config user.name "CircleCI User"
            git checkout $CIRCLE_BRANCH           
            git add manifests/$APP_NAME.yaml
            git add manifests/kustomization.yaml
            git commit -am "Bumps docker tag [skip ci]"
            git push origin $CIRCLE_BRANCH
workflows:
  Deploy-App-on-AKS:
    jobs:
      - build-docker-image
      - bump-docker-tag-kustomize:
          requires:
            - build-docker-image
      - aks-deploy:
          cluster-name: $CLUSTER_NAME
          resource-group: $RESOURCE_GROUP
          requires:
            - bump-docker-tag-kustomize

There seems to be quite a bit going on here, but this workflow does 3 things:

  1. It builds an updated Docker image using the checked out code from the associated Github repository, and pushes this code the Azure Container Registry (ACR) via the build-docker-image job.
  2. It updates the Docker image tag as well as the necessary Kubernetes configuration via the bump-docker-tag-kustomize job.
  3. It deploys the changes to the Azure Kubernetes Service (AKS) cluster via the aks-deploy job.

To run this project on your CircleCI account, you will need to migrate your code to a repository on your Github account. Create a new repository, replacing REPOSITORY_URL with the correct URL. Run these commands:

git remote set-url origin <REPOSITORY_URL>
git add .
git commit -m "CI/CD Pipeline configuration"
git push origin main

Setting up the project on CircleCI

Next, connect the GitHub repository to your CircleCI account. Go to your CircleCI dashboard and select the Projects tab on the left panel. Click the Set Up Project button for the GitHub repository containing the code.

Select your config.yml file. You can select the Fastest option because you have included the configuration in your repository. Type in the branch name (main in our case) and click Set Up Project.

On first run, the process will fail because you haven’t yet set up a user key and added all the environment variables CircleCI needs.

To set up the user key, select the SSH Keys option from the left panel of the Project Settings page. From the User Key section, click Authorize with GitHub. CircleCI uses the user key to push changes to your GitHub account on behalf of the repository owner, during the execution of the workflow.

To configure the environment variables, select the Environment Variables option from the left panel of the Project Settings page. Select Add Environment Variable. Next, type the environment variable and the value you want it to be assigned to.

The environment variables required are as follows:

  • APP_NAME is the Container Image Name (dotnetapi-aks-app)
  • AZURE_SP is the username for your Azure Service Principal
  • AZURE_SP_PASSWORD is the password for your Azure Service Principal
  • AZURE_SP_TENANT is the tenant ID for your Azure Service Principal
  • CLUSTER_NAME is the AKS cluster name (DotnetCluster)
  • RESOURCE_GROUP is the name of the AKS Resource Group (AzureRG)
  • SSH_FINGERPRINT is the SSH Fingerprint of the user key used for pushing the updated Docker tag to GitHub.

Note: To locate the *SSH_FINGERPRINT*, go to Project Settings and select SSH Keys from the sidebar. Scroll down to the User Key section, then copy the key. This key is displayed only after you click the Authorize with GitHub button.

With these variables in place, you can rerun the workflow. However instead of starting from the beginning , feel free to restart from where the workflow failed.

Deploy app to AKS

This time, the entire process runs without any errors and your build status is set to Success.

Accessing the application on AKS

With your application deployed successfully, you can now interact with the application hosted on the AKS cluster. To do that though, you will need the external IP of your AKS cluster. Run the following commands to get the external IP of your cluster.

az aks get-credentials --resource-group AzureRG --name DotnetCluster

kubectl get all --namespace dotnetapi

Deploy app to AKS

To access your API, navigate to http://<EXTERNAL_IP>/api/weather in your browser.

Conclusion

There you have it, you have successfully built a pipeline for a .NET project using the Azure Container Registry (ACR) and Azure Kubernetes Service (AKS). In this article, you learned how to create a service principal for your Azure account via the Azure CLI, create an AKS cluster, and update that cluster via a CircleCI pipeline. This makes a more reliable deployment process — one that can even run at the dreaded Friday hour.

The entire project, complete with CircleCI configuration and Kubernetes manifest, is available on GitHub


Oluyemi is a tech enthusiast with a background in Telecommunication Engineering. With a keen interest in solving day-to-day problems encountered by users, he ventured into programming and has since directed his problem solving skills at building software for both web and mobile. A full stack software engineer with a passion for sharing knowledge, Oluyemi has published a good number of technical articles and blog posts on several blogs around the world. Being tech savvy, his hobbies include trying out new programming languages and frameworks.

Read more posts by Olususi Oluyemi