GitLab Runner в Kubernetes

Published: 2026-02-24

При запуске джоб GitLab CI внутри Kubernetes каждая джоба получает свежий под, ресурсы ограничиваются на уровне джобы, а хост раннера не нужно чистить. Разберём деплой через Helm, настройку executor-а Docker-in-Docker, лимиты ресурсов, кэш и типичные проблемы.


HelmRelease

yamlapiVersion: helm.toolkit.fluxcd.io/v2
kind: HelmRelease
metadata:
  name: gitlab-runner
  namespace: gitlab-runner
spec:
  chart:
    spec:
      chart: gitlab-runner
      version: "0.63.*"
      sourceRef:
        kind: HelmRepository
        name: gitlab
        namespace: flux-system
  values:
    gitlabUrl: "https://gitlab.example.com"
    runnerRegistrationToken: "${RUNNER_TOKEN}"
    concurrent: 10
    rbac:
      create: true
      clusterWideAccess: false
    runners:
      tags: "k8s,dev"
      locked: false
      config: |
        [[runners]]
          [runners.kubernetes]
            namespace = "gitlab-runner"
            image = "alpine:3.19"
            privileged = true
            [[runners.kubernetes.volumes.empty_dir]]
              name = "docker-certs"
              mount_path = "/certs/client"
              medium = "Memory"

privileged: true обязателен для Docker-in-Docker. В окружениях, где DinD не нужен, ставьте privileged: false и используйте kaniko или buildah.

concurrent: 10 — максимум параллельных джоб на все поды раннера. Если джобы встают в очередь, увеличьте значение — и заодно добавьте ресурсов узлам.


Токен регистрации через ESO

yamlapiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: gitlab-runner-token
  namespace: gitlab-runner
spec:
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: gitlab-runner-secret
    template:
      data:
        runner-registration-token: "{{ .token }}"
  data:
    - secretKey: token
      remoteRef:
        key: secret/infra/gitlab-runner
        property: registration_token

Затем ссылаемся на этот Secret в HelmRelease:

yamlvalues:
  existingRunnerSecret: gitlab-runner-secret

Никогда не вставляйте токен регистрации в values-файл или CI-переменные открытым текстом.


Лимиты ресурсов на джобу

toml[[runners]]
  [runners.kubernetes]
    [runners.kubernetes.pod_annotations]
      "cluster-autoscaler.kubernetes.io/safe-to-evict" = "false"
    [runners.kubernetes.build_container_resources]
      [runners.kubernetes.build_container_resources.requests]
        cpu = "500m"
        memory = "512Mi"
      [runners.kubernetes.build_container_resources.limits]
        cpu = "2"
        memory = "2Gi"
    [runners.kubernetes.helper_container_resources]
      [runners.kubernetes.helper_container_resources.requests]
        cpu = "100m"
        memory = "128Mi"
      [runners.kubernetes.helper_container_resources.limits]
        cpu = "500m"
        memory = "256Mi"

Requests без limits приводят к OOM kill. Слишком низкие limits замедляют сборки. cpu: "2" — разумный потолок для docker build.

safe-to-evict: "false" не даёт cluster autoscaler вытеснять поды с активными джобами при уменьшении числа узлов.


Сервис Docker-in-Docker

yaml# .gitlab-ci.yml
build:
  image: docker:24
  services:
    - name: docker:24-dind
      alias: docker
  variables:
    DOCKER_HOST: tcp://docker:2376
    DOCKER_TLS_CERTDIR: /certs
    DOCKER_TLS_VERIFY: 1
    DOCKER_CERT_PATH: /certs/client
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

Том docker-certs типа emptyDir общий для сервиса dind и build-контейнера — это обеспечивает mutual TLS без --privileged на сервисном контейнере.

Альтернативы DinD

Инструмент Privileged Скорость Примечание
Docker-in-Docker Да Быстро Требует privileged
Kaniko Нет Средне Медленнее, но без прав
Buildah Нет Средне Rootless

Кэш

toml[[runners]]
  [runners.cache]
    Type = "s3"
    Shared = true
    [runners.cache.s3]
      ServerAddress = "minio.infra.svc.cluster.local:9000"
      BucketName = "gitlab-runner-cache"
      Insecure = true

В .gitlab-ci.yml:

yamlcache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - .npm/
    - vendor/

Без кэша каждая CI-джоба заново скачивает все зависимости. С S3-кэшем повторные джобы выполняются в 3–5 раз быстрее.


Отладка

bash# Логи раннера
kubectl logs -n gitlab-runner deploy/gitlab-runner -f

# Поды с активными джобами
kubectl get pods -n gitlab-runner --field-selector=status.phase=Running

# Зайти в под джобы (пока она работает)
kubectl exec -n gitlab-runner <job-pod-name> -c build -- /bin/sh

# Список зарегистрированных раннеров
kubectl exec -n gitlab-runner deploy/gitlab-runner -- \
  gitlab-runner list

Типичные проблемы:

  • Под джобы завис в Pending — недостаточно ресурсов на узлах
  • Docker socket not found — убедитесь, что задан DOCKER_HOST=tcp://docker:2376, а не unix-сокет
  • TLS handshake failed — том docker-certs должен монтироваться по одному и тому же пути в build- и dind-контейнерах