Серверное оборудование для бизнеса — надёжные решения для любых задач Подробнее →
Статья

GitLab CI/CD: пайплайн для деплоя на 50 серверов

Деплоить на один сервер умеет любой скрипт. Деплоить на 50 серверов одновременно, с контролем ошибок, роллбэком и без потери сна — это уже инженерная задача. Разберём как построить такой пайплайн на GitLab CI/CD.

GitLab Runner: shared vs specific

Прежде чем писать .gitlab-ci.yml, нужно понять где будут выполняться джобы.

Shared runners — предоставляются GitLab SaaS, подходят для сборок и тестов, но для деплоя в вашу инфраструктуру они не имеют доступа к вашим серверам. Используйте для CI-фазы.

Specific (self-hosted) runners — устанавливаете сами в своей сети. Именно они нужны для деплоя. Регистрация:

# Установка на Ubuntu
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash
apt install gitlab-runner

# Регистрация
gitlab-runner register 
  --url https://gitlab.example.com 
  --registration-token $TOKEN 
  --executor shell 
  --tag-list deploy,production

Для деплоя используйте executor shell или docker — зависит от того, нужна ли изоляция окружения сборки.

Структура .gitlab-ci.yml

Базовая структура пайплайна с тремя стейджами:

stages:
  - build
  - test
  - deploy

variables:
  APP_NAME: myapp
  DEPLOY_USER: deploy
  ARTIFACT_PATH: dist/

build:
  stage: build
  script:
    - make build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

test:
  stage: test
  script:
    - make test
  dependencies:
    - build

Артефакты (artifacts) — это то, что передаётся между джобами. Без явного указания dependencies джоба получает артефакты всех предыдущих.

Деплой через SSH: rsync + Ansible

Два основных подхода для деплоя на множество серверов:

rsync — быстрый, простой, минимум зависимостей:

deploy:
  stage: deploy
  script:
    - eval $(ssh-agent -s)
    - echo "$DEPLOY_SSH_KEY" | ssh-add -
    - rsync -avz --delete dist/ $DEPLOY_USER@$SERVER:/opt/$APP_NAME/
    - ssh $DEPLOY_USER@$SERVER "systemctl restart $APP_NAME"

Ansible — когда нужна идемпотентность и сложная логика:

deploy:
  stage: deploy
  script:
    - ansible-playbook -i inventory/production deploy.yml 
        --extra-vars "version=$CI_COMMIT_SHORT_SHA"

SSH-ключ деплоя храните в CI/CD Variables (Settings → CI/CD → Variables), тип File для приватного ключа.

Parallel matrix: деплой на 50 серверов

Ключевая фича для масштабирования — parallel:matrix. Вместо последовательного деплоя запускаем джобы параллельно по группам серверов:

deploy:
  stage: deploy
  parallel:
    matrix:
      - SERVER_GROUP:
          - web-01..10
          - web-11..20
          - web-21..30
          - app-01..10
          - app-11..20
  script:
    - ansible-playbook -i inventory/$SERVER_GROUP deploy.yml

GitLab создаст 5 параллельных джоб, каждая деплоит свою группу серверов. Время деплоя сокращается пропорционально числу параллельных потоков.

Максимум параллельных джоб на instance задаётся конкурентностью раннера (concurrent в /etc/gitlab-runner/config.toml).

Environments: staging → production

GitLab Environments позволяют отслеживать что и куда задеплоено:

deploy_staging:
  stage: deploy
  environment:
    name: staging
    url: https://staging.example.com
  script:
    - ansible-playbook -i inventory/staging deploy.yml
  only:
    - develop

deploy_production:
  stage: deploy
  environment:
    name: production
    url: https://example.com
  script:
    - ansible-playbook -i inventory/production deploy.yml
  when: manual
  only:
    - main

when: manual для продакшена — обязательно. Автодеплой в прод без подтверждения человека допустим только если у вас идеальное тестовое покрытие (и то я бы поспорил).

Rollback через git revert + auto-deploy

Быстрый роллбэк через GitLab UI: в разделе Deployments → Environments нажмите Re-deploy на предыдущий деплой. GitLab запустит пайплайн с тем же коммитом.

Для автоматического роллбэка при падении healthcheck:

deploy_production:
  stage: deploy
  script:
    - ansible-playbook -i inventory/production deploy.yml
    - sleep 30
    - curl -f https://example.com/health || (
        ansible-playbook -i inventory/production rollback.yml &&
        exit 1
      )

В rollback.yml — переключение симлинка на предыдущую версию или откат через пакетный менеджер.

Secrets через CI/CD Variables

Никогда не храните секреты в репозитории. GitLab CI/CD Variables — правильное место:

  • Protected: доступны только в защищённых ветках/тегах
  • Masked: скрываются в логах джоб
  • File: сохраняются как временный файл, путь передаётся в переменной
deploy:
  script:
    - ansible-playbook deploy.yml 
        --vault-password-file $ANSIBLE_VAULT_PASSWORD_FILE

Для особо критичных секретов — интеграция с HashiCorp Vault через secrets: синтаксис GitLab.

Кэширование зависимостей

Кэш ускоряет сборку, но требует правильной настройки ключа:

cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/
  policy: pull-push

Ключ на основе package-lock.json означает: кэш обновляется только когда меняются зависимости. Используйте policy: pull в тестовых джобах чтобы не перезаписывать кэш.

Мониторинг пайплайнов

Несколько практик для поддержания здоровья пайплайнов:

  • Badges в README репозитория — моментальный статус
  • Slack/Telegram уведомления через webhook в настройках проекта
  • Pipeline schedules — регулярные smoke-тесты на продакшене
  • Failed jobs — настройте алерты через GitLab API или интеграцию с PagerDuty

Метрики пайплайнов доступны через GitLab API: время выполнения, процент успешных запусков, самые частые точки отказа.

Итог

50 серверов — не предел для GitLab CI/CD. Правильная архитектура с parallel matrix, self-hosted runners и Ansible позволяет масштабироваться и до 500. Главное — вложите время в правильную структуру с самого начала: environments, secrets management, rollback стратегию. Переделывать пайплайн под нагрузкой значительно болезненнее.

4 Ответа

  1. Хорошая статья, но у нас в итоге ушли с GitLab на GitHub Actions — там маркетплейс экшенов куда богаче, и secrets management через OIDC с облачными провайдерами работает прямо из коробки. Для GitLab это всё тоже можно, но больше ручной работы.

  1. По кэшированию pip добавлю: ключ лучше делать по файлу requirements.txt плюс версии Python. Иначе при смене питона кэш не инвалидируется и потом ловишь странные ошибки несовместимости пакетов.

  1. Self-hosted runners — отдельная история. Запускать их в Kubernetes через gitlab-runner helm chart реально удобно, автоскейлинг по нагрузке работает хорошо. Только надо правильно настроить лимиты ресурсов иначе джобы начинают конкурировать с продом.