End-to-End DevOps: Kubernetes Project Management with Github Actions on Azure AKS

Mehmet kanus
Hedgus
Published in
7 min readDec 20, 2023

This article provides a guide on how to implement DevOps principles and GitHub Actions for CI/CD at each stage of the software development process. Within this title, strategies, best practices, and tips for effectively managing projects on Kubernetes are addressed.

What is Github Actions?

GitHub Actions CI/CD (Continuous Integration/Continuous Delivery) is a set of features provided by GitHub to automate various aspects of the software development lifecycle. GitHub Actions allows you to define workflows as code directly in your GitHub repository, enabling automation of tasks such as building, testing, and deploying your applications.

Read more about Github Actions here.

Prerequisite:

  • Azure & Github Account
  • Basic understanding of Azure, Azure AKS, Azure CLI & Github Actions
  • Log in to Azure using Azure CLI and create a service principal. Then, store the service principal information in a location like the following
  • - AZURE_APP_ID
  • - AZURE_PASSWORD_ID
  • - AZURE_TENANT_ID

Lets, start with the configuration of the project

Step 1:- Create a Repository

  • If working independently, create a project/repository, or if within a company, establish a project/repository under a designated group. Ensure that your project/repositories are set to private. This is crucial because your access key and secret key will be stored in the variables section. https://github.com/mehmetkanus17/k8s.git

Step 2: After creating the repository, use Azure CLI in the terminal to create a service principal.

az ad sp create-for-rbac --name k8sservicep \
--role contributor \
--scopes /subscriptions/<your_subscriptions_id>

Step 3: After creating the service principal, in the ‘Settings/Secrets and Variables’ section under the repository or organization, we define the information from the service principal output as secrets. The purpose is to allow the service principal to connect to Azure as an API on our behalf and be able to create the necessary resources in Azure AKS.

Step 4:- Create a workflow file

  • For your application to be automatically deployed to Azure AKS, you need to create a workflow file.
  • Create azure-kubernetes-service.yaml file and add the below code to it
  • The below job will run on every push and pull request that happens on the main branch.

name: Build and deploy to k8s AKS
on:
push:
branches:
- main
# - staging

env:
CLUSTER_NAME: k8s-cluster
RESOURCE_GROUP: rg-k8s
WEB_IMAGE: ghcr.io/mkanus-inc/k8s/web
RESULT_IMAGE: ghcr.io/mkanus-inc/k8s/result
WEB_SERVER_MANIFEST_PATH: "${{ github.workspace }}/web_image"
RESULT_SERVER_MANIFEST_PATH: "${{ github.workspace }}/result_image"
REGISTRY: ghcr.io
LOCATION: eastus
NODE_SIZE: Standard_D8ds_v5

jobs:
creating-cluster:
if: "contains(github.event.head_commit.message, 'creating-cluster')"
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Log in with service principle to Azure
run: |
az login --service-principal --username ${{ secrets.AZURE_APP_ID }} --password ${{ secrets.AZURE_PASSWORD_ID }} --tenant ${{ secrets.AZURE_TENANT_ID }}

- name: creating resource group
run: |
az group create --name ${{ env.RESOURCE_GROUP }} --location ${{ env.LOCATION }}

- name: creating azure aks
run: |
az group create --name ${{ env.RESOURCE_GROUP }} --location ${{ env.LOCATION }}
az aks create --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }} --location ${{ env.LOCATION }} --kubernetes-version 1.28.3 --tier standard --enable-cluster-autoscaler --node-count 2 --min-count 2 --max-count 5 --max-pods 110 --node-vm-size ${{ env.NODE_SIZE }} --network-plugin azure --network-policy azure --load-balancer-sku standard --enable-addons monitoring --generate-ssh-keys

- name: connect to AKS cluster
run: |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }}

- name: creating imagePullSecrets
run: |
kubectl create secret docker-registry ghcr --docker-server=https://ghcr.io --docker-username=mehmetkanus17 --docker-password=ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX --docker-email=mehmetkanus17@gmail.com

web-build:
if: "contains(github.event.head_commit.message, 'creating-cluster') || contains(github.event.head_commit.message, 'run-manager') || contains(github.event.head_commit.message, 'run-all')"
needs: creating-cluster
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

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

- run: |
cd ${{ github.workspace }}/web_image

- name: tagging image and metadata
uses: docker/metadata-action@v4
id: metadata
with:
images: ${{ env.WEB_IMAGE }}
tags: |
${{ github.sha }}
latest

- name: login GHCR
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: build and push image to repository
uses: int128/kaniko-action@v1
with:
context: ${{ github.workspace }}/web_image
dockerfile: Dockerfile
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}

result-build:
if: "contains(github.event.head_commit.message, 'creating-cluster') || contains(github.event.head_commit.message, 'run-manager') || contains(github.event.head_commit.message, 'run-all')"
needs: creating-cluster
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

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

- run: |
cd ${{ github.workspace }}/result_image

- name: tagging image and metadata
uses: docker/metadata-action@v4
id: metadata
with:
images: ${{ env.RESULT_IMAGE }}
tags: |
${{ github.sha }}
latest

- name: login GHCR
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: build and push image to repository
uses: int128/kaniko-action@v1
with:
context: ${{ github.workspace }}/result_image
dockerfile: Dockerfile
push: true
tags: ${{ steps.metadata.outputs.tags }}
labels: ${{ steps.metadata.outputs.labels }}

mysql-deploy:
if: "contains(github.event.head_commit.message, 'creating-cluster') || contains(github.event.head_commit.message, 'run-proxy') || contains(github.event.head_commit.message, 'run-all')"
needs: creating-cluster
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Log in with service principle to Azure
run: |
az login --service-principal --username ${{ secrets.AZURE_APP_ID }} --password ${{ secrets.AZURE_PASSWORD_ID }} --tenant ${{ secrets.AZURE_TENANT_ID }}

- name: connect to AKS cluster
run: |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }}

- name: deploy proxy manifest yaml files
run: |
cd ${{ github.workspace }}/mysql_deployment
kubectl apply -f .

web-deploy:
if: "contains(github.event.head_commit.message, 'creating-cluster') || contains(github.event.head_commit.message, 'run-manager') || contains(github.event.head_commit.message, 'run-all')"
needs: web-build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Log in with service principle to Azure
run: |
az login --service-principal --username ${{ secrets.AZURE_APP_ID }} --password ${{ secrets.AZURE_PASSWORD_ID }} --tenant ${{ secrets.AZURE_TENANT_ID }}

- name: connect to AKS cluster
run: |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }}

- name: deploy web manifest yaml files
run: |
cd ${{ github.workspace }}/web_server
kustomize edit set image ${WEB_IMAGE}=:${{ github.sha }}
kubectl apply -k .

result_deploy:
if: "contains(github.event.head_commit.message, 'creating-cluster') || contains(github.event.head_commit.message, 'run-proxy') || contains(github.event.head_commit.message, 'run-all')"
needs: result-build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Log in with service principle to Azure
run: |
az login --service-principal --username ${{ secrets.AZURE_APP_ID }} --password ${{ secrets.AZURE_PASSWORD_ID }} --tenant ${{ secrets.AZURE_TENANT_ID }}

- name: connect to AKS cluster
run: |
az aks get-credentials --resource-group ${{ env.RESOURCE_GROUP }} --name ${{ env.CLUSTER_NAME }}

- name: deploy proxy manifest yaml files
run: |
cd ${{ github.workspace }}/result_server
kustomize edit set image ${RESULT_IMAGE}=:${{ github.sha }}
kubectl apply -k .
  • You can define and use variables according to your needs.
  • This job is triggered when a commit message contains the phrase ‘creating-cluster’. It runs on the latest Ubuntu environment. The choice of Ubuntu as the operating system for this job is driven by its widespread use, extensive package support, and a large community. The job is designed to handle tasks related to the creation of a cluster, leveraging the capabilities of the Ubuntu environment to provide an automated workflow for initializing necessary resources.
  • “Checkout repository,” utilizes the GitHub Actions `actions/checkout@v3` action. Its primary function is to retrieve the contents of the repository where the workflow is executed. This step is crucial at the beginning of most workflows, ensuring that subsequent actions have access to the latest version of the codebase. In this case, it is responsible for fetching the files and source code from the repository to enable further actions in the workflow.
  • The rest of the jobs in the workflow file are as explained in my previous article. Therefore, I won’t go into much detail here.

Step 5: Check the output

  • Now, As soon as you commit your workflow file Github will trigger the action and the resources will be going to create on the Azure AKS and Github Packages (container registry).
  • After running the job you will see that all the steps run perfectly and there was no error. So you will have passedwritten in the green color as each step job successfully.

Azure AKS resources.

  • cluster and nodes
  • pods
  • github packages
  • Don’t forget to delete all resources.

--

--