gitlab, gitlab runners, k3s, and building all the things

Saturday, June 25, 2022

Although originally using Gitea for hosting, and Drone for my CI engine, this setup proved to be difficult. I continually had problems with Done, and actually had the Gitea database corrupt at one point. With all this I decided to just move back to Gitlab. They run a great service, and it’s what we use at work so I am quite familiar with them and their CI/CD setup. I installed the Gitlab Runner on my internal K3s cluster, and am now using that to do all my builds and deployments behind my firewall. I hit a few bumps along the way, so I wanted to document the final setup for posterity and anyone else who might be having issues as well.

Gitlab Runners

Gitlab runners are deployments/pods that run on your cluster and periodically check in with Gitlab to see if there are any builds to run. They can be attached to your projects in one of two ways. The first way is to register a runner for every project. Although you can do this, frankly it’s a little crazy. Maybe if a single project has a very special build environment that is different from the rest of your setup, but generally this would not be the recommended way to use runners. The second way is to attach the runner to a group. Now, if you are in an enterprise/work environment this could mean that you attach them at the top most level (i.e. your organization), or maybe for each team that has it’s own group. For personal use, although it seems weird, you need to create a group and then attach the Gitlab runner to that specific group. This will allow every project in that group to access the runner.

Once you have your group setup, navigate to the “CI/CD” section and click on “Runners”. Up in the top corner you will see a drop down that says “Register a group runner”. From here you will see the registration token. Save that as we will need it for the Helm chart next.

Installing the Runner using Helm

Now add the Helm chart for the runner and update the repos:

$ helm repo add gitlab https://charts.gitlab.io
$ helm repo update

Next you will need to setup your values.yaml file. Here is the full values.yaml from Gitlab. Below is the values.yaml that I used:

image:
  registry: registry.gitlab.com
  image: gitlab-org/gitlab-runner
  # tag: alpine-v11.6.0

imagePullPolicy: IfNotPresent

revisionHistoryLimit: 2

gitlabUrl: https://gitlab.com

runnerRegistrationToken: "CHANGE ME BEFORE RUNNING"

terminationGracePeriodSeconds: 3600
concurrent: 10
checkInterval: 30

logLevel: warn
logFormat: text

rbac:
  create: true

  rules:
    - apiGroups: [""]
      resources: ["pods", "configmaps", "jobs", "secrets", "cronjobs", "services", "deployments", "daemonsets", "statefulsets", "pods/attach", "pods/exec"]
      verbs: ["get", "list", "watch", "create", "update","delete"]
    - apiGroups: [""]
      resources: ["pods/log"]
      verbs: ["get"]

  clusterWideAccess: true

  podSecurityPolicy:
    enabled: false
    resourceNames:
    - gitlab-runner

runners:
  config: |
    [[runners]]
      [runners.kubernetes]
        namespace = "{{.Release.Namespace}}"
        image = "ubuntu:16.04"
        service_account = "gitlab-runner"    

  maximumTimeout: "3600"
  runUntagged: true

A few important things to point out in here. First, make sure you set your runnerRegistrationToken. This is what tells Gitlab what group the runner belongs to. Second, is the RBAC security setup. My runner is fairly restricted to the things that I think it would do on a normal Helm deployment. You may need more or less. I also have clusterWideAccess set to true in order to allow my runner to work across namespaces. Finally, under the runners configuration make sure to set the service_account that the runners will run as! This is kind of a hidden setting and I can tell you from an hour of pounding my head into a wall that deployments will not work without this setting.

Once your values.yaml file is ready to go, install it into your cluster. I like to keep my resources separate so I deployed it to a new namespace named gitlab:

$ k create ns gitlab
$ kns gitlab
$ helm install gitlab-runner gitlab/gitlab-runner -f values.yaml

Once your pod is installed and running you should see it online in the Gitlab interface under the “Runners” section where we got the registration token from before. Now it’s time to use it!

Setting up CI/CD with Gitlab Runner

I’m not going to explain a whole lot here as everyone’s setup is going to be different. I do want to go over two major pieces here though. Building containers without access to a Docker daemon, and deploying into your cluster using Helm.

Building containers

When using shared runners with Gitlab, you generally use a dind container (Docker in Docker) which allows you to build a container as your normally would. This is not the case now with Kubernetes. For building containers with Kubernetes, I highly recommend using Kaniko. Here is the build section out of my .gitlab-ci.yml template that uses Kaniko and the built-in Gitlab container registry (because it’s free). This works out of the box with no additional configuration:

build:
  image:
    name: gcr.io/kaniko-project/executor:debug
    entrypoint: [""]
  stage: build
  variables:
    REGISTRY: ${CI_REGISTRY_IMAGE}
    TAG: ${CI_COMMIT_SHORT_SHA}
    KANIKO_VERBOSITY: "info"
  script:
    - mkdir -p /kaniko/.docker
    - export GITLAB_AUTH=$(echo -n "${CI_REGISTRY_USER}:${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')
    - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"${GITLAB_AUTH}\"}}}" > /kaniko/.docker/config.json
    - >-
      /kaniko/executor
      --cache=true
      --context "${CI_PROJECT_DIR}"
      --dockerfile "${CI_PROJECT_DIR}/Dockerfile"
      --destination ${REGISTRY}:${TAG}
      --verbosity ${KANIKO_VERBOSITY}      

Deploying with Helm

As long as you setup your runners correctly, the snippet of code below should just work™. Make sure to change the name and namespace for your own project.

deploy:
  image: alpine/helm:latest
  stage: deploy
  variables:
    TAG: ${CI_COMMIT_SHORT_SHA}
  script:
    - cd helm
    - helm upgrade NAME . -f values.yaml -n NAMESPACE --set image.tag=${TAG}
  only:
    - master
    - main

This is where you will see failures if you didn’t setup the service account for the runners. If that is the case you will probably see an error that says something like:

Error: UPGRADE FAILED: query: failed to query with labels: secrets is forbidden: User "system:serviceaccount:gitlab:default" cannot list resource "secrets" in API group "" in the namespace "gitlab"

This error message is telling you that the serviceAccount it’s using is in the gitlab namespace, and it’s named default. This is not correct, as the RBAC settings for the installation created a serviceAccount user named gitlab-runner. Go back and double check that your serviceAccount was created and make sure your runner configuration is set to use it.

I hope this helps someone to not have to search around and stumble for a few hours like I did!

devopskubernetesciinfrastructure

nginx-ingress, cert-manager, and default wildcard certificates