DevOps Blog

Deploying a Kubernetes app with an automated Gitlab pipeline

Written by Yohan Gracia | 06-Nov-2019 23:00:00

How does a GitLab pipeline work?

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.

What runners do I need and how to deploy them?

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).

Deploy your Kubernetes app with the pipeline

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.