In the realm of modern software development, the adoption of Continuous Integration (CI) and Continuous Deployment (CD) practices has become an essential standard. These methodologies, at the heart of DevOps practices, offer a multitude of benefits for developers, technical teams, and the business as a whole. Utilizing GitHub Actions to automate CI/CD workflows, combined with deployment on a Microk8s cluster, constitutes a powerful approach for accelerating the software development lifecycle while ensuring high quality and reliability of applications.
Quick introduction
Advantages of CI/CD
Speed and Efficiency in Deliveries: CI/CD allows for complete automation of the build and deployment process, thus reducing manual errors and significantly accelerating time to market.
Improved Code Quality: With automated tests running on every commit, developers can quickly detect and fix bugs, thereby improving the overall quality of the code.
Continuous Feedback: CI/CD provides developers with instant feedback on the state of their code, facilitating continuous improvement and rapid adaptation to changes.
GitHub Actions for CI/CD Automation
Seamless Integration: GitHub Actions integrates seamlessly with GitHub, offering a native solution for automating CI/CD workflows without leaving the GitHub ecosystem.
Customization and Flexibility: With the ability to create custom workflows, GitHub Actions adapts to almost any build and deployment scenario, offering unmatched flexibility.
Community and Support: GitHub Actions benefits from a vast user community and an abundance of shared resources, allowing developers to easily find pre-built actions and best practices.
Microk8s for Deployment
Lightweight and Simple: Microk8s is a Kubernetes distribution designed to be lightweight and easy to install, making it ideal for development, testing, and production environments, especially for projects requiring a minimal system footprint.
Portable and Versatile: Compatible with Linux, Windows, and macOS architectures, Microk8s ensures increased application portability and facilitates deployment across various environments.
Production-Ready: With automatic updates and a rich ecosystem of add-ons, Microk8s is a robust solution for deploying applications in production environments, while offering simplified cluster management.
The combination of GitHub Actions for CI/CD automation and Microk8s for deployment creates a highly efficient, resilient, and scalable development workflow. This pairing fosters a culture of rapid innovation, where developers can focus on creating value rather than on the operational aspects of application deployment.
This article will focus on the CI/CD part. Other features, such as high availability, monitoring and application exposure will be presented later.
What we need to start
- A GitHub account.
- A self-hosted Ubuntu server. You will need at least 3 servers for high availability, not covered at this step.
- And obviously, the sources of your application.
Yes, that's all...
Initial setup
First install Microk8s with this simple command:
sudo snap install microk8s
To build the application, you will need Docker installed. Some alternatives are available but off topic.
sudo snap install docker
sudo addgroup --system docker
sudo adduser $USER docker
newgrp docker
sudo snap disable docker
sudo snap enable docker
To facilitate further microk8s commands, add your user to the microk8s group. It will also be needed by our simple CI/CD script to deploy without needing a password.
sudo usermod –a –G microk8s $USER
newgrp microk8s
A few words on RBAC
Role-Based Access Control (RBAC) is a method for regulating access to computer or network resources based on the roles of individual users within an organization. RBAC helps in ensuring that only authorized users have access to certain resources, and their permissions are aligned with their specific roles and responsibilities. This approach simplifies the management of user permissions, enhances security by minimizing the risk of unauthorized access, and makes it easier to comply with regulatory requirements. By assigning roles with predefined permissions, RBAC streamlines the process of granting or revoking access, making it a fundamental component of secure system administration and access management strategies.
While RBAC is not the main focus of this article, it's necessary to briefly touch upon it. Some features of Microk8s that we will enable later will not be configured in the same way depending on whether we activate RBAC or not. So let's activate it right away to avoid future manipulations.
microk8s enable rbac
Enable the built-in container registry
To store container images, we need a registry. In this simple example, we will take advantage of the Microk8s built-in container registry. The GitHub action runner will build our application container image and push it to the registry. Then Containerd, the default Kubernetes container runtime, will pull our image from the registry to instanciate container.
Let's enable the registry:
microk8s enable registry
Since the registry must be reachable from any node of our cluster, we need to add some configuration to Containerd to let it access the registry not only from localhost but also from an external interface, and we to make it on all the nodes. Let's say our first node, the one that hosts the registry, has the 172.16.1.1 ip address, just replace with the right one in what follows.
sudo mkdir -p /var/snap/microk8s/current/args/certs.d/172.16.1.1:32000
sudo nano /var/snap/microk8s/current/args/certs.d/172.16.1.1:32000/hosts.toml
Copy/paste this then save and exit:
server = "http://172.16.1.1:32000"
[host."http://172.16.1.1:32000"]
capabilities = ["pull", "resolve"]
Restart MicroK8s to have the new configuration loaded:
microk8s stop
microk8s start
The last step in this chapter is to configure Docker to access this registry to push images.
sudo nano /var/snap/docker/current/config/daemon.json
Copy/paste this then save and exit:
{
"log-level": "error",
"insecure-registries" : ["172.16.1.1:32000"],
"max-concurrent-uploads": 1
}
Restart Docker:
sudo snap restart docker
Install a GitHub Action self-hosted runner
Go sign-in to your GitHub account. To add an action runner to your repository and download it, just follow the link for instructions: https://docs.github.com/en/actions/hosting-your-own-runners/managing-self-hosted-runners/adding-self-hosted-runners
You will see instructions showing you how to download the runner application and install it on your self-hosted runner machine.
Open a shell on your self-hosted runner machine and run each shell command in the order shown.
When prompted, chose wisely a label for this runner. You will be able to select the runner from your workflow by label (see next chapter).
At the end, to install the runner as a service so it keeps working after system restarts:
sudo ./svc.sh install
Add a GitHub action workflow
This is the final step. Now that we have all already set up, we need to add a file to our solution to describe the build and deployment process.
In your source code root, add a .github
folder and a workflows
subfolder. In the workflows
subfolder, add a yml file. For convenience, name the file with the name of your app and the branch you want to deploy (e.g. myapp-develop.yml).
Since it is a yaml file, pay attention to the indentation.
First, give a name to this workflow. You will see it in the GitHub Actions tab of your repository.
name: MyApp develop
...
Then, add a trigger. You can automatically trigger the workflow on push on a branch ("push"). Or you may want to control when you trigger it from your GitHub repository ("workflow_dispatch"). Here, we want both: trigger the workflow on every push and have the ability to trigger it by hand.
...
on:
workflow_dispatch:
push:
branches: ['develop']
...
If you need some environment variables, you can add some easily.
...
env:
REGISTRY: "172.16.1.1:32000"
IMAGE_NAME: "myapp"
NAMESPACE: "myapp-namespace"
DOCKERFILE: "./src/MyApp/Dockerfile"
DOCKERCONTEXT: "."
...
And finaly, the job and its steps. Notice the MyWiselyChosenLabel in the runs-on directive. The steps are quite easy. First we create a tag for our container image, based on date and time. Then we get the source code, build the image and push it to the microk8s registry. The last step consist in telling Microk8s the new image to use for our application.
...
jobs:
build-and-push-image:
runs-on: [self-hosted,X64,Linux,MyWiselyChosenLabel]
permissions:
contents: read
packages: write
steps:
- name: Set current date to tag the built image with
id: version
run: echo "builddate=$(date +'%Y-%m-%dT%H-%M-%S')" >> $GITHUB_OUTPUT
- name: Checkout repository
uses: actions/checkout@v3
- name: Build docker image
run: "docker build --no-cache -f ${{ env.DOCKERFILE }} -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.builddate }} ${{ env.DOCKERCONTEXT }}"
- name: Push docker image to registry
run: "docker image push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.builddate }}"
- name: Rollout restart MyApp deployment with new version
run: "microk8s kubectl set image deployment/${{ env.IMAGE_NAME }}-deployment ${{ env.IMAGE_NAME }}=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.version.outputs.builddate }} -n ${{ env.NAMESPACE }} --record"
Our workflow is very simple. It will deploy your app but lacks of automated unit tests run. This will be part of a future article.
Final thoughts
At this step, we have seen how to create a complete CI/CD system with GitHub, Docker and Microk8s. However we miss some steps for it to work. Indeed we need a Kubernetes deployment manifest for our application. Key aspects and functionalities of Kubernetes Deployments include automated pod management, scaling, rolling updates and rollbacks, versioning and revision history and self-healing. We will focus on this topic in the next article of this series.
Have a goat day 🐐
Join the conversation.