Deploy a containerized .NET Core app to Azure Kubernetes Service (AKS)
Fullstack Developer and Tech Author
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.
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.
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.
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:
- 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. - It updates the Docker image tag as well as the necessary Kubernetes configuration via the
bump-docker-tag-kustomize
job. - 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 PrincipalAZURE_SP_PASSWORD
is the password for your Azure Service PrincipalAZURE_SP_TENANT
is the tenant ID for your Azure Service PrincipalCLUSTER_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.
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
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