Skip to content

.NET weatherapi application pushed to ACR and deployed to AKS using GitHub Actions

ivan edited this page Jun 29, 2025 · 2 revisions

for the azure container registry method

  • first set the secrets in your repo settings
  • head on over to actions and run the pipeline
  • this should send the container to ACR

To pull and run the image from ACR

  • docker pull acrcrg.azurecr.io/aspcoresample:<image_id_from_acr>
  • then to run the image
  • docker run -p 8080:8080 -e WEATHER_API_KEY=your_key acrcrg.azurecr.io/aspcoresample:1b214a811629ad5572caa8790bec50e805b660b4
  • Then as above head on over to a local browser and so and so

Now to test out the deployment via aks

WITHOUT RBAC

az login
az aks get-credentials --resource-group crg --name akscrg
kubectl get pods
kubectl create secret generic weather-api-secret --from-literal=WEATHER_API_KEY=ENTER_YOUR_ACTUAL_KEY_HERE
kubectl apply -f deployment.yaml
kubectl get svc

if youd prefer forwarding it locallly and testing it

kubectl port-forward svc/weather-api-service 8080:80

go to the public ip and forward the query as before

to stop the server temporarily scale to 0

 kubectl scale deployment weather-api --replicas=0

and to resume

kubectl scale deployment weather-api --replicas=1

Dockerfile Used

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /app

#Improve layer caching and rebuild time
COPY *.csproj ./
RUN dotnet restore

# Copy everything and restore dependencies
COPY . .
RUN dotnet publish -c Release -o out

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS runtime
WORKDIR /app

# Copy the published app from build stage
COPY --from=build /app/out ./

# Expose port 80
EXPOSE 80

#Runtime environment variables
ENV ASPNETCORE_ENVIRONMENT=Production
ENV WEATHER_API_KEY=__REPLACE_AT_RUNTIME__

# Run the app
ENTRYPOINT ["dotnet", "WeatherAPIWrapper.dll"]

And pipeline used

name: Build and Push The dotnet app

on:
  push:
    branches: [master]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-latest

    defaults:
      run:
        shell: bash

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

      - name: Set up .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: 8.0.x

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release

      - name: Publish
        run: dotnet publish -c Release -o out

      - name: Docker Login
        uses: azure/docker-login@v1
        with:
          login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build and Push Docker image
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ secrets.REGISTRY_LOGIN_SERVER }}/weatherapiwrapper:latest
          file: Dockerfile
          build-args: |
            WEATHER_API_KEY=${{ secrets.WEATHER_API_KEY }}

Deployment Done using kubectl

deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: weather-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: weather-api
  template:
    metadata:
      labels:
        app: weather-api
    spec:
      containers:
        - name: weather-api
          image: acrrdx40.azurecr.io/weatherapiwrapper:latest
          ports:
            - containerPort: 80
          env:
            - name: WEATHER_API_KEY
              valueFrom:
                secretKeyRef:
                  name: weather-api-secret
                  key: WEATHER_API_KEY
---
apiVersion: v1
kind: Service
metadata:
  name: weather-api-service
spec:
  type: LoadBalancer
  selector:
    app: weather-api
  ports:
    - port: 80
      targetPort: 80

WITH RBAC

Firstly Generate The RBAC using az-cli or the gui

setting up the environment

az login
ACR_NAME=acrrdx40
AKS_RESOURCE_GROUP=cr_kr_crg
AKS_CLUSTER_NAME=aksclusterrdx40
az aks show --resource-group $AKS_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query identityProfile.kubeletidentity.objectId -o tsv
#The output of above is your kubelet identity
KUBELET_IDENTITY_ID=bf347f0d-52fd-4483-8766-f825952d2bc8
az acr show --name $ACR_NAME --query id --output tsv
#The output of above is your ACR identity
ACR_ID=/subscriptions/82c2d825-ab4e-45b7-98df-2dbb21f39192/resourceGroups/cr_kr_crg/providers/Microsoft.ContainerRegistry/registries/acrrdx40
az ad sp create-for-rbac --name "<ENTER_THE_NAME>" --role <ENTER THE ROLE> --scopes /subscriptions/<ENTER_THE_SUBSCRIPTION_UNDER_WHICH>/resourceGroups/<ENTER_THE_RESOURCE_GROUP_UNDER_WHICH> --sdk-auth


## for example, a reference:
az ad sp create-for-rbac --name "gh-aks-deployer" --role contributor --scopes /subscriptions/82c2d825-ab4e-45b7-98df-2dbb21f39192/resourceGroups/cr_kr_crg --sdk-auth
  • The above should result in an output as so, Creating 'contributor' role assignment under scope '/subscriptions/82c2d825-ab4e-45b7-98df-2dbb21f39192/resourceGroups/cr_kr_crg' The output includes credentials that you must protect. Be sure that you do not include these credentials in your code or check the credentials into your source control. For more information, see https://aka.ms/azadsp-cli
{
  "clientId": "your_client_id",
  "clientSecret": "your_client_secret",
  "subscriptionId": "your_subscription_id",
  "tenantId": "your_tenant_id",
  "activeDirectoryEndpointUrl": "https://login.microsoftonline.com",
  "resourceManagerEndpointUrl": "https://management.azure.com/",
  "activeDirectoryGraphResourceId": "https://graph.windows.net/",
  "sqlManagementEndpointUrl": "https://management.core.windows.net:8443/",
  "galleryEndpointUrl": "https://gallery.azure.com/",
  "managementEndpointUrl": "https://management.core.windows.net/"
}
  • and then head on over to repo secrets and add the above json as a secret with the Name AZURE_CREDENTIALS
  • Then update the .github/workflow/deploy.yml as so
name: Build, Push and Deploy .NET Weather API

on:
  push:
    branches: [master]
  workflow_dispatch:

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

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

      - name: Set up .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: 8.0.x

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release

      - name: Publish
        run: dotnet publish -c Release -o out

      - name: Docker Login to ACR
        uses: azure/docker-login@v1
        with:
          login-server: ${{ secrets.REGISTRY_LOGIN_SERVER }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}

      - name: Build and Push Docker Image
        uses: docker/build-push-action@v5
        with:
          context: .
          file: Dockerfile
          push: true
          tags: ${{ secrets.REGISTRY_LOGIN_SERVER }}/weatherapiwrapper:latest
          build-args: |
            WEATHER_API_KEY=${{ secrets.WEATHER_API_KEY }}

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

      - name: Set AKS Context
        uses: azure/aks-set-context@v3
        with:
          resource-group: cr_kr_crg
          cluster-name: aksclusterrdx40

      - name: Create secret in AKS (if not already present)
        run: |
          kubectl create secret generic weather-api-secret \
            --from-literal=WEATHER_API_KEY=${{ secrets.WEATHER_API_KEY }} \
            --dry-run=client -o yaml | kubectl apply -f -

      - name: Deploy to AKS
        run: kubectl apply -f deployment.yaml
  • and the deployment.yml which would in a typical project structure be in a k8s directory as so,
apiVersion: apps/v1
kind: Deployment
metadata:
  name: weather-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: weather-api
  template:
    metadata:
      labels:
        app: weather-api
    spec:
      containers:
        - name: weather-api
          image: acrrdx40.azurecr.io/weatherapiwrapper:latest
          ports:
            - containerPort: 80
          env:
            - name: WEATHER_API_KEY
              valueFrom:
                secretKeyRef:
                  name: weather-api-secret
                  key: WEATHER_API_KEY
---
apiVersion: v1
kind: Service
metadata:
  name: weather-api-service
spec:
  type: LoadBalancer
  selector:
    app: weather-api
  ports:
    - port: 80
      targetPort: 80

Pipelines From ACR To AKS

  • Now this can be done in two ways a)Manually triggered on github actions b) Detecting changes to ACR using Azure Event Grid and Azure Functions

  • The pipeline for ACR to AKS, is attached below

name: Deploy .NET Weather API

on:
  workflow_dispatch:

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

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

      - name: Set up .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: 8.0.x

      - name: Restore dependencies
        run: dotnet restore

      - name: Build
        run: dotnet build --configuration Release

      - name: Publish
        run: dotnet publish -c Release -o out
        
      - name: Azure Login
        uses: azure/login@v1
        with:
          creds: ${{ secrets.AZURE_CREDENTIALS }}

      - name: Set AKS Context
        uses: azure/aks-set-context@v3
        with:
          resource-group: cr_kr_crg
          cluster-name: aksclusterrdx40

      - name: Create secret in AKS (if not already present)
        run: |
          kubectl create secret generic weather-api-secret \
            --from-literal=WEATHER_API_KEY=${{ secrets.WEATHER_API_KEY }} \
            --dry-run=client -o yaml | kubectl apply -f -

      - name: Deploy to AKS
        run: kubectl apply -f deployment.yaml