28. GitLab CI для Go
Энтерпрайз-конвейер: Архитектура GitLab CI/CD
В то время как публичные проекты и стартапы традиционно выбирают открытое облако GitHub, в корпоративном секторе, банковских системах и закрытых контурах стандартом де-факто выступает GitLab CI/CD.
Главное архитектурное отличие GitLab CI кроется в модели доставки задач. Если многие облачные платформы используют push-модель (сервер инициирует соединение с исполнителем), то GitLab CI спроектирован по Pull-based модели:
- Агент (GitLab Runner) устанавливается на целевой машине и сам с заданной периодичностью опрашивает центральный сервер GitLab по защищенному HTTPS-каналу: «Есть ли работа для меня?».
- Получив задачу, Runner скачивает репозиторий, исполняет шаги в изолированной среде и отправляет результаты обратно.
Такая топология идеальна для строгой корпоративной безопасности: раннеры могут находиться за корпоративными файрволами, в демилитаризованных зонах (DMZ), на мощных физических bare-metal серверах или внутри изолированных Kubernetes-кластеров. Серверу GitLab не требуется знать внутренние IP-адреса машин сборки и пробивать входящие сетевые порты.
Концепция .gitlab-ci.yml: Этапы и Задания
Поведение конвейера описывается в едином манифесте .gitlab-ci.yml, расположенном в корне репозитория. Вся структура опирается на две базовые концепции:
- Stages (Этапы): Логические фазы конвейера (например,
lint,test,build,deploy). Все задания внутри одного этапа выполняются параллельно. Переход к следующему этапу происходит только тогда, когда все задачи текущего этапа завершились успехом. - Jobs (Задания): Конкретные вычислительные сценарии, выполняемые конкретным раннером.
flowchart TD
subgraph Pipeline ["GitLab Pipeline"]
direction LR
S1["Stage: Lint"] --> S2["Stage: Test"] --> S3["Stage: Build"]
end
S1 --> J1["Job: go vet"]
S2 --> J2["Job: unit tests"]
S2 --> J3["Job: integration tests"]
S3 --> J4["Job: docker build"]
J2 --> A1["Artifact coverage.out"]
J4 --> A2["Artifact image.tar"]
style Pipeline fill:#1e293b,stroke:#9ca3af,color:#fff
style S1 fill:#374151,stroke:#38bdf8,color:#fff
style S2 fill:#374151,stroke:#10b981,color:#fff
style S3 fill:#374151,stroke:#f59e0b,color:#fff
Кэширование и Артефакты: Не путать теплое с мягким
Одна из самых распространенных ошибок начинающих инженеров в GitLab CI — смешение понятий cache и artifacts. Это принципиально разные механизмы с разной физической природой и назначением:
- Cache (Кэш):
- Назначение: Ускорение последующих прогонов пайплайна (переиспользование скачанных Go-модулей).
- Надежность: Best effort (без гарантий). Если кэш поврежден, очищен администратором или раннер запущен на новой машине, пайплайн обязан успешно собраться с нуля.
- Хранение: Локальный каталог на машине раннера или объектное S3-хранилище (MinIO).
- Artifacts (Артефакты):
- Назначение: Гарантированная передача результатов работы текущего задания следующим этапам того же конвейера (бинарный файл, отчет покрытия тестами, файл лога).
- Надежность: Абсолютная гарантия. GitLab сохраняет артефакты на координаторе и передает их в зависимые джобы.
- Хранение: Централизованное хранилище GitLab с настраиваемым временем жизни (
expire_in).
Пример правильной конфигурации кэширования модулей Go и выгрузки артефактов:
variables:
GOPATH: $CI_PROJECT_DIR/.go
cache:
key:
files:
- go.sum
paths:
- .go/pkg/mod/
stages:
- test
unit_tests:
stage: test
image: golang:1.22
script:
- go test -race -coverprofile=coverage.out ./...
artifacts: # Сохраняем отчет для следующей джобы или просмотра в UI
paths:
- coverage.out
expire_in: 1 week
Переопределение переменной GOPATH на поддиректорию внутри проекта $CI_PROJECT_DIR/.go критически важно: GitLab Runner по умолчанию умеет архивировать в кэш только те пути, которые находятся внутри клонированного репозитория.
Сборка контейнеров: Docker-in-Docker (DinD) vs Kaniko
Когда пайплайн доходит до этапа упаковки сервиса в образ, возникает вопрос: как собрать Docker-образ внутри раннера, который сам работает в контейнере?
Вариант 1: Docker-in-Docker (DinD)
Исторический метод, при котором контейнер сборки обращается к соседнему контейнеру-демону docker:dind:
services:
- docker:dind
build:
image: docker:latest
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
Фундаментальная угроза безопасности: Для запуска docker:dind раннер обязан работать в привилегированном режиме (privileged: true). Это открывает процессу прямой доступ к хостовому ядру Linux, системным устройствам (/dev) и таблицам маршрутизации. Любая брешь в зависимостях приложения позволяет злоумышленнику совершить побег из контейнера (container escape) и захватить родительский сервер.
Вариант 2: Kaniko (Рекомендуемый стандарт безопасности)
Утилита Kaniko от Google создана специально для компиляции и сборки Docker-образов внутри контейнеров без демона Docker и без root-привилегий.
Kaniko разбирает Dockerfile, выполняет системные команды в своем собственном пространстве пользователя (userspace) и формирует слои файловой системы в виде tar-архивов, после чего отправляет их напрямую в реестр по протоколу Docker Registry v2:
build:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
Развертывание (Environments)
GitLab CI предоставляет развитую подсистему Environments, визуализирующую состояние развертывания на стендах (development, staging, production) и позволяющую встроить человеческий контроль через ручные триггеры (when: manual):
deploy_prod:
stage: deploy
script:
- ./deploy.sh production
environment:
name: production
url: https://prod.myapp.com
when: manual # Требует ручного нажатия кнопки
only:
- main
Директива when: manual превращает этап выкатки в продакшен в контролируемый релизный шлюз. Ответственный релиз-инженер или тимлид нажимает кнопку «Play» в веб-интерфейсе GitLab только после того, как функционал проверен на стейджинге. В случае сбоя интерфейс GitLab позволяет выполнить мгновенный откат (Rollback) к предыдущему успешному развертыванию в 1 клик.
Итог
- GitLab CI опирается на безопасную архитектуру Pull-based: агенты сами опрашивают сервер, что позволяет развертывать раннеры в любых изолированных сетях.
- Четко разделяйте
cache(необязательное ускорение сборки модулейgo mod) иartifacts(строго гарантированная передача бинарников и отчетов между стадиями). - Избегайте привилегированного режима
docker:dind— для безопасной сборки контейнеров в изолированных кластерах используйте Kaniko. - Используйте секцию
environmentи директивуwhen: manualдля обеспечения контролируемых и воспроизводимых релизов в продакшен.
Мы научились конфигурировать пайплайны на ведущих CI-платформах. Теперь обратимся к самому ценному содержанию этих конвейеров — автоматизированному тестированию.
В следующей статье мы разберем, как выжать максимум из встроенных тестов Go в распределенном конвейере: 29. Тестирование в CI.