Running GitLab Locally in Kubernetes
Photo by unsplash

Introduction

In a previous post, I discussed running Kubernetes locally on my Mac M1 using tools like orbstack and kind, complete with local DNS resolution.

Problem

My primary challenge was running GitLab locally. While several methods exist, such as using docker with docker-compose, these approaches often led to startup issues and failed to work reliably.

I then explored using the GitLab Helm chart, with comprehensive documentation available here. My goals included achieving local DNS resolution and correctly handling self-signed certificates. In this case orbstack proved to be highly effective.

Solution 1

The solution leverages the tool orbstack

Initial Setup

Orbstack simplifies the process by making the domain k8s.orb.local available on your local DNS on a Mac. This allows us to deploy GitLab using the Helm chart with the correct configuration right from the start.

Here is the example code. I am using the values-minikube-minimum.yaml, which has been patched to work with orbstack.

kubectl config use-context orbstack

kubectl create ns gitlab
helm repo add gitlab https://charts.gitlab.io/
helm repo update

# https://docs.gitlab.com/charts/releases/7_0.html
# as the chart version is mapped here to the app/GitLab version
helm upgrade --install gitlab gitlab/gitlab \
 --timeout 120s \
 --namespace gitlab \
 --version 7.11.2 \
 -f https://gitlab.com/gitlab-org/charts/gitlab/-/raw/v7.11.2/examples/values-minikube-minimum.yaml \
 --set global.hosts.domain="k8s.orb.local" \
 --set global.hosts.externalIP="" \
 --set nginx-ingress.enabled=true

# wait until the webservice is up
kubectl -n gitlab rollout status --timeout 2m deploy gitlab-webservice-default
# test without valid cert chain: -k flag
curl -fsSkIL gitlab.k8s.orb.local

In the second step, I store the Certificate chain (includes the CA), as I need it for the Mac Keychain to see the Root CA as valid:

# crt with chain
kubectl get secret -n gitlab gitlab-wildcard-tls-chain -o jsonpath={'.data.gitlab\.k8s\.orb\.local\.crt'} | base64 --decode > gitlab.k8s.orb.local.crt
# add it to the Mac keychain
security add-trusted-cert -d -r trustRoot -k $HOME/Library/Keychains/login.keychain-db gitlab.k8s.orb.local.crt

And it shoud look like this and trusted gitlab-keychain

Now test the access again, but with a valid keychain

# Note: no -k
curl -fsSIL gitlab.k8s.orb.local

If you get the following error on Mac curl: (35) OpenSSL/3.0.13: error:16000069:STORE routines::unregistered scheme, it is because of the version of curl. For me it was:

curl --version

curl 8.7.1 (aarch64-apple-darwin23.5.0) libcurl/8.7.1 OpenSSL/3.0.13 zlib/1.3.1 brotli/1.1.0 zstd/1.5.6 libidn2/2.3.7 libpsl/0.21.5 libssh2/1.11.0 nghttp2/1.61.0
Release-Date: 2024-03-27

I fixed it by running the command from a docker container:

docker run -it --network host -v $(pwd):/build alpine:3 sh
apk update && apk add curl
curl --version
cp /build/gitlab.k8s.orb.local.crt  /usr/local/share/ca-certificates/
update-ca-certificates 
WARNING: ca-cert-gitlab.k8s.orb.local.pem does not contain exactly one certificate or CRL: skipping
# this message can be ignored. CRT is loaded neverthelesse
# see https://github.com/gliderlabs/docker-alpine/issues/30#issuecomment-303480386

curl -IL gitlab.k8s.orb.local

In action, it looks like this.

gitlab-curl

Now open your browser on https://gitlab.k8s.orb.local and login with user root and password, which you retrieve as follows 🎉

kubectl get secret -n gitlab gitlab-gitlab-initial-root-password -ojsonpath='{.data.password}' | base64 --decode ; echo

The login screen looks as follows:

gitlab-root-login

Now that I have GitLab running and are logged in as root, let’s create a Personal access token (PAT) gitlab-pat

Then you can test the access:

# Note: no -k flag, as the crt is in our mac keychain
export GITLAB_TOKEN=<token>
curl -IL -H "PRIVATE-TOKEN: $GITLAB_TOKEN" https://gitlab.k8s.orb.local/api/v4/version | jq 

will give the details

{
  "version": "16.11.2-ee",
  "revision": "d210b947e3e",
  "kas": {
    "enabled": true,
    "externalUrl": "wss://kas.k8s.orb.local",
    "version": "16.11.2"
 },
  "enterprise": true
}

Docker-in-Docker (DinD) jobs

Download the file https://gitlab.com/gitlab-org/charts/gitlab/-/raw/v7.11.2/examples/values-minikube-minimum.yaml

and then I adapt it as follows. But note to adapt the path of your docker socket. You can find this out via

docker context ls

NAME         DESCRIPTION                               DOCKER ENDPOINT                
default      Current DOCKER_HOST based configuration   unix:///var/run/docker.sock
orbstack *   OrbStack                                  unix:///Users/<user>/.orbstack/run/docker.sock

and replace this in the following path gitlab-runner.runner.config then in path = "...".

See now the full example:

# values-minikube.yaml
# This example intended as baseline to use Minikube for the deployment of GitLab
# - Minimized CPU/Memory load, can fit into 3 CPU, 6 GB of RAM (barely)
# - Services that are not compatible with how Minikube runs are disabled
# - Some services entirely removed, or scaled down to 1 replica.
# - Configured to use 192.168.99.100, and nip.io for the domain

# Minimal settings
global:
  ingress:
    configureCertmanager: false
    class: "nginx"
  hosts:
    domain: k8s.orb.local # <== Change here
    # No external IP
    # externalIP: 192.168.99.100 # <== Change here
  # Disable Rails bootsnap cache (temporary)
  rails:
    bootsnap:
      enabled: false
  shell:
    # Configure the clone link in the UI to include the high-numbered NodePort
    # value from below (`gitlab.gitlab-shell.service.nodePort`)
    port: 32022
# Don't use certmanager, we'll self-sign
certmanager:
  install: false
# Use the `ingress` addon, not our Ingress (can't map 22/80/443)
nginx-ingress:
  enabled: true # <== Change here
# Save resources, only 3 CPU
prometheus:
  install: false
gitlab-runner: # <== Change here the whole block
  install: true 
  # "gitlab-wildcard-tls-chain" assumes your release name is "gitlab". If it is set to something else,
  #   replace "gitlab" below with your own release name.
  certsSecretName: gitlab-wildcard-tls-chain
  runners:
    privileged: true
    # for dind https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#docker-in-docker-with-tls-disabled-in-kubernetes
    # fix https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2578#note_603009975
    config: |
      [[runners]]
        [runners.kubernetes]
          [runners.kubernetes.volumes]
            [[runners.kubernetes.volumes.host_path]]
              name = "docker-socket"
              path = "/Users/mavogel/.orbstack/run/docker.sock"
              mount_path = "/var/run/docker.sock"
              read_only = false      
# Reduce replica counts, reducing CPU & memory requirements
gitlab:
  webservice:
    minReplicas: 1
    maxReplicas: 1
  sidekiq:
    minReplicas: 1
    maxReplicas: 1
  gitlab-shell:
    minReplicas: 1
    maxReplicas: 1
    # Map gitlab-shell to a high-numbered NodePort to support cloning over SSH since
    # Minikube takes port 22. However on orbstack, we could use port 22, but we leave it
    service:
      type: NodePort
      nodePort: 32022
registry:
  hpa:
    minReplicas: 1
    maxReplicas: 1

As a next step, I will add runners with dind, so I can make use of the docker socket 💡 within the GitLab runner.

helm upgrade --install gitlab gitlab/gitlab \
 --timeout 120s \
 --namespace gitlab \
 --version 7.11.2 \
 -f adapted-values-minikube-minimum.yaml

You can now see a gitlab-gitlab-runner-* pod running. The content of the lower split are the logs of that pod and how it successfully registers with the Gitlab instance 🎉

gitlab-runner-register

Now, of course, we want to test this, and the best way is to create an example project with the following .gitlab-ci.yml:

docker:test:
  image: docker:24.0.5
  variables:
    # I use the DOCKER_HOST default value as I mount the socket
    # and connect insecurely
    DOCKER_TLS_CERTDIR: ""
  script:
 - printenv | grep DOCKER | sort
 - docker version

If we now run this pipeline, we see the following output that confirms that the docker cli client in the container could successfully connect to the underlying server/daemon we mounted into the runner 🎉

gitlab-dind-job

Teardown

Of course, we want to tear everything down after our tests, to free the resources. You can achieve this as follows:

helm uninstall gitlab -n gitlab --wait
# watch teardown
watch kubectl get po -A
kubectl delete ns gitlab

At the next rollout of the helm chart you will still have the data for Gitlab, because it is stored in a pv.

If you want to fully clean your Gitlab data, remove the pvc's as well:


kubectl get pvc -n gitlab
# remove them
kubectl delete pvc -n gitlab -l release=gitlab
kubectl delete pvc -n gitlab -l app.kubernetes.io/instance=gitlab

Now everything is clean 🗑️

Solution 2

If you cannot or are not allowed to use orbstack due to company rules, you can also use kind. See also the official Gitlab docs for more details, as it is also the recommended way to develop its helm chart.

Ok, let’s start it off.

Start kind with the following config kind-gitlab-ssl.yaml

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  # to be able to run dind in the GitLab pipelines 
  # see https://github.com/kubernetes-sigs/kind/issues/1281#issuecomment-578052484
  extraMounts:
  - hostPath: /var/run/docker.sock # <-- or use the orbstack socket
    containerPath: /var/run/docker.sock
  extraPortMappings:
    # containerPort below must match the values file:
    #   nginx-ingress.controller.service.nodePorts.https
    # Change hostPort if port 443 is already in use.
  - containerPort: 32443
    hostPort: 443
    listenAddress: "0.0.0.0"
    # containerPort below must match the values file:
    #   nginx-ingress.controller.service.nodePorts.ssh
    # Using high-numbered hostPort assuming port 22 is
    #   already in use.
  - containerPort: 32022
    hostPort: 32022
    listenAddress: "0.0.0.0"

with the command:

kind create cluster \
 --name gitlab \
 --image kindest/node:v1.29.2 \
 --config kind-gitlab-ssl.yaml \
 --wait 1m

When the cluster is up and running, you can use the example files from the Gitlab helm repository:

# get your ip:
## mac: ipconfig getifaddr en0
## linux: hostname -i
export MY_IP=$(ipconfig getifaddr en0)

# switch the k8s context
kubectl config use-context kind-gitlab

# install Gitlab
helm upgrade --install gitlab gitlab/gitlab \
 --timeout 120s \
 --namespace gitlab \
 --set global.hosts.domain=$MY_IP.nip.io \
 --set global.hosts.externalIP="" \
 -f https://gitlab.com/gitlab-org/charts/gitlab/-/raw/v7.11.2/examples/kind/values-base.yaml \
 -f https://gitlab.com/gitlab-org/charts/gitlab/-/raw/v7.11.2/examples/kind/values-ssl.yaml \
 --set nginx-ingress.enabled=true

The teardown is simpler here, as we can delete the whole cluster:

kind delete cluster --name gitlab

Conclusion

In this blog post, I shared my exploration of how to set up GitLab locally to evolve pipelines and its jobs by using orbstack or kind and the corresponding helm chart.

Enjoy ❤️

Like what you read? You can hire me, or drop me a message to see which services 💻 may help you 👇