GitLab Runner с Kubernetes executor: масштабируемый CI/CD

Зачем Kubernetes executor для GitLab Runner

Стандартный GitLab Runner с shell или docker executor работает на одном сервере — количество параллельных задач ограничено его ресурсами. Kubernetes executor запускает каждую CI/CD-задачу в отдельном поде кластера: под создаётся при старте задачи и удаляется по завершении. Это даёт:

  • Автомасштабирование: 100 параллельных пайплайнов потребляют ресурсы только во время выполнения
  • Изоляция: каждая задача работает в чистом окружении без артефактов от предыдущих сборок
  • Гибкость: разные задачи используют разные образы — Java 17, Node 20, Python 3.12 — без предустановки на хосте
  • Экономия: в облаке (GKE, YMK) поды используют ресурсы кластера, масштабируемого через Cluster Autoscaler

В этом руководстве развернём GitLab Runner в Kubernetes через Helm, настроим кеширование, секреты, лимиты ресурсов и оптимизируем пайплайны.

Установка GitLab Runner через Helm

Добавляем Helm-репозиторий GitLab:

helm repo add gitlab https://charts.gitlab.io
helm repo update

Создаём файл значений values.yaml:

gitlabUrl: https://gitlab.company.ru/
runnerToken: "glrt-xxxxxxxxxxxxxxxxxxxx"

rbac:
  create: true
  clusterWideAccess: false

runners:
  config: |
    [[runners]]
      name = "k8s-runner"
      executor = "kubernetes"
      [runners.kubernetes]
        namespace = "gitlab-ci"
        image = "ubuntu:22.04"
        privileged = false
        cpu_limit = "2"
        cpu_request = "500m"
        memory_limit = "4Gi"
        memory_request = "1Gi"
        service_cpu_limit = "1"
        service_memory_limit = "2Gi"
        poll_timeout = 600
        [runners.kubernetes.pod_annotations]
          "cluster-autoscaler.kubernetes.io/safe-to-evict" = "true"
        [runners.kubernetes.node_selector]
          "workload" = "ci"
        [runners.kubernetes.pod_labels]
          "app" = "gitlab-ci-job"

Деплоим:

kubectl create namespace gitlab-ci
helm install gitlab-runner gitlab/gitlab-runner \
    --namespace gitlab-ci \
    -f values.yaml

Получение токена Runner

В GitLab перейдите в Settings → CI/CD → Runners → New project runner. Выберите теги (например, k8s, docker) и скопируйте токен вида glrt-xxx. Для group-level runner используйте настройки группы. Runner можно также зарегистрировать через API:

curl --request POST "https://gitlab.company.ru/api/v4/user/runners" \
    --header "PRIVATE-TOKEN: glpat-xxxx" \
    --data "runner_type=project_type&project_id=42&tag_list=k8s,docker"

Настройка кеширования для ускорения сборок

Без кеша каждая задача скачивает зависимости заново (npm install, pip install, mvn dependency:resolve). Настроим кеширование через S3-совместимое хранилище:

# В values.yaml добавляем секцию кеша
runners:
  cache:
    cacheType: s3
    s3BucketName: gitlab-ci-cache
    s3BucketLocation: ru-central1
    s3ServerAddress: storage.yandexcloud.net
    secretName: s3-cache-secret

Создаём секрет:

kubectl create secret generic s3-cache-secret \
    --namespace gitlab-ci \
    --from-literal=accesskey="YCAJE..." \
    --from-literal=secretkey="YCP..."

В .gitlab-ci.yml используем кеш:

build:
  stage: build
  image: node:20-alpine
  tags: [k8s]
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
  script:
    - npm ci
    - npm run build

Кеш сохраняется между запусками пайплайна для одной ветки. Первый запуск загружает зависимости, последующие используют кеш.

Сборка Docker-образов внутри Kubernetes

Сборка образов в Kubernetes — нетривиальная задача, так как privileged-режим создаёт риски безопасности. Безопасные альтернативы:

Kaniko: сборка без привилегий

Kaniko собирает Docker-образы из Dockerfile без Docker-демона:

# .gitlab-ci.yml
build-image:
  stage: build
  tags: [k8s]
  image:
    name: gcr.io/kaniko-project/executor:v1.22.0-debug
    entrypoint: [""]
  script:
    - |
      /kaniko/executor \
        --context "${CI_PROJECT_DIR}" \
        --dockerfile "${CI_PROJECT_DIR}/Dockerfile" \
        --destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}" \
        --destination "${CI_REGISTRY_IMAGE}:latest" \
        --cache=true \
        --cache-repo="${CI_REGISTRY_IMAGE}/cache"

Для аутентификации в Container Registry создайте конфигурационный файл:

build-image:
  before_script:
    - mkdir -p /kaniko/.docker
    - echo "{\"auths\":{\"${CI_REGISTRY}\":{\"auth\":\"$(printf "%s:%s" "${CI_REGISTRY_USER}" "${CI_REGISTRY_PASSWORD}" | base64 | tr -d '\n')\"}}}"
      > /kaniko/.docker/config.json

Buildah: OCI-сборка

Buildah — альтернатива от Red Hat, работающая в rootless-режиме:

build-image:
  stage: build
  tags: [k8s]
  image: quay.io/buildah/stable:latest
  variables:
    STORAGE_DRIVER: vfs
  script:
    - buildah bud --layers -t ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA} .
    - buildah login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - buildah push ${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA}

Секреты и переменные окружения

Для передачи секретов (API-ключи, пароли БД) в CI-задачи используйте Kubernetes secrets через конфигурацию Runner:

# В values.yaml
runners:
  config: |
    [[runners]]
      executor = "kubernetes"
      [runners.kubernetes]
        namespace = "gitlab-ci"
        [runners.kubernetes.pod_security_context]
          run_as_non_root = true
          run_as_user = 1000
        [[runners.kubernetes.volumes.secret]]
          name = "app-secrets"
          mount_path = "/secrets"
          read_only = true

Создаём секрет в кластере:

kubectl create secret generic app-secrets \
    --namespace gitlab-ci \
    --from-literal=DB_PASSWORD="p@ssw0rd" \
    --from-literal=API_KEY="sk-xxx"

В задаче секреты доступны как файлы в /secrets/. Также можно использовать GitLab CI/CD Variables (Settings → CI/CD → Variables) — они передаются как переменные окружения в каждую задачу.

Оптимизация пайплайнов

Несколько практик для ускорения CI/CD в Kubernetes:

Параллельное выполнение

Разбивайте тесты на параллельные задачи:

test:
  stage: test
  tags: [k8s]
  parallel: 4
  script:
    - npm run test -- --shard=${CI_NODE_INDEX}/${CI_NODE_TOTAL}

Каждый шард запускается в отдельном поде, сокращая время тестирования в 4 раза.

Условное выполнение

Запускайте задачи только при изменении соответствующих файлов:

build-backend:
  stage: build
  tags: [k8s]
  rules:
    - changes:
        - backend/**/*
        - Dockerfile.backend
  script:
    - echo "Building backend"

build-frontend:
  stage: build
  tags: [k8s]
  rules:
    - changes:
        - frontend/**/*
        - Dockerfile.frontend
  script:
    - echo "Building frontend"

Resource requests и node affinity

Задачи сборки требуют больше ресурсов, чем линтинг. Используйте разные конфигурации:

# В .gitlab-ci.yml через переменные
build:
  tags: [k8s]
  variables:
    KUBERNETES_CPU_REQUEST: "2"
    KUBERNETES_CPU_LIMIT: "4"
    KUBERNETES_MEMORY_REQUEST: "4Gi"
    KUBERNETES_MEMORY_LIMIT: "8Gi"

lint:
  tags: [k8s]
  variables:
    KUBERNETES_CPU_REQUEST: "200m"
    KUBERNETES_MEMORY_REQUEST: "256Mi"

Деплой в Kubernetes из пайплайна

Финальный этап пайплайна — деплой приложения в тот же или другой кластер:

deploy-production:
  stage: deploy
  tags: [k8s]
  image: bitnami/kubectl:1.29
  environment:
    name: production
    url: https://app.company.ru
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
      when: manual
  script:
    - kubectl config use-context $KUBE_CONTEXT
    - kubectl set image deployment/myapp \
        app=${CI_REGISTRY_IMAGE}:${CI_COMMIT_SHA} \
        --namespace production
    - kubectl rollout status deployment/myapp \
        --namespace production --timeout=300s

Для Helm-деплоев:

deploy-helm:
  stage: deploy
  tags: [k8s]
  image: alpine/helm:3.14
  script:
    - helm upgrade --install myapp ./chart \
        --namespace production \
        --set image.tag=${CI_COMMIT_SHA} \
        --set replicas=3 \
        --wait --timeout 5m

Часто задаваемые вопросы

Ограничение задаётся параметром concurrent в конфигурации Runner. Реальный лимит определяется ресурсами кластера: если каждая задача запрашивает 1 CPU и 2 ГБ RAM, кластер с 32 ядрами и 64 ГБ может обработать около 30 параллельных задач (с учётом системных подов).

Используйте kubectl get pods -n gitlab-ci для просмотра подов. Логи пода: kubectl logs <pod-name> -n gitlab-ci -c build. Для интерактивной отладки добавьте sleep 3600 в script задачи и подключитесь через kubectl exec -it <pod> -n gitlab-ci -- sh.

Нет. Runner с Kubernetes executor может деплоить в другие кластеры через kubeconfig. Однако для CI-задач (сборка, тесты) рекомендуется ставить Runner в тот же кластер — это снижает latency при создании подов и упрощает доступ к кешу.

Настройте ResourceQuota и LimitRange в namespace gitlab-ci. ResourceQuota ограничивает суммарное потребление (например, не более 32 CPU и 64 ГБ RAM на все CI-поды), а LimitRange — ресурсы отдельного пода.

Нужна помощь с настройкой?

Специалисты АйТи Фреш помогут с внедрением и настройкой — 15+ лет опыта, обслуживание от 15 000 ₽/мес

📞 Связаться с нами
#gitlab runner kubernetes#kubernetes executor#ci/cd kubernetes#gitlab ci#gitlab runner настройка#kubernetes ci/cd#gitlab pipeline#helm gitlab runner
Комментарии 0

Оставить комментарий

загрузка...