Procedures for Deploying .NET 9 App to Azure Kubernetes Service (AKS)

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    Procedures for Deploying .NET 9 App to Azure Kubernetes Service (AKS)

    πŸš€ Table of Contents

    1. Introduction
    2. Required software installations and verification.
    3. Create the .NET Application
    4. Containerize Your Application
    5. Set Up Azure Infrastructure
    6. Create Kubernetes Configuration
    7. Set Up GitHub Actions CI/CD
    8. Access Your Deployed Application
    9. Test Continuous Deployment
    10. Clean Up Resources (Optional)
    11. Conclusion.


    πŸš€ 1. Introduction

    Deploying a .NET 9 application to Azure Kubernetes Service (AKS) unlocks the full potential of cloud-native architecture with cutting-edge performance and scalability. This article outlines a streamlined deployment workflowβ€”from containerizing your .NET 9 codebase to orchestrating it within AKS clusters. Whether you're modernizing legacy systems or launching new microservices, you'll gain practical insights to build, ship, and scale with confidence in the Azure ecosystem.



    πŸš€ 2. Required software installations and verification
    • Visual Studio Code - Download here
    • NET 8 SDK - Download here
    • Git - Download here
    • Docker Desktop - Download here
    • Important: Start Docker Desktop after installation
    • Azure CLI - Download here
    • kubectl - Download here
    • GitHub CLI (Optional) - Download here

      πŸš€ 3.0 CREATE THE .NET APPLICATION
      3.1 Set Up Project Structure
      First, open your VSCode and set your files to "autosave. " Then, navigate to the file on your laptop and create a new folder named "DotNetApp."



      Create the new Folder DotNetApp or choose any name of your choice

      Open the new Folder

      Open the terminal, click on the top screen's view section, and then select terminal.

      Run the following code in VSCode as shown in the screenshots: mkdir weather-app-demo
      cd weather-app-demo
      mkdir WeatherApp
      cd WeatherApp

      3.2 Initialize .NET Project
      Run dotnet new webapi -minimal

      3.3. Add Required Dependencies
      Run the following separately: a. dotnet add package Microsoft.Extensions.Diagnostics.HealthChecks

      b. dotnet add package Swashbuckle.AspNetCore

      3.4. Create the Application Code
      Replace the contents of Program.cs with the code below;
      using Swashbuckle.AspNetCore.SwaggerUI;


    var builder = WebApplication.CreateBuilder(args);


    // Add services to the container

    builder.Services.AddHealthChecks();

    builder.Services.AddEndpointsApiExplorer();

    builder.Services.AddSwaggerGen();


    // Configure Kestrel to listen on port 8080 (required for containers)

    builder.WebHost.ConfigureKestrel(options =>

    {

    options.ListenAnyIP(8080);

    });


    var app = builder.Build();


    // Configure the HTTP request pipeline

    if (app.Environment.IsDevelopment() || app.Environment.IsProduction())

    {

    app.UseSwagger();

    app.UseSwaggerUI(c =>

    {

    c.SwaggerEndpoint("/swagger/v1/swagger.json", "Weather API v1");

    c.RoutePrefix = "swagger";

    });

    }


    // Define API endpoints

    app.MapGet("/", () => new

    {

    Message = "Welcome to the Weather App!",

    Version = "1.0.0",

    Environment = app.Environment.EnvironmentName,

    Timestamp = DateTime.UtcNow

    })

    .WithName("GetWelcome")

    .WithTags("General");


    app.MapGet("/weather", () =>

    {

    var summaries = new[]

    {

    "Freezing", "Bracing", "Chilly", "Cool", "Mild",

    "Warm", "Balmy", "Hot", "Sweltering", "Scorching"

    };




    var forecast = Enumerable.Range(1, 5).Select(index => new
    {
    Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)) ,
    TemperatureC = Random.Shared.Next(-20, 55),
    Summary = summaries[Random.Shared.Next(summaries.Length)]
    })
    .Select(temp => new
    {
    temp.Date,
    temp.TemperatureC,
    TemperatureF = 32 + (int)(temp.TemperatureC / 0.5556),
    temp.Summary
    });

    return forecast;





    })

    .WithName("GetWeatherForecast")

    .WithTags("Weather");


    // Health check endpoint (required for Kubernetes)

    app.MapHealthChecks("/health")

    .WithTags("Health");


    app.Run();



    3.5. Test Locally

    Run dotnet run



    Open your browser and test these endpoints:

    http://localhost:8080/ - Welcome message



    http://localhost:8080/weather - Weather forecast



    http://localhost:8080/swagger - API documentation



    http://localhost:8080/health - Health check



    Press Ctrl+C to stop the application.



    πŸš€ 4. CONTAINERIZE YOUR APPLICATION

    4.1. Create a Dockerfile file (no extension) in the Weatherapp folder. Paste the code below into the file;


    Multi-stage build for optimized image size

    Build stage

    FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build

    ARG BUILD_CONFIGURATION=Release

    WORKDIR /src


    βœ… Copy csproj from current dir (Dockerfile context = Weatherapp/)

    COPY ["Weatherapp.csproj", "./"]

    RUN dotnet restore "Weatherapp.csproj"


    Copy source code and build

    COPY . .

    RUN dotnet build "Weatherapp.csproj" -c $BUILD_CONFIGURATION -o /app/build


    Publish stage

    FROM build AS publish

    ARG BUILD_CONFIGURATION=Release

    RUN dotnet publish "Weatherapp.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false


    Runtime stage

    FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base

    WORKDIR /app

    EXPOSE 8080

    ENV ASPNETCORE_URLS=http://+:8080

    ENV ASPNETCORE_ENVIRONMENT=Production


    Install curl

    RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*


    Final stage

    FROM base AS final

    WORKDIR /app

    COPY --from=publish /app/publish .

    ENTRYPOINT ["dotnet", "WeatherApp.dll"]



    4.2 Create .dockerignore

    Create a .dockerignore file to exclude unnecessary files. Paste the file below into the Dockerignore file.


    Build outputs

    bin/

    obj/

    out/


    IDE files

    .vs/

    .vscode/

    *.user

    *.suo


    OS files

    .DS_Store

    Thumbs.db


    Git

    .git/

    .gitignore


    Documentation

    README.md

    *.md


    Docker files

    Dockerfile

    Dockerfile.*

    .dockerignore


    Logs

    .log

    logs/



    **4.3 Build and Test Container
    *

    Run the following code;


    Build the Docker image

    docker build -t weather-app: local .




    Run the container

    docker run -d -p 8080:8080 --name weather-test weather-app:local




    Test the containerized app

    curl http://localhost:8080/



    curl http://localhost:8080/weather



    # Clean up

    Run docker stop weather-test



    docker rm weather-test



    5. SET UP AZURE INFRASTRUCTURE

    Run az login

    This connects to your Azure account and opens up a list of subscriptions.



    5.2 Select your subscription

    Choose your subscription from the list of others, or simply run the following;


    List available subscriptions

    az account list --output table


    Set the subscription you want to use

    az account set --subscription "Your-Subscription-Name"

    5.3. Create a resource group

    Run the following code;

    az group create --name student-demo --location eastus



    5.4. Create a Container Registry


    Replace 'studentdemo2024acr' with a unique name (add your initials/year)

    az acr create \

    --resource-group student-demo \

    --name studentdemo2074acr \

    --sku Basic



    5.5. Build and Push Image Using ACR

    From the folder that contains your Dockerfile, run:

    az acr build \

    --registry studentdemo2074acr \

    --image weather-app:latest \

    .





    5.6 Create AKS Cluster with ACR integration.

    Run this code

    az aks create \

    --resource-group student-demo \

    --name student-aks-cluster \

    --node-count 1 \

    --node-vm-size Standard_B2s \

    --attach-acr studentdemo2074acr \

    --enable-managed-identity \

    --generate-ssh-keys



    5.7. Connect To Your Cluster


    Download cluster credentials

    az aks get-credentials \

    --resource-group student-demo \

    --name student-aks-cluster




    # Verify connection

    kubectl get nodes



    5.8. Verify ACR Integration (Optional)


    Get the managed identity client ID

    Run the code;

    CLIENT_ID=$(az aks show \

    --resource-group student-demo \

    --name student-aks-cluster \

    --query "identityProfile.kubeletidentity.clientId" \

    --output tsv)


    echo "Kubelet Client ID: $CLIENT_ID"



    # Verify the role assignment exists

    az role assignment list \

    --assignee $CLIENT_ID \

    --scope $(az acr show --name studentdemo2074acr --query id --output tsv) \

    --output table




    You should see an AcrPull role assignment. If not, create it manually:


    Only if the role assignment doesn't exist

    Run;

    az role assignment create \

    --assignee $CLIENT_ID \

    --role AcrPull \

    --scope $(az acr show --name studentdemo2074acr --query id --output tsv)

    6. CREATE KUBERNETES CONFIGURATION

    6.1. Set Up Kubernetes Manifests Directory

    Run the following code;

    cd .. (# Back to weather-app-demo folder)



    mkdir k8s

    cd k8s



    6.2. Create Deployment Configuration

    Create deployment.yaml

    paste the code below into the file;

    apiVersion: apps/v1

    kind: Deployment

    metadata:

    name: weather-app

    namespace: default

    labels:

    app: weather-app

    version: v1

    spec:

    replicas: 1

    selector:

    matchLabels:

    app: weather-app

    template:

    metadata:

    labels:

    app: weather-app

    version: v1

    spec:

    imagePullSecrets:

    - name: acr-pull-secret

    containers:

    - name: weather-app

    image: studentdemo2074acr.azurecr.io/weather-app:




    imagePullPolicy: Always
    ports:
    - name: http
    containerPort: 8080
    protocol: TCP
    env:
    - name: ASPNETCORE_ENVIRONMENT
    value: "Production"
    resources:
    requests:
    memory: "128Mi"
    cpu: "100m"
    limits:
    memory: "512Mi"
    cpu: "500m"
    livenessProbe:
    httpGet:
    path: /health
    port: http
    initialDelaySeconds: 30
    periodSeconds: 10
    timeoutSeconds: 5
    failureThreshold: 3
    readinessProbe:
    httpGet:
    path: /health
    port: http
    initialDelaySeconds: 10
    periodSeconds: 5
    timeoutSeconds: 3
    failureThreshold: 3
    startupProbe:
    httpGet:
    path: /health
    port: http
    initialDelaySeconds: 15
    periodSeconds: 5
    timeoutSeconds: 3
    failureThreshold: 30







    Note: There's no need for imagePullSecrets because AKS with --attach-acr handles authentication automatically!

    6.3. Create Service Configuration

    Create service.yaml and paste the code below into the file.

    apiVersion: v1

    kind: Service

    metadata:

    name: weather-app-service

    labels:

    app: weather-app

    spec:

    type: LoadBalancer

    selector:

    app: weather-app

    ports:
    • name: http
      port: 80
      targetPort: 8080
      protocol: TCP

      6.4 Deploy to Kubernetes
      Run the codes individually.
      # Apply Kubernetes manifests
      kubectl apply -f deployment.yaml

      kubectl apply -f service.yml

      # Check deployment status
      kubectl get deployments

      kubectl get pods

      kubectl get services


    Watch pods until they're running

    kubectl get pods --watch



    6.5. Troubleshoot if Needed

    If you see ImagePullBackOff:


    Check pod events

    kubectl describe pod $(kubectl get pods -l app=weather-app -o jsonpath='{.items[0].metadata.name}')


    Check if the image exists in ACR

    az acr repository show-tags --name studentdemo2024acr --repository weather-app --output table


    Force a new pull

    kubectl rollout restart deployment/weather-app

    7.0. SET UP GITHUB ACTIONS CI/CD

    7.1. Initialize Git Repository

    Run the following codes

    cd .. (# Back to weather-app-demo folder)




    Initialize Git repository

    git init

    git add .

    git commit -m "Initial commit: Weather App with Docker and Kubernetes"



    7.2. Create Azure Service Principal


    Get your subscription ID

    SUBSCRIPTION_ID=$(az account show --query id --output tsv)

    echo "Subscription ID: $SUBSCRIPTION_ID"




    Create service principal with contributor role

    az ad sp create-for-rbac \

    --name "weather-app-github-sp" \

    --role contributor \

    --scopes /subscriptions/$SUBSCRIPTION_ID/resourceGroups/student-demo \

    --sdk-auth



    Important: Copy the entire JSON output in the red box - you'll need it for GitHub secrets!

    7.3. Create GitHub Repository


    Create GitHub repository (using GitHub CLI)

    gh auth login # Follow the prompts







    gh repo create weather-app-demo --public --source=. --push



    Alternative: Create the repository manually on GitHub.com and push your code.

    7.4. Configure GitHub Secrets

    Navigate to your GitHub repository:


    Go to Settings β†’ Secrets and variables β†’ Actions

    Click New repository secret and add these secrets:



    Navigate to secrets and variables








    Secret Name

    Value

    AZURE_CREDENTIALS

    The JSON output from step 5.2

    ACR_NAME

    studentdemo2074acr (your ACR name)

    RESOURCE_GROUP

    student-demo

    CLUSTER_NAME

    student-aks-cluster









    7.5. Create GitHub Workflow

    Create the workflow directory and file by running the code below;

    mkdir -p .github/workflows

    touch .github/workflows/deploy.yml

    name: Build and Deploy to AKS


    on:

    push:

    branches: [main]

    workflow_run:

    workflows: ["CI - Build and Test"]

    types: [completed]

    branches: [main]


    jobs:

    build-and-deploy:

    runs-on: ubuntu-latest

    if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'push' }}




    steps:
    - name: Checkout code
    uses: actions/checkout@v4

    - name: Setup .NET 9.0
    uses: actions/setup-dotnet@v4
    with:
    dotnet-version: '9.0.x'

    - name: Restore dependencies
    run: dotnet restore WeatherApp/WeatherApp.csproj

    - name: Build application
    run: dotnet build WeatherApp/WeatherApp.csproj --configuration Release --no-restore

    - name: Run tests
    run: dotnet test WeatherApp/WeatherApp.csproj --no-build --verbosity normal

    - name: Azure Login
    uses: azure/login@v1
    with:
    creds: ${{ secrets.AZURE_CREDENTIALS }}

    - name: Login to Azure Container Registry
    run: |
    az acr login --name ${{ secrets.ACR_NAME }}

    - name: Set IMAGE_NAME environment variable
    run: echo "IMAGE_NAME=weather-app" >> $GITHUB_ENV

    - name: Set NAMESPACE environment variable
    run: echo "NAMESPACE=default" >> $GITHUB_ENV

    - name: Build and push Docker image to ACR
    run: |
    IMAGE_TAG=${{ secrets.ACR_NAME }}.azurecr.io/$IMAGE_NAME:${{ github.sha }}
    docker build -t $IMAGE_TAG WeatherApp/
    docker push $IMAGE_TAG
    echo "IMAGE_TAG=$IMAGE_TAG" >> $GITHUB_ENV

    - name: Test Docker image
    run: |
    docker run -d -p 8080:8080 -e ASPNETCORE_URLS=http://+:8080 --name test-container $IMAGE_TAG
    sleep 10
    docker logs test-container
    curl -f http://localhost:8080/health || exit 1
    docker stop test-container
    docker rm test-container

    - name: Deploy to AKS
    if: github.ref == 'refs/heads/main'
    run: |
    # Get AKS credentials
    az aks get-credentials \
    --resource-group ${{ secrets.RESOURCE_GROUP }} \
    --name ${{ secrets.CLUSTER_NAME }} \
    --overwrite-existing

    # Check if deployment exists, if not create it
    if ! kubectl get deployment/weather-app -n default > /dev/null 2>&1; then
    echo "Creating new deployment..."
    if [ -d "k8s/" ]; then
    kubectl apply -f k8s/ -n default
    else
    echo "No k8s/ directory found. Please create Kubernetes manifests."
    exit 1
    fi
    else
    echo "Updating existing deployment..."
    kubectl set image deployment/weather-app \
    weather-app=$IMAGE_TAG \
    -n default \
    --record
    fi

    # Wait for deployment to complete
    kubectl rollout status deployment/weather-app -n default --timeout=300s

    # Get service info
    kubectl get service weather-app-service -n default || echo "Service not found"

    # Verify pods
    kubectl get pods -n default -l app=weather-app

    # Simple smoke test
    kubectl port-forward service/weather-app-service 8080:80 -n default &
    sleep 5
    curl -f http://localhost:8080/health || echo "Health check failed"
    pkill -f "kubectl port-forward"







    7.6. 5.6 Deploy Your Application

    git add .

    git commit -m "Add GitHub Actions CI/CD pipeline"

    git push origin main



    Monitor the deployment:

    Go to your GitHub repository

    Click the Actions tab

    Watch your workflow run in real-time



    **8.0 Access Your Deployed Application


    8.1 Access Your Deployed Application

    Get External IP Address


    Check service status

    Run kubectl get service weather-app-service




    Wait for EXTERNAL-IP (may take 2-5 minutes)

    Run kubectl get service weather-app-service --watch



    8.2. Test Your Live Application


    Test the endpoints

    curl http://YOUR-EXTERNAL-IP/





    curl http://YOUR-EXTERNAL-IP/weather






    curl http://YOUR-EXTERNAL-IP/health






    Or open in browser

    open http://YOUR-EXTERNAL-IP/swagger # macOS





    start http://YOUR-EXTERNAL-IP/swagger # Windows

    9. Test Continuous Deployment

    Make a Code Change

    Edit WeatherApp/Program.cs and update the welcome message:

    app.MapGet("/", () => new

    {

    Message = "Hurray! Welcome to the Updated Weather App! 🌀️",

    Version = "1.1.0",

    Environment = app.Environment.EnvironmentName,

    Timestamp = DateTime.UtcNow,

    DeployedBy = "GitHub Actions"

    })



    Deploy the Changes by running the following code

    git add ../Weatherapp/Program.cs

    git commit -m "Update welcome message and version"









    # Test the endpoints on a browser







    10. Clean Up Resources (Optional)

    Option 1: Delete Individual Resources


    Delete Kubernetes resources

    kubectl delete -f k8s/


    Delete AKS cluster

    az aks delete --resource-group student-demo --name student-aks-cluster --yes


    Delete Container Registry

    az acr delete --name studentdemo2024acr --yes


    Option 2: Delete Everything (Recommended)


    Delete the entire resource group (removes all Azure resources)

    az group delete --name student-demo --yes --no-wait


    πŸš€ Conclusion: Seamless Deployment, Scalable Success

    Deploying a .NET 9 application to Azure Kubernetes Service (AKS) is no longer a daunting taskβ€”it’s a streamlined process that empowers developers to harness the full potential of cloud-native architecture. You ensure a robust, scalable, and resilient deployment pipeline by following the outlined proceduresβ€”from containerizing the app to configuring YAML manifests and leveraging Azure CLI. With AKS and .NET 9 working in tandem, your applications are primed for performance, agility, and future growth.




    More...
Working...