Firstly, I need to explain how GitLab pipeline works. You must have a .gitlab-ci.yml
file in your repository. This file contains the different steps of your pipeline. Here is an example:
# Pipeline steps list
stages:
- test
- build
- deploy
# Lint and unit tests
lint-test:
stage: test
image:
name: node:18.12.1
script:
- npm install
- npm run lint && npm run test
# Build step
build-push:
stage: build
image:
name: docker:stable
services:
- docker:stable-dind
script:
- docker build --build-arg NPM_TOKEN=$NPM_RO_TOKEN -t $REPO_NAME .
- docker tag $REPO_NAME $REPO_REGISTRY_URL:$TAG
- docker push $REPO_REGISTRY_URL:$TAG
- docker rmi $REPO_NAME $REPO_REGISTRY_URL:$TAG
As you can see, I defined here 3 stages: test, build, and deploy. I also have 2 jobs that belong to the stages: lint-test and build-push (they could be named after their stage but I choose to name them accordingly to their actions).
Stages are executed sequentially following the defined order, and if there are several jobs for one stage, they are executed in parallel. In this example, they are executed every time you push some code in your repository, but Gitlab allows you to add a filter: execute a step on a specific branch, tag, and even trigger it manually.
You can notice that I don’t have a single job in the deployment stage: I will show it to you in the last part of this tutorial.
To understand the remaining parts of this CI, you must know what a gitlab-runner is. It is the process that executes your jobs. Gitlab-runners can be a process running on a VM, a pod in Kubernetes, and even scale as you need to use them. You can have several runners and differentiate them using tags and assign specific jobs or projects to them.
When you run a job, you can choose to use the default runner image or to execute your job in a specific image: in my example, the lint-test job is running inside a node:18.12.1
image. I also set up a service in the second job: a service is a Docker container linked to your job and executed at the same time.
For example, you can use it to link db instances for your tests or use docker-in-docker as I did.
As you can see, there are several environment variables: you have to add a variable
bloc. It can be global or specific to your job. The other way is to set them in the Settings>CI/CD>Variables
tab in your gitlab.
Note that you can set them for your group or only your repository. There are also predefined ci-cd variables for every pipeline, such as commit hash or branch name.
The test and build stages can be run on the runner you want, depending on your actual setup. Sometimes a docker or docker+machine executor is better to use. To deploy this kind of runner you will find more information in the GitLab documentation.
Let’s focus on the deployment stage. To deploy your app on your Kubernetes cluster it’s a good idea to have a GitLab runner as a pod. It can be deployed in a few minutes with the official Helm Chart and allows you to leverage the serviceAccount capabilities of Kubernetes in order to avoid handling authentication with API server directly in your CI.
Here are the simplified values.yaml for the helm chart:
## GitLab Runner Image
##
## By default it's using registry.gitlab.com/gitlab-org/gitlab-runner:alpine-v{VERSION}
## where {VERSION} is taken from Chart.yaml from appVersion field
## Specify a imagePullPolicy
imagePullPolicy: IfNotPresent
## The GitLab Server URL (with protocol) that want to register the runner against
gitlabUrl: https://<GITLAB_URL>
## The Registration Token for adding new Runners to the GitLab Server.
runnerRegistrationToken: ""
## Unregister all runners before termination
unregisterRunners: true
## Configure the maximum number of concurrent jobs
concurrent: 10
## Defines in seconds how often to check GitLab for a new builds
checkInterval: 30
## For RBAC support:
rbac:
create: true
## Run the gitlab-bastion container with the ability to deploy/manage containers of jobs
## cluster-wide or only within namespace
clusterWideAccess: true
## Configure integrated Prometheus metrics exporter
metrics:
enabled: true
## Configuration for the Pods that that the runner launches for each new job
runners:
## Default container image to use for builds when none is specified
image: ubuntu:22.04
## Specify whether the runner should be locked to a specific project: true, false. Defaults to true.
locked: false
## Specify the tags associated with the runner. Comma-separated list of tags.
tags: "k8s,dev"
## Run all containers with the privileged flag enabled
## To disable it you may need to change images and scripts used in your CI/CD
## Needed to build docker image with docker-dind
privileged: true
## Distributed runners caching
cache: {}
## Build Container specific configuration and limits
builds: {}
## Service Container specific configuration and limits
services: {}
## Helper Container specific configuration and limits
helpers: {}
## Service Account to be used for runners
serviceAccountName: gitlab-runner-gitlab-runner
## Configure resource requests and limits
resources: {}
## Affinity for pod assignment
affinity: {}
## Node labels for pod assignment
nodeSelector: {}
## List of node taints to tolerate
tolerations: []
## Configure environment variables that will be present when the registration command runs
envVars:
- name: RUNNER_EXECUTOR
value: kubernetes
## list of hosts and IPs that will be injected into the pod's hosts file
hostAliases: []
## Annotations to be added to manager pod
podAnnotations: {}
You must have Helm 3 installed on your computer before executing the following steps. Fill in the previous values.yaml
with your own values and then execute the 2 commands below (you must be connected to your Kubernetes cluster):
helm repo add gitlab https://charts.gitlab.io
helm install --namespace <NAMESPACE> --name gitlab-runner -f <PATH>/values.yaml gitlab/gitlab-runner
Your gitlab runner should be running in your Kubernetes cluster and you should see it inside the Gitlab interface in the CI/CD>Runners
section (of the group or repository you fetched the registration token).
There is one condition to deploy your project with the job I showed you, your application must use Helm and so you need a path to your Helm Chart.
# Deployment step
deploy:
stage: deploy
image: alpine/helm:3.10.2
script:
- helm init
- helm --namespace $NAMESPACE upgrade -i $REPO_NAME --set image.tag=$TAG -f $PATH_TO_VALUES
tags:
- k8s
- dev
You can note the tags notation is used to select the Kubernetes runner you just deployed.
Your runner and pipeline are now ready, you just have to add the previous job to your .gitlab-ci.yml
to deploy your Kubernetes app using Gitlab-CI! Gitlab also provides Kubernetes integration tools which allow you to manage and monitor your cluster using the GitLab interface!
If you are still indecisive between using GitLab pipeline or GitHub and CodeBuild, this article can help you form your own opinion.
You can check out our article about Kubernetes productivity tips and tricks to go further on pipeline improvement.