GitLab: self-managed runner for CI/CD jobs on GCP VM instances

The globally shared set of GitLab runners for CI/CD jobs works well for building binaries, publishing images, and reaching out to publicly available endpoints for services and infrastructure building.

But the ability to run a private, self-managed runner can grant pipelines entirely new levels of functionality on several fronts:

  • Can communicate openly to private, internal services
  • Can process sensitive data or geographically fenced data
  • Can be sized to handle more complex processing tasks

In this article I will lead you through the deployment of a self-managed runner on a GCP VM instance, but there is no requirement to be deployed into a public hyperscaler.  The installed runner initiates the communication back to the GitLab server so the runner can be installed behind your corporate firewall or even in your home lab on a private network.

Create GCP VM Linux Runner

The GitLab self-managed Runner needs a host, and for this article we are going to use an Ubuntu image on the Google Cloud Platform.  How you create the GCP VM linux instance is really up to your preference:

  • Create using GCP console web UI
  • Create using gcloud
  • Create using Terraform

Here is a GCP documentation page that shows each of these methods.

Whatever the creation method, add a metadata field, “gl_executor=docker” to this VM Instance.  This can be done with gcloud add-metadata like shown below or in the metadata block of a Terraform google_compute_instance.

# to add metadata
gcloud compute instances add-metadata <VM_NAME> --metadata=gl_executor=docker

# to show metadata on VM instance, should see 'gl_executor' key
gcloud compute instances describe <VM_NAME> --format="value(metadata)"

This metadata field is used to mark the Docker Executor type (shell, docker, etc) for automation in later sections.

Prepare GCP Runner as Docker Executor

Your GitLab self-managed runner can be one of several Executor types, but for this article we will be creating a Docker Executor.  This means that CI/CD jobs will be run inside a Docker container, which means Docker must be installed at the host level.

SSH into the GCP VM instance, and install the Docker daemon as fully described in my article.

We will come back to this GCP VM in a later section to install the gitlab-runner as a service, but first we need to do some GitLab setup.

GitLab Access Token for registering new runner

We will be making a GitLab API call for registering a new runner.  This could be done from the GitLab web UI, but it provides better automation support to show the API call.

But in order to make the GitLab API call, we need an Access Token that has the correct privilege.  Here are your options, choose one:

We will also add the ‘read_api’ scope so that we can resolve the group/project name to its unique id as part of the automation.

Personal Access Token

Follow the documentation here and go to Main Page > Click your Avatar > Settings > Edit Profile > Access Tokens and “Add new token”, and use these values:

  • name=create-runner
  • scope=read_api,create_runner

Group Access Token

Follow the documentation here and go to Main Page > Click your Avatar > Settings > Edit Profile > Access Tokens and “Add new token”, and use these values:

  • name=create-runner
  • scope=read_api,create_runner
  • role=Owner

The ability to create a Group Access Token is only available with GitLab Premium, so if you are at the free tier you must fallback to using a GitLab Personal Access Token (PAT) to create a Group runner.

Project Access Token

Follow the documentation here and from your project repository go to Settings > Access Tokens and “Add new token”, and use these values:

  • name=create-runner
  • scope=read_api,create_runner
  • role=Maintainer

The ability to create a Project Access Token (when your project is in a group, not in the root namespace) is only available with GitLab Premium, so if you are at the free tier you must fallback to using GitLab Personal Access Token (PAT) to create a Project runner.

Register new runner

Define the base URL of your GitLab server, which for public GitLab is below, but will differ if you have a private instance.

GITLAB_URL=https://gitlab.com

The GitLab Runners API will return a message with the following fields: id, token, and token_expires_at. You must save the value for the token as it will only be displayed once and must be used later when registering agents (RUNNER_TOKEN).

Option 1: Register Group Runner

If you want to register a Runner for all the projects with membership in a GitLab Group, then register a group runner.

# either personal or group Access Token with 'read_api,create_runner' scope
ACCESS_TOKEN=<personal-or-group-AccessToken>

# GitLab group name and job tag it will consume
group_name=my-gitlab-group 
tag=mygrouprunner

# invoke API to resolve group id
sudo apt install jq -y
group_id=$(curl --fail -sX GET "$GITLAB_URL/api/v4/groups?search=$group_name" --header "PRIVATE-TOKEN: $ACCESS_TOKEN" | jq ".[] | select (.name==\"$group_name\").id")

# invoke API to register group runner
echo "about to register group runner $tag for group $group_name with id $group_id"
curl -sX POST $GITLAB_URL/api/v4/user/runners --data runner_type=group_type --data "group_id=$group_id" --data "description=$tag" --data "tag_list=$tag,$group_name" --header "PRIVATE-TOKEN: $ACCESS_TOKEN"

Option 2: Register Project Runner

If you want to register a Runner for a specific Project, then register a project runner.

# either personal or Project Access Token with 'read_api,create_runner' scope
ACCESS_TOKEN=<pesonal-or-project-AccessToken>

# GitLab project name and job tag it will consume
project_name=my-project-name
tag=myprojectrunner

# invoke API to resolve project id
sudo apt install jq -y
project_id=$(curl --fail -sX GET "$GITLAB_URL/api/v4/projects?simple=true&search=$project_name" --header "PRIVATE-TOKEN: $ACCESS_TOKEN" | jq ".[] | select (.name==\"$project_name\").id")

# invoke API to register project runner
echo "about to register project runner $tag for project $project_name with id $project_id"
curl -sX POST $GITLAB_URL/api/v4/user/runners --data runner_type=project_type --data "locked=true" --data "project_id=$project_id" --data "description=$tag" --data "tag_list=$tag,$project_name" --header "PRIVATE-TOKEN: $ACCESS_TOKEN"

Validation

You should now be able to see the Runner from the GitLab web UI.  It will be have a gray inactive icon because there is no active gitlab-runner attached yet.

Project Runner

Project > Settings > CI/CD > Project runners

Group Runner

Group > Build > Runners

Project in Group > Settings > CI/CD > Project Runners

And for projects that are in the group, if you scroll down, you will see the group runners.

Install gitlab-runner service on GCP VM

SSH into the GCP VM instance, and run the following commands to install the gitlab-runner as a systemd service.

# setup variables
export GITLAB_URL=https://gitlab.com
export GL_NAME=$(curl 169.254.169.254/computeMetadata/v1/instance/name --header "Metadata-Flavor:Google")
export GL_EXECUTOR=$(curl --fail 169.254.169.254/computeMetadata/v1/instance/attributes/gl_executor --header "Metadata-Flavor:Google")

# 'token' returned earlier when registering runner via API
export RUNNER_TOKEN=<token-value-from-API-Registration>

# install gitlab-runner binaries
sudo apt update
curl --location "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
sudo apt update && sudo apt install -y gitlab-runner

# register agent with GitLab
sudo gitlab-runner register --non-interactive --url $GITLAB_URL --token "$RUNNER_TOKEN" --name="$GL_NAME" --executor="$GL_EXECUTOR" --docker-image="alpine:3.18.4"

# show configuration values
sudo cat /etc/gitlab-runner/config.toml

# show status of service
sudo systemctl enable gitlab-runner
sudo systemctl status gitlab-runner --no-pager
sudo journalctl -u gitlab-runner --no-pager

If you see “Verifying runner… is not valid” and the contents of “/etc/gitlab-runner/config.toml” are not fully populated, the most likely cause is that the registration token has timed out.  Fix by running the registration API again, and get a fresh runner token.

The grayed out icons in the GitLab web UI from earlier should be green now, with an example below from an active Project Runner.

Validate job execution on Runner

Project Runner validation

My sample .gitlab-ci.yml uses the “myprojectrunner” tag on its tasks to explicitly send jobs to the custom Project Runner.

variables:
  MY_RUNNER_TAG: myprojectrunner

test-no-tag:
  script: |
    echo "hello from $CI_PROJECT_NAME with no tag" | tee /tmp/no-tag-$RANDOM.txt

test-with-tag:
  script: |
    echo "hello from $CI_PROJECT_NAME with tag $RUNNER_TAG " | tee /tmp/with-tag-$RANDOM.txt
  tags:
    - $MY_RUNNER_TAG

test-docker-custom-image:
  image: registry.gitlab.com/gitlab-pipeline-helpers/docker-python-ansible-user-venv:1.0.0
  script: |
    ansible --version | tee /tmp/docker-ansible-$RANDOM.txt
  tags:
    - $MY_RUNNER_TAG

The project runner is configured to run untagged jobs, so it will pick up ‘test-no-tag’ as well.  Clicking into the pipeline job details will show the job ran on “myprojectrunner” and not one of the globally shared runners.

Group Runner validation

My sample .gitlab-ci.yml uses the “mygrouprunner” tag on its tasks to explicitly send jobs to the custom Group Runner.

variables:
  MY_RUNNER_TAG: mygrouprunner

test-no-tag:
  script: |
    echo "hello from $CI_PROJECT_NAME with no tag" | tee /tmp/no-tag-$RANDOM.txt

test-with-tag:
  script: |
    echo "hello from $CI_PROJECT_NAME with tag $RUNNER_TAG " | tee /tmp/with-tag-$RANDOM.txt
  tags:
    - $MY_RUNNER_TAG

test-docker-custom-image:
  image: registry.gitlab.com/gitlab-pipeline-helpers/docker-python-ansible-user-venv:1.0.0
  script: |
    ansible --version | tee /tmp/docker-ansible-$RANDOM.txt
  tags:
    - $MY_RUNNER_TAG

The group runner is configured to run untagged jobs, so it will pick up ‘test-no-tag’ as well.  Clicking into the pipeline job details will show the job ran on “mygrouprunner” and not one of the globally shared runners.

 

REFERENCES

Gitlab, creating a Group/Project level Access Token (group level only available to premium subscription)

GitLab, creating a Personal Access Token

GitLab self-managed runners

GitLab Runners API, register a new runner

GitLab create, register, run your own project runner

GitLab repo, create GKE cluster and install gitlab agent using Terraform

GitLab, automate creation of GitLab runners

GitLab docs, automate runner creation

GitLab docs, Executor types (shell,docker)

rz-codes.com, GitLab runner on GCP VM instance

Ricardo Mendez, custom Gitlab executor for stderr/stdin

John Freeman, understanding GitLab runner

chicago.edu, good explanation of gitlab runner settings

 

NOTES

The same Group register token can be used from 2 different runner hosts, and it will add 2 runners to the same group and be shown as called out below.