Terraform “Zero Cloud”: how to create a GitLab Runner

PengB
6 min readDec 7, 2022

--

When talking about Terraform, people often think about Public Cloud. Terraform is widely known as an IaC(Infrastructure as Code) automation tool to manage Cloud resources. Usually used by provisioning the infras on Public Cloud: AWS, GCP, Azure, Alibaba, OVH etc. or other Private Cloud solutions: Cloudstack, VMware etc.

Terraform Providers

Using “as Code” tool, all configurations are written in files and some Libs should be used to translate them to some actions.

Terraform uses Terraform Providers to translate .tf configuration files to API Calls. For example, to work with AWS, you have terraform aws provider to interacte with AWS Cloud exposed API.

Terraform Provider

There are more than 2600 providers in Terraform Provider Registry.

Terraform won’t work without Providers.

It’s designed not only for Public Cloud but also powerfully for any plateforms that expose APIs.

Terraform “Zero Cloud” scenario

As it’s “Zero Cloud”, we don’t play with Cloud in this blog. I will use Terraform to create a containerized application locally: GitLab Private Runner registered on gitlab.com. You could also use it on any other On-Premise GitLab instances.

Blog labs

Objectif is to use terraform to deploy an application.

Here I will register on gitlab.com a gitlab runner running in docker on my laptop. But you can use it on any docker daemons.

I split .tf config into 3 files. You could do all-in-one, Terraform will run all .tf files together.

Project “terraform_gitlab_runner” structure:

➜  tree -a terraform_gitlab_runner
terraform_gitlab_runner
├── 00-provider.tf
├── 01-variables.tf
└── 02-runner.tf

Let’s check the 00-provider.tf. As discussed above, the provider is mandatory for terraform project:

############ 00-provider.tf ############
# define terraform providers
terraform {
required_providers {
gitlab = {
source = "gitlabhq/gitlab"
version = "~> 3.20.0"
}
docker = {
source = "kreuzwerker/docker"
version = "~> 2.23.1"
}
}
}

# authentication information for gitlab instance
provider "gitlab" {
token = var.gitlab_access_token
base_url = var.gitlab_url
}

# use local docker daemon by unix socket
# you could also use a remote docker daemon
provider "docker" {
host = "unix:///var/run/docker.sock"
}
  • Gitlab provider is amied to connect to GitLab and get registration token in this example.
  • Docker provider is used to connect to local docker daemon to run gitlab runner in a docker container.

To register a gitlab runner, we need:

  • a gitlab instance URL
  • the gitlab access token
  • gitlab project id(project in which to register a specific runner)

Here are the 3 variables defined in 01-variables.tf

############ 01-variables.tf ############
variable "gitlab_url" {
description = "GitLab URL"
type = string
# I register the runner to gitlab.com
default = "https://gitlab.com/"
}

variable "gitlab_access_token" {
description = "GitLab access token"
type = string
# GL personal access token
#default = "YOUR_GL_ACCESS_TOKEN_HERE"
}

variable "gitlab_project_id" {
description = "Project ID, register specific runner in this project"
type = number
# get from project home page
#default = YOUR_PROJECT_ID
}

Now the providers and variables are ready, I go to the Terraform Resources.

To register a runner, 1 data source and 3 resources in 02-runner.tf:

  • gitlab_project” data source to fetch project facts from gitlab.com
  • pull the gitlab runner image “gitlab/gitlab-runner:latest” on my laptop
  • run a gitlab runner container, and the runner agent will be running in this container
  • register the runner on gitlab and configure it by file config.toml
############ 02-runner.tf ############

# get project runner registration token
data "gitlab_project" "my-project" {
id = var.gitlab_project_id
}

# pull gitlab runner latest image
resource "docker_image" "gitlab-runner-image" {
name = "gitlab/gitlab-runner:latest"
keep_locally = "true"
}

# run gitlab runner container
resource "docker_container" "gitlab-runner" {
name = "gitlab-runner"
image = docker_image.gitlab-runner-image.image_id
restart = "always"
volumes {
host_path = "/var/run/docker.sock"
container_path = "/var/run/docker.sock"
}
volumes {
host_path = "/srv/gitlab-runner/config"
container_path = "/etc/gitlab-runner"
}
}

# register to gitlab project and generate config.toml
resource "docker_container" "oneshot-gitlab-runner-register" {
name = "oneshot-gitlab-runner-register"
image = docker_container.gitlab-runner.image
rm = "true"
remove_volumes = "true"

volumes {
host_path = "/srv/gitlab-runner/config"
container_path = "/etc/gitlab-runner"
}

command = ["register"]

env = ["REGISTER_NON_INTERACTIVE=true", "RUNNER_NAME=my gitlab runner", "CI_SERVER_URL=${var.gitlab_url}",
"REGISTRATION_TOKEN=${data.gitlab_project.my-project.runners_token}", "RUNNER_TAG_LIST=docker",
"REGISTER_LOCKED=false", "RUNNER_EXECUTOR=docker", "DOCKER_PRIVILEGED=false", "REGISTER_PAUSED=true",
"DOCKER_CPUS=1", "DOCKER_MEMORY=2", "DOCKER_IMAGE=alpine:latest", "REGISTER_RUN_UNTAGGED=true"]
}

I use “gitlab_project.my-project” data source to get project registration token and will be used in the register step.

“docker_container.gitlab-runner” is the runner running on my laptop and it mounts a volume on host /srv/gitlab-runner/config in which “docker_container.oneshot-gitlab-runner-register” will create the “config.toml”. We check the file in the end of this blog.

docker_container.oneshot-gitlab-runner-register” runs only the action “register” in order to create a config file “config.toml” and then exits. You can check the runner ENV available config by running command:

docker run gitlab/gitlab-runner:latest register --help

Just Run Terraform

Basic Terraform commands :

  • terraform init
  • terraform plan -out=main.tfplan
  • terraform apply main.tfplan

Init Terraform to download Providers.

➜  terraform init

Initializing the backend...

Initializing provider plugins...
- Finding gitlabhq/gitlab versions matching "~> 3.20.0"...
- Finding kreuzwerker/docker versions matching "~> 2.23.1"...
- Installing gitlabhq/gitlab v3.20.0...
- Installed gitlabhq/gitlab v3.20.0 (self-signed, key ID 0D47B7AB85F63F65)
- Installing kreuzwerker/docker v2.23.1...
- Installed kreuzwerker/docker v2.23.1 (self-signed, key ID BD080C4571C6104C)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

After init, in the current workspace, it creates a .terraform folder which contients the provider Libs and a provider version lock file .terraform.lock.hcl:

➜  tree -a terraform_gitlab_runner
terraform_gitlab_runner
├── 00-provider.tf
├── 01-variables.tf
├── 02-runner.tf
├── .terraform
│ └── providers
│ └── registry.terraform.io
│ ├── gitlabhq
│ │ └── gitlab
│ │ └── 3.20.0
│ │ └── linux_amd64
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ └── terraform-provider-gitlab_v3.20.0
│ └── kreuzwerker
│ └── docker
│ └── 2.23.1
│ └── linux_amd64
│ ├── CHANGELOG.md
│ ├── LICENSE
│ ├── README.md
│ └── terraform-provider-docker_v2.23.1
└── .terraform.lock.hcl

Then plan into a .tfplan file, the output is too long to copy, so ignored 😋:

➜  terraform plan -out=main.tfplan
...
...
Saved the plan to: main.tfplan

To perform exactly these actions, run the following command to apply:
terraform apply "main.tfplan"

and run apply (ignore the output):

➜  terraform apply main.tfplan
...
...
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

That’ all! Let’s check the result!

On gitlab, we see a new runner “my gitlab runner”:

On my laptop, the “gitlab-runner” container is running:

➜  docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
644bc9dda232 8e27429de8a3 "/usr/bin/dumb-init …" 4 minutes ago Up 4 minutes gitlab-runner

➜ docker logs gitlab-runner
Runtime platform arch=amd64 os=linux pid=7 revision=133d7e76 version=15.6.1
Starting multi-runner from /etc/gitlab-runner/config.toml... builds=0
Running in system-mode.

Configuration loaded builds=0
listen_address not defined, metrics & debug endpoints disabled builds=0
[session_server].listen_address not defined, session endpoints disabled builds=0
Initializing executor providers builds=0
Configuration loaded builds=0
Checking for jobs...nothing runner=_dCUEdG2

Check the config file /srv/gitlab-runner/config/config.toml created by “docker_container.oneshot-gitlab-runner-register

➜  cat /srv/gitlab-runner/config/config.toml
concurrent = 1
check_interval = 0
shutdown_timeout = 0

[session_server]
session_timeout = 1800

[[runners]]
name = "my gitlab runner"
url = "https://gitlab.com/"
id = 19621524
...
...
[runners.docker]
tls_verify = false
image = "alpine:latest"
memory = "2"
cpus = "1"
privileged = false
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
shm_size = 0

Now you have a gitlab runner running in a local docker container.

If you want, you could combine these .tf files with some Cloud VM provisioning terraform to create a VM and then deploy a runner.

Terraform is more powerful than just managing the Cloud resources!

Happy Reading!

--

--

PengB

an IT guy @Adeo DevDataOps. Like nature and self-challenge. Former @WorldlineExpertCommunity.