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:
- Personal Access Token – with the scope ‘create_runner’
- Group Access Token – needs to have scope ‘create_runner’
- Project Access Token – with the scope ‘create_runner’
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 Personal Access Token
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.