GitLab Runner is an open source application written in Go that runs CI/CD jobs dispatched by GitLab. Because CI workloads are bursty by nature, Container Compute Service (ACS) is a good match: it provisions resources on demand, scales fast, and automatically caches images to speed up later job starts. This topic explains how to deploy GitLab Runner in an ACS cluster using the Kubernetes executor, build container images, and reduce CI resource costs with ACS scaling policies.
How it works
GitLab Runner uses the Kubernetes executor to create a pod for each CI job. The runner manager pod communicates with the Kubernetes API to schedule job pods, waits for results, and reports them back to GitLab. After each job completes, the job pod is deleted.
Key configuration areas:
-
Runner manager pod — initialized via
values.yamland installed with Helm -
Kubernetes executor — configured under
runners.configinvalues.yaml
For the full configuration reference, see Configure Jihu GitLab Runner.
Prerequisites
Before you begin, ensure that you have:
-
Connected to your ACS cluster using kubectl. See Connect to a Kubernetes cluster using kubectl
-
Helm installed locally
-
A GitLab instance accessible from the cluster
-
A runner token from your GitLab instance. See Generate a runner token for a project or group
Install GitLab Runner
This example installs gitlab-runner 17.3.1 using Helm chart version 0.68.1. For other versions, see GitLab Runner Helm chart.
Step 1: Get the GitLab Runner chart
helm repo add gitlab https://charts.gitlab.io
helm repo update gitlab
helm pull gitlab/gitlab-runner --version 0.68.1 && tar zvxf gitlab-runner-0.68.1.tgz
Step 2: Create values.yaml
Three parameters are required to get GitLab Runner running:
| Parameter | Description |
|---|---|
gitlabUrl |
The URL of your GitLab server. Example: https://gitlab.example.com |
runnerToken |
The runner token from your GitLab instance. The runner manager uses this token as an identifier to associate the pods and Secrets it creates. |
rbac.create |
Set to true to automatically create a service account with the required RBAC rules. |
The following values.yaml shows a working configuration with recommended settings:
## GitLab Runner Image
##
## By default it's using registry.gitlab.com/gitlab-org/gitlab-runner:alpine-v{VERSION}
## where {VERSION} is taken from Chart.yaml from appVersion field
##
## ref: https://gitlab.com/gitlab-org/gitlab-runner/container_registry/29383?orderBy=NAME&sort=asc&search[]=alpine-v&search[]=
##
## Note: If you change the image to the ubuntu release
## don't forget to change the securityContext;
## these images run on different user IDs.
##
...
## The GitLab Server URL (with protocol) that want to register the runner against
## ref: https://docs.gitlab.com/runner/commands/index.html#gitlab-runner-register
##
gitlabUrl: https://jihulab.com/
## DEPRECATED: The Registration Token for adding new Runners to the GitLab Server.
##
## ref: https://docs.gitlab.com/ee/ci/runners/new_creation_workflow.html
##
# runnerRegistrationToken: ""
## The Runner Token for adding new Runners to the GitLab Server. This must
## be retrieved from your GitLab instance. It is the token of an already registered runner.
## ref: (we don't have docs for that yet, but we want to use an existing token)
##
runnerToken: "glrt-t3_sz6xxxxxxxxxDsWF77"
#
## Unregister all runners before termination
##
## Updating the runner's chart version or configuration will cause the runner container
## to be terminated and created again. This may cause your Gitlab instance to reference
## non-existant runners. Un-registering the runner before termination mitigates this issue.
## ref: https://docs.gitlab.com/runner/commands/index.html#gitlab-runner-unregister
##
unregisterRunners: true
## When stopping the runner, give it time to wait for its jobs to terminate.
##
## Updating the runner's chart version or configuration will cause the runner container
## to be terminated with a graceful stop request. terminationGracePeriodSeconds
## instructs Kubernetes to wait long enough for the runner pod to terminate gracefully.
## ref: https://docs.gitlab.com/runner/commands/#signals
terminationGracePeriodSeconds: 3600
## Set the certsSecretName in order to pass custom certficates for GitLab Runner to use.
## Provide resource name for a Kubernetes Secret Object in the same namespace,
## this is used to populate the /home/gitlab-runner/.gitlab-runner/certs/ directory
## ref: https://docs.gitlab.com/runner/configuration/tls-self-signed.html#supported-options-for-self-signed-certificates-targeting-the-gitlab-server
##
# certsSecretName:
## Configure the maximum number of concurrent jobs
## ref: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section
##
concurrent: 10
...
## For RBAC support:
rbac:
## Specifies whether a Role and RoleBinding should be created
## If this value is set to `true`, `serviceAccount.create` should also be set to either `true` or `false`
##
create: true
## Define the generated serviceAccountName when create is set to true
## It defaults to "gitlab-runner.fullname" if not provided
## DEPRECATED: Please use `serviceAccount.name` instead
generatedServiceAccountName: ""
...
## Configuration for the Pods that the runner launches for each new job
##
runners:
# runner configuration, where the multi line string is evaluated as a
# template so you can specify helm values inside of it.
#
# tpl: https://helm.sh/docs/howto/charts_tips_and_tricks/#using-the-tpl-function
# runner configuration: https://docs.gitlab.com/runner/configuration/advanced-configuration.html
config: |
[[runners]]
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
image = "alpine"
## Absolute path for an existing runner configuration file
## Can be used alongside "volumes" and "volumeMounts" to use an external config file
## Active if runners.config is empty or null
configPath: ""
...
The following table describes the key parameters:
| Parameter | Description |
|---|---|
gitlabUrl |
The URL of the GitLab server where you want to register the runner. Example: https://gitlab.example.com. |
runnerToken |
The runner token obtained from GitLab. The runner manager uses this token to associate the pods and Secrets it creates. |
rbac |
Enables role-based access control (RBAC). When set to create: true, a service account is created automatically. |
concurrent |
Maximum number of concurrent jobs. Default: 10. For high-concurrency workloads, increase the resource limits on the runner manager pod. |
unregisterRunners |
Runs the unregister command before the manager pod terminates. This prevents the GitLab instance from holding references to stale runners after pod restarts. See FAQ. |
runners.config |
Runner configuration specified as a multi-line string. Modify this string to change executor settings. |
Step 3: Install GitLab Runner
helm install --namespace default gitlab-runner -f values.yaml --version 0.68.1 gitlab/gitlab-runner
Verify the installation
kubectl get pod | grep gitlab
Expected output:
gitlab-runner-7c5b4xxxxx-xxxxx 1/1 Running 0 5m17s
Build a container image
GitLab Runner supports two approaches for building container images in a Kubernetes cluster. Choose based on whether your environment allows privileged mode:
| Approach | Requires privileged mode | Use when |
|---|---|---|
| Docker-in-Docker (dind) | Yes | You have privileged mode enabled and need full Docker daemon access |
| kaniko | No | Your environment restricts privileged mode (recommended for most cases) |
Build an image with Docker-in-Docker
Docker-in-Docker runs the Docker daemon directly inside the build container.
This mode requires privileged = true. To perform this operation, you must submit a ticket to enable privileged mode for ACS pods before proceeding.
For a sample project, see Java demo.
Step 1: Update values.yaml
To update runner configuration, run helm upgrade rather than editing the ConfigMap directly....
runners:
config: |
[[runners]]
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
image = "registry.cn-hangzhou.aliyuncs.com/acs-demo-ns/docker:27-dind"
privileged = true
cpu_limit = 2
cpu_request = 2
memory_limit = "4Gi"
memory_request = "4Gi"
ephemeral_storage_request = "30Gi"
ephemeral_storage_limit = "30Gi"
[[runners.kubernetes.volumes.empty_dir]]
name = "docker-certs"
mount_path = "/certs/client"
medium = "Memory"
[[runners.feature_flags]]
FF_USE_POD_ACTIVE_DEADLINE_SECONDS = true
...
| Parameter | Description |
|---|---|
image |
The base image for dind builds. This example uses the Docker community dind image. |
privileged |
Enables privileged mode for the runner pod. Required for Docker-in-Docker. |
cpu_request / cpu_limit |
CPU allocation for the container. ACS minimum is 0.25 vCores. Adjust based on build requirements. |
memory_request / memory_limit |
Memory allocation. ACS minimum is 0.5 GiB. Adjust based on build requirements. |
ephemeral_storage_request / ephemeral_storage_limit |
Ephemeral storage allocation. ACS provides 30 GiB free by default and automatically caches images to speed up later jobs. |
FF_USE_POD_ACTIVE_DEADLINE_SECONDS |
Enables the activeDeadlineSeconds feature gate, which sets the pod TTL (time to live) to the job timeout. This terminates pods that become disassociated due to unknown causes. |
For other parameters, see Kubernetes executor.
Step 2: Create the `.gitlab-ci.yml` file
This example launches the dockerd process directly in the build container, without a separate service container.
image: registry.cn-hangzhou.aliyuncs.com/acs-demo-ns/docker:27-dind
stages:
- build
variables:
# When using dind service, you must instruct Docker to talk with
# the daemon started inside of the service. The daemon is available
# with a network connection instead of the default
# /var/run/docker.sock socket.
DOCKER_HOST: tcp://localhost:2376
#
# The 'docker' hostname is the alias of the service container as described at
# https://docs.gitlab.com/ee/ci/services/#accessing-the-services.
# If you're using GitLab Runner 12.7 or earlier with the Kubernetes executor and Kubernetes 1.6 or earlier,
# the variable must be set to tcp://localhost:2376 because of how the
# Kubernetes executor connects services to the job container
# DOCKER_HOST: tcp://localhost:2376
#
# Specify to Docker where to create the certificates. Docker
# creates them automatically on boot, and creates
# `/certs/client` to share between the service and job
# container, thanks to volume mount from config.toml
DOCKER_TLS_CERTDIR: "/certs"
# These are usually specified by the entrypoint, however the
# Kubernetes executor doesn't run entrypoints
# https://gitlab.com/gitlab-org/gitlab-runner/-/issues/4125
DOCKER_TLS_VERIFY: 1
DOCKER_CERT_PATH: "$DOCKER_TLS_CERTDIR/client"
before_script:
- echo "before task"
- sh /usr/local/bin/dockerd-entrypoint.sh &
- sleep 10s
build_image:
stage: build
tags:
- demo
script:
- docker info
- sleep 1d
- docker build --network host -t demo:v1.0.0 -f Dockerfile .
- docker push demo:v1.0.0
| Step | Description |
|---|---|
before_script |
Launches the dockerd process and waits for it to initialize. |
build_image |
Builds the image. Uses host network mode (--network host) so the dockerd process can reach the external network through the container network. |
Build an image with kaniko
kaniko is an open source tool that builds container images without requiring Docker or privileged mode. kaniko is suitable for scenarios where the system limits access to Docker or the system runs in the Kubernetes environment.
GitLab CI uses the Shell executor for command execution, so the base image must support sh. This example uses the debug version of the kaniko executor image, which includes a shell.
Step 1: Update values.yaml
...
runners:
config: |
[[runners]]
[runners.kubernetes]
namespace = "{{.Release.Namespace}}"
ephemeral_storage_request = "30Gi"
ephemeral_storage_limit = "30Gi"
[[runners.feature_flags]]
FF_USE_POD_ACTIVE_DEADLINE_SECONDS = true
...
Step 2: Create the `.gitlab-ci.yml` file
stages:
- build
variables:
KUBERNETES_POD_LABELS_1: "alibabacloud.com/compute-class=general-purpose"
KUBERNETES_POD_LABELS_2: "alibabacloud.com/compute-qos=best-effort"
build_image:
stage: build
image:
name: registry.cn-hangzhou.aliyuncs.com/acs-demo-ns/kaniko-executor:v1.21.0-amd64-debug
entrypoint: [""]
tags:
- demo
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
Reduce CI costs with ACS scaling policies
You can select one of the following methods to use the BestEffort pods provided by ACS to reduce the resource costs of job execution in the CI/CD pipeline.
Method 1: Configure BestEffort QoS for all pods in the cluster
We recommend that you configure the BestEffort QoS class for all pods in the cluster. You can also select other QoS classes based on your business requirements.
Add pod labels in values.yaml to apply BestEffort QoS to every job pod the runner creates:
...
runners:
config: |
[[runners]]
[runners.kubernetes]
...
pod_labels_overwrite_allowed = ".*" # Allow the system to overwrite the labels of project pods based on variables.
[[runners.kubernetes.pod_labels]]
"app" = "acs-gitlab-runner"
"alibabacloud.com/compute-class" = "general-purpose"
"alibabacloud.com/compute-qos" = "best-effort" # Configure the BestEffort QoS class for pods.
...
To apply different QoS classes per project, add the corresponding KUBERNETES_POD_LABELS_* variables in the project's .gitlab-ci.yml instead. See the kaniko example above.
Method 2: Configure a ResourcePolicy for automatic failover
A ResourcePolicy lets you define a custom scheduling priority. Job pods are scheduled to BestEffort capacity first. When BestEffort pods are exhausted in a region, the system automatically creates Default pods to ensure the continuity and availability of your CI jobs.
apiVersion: scheduling.alibabacloud.com/v1alpha1
kind: ResourcePolicy
metadata:
name: rp-demo
namespace: default
spec:
selector: # Specify the label selector used to select pods. In this example, the ResourcePolicy is applied to pods that have the app=stress label.
app: acs-gitlab-runner
units: # Specify the priorities of different types of nodes for pod scheduling.
- resource: acs # Prioritize BestEffort resources.
podLabels:
alibabacloud.com/compute-class: general-purpose
alibabacloud.com/compute-qos: best-effort
- resource: acs # Apply for Default resources when the preceding resources are out of stock.
podLabels:
alibabacloud.com/compute-class: general-purpose
alibabacloud.com/compute-qos: default
FAQ
Why are there residual pods after the runner manager pod restarts?
When the manager pod is recreated with a different runner token, it loses the ability to manage the job pods created under the previous token. Those pods remain but are no longer tracked.
To avoid this:
-
Recreate the runner in GitLab and update the runner token in the configuration file.
-
If you use registration tokens, mount a Secret to the manager pod containing the token from the initial registration. This ensures the same token is reused across restarts.
-
Enable
FF_USE_POD_ACTIVE_DEADLINE_SECONDS. This sets a TTL on each job pod equal to the job timeout, so orphaned pods are cleaned up automatically.