GitLab CI/CD

CI staat voor Continuous Integration en is een onderdeel van CI/CD, Continuous Integration/Continuous Deployment. Een mond vol maar het gaat om het automatisch uitvoeren van scripts om software(packages) uit te rollen op systemen waarbij de automatische taken zorgen voor minder fouten en consistente uitvoer.

In GitLab zal de code in een repository wijzigen zodra een software-ontwikkelaar daar mee bezig is. Op het moment dat de wijzigingen ge-pushed worden zal een trigger geactiveerd worden om een script uit te voeren wat de applicatie zal ‘builden‘ en de wijzigingen zal ‘testen‘. Dit gebeurt telkens als een wijziging of aanvulling ge-pushed wordt. Telkens op dezelfde manier en dat is waarom dit proces Continuous Integration genoemd wordt.

Als na het builden en testen het script ook nog eens de wijzigingen of aanvullingen automatisch gaat ‘deployen‘, dan spreken we over ‘Continuous Deployment‘. Indien er een handmatige actie vereist is om deze laatste stap uit te voeren, dan noemen we dit ‘Continuous Delivery‘.

Om GitLab CI/CD te kunnen gebruiken dient er een bestand in de repository van het project aanwezig te zijn met de naam ‘.gitlab-ci.yml‘. Hierin worden de stappen gedefinieerd die uitgevoerd zullen gaan worden op het moment dat een wijziging wordt ge-pushed. GitLab herkent dit bestand en triggert de uitvoer dan automatisch. De uitvoer van het CI/CD pipeline, zoals dit proces heet, wordt gedaan door een zogenaamde ‘gitlab runner‘.

GitLab Runners

GitLab Runners zijn applicaties die de uitvoer van een pipeline script verzorgt. Deze runners draaien, bij voorkeur, op ander machines dan de GitLab server en kunnen via de package manager geïnstalleerd worden. GitLab Runners kunnen ook in een docker container draaien en zodoende dus ook uitgerold worden in een Kubernetes cluster.

Op een Linux host kan de gitlab-runner geïnstalleerd worden met de package manager, ‘apt’ of ‘yum’ of welke dan ook gebruikt wordt op de Linux variant.

Nadat de gitlab-runner is geinstalleerd dient deze gekoppeld te worden aan het GitLab project. Dit verloopt middels de ‘registration‘ waarbij een ‘token‘ gegenereerd wordt door de GitLab server. Dit kan per project, per groep of per GitLab server zijn.

De GitLab Runner applicatie dient daarnaast een ‘executor‘ te hebben, deze bepaald hoe het script uitgevoerd wordt. Een executor kan bijvoorbeeld een docker engine zijn, of een Linux shell op een Raspberry Pi, of een powershell op een Windows machine, etc. Handig is het om dit met de ‘tags‘ van de runner mee te geven.

Een voorbeeld van een GitLab Runner registration met docker als executor:

gitlab-runner register -n \ 
  --url https://gitlab.digitalinfo.nl/ \
  --registration-token <de token uit de settings>  \
  --executor docker \
  --tag-list "kubernetes,production" \
  --description "Docker Runner" \
  --docker-image "docker:stable" \
  --docker-privileged

Het token wordt gegenereerd door GitLab en kan teruggevonden worden in de instellingen van het project (specific runners), van de groep (group runners), of van de server (shared runners). Bijvoorbeeld in Settings -> CI/CD -> Runners van een project:


Koppeling Gitlab – Kubernetes

Binnen GitLab kan een Kubernetes cluster gekoppeld worden waardoor o.a. de gitlab-runners op een Kubernetes cluster uitgevoerd zullen worden. Als Administrator, kies voor ‘Kubernetes‘ via de ‘Admin Area‘.

Wat nodig is voor het koppelen is het volgende:

  • Naam van het cluster
  • Environment scope
  • URL van het cluster
  • Certificaat van de Certificate Authority voor dit cluster
  • Token van een Service Account met voldoende rechten

De naam van het cluster is een vereiste, de environment scope is standaard ‘*‘ maar kan ook bv. ‘production’ zijn om onderscheid te maken tussen meerdere gekoppelde clusters. Deze ‘environment’ kan dan meegegeven worden in ‘gitlab-ci.yml’ stages.

De URL van het cluster kan teruggevonden worden via ‘kubectl cluster-info’, het certificaat kan uit een secret opgehaald worden:

$ kubectl get secrets | grep default-token
$ kubectl get secret default-token-xxxxx -o jsonpath="{['data']['ca.crt']}" | base64 --decode

Een service-account voor GitLab kan toegevoegd worden om de token hiervan te kunnen gebruiken voor de koppeling. Gebruik bv. de volgende manifest om dit Service Account aan te maken, bv. gitlab-admin.yaml:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitlab
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: gitlab-admin
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: gitlab
  namespace: kube-system 

De kan applied worden om vervolgens de token hiervan op te halen:

$ kubectl apply -f gitlab-admin.yaml
$ kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep gitlab | awk '{print $1}')

Gebruik nu de token die op het scherm wordt getoond.

Na het toevoegen van het cluster kan een applicatie toegevoegd worden. Kies voor de zojuist toegevoegde cluster

via ‘Applicaties’ kan nu de Gitlab-Runner toegevoegd worden:

Er wordt een namespace ‘gitlab-managed-apps‘ aangemaakt met hierin de applicatie ‘runner-gitlab-runner‘ die de runners zal aanmaken op het moment dat een pipeline gaat lopen vanuit GitLab. Bijvoorbeeld voor GitLab project #20:


.gitlab-ci.yml

Het script dat de pipeline triggert is ‘.gitlab-ci.yml‘ en hierin worden de stappen vastgelegd voor bv. build, test en deploy. Een voorbeeld om een docker image te maken aan de hand van een Dockerfile en deze in de registry te plaatsen:

variables:
  DOCKER_IMAGE: registry.digitalinfo.nl/digital-info/rancher/go-app:v1.1

before_script:
  - docker login -u $CI_DEPLOY_USER -p CI_DEPLOY_PASSWORD $CI_REGISTRY

stages:
  - build

build:
   stage: build
   script:
     - docker build -t $DOCKER_IMAGE .
     - docker push $DOCKER_IMAGE 

De variable DOCKER_IMAGE bevat de naam van de docker image, de stap ‘before_script’ voert een taak uit voordat de stages uitgevoerd worden en stage build voert de docker-commando’s uit. Het resultaat is dat telkens als iets ge-pushed wordt zal een nieuwe image in de registry geplaatst worden.

In de CI/CD Pipelines van het project wordt de status weergegeven:


config.toml voorbeeld:

concurrent = 1
 check_interval = 0
 [session_server]
   session_timeout = 1800
 [[runners]]
   name = "some_name"
   url = "https://my.url.com"
   token = "a_token"
   executor = "docker"
   pre_build_script = "export DOCKER_HOST=tcp://docker:2375"
   environment = ["DOCKER_DRIVER=overlay2", "DOCKER_TLS_CERTDIR="]
   [runners.custom_build_dir]
   [runners.cache]
     [runners.cache.s3]
     [runners.cache.gcs]
     [runners.cache.azure]
   [runners.docker]
     tls_cert_path = ""
     tls_verify = false
     image = "ruby:2.6"
     privileged = true
     disable_entrypoint_overwrite = false
     oom_kill_disable = false
     disable_cache = false
     volumes = ["/cache"]
     shm_size = 0

Hierna is alleen nog het volgende nodig in de .gitlab-ci.yml:

services:
   - docker:dind