Use GitLab Runner to build a CI/CD environment in an ACS cluster

更新时间:
复制 MD 格式

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

image

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.yaml and installed with Helm

  • Kubernetes executor — configured under runners.config in values.yaml

For the full configuration reference, see Configure Jihu GitLab Runner.

Prerequisites

Before you begin, ensure that you have:

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.

Important

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.

Important

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.