DevOps  

Deploying a .NET Weather Forecast App to AKS Using GitHub Actions and Argo CD

What is GitOps and Why Use It?

GitOps is a modern approach to Continuous Deployment (CD) that uses Git as a single source of truth for managing application deployments. Instead of manually applying Kubernetes configurations, GitOps tools like Argo CD automatically sync your cluster state with your Git repository.

What Are Argo CD and GitHub Actions?

Tools Description
GitHub Action A declarative, GitOps-based Continuous Delivery tool for Kubernetes. It ensures your cluster matches the desired state in your Git repository.
Argo CD A CI/CD automation tool that runs workflows for building, testing, and deploying applications automatically.

What is Argo CD?

Argo CD is a declarative, GitOps-based continuous delivery tool designed for Kubernetes. It enables teams to manage application deployments using Git as the single source of truth. This ensures that deployments are automated, consistent, and easily auditable.

Why Use Argo CD?

  • GitOps Workflow: Uses Git repositories as the source of truth for Kubernetes deployments.
  • Automated Deployments: Ensures applications are always running the desired state.
  • Self-Healing: Detects and corrects configuration drifts automatically.
  • Rollback Capabilities: Easily roll back to previous versions in case of failure.
  • Multi-Cluster Support: Deploy applications across multiple Kubernetes clusters.
  • Declarative Configuration: Uses YAML manifests, making deployments predictable and maintainable.

Benefits of Argo CD

  • Increased Deployment Speed: Automates the deployment pipeline, reducing manual intervention.
  • Improved Security: Uses Git as the central source of truth, minimizing configuration drift.
  • Enhanced Observability: Provides a UI and CLI to monitor deployments and sync status.
  • Scalability: Supports complex, large-scale deployments with ease.

Real-World Use Cases of Argo CD

  • Fintech Applications: Ensuring compliance-driven infrastructure management.
  • E-commerce Platforms: Handling frequent updates and high traffic.
  • AI/ML Models: Deploying and updating machine learning models in production environments.

What Will We Achieve in This Article?

Below are the things we will look into as part of this article.

  • A .NET application deployed on Azure Kubernetes Service (AKS)
  • Argo CD syncing the app with a Git repository
  • GitHub Actions automating build and deployment

High-Level Architecture

  • The developer pushes code to a GitHub repository.
  • GitHub Actions builds the Docker image and pushes it to Azure Container Registry (ACR).
  • Argo CD detects the new image and updates the Kubernetes deployment.
  • AKS pulls the updated image and runs the application.

ARGO

Fig Ref: URL

Prerequisites

Before diving in, ensure you have,

  • An Azure Account with AKS (Azure Kubernetes Service) set up
  • Azure Container Registry (ACR)
  • Basic understanding of Docker, Kubernetes & GitHub Actions

Installation of Argo CD on AKS

Argo CD must be installed on AKS before using it. Here’s how,

Step 1. Install Argo CD in AKS.

//Creating namespace for argo cd
kubectl create namespace argocd

//Installation of argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

This creates an argocd namespace and installs all necessary Argo CD components.

Check if all the pods are running for argocd.

kubectl get all -n argocd

CMD

Step 2. Expose the Argo CD UI.

Check if all the pods are running for argoCD

kubectl port-forward svc/argocd-server -n argocd 8080:443

Now, you can access Argo CD UI at: https://localhost:8080

Step 3. Log in to Argo CD CLI.

Get the default admin password.

kubectl get secrets -n argocd argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 --decode

Log in using CLI.

argocd login <ARGOCD_SERVER> --username admin --password <PASSWORD> --insecure

CLI

Now, you can use Argo CD to manage Kubernetes deployments.

Setting Up the Application Codebase

Step 1. Setting Up the .NET Weather API App.

Our application follows a simple structure, with a Dockerfile used to containerize the app.

Dockerfile (ArgoWorkflowDemo/ArgoWorkflowDemo/Dockerfile)

# Use the official .NET Core SDK as a parent image
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build

WORKDIR /app

# Copy the project file and restore any dependencies (use .csproj for the project name)
COPY *.csproj ./

RUN dotnet restore

# Copy the rest of the application code
COPY . .

# Publish the application
RUN dotnet publish -c Release -o out

# Build the runtime image
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime

WORKDIR /app
COPY --from=build /app/out ./

# Expose the port your application will run on
EXPOSE 80

# Start the application
ENTRYPOINT ["dotnet", "ArgoWorkflowDemo.dll"]
  • Base Image: Uses the official .NET 8 runtime image.
  • Working Directory: Sets the working path inside the container.
  • Copy Files: Copies the app files into the container.
  • Entry Point: Runs the application inside the container.

Step 2. Writing Kubernetes Manifests.

We define two primary Kubernetes manifests.

  • Application.yaml (Argo CD Application)
  • deployment.yaml (Kubernetes Deployment and Service)

Argo CD Application Manifest (k8s/application.yaml)

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: weatherapi
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/Jaydeep-007/argo-cd-demo
    path: k8s
    targetRevision: main
  destination:
    server: https://kubernetes.default.svc
    namespace: default
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
  • Auto-sync & Self-heal: Ensures the app is always deployed correctly.
  • Source Repository: Links to the GitHub repository.
  • Namespace: Defines the target Kubernetes namespace.

Deployment YAML (k8s/deployment.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: weatherapi
  namespace: argocd
spec:
  replicas: 2
  selector:
    matchLabels:
      app: weatherapi
  template:
    metadata:
      labels:
        app: weatherapi
    spec:
      containers:
        - name: weatherapi
          image: jddemoacr.azurecr.io/weatherapi:63c6e53528b0f817980212a121b7a73830c3664a
          ports:
            - containerPort: 80
          env:
            - name: ASPNETCORE_ENVIRONMENT
              value: "Production"
---
apiVersion: v1
kind: Service
metadata:
  name: weatherapi
  namespace: argocd
spec:
  type: LoadBalancer
  ports:
    - port: 80
      targetPort: 80
  selector:
    app: weatherapi

Deployment Resource (Deployment - apps/v1)

  • Name: weatherapi
  • Namespace: argocd
  • Replicas: 2 (Ensures high availability)
  • Labels: app: weatherapi (Used for identifying resources)
  • Container Image: jddemoacr.azurecr.io/weatherapi:63c6e53528b0f817980212a121b7a73830c3664a
  • Port: Exposes the container on port 80
  • Environment Variable:
  • ASPNETCORE_ENVIRONMENT=Production (Sets production environment)

Service Resource (Service - v1)

  • Type: LoadBalancer (Exposes service externally)
  • Ports: Map port 80 on the cluster to 80 inside the container
  • Selector: Matches app: weatherapi to link the service with the pods

Step 3. GitHub Actions Workflow for Deployment.

This workflow automates.

  • Building and pushing the Docker image to ACR.
  • Updating the Kubernetes manifest with the latest image.
  • Triggering Argo CD to sync changes.

Workflow File: .github/workflows/deploy.yml

Note. Please configure all your secrets under github Actions from the repository settings.

name: Deploy to AKS via Argo CD

on:
  push:
    branches:
      - main

env:
  IMAGE_NAME: weatherapi
  REGISTRY: jddemoacr.azurecr.io  # Replace with your ACR or Docker Hub URL
  TAG: ${{ github.sha }}

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v3

      - name: Login to Azure Container Registry
        run: echo "${{ secrets.ACR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.ACR_USERNAME }} --password-stdin

      - name: Build and Push Docker Image
        run: |
          docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }} -f ArgoWorkflowDemo/ArgoWorkflowDemo/Dockerfile ArgoWorkflowDemo/ArgoWorkflowDemo
          docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}

      - name: Update Kubernetes Deployment YAML
        run: |
          sed -i "s|image: .*|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}|" k8s/deployment.yaml
          git config --global user.email "[email protected]"
          git config --global user.name "GitHub Actions"
          git add k8s/deployment.yaml
          git diff --quiet && git diff --staged --quiet || git commit -m "Update image to ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}"
          git push origin main || echo "No changes to push"

      - name: Setup Kubeconfig
        run: |
          mkdir -p $HOME/.kube
          echo "${{ secrets.KUBE_CONFIG_DATA }}" | base64 --decode > $HOME/.kube/config
          chmod 600 $HOME/.kube/config
          export KUBECONFIG=$HOME/.kube/config
          kubectl config view --minify
          kubectl get nodes

      - name: Check if Argo CD CLI is Installed
        id: check-argocd
        run: |
          if command -v argocd &> /dev/null; then
            echo "Argo CD is already installed"
            echo "installed=true" >> $GITHUB_ENV
          else
            echo "Argo CD is not installed"
            echo "installed=false" >> $GITHUB_ENV
          fi

      - name: Install Argo CD CLI
        if: env.installed == 'false'
        run: |
          curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
          chmod +x /usr/local/bin/argocd

      - name: Login to ArgoCD
        run: |
          argocd login ${{ secrets.ARGOCD_SERVER }} --username admin --password ${{ secrets.ARGOCD_PASSWORD }} --insecure

      - name: Sync Argo CD Application
        run: |
          argocd app sync weatherapi

      - name: Wait for Deployment to Complete
        run: |
          argocd app wait weatherapi --health --timeout 180

Overview of the Workflow

This GitHub Actions workflow automates.

  • Building a Docker image for the .NET Weather API.
  • Pushing the image to Azure Container Registry (ACR).
  • Updating the Kubernetes deployment YAML to use the new image.
  • Syncing with Argo CD to deploy the updated application to AKS.

Step-by-Step Breakdown

1. Workflow Trigger

on:
  push:
    branches:
      - main

This workflow runs whenever code is pushed to the main branch.

2. Environment Variables

env:
  IMAGE_NAME: weatherapi
  REGISTRY: jddemoacr.azurecr.io
  TAG: ${{ github.sha }}

Defines global variables.

  • IMAGE_NAME: The Docker image name (weatherapi).
  • REGISTRY: The container registry where images are stored.
  • TAG: Uses Git commit SHA as the image version (ensures each image is unique).

3. Job: build-and-deploy

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

Runs the workflow on an Ubuntu-based GitHub Actions runner.

4. Checkout Repository

      - name: Checkout Code
        uses: actions/checkout@v3

Fetches the latest source code from the repository.

5. Log in to Azure Container Registry (ACR)

      - name: Login to Azure Container Registry
        run: echo "${{ secrets.ACR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u ${{ secrets.ACR_USERNAME }} --password-stdin

Authenticates with Azure Container Registry (ACR) using GitHub Secrets.

6. Build and Push Docker Image

      - name: Build and Push Docker Image
        run: |
          docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }} -f ArgoWorkflowDemo/ArgoWorkflowDemo/Dockerfile ArgoWorkflowDemo/ArgoWorkflowDemo
          docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}
  • Builds a Docker image using the .NET Weather API’s Dockerfile.
  • Tag the image with the commit SHA and push it to ACR.

7. Update Kubernetes Deployment YAML

      - name: Update Kubernetes Deployment YAML
        run: |
          sed -i "s|image: .*|image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}|" k8s/deployment.yaml
          git config --global user.email "[email protected]"
          git config --global user.name "GitHub Actions"
          git add k8s/deployment.yaml
          git diff --quiet && git diff --staged --quiet || git commit -m "Update image to ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.TAG }}"
          git push origin main || echo "No changes to push"
  • Update k8s/deployment.yaml to use the newly built image.
  • Commits and pushes the updated YAML file back to the repository.

8. Set up Kubeconfig (Access AKS Cluster)

Note: Here I used pre-configured kube config secrets, but in real-time, you should use Azure AD and managed identity for better security.

      - name: Setup Kubeconfig
        run: |
          mkdir -p $HOME/.kube
          echo "${{ secrets.KUBE_CONFIG_DATA }}" | base64 --decode > $HOME/.kube/config
          chmod 600 $HOME/.kube/config
          export KUBECONFIG=$HOME/.kube/config
          kubectl config view --minify
          kubectl get nodes
  • Authenticates with the Kubernetes cluster (AKS) using a pre-configured kubeconfig file stored as a GitHub Secret.
  • Runs kubectl get nodes to confirm connectivity.

9. Check if Argo CD CLI is Installed

      - name: Check if Argo CD CLI is Installed
        id: check-argocd
        run: |
          if command -v argocd &> /dev/null; then
            echo "Argo CD is already installed"
            echo "installed=true" >> $GITHUB_ENV
          else
            echo "Argo CD is not installed"
            echo "installed=false" >> $GITHUB_ENV
          fi
  • Checks if Argo CD CLI is already installed.
  • Saves the result (installed=true/false) as an environment variable.

10. Install Argo CD CLI (If Not Installed)

      - name: Install Argo CD CLI
        if: env.installed == 'false'
        run: |
          curl -sSL -o /usr/local/bin/argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
          chmod +x /usr/local/bin/argocd

Downloads and installs the Argo CD CLI tool if it’s missing.

11. Log in to Argo CD

      - name: Login to ArgoCD
        run: |
          argocd login ${{ secrets.ARGOCD_SERVER }} --username admin --password ${{ secrets.ARGOCD_PASSWORD }} --insecure

Logs in to Argo CD using GitHub Secrets.

12. Sync Application with Argo CD

      - name: Sync Argo CD Application
        run: |
          argocd app sync weatherapi

Triggers Argo CD to deploy the updated Kubernetes manifests.

13. Wait for Deployment to Complete

      - name: Wait for Deployment to Complete
        run: |
          argocd app wait weatherapi --health --timeout 180
  • Waits for the deployment to reach a healthy state in Argo CD.
  • The timeout is set to 180 seconds.

Testing and Verification

We'll add a new "Hello World" API endpoint in the .NET Weather API controller, push the changes, and verify if it gets deployed automatically.

Step 1. Modify the .NET Controller.

Open the existing WeatherForecastController.cs and add a new HelloWorld method.

Path: ArgoWorkflowDemo/ArgoWorkflowDemo/Controllers/WeatherForecastController.cs

[HttpGet("hello")]
public IActionResult HelloWorld()
{
    return Ok("Hello, Argo CD Deployment is Successful!");
}

Step 2. Commit and Push Changes to GitHub.

  • Save the file.
  • Open Terminal / Command Prompt in the repository folder.
  • Run the following commands to commit and push.
git add ArgoWorkflowDemo/ArgoWorkflowDemo/Controllers/WeatherForecastController.cs
git commit -m "Added Hello World endpoint for Argo CD test"
git push origin main

Step 3. Verify GitHub Actions Execution.

  • Go to your GitHub repository.
  • Navigate to Actions -> Deploy to AKS via Argo CD workflow
  • Ensure all steps run successfully.

CD workflow

Build

Step 4. Verify the Build and Publish Image.

You can verify the updated weather api docker image, which was published with the latest updated change.

Publish image

Step 5. Verify Argo CD Deployment.

  • Open Argo CD UI
  • Check if the weatherapi application is in a Synced & Healthy state.

CD UI

Sync

If you click on any of the above Kubernetes resources, you can check events, logs, and any configuration file.

Configuration file

Event

Logs

Step 6. Test the Deployed API on AKS.

Get the external IP of the weatherapi service.

kubectl get services -n argocd

External IP

Access the Hello World API in a browser.

API

Conclusion

In this article, we’ve successfully built a GitOps-powered CI/CD pipeline for deploying a .NET application to AKS using Argo CD and GitHub Actions. By leveraging modern CI/CD practices, we automated the entire process from code changes to deployment, ensuring a seamless and scalable solution for any software engineer.

Key Takeaways

  1. GitOps enables automated, reliable, and consistent deployments to Kubernetes using Argo CD.
  2. GitHub Actions acts as the CI/CD tool, automating tasks like building, testing, and deploying our .NET application.
  3. With AKS, we set up a scalable Kubernetes cluster that runs the app and handles traffic efficiently.