Как происходит процесс сборки в CI/CD

«Как происходит процесс сборки в CI/CD» — вопрос из категории CI/CD, который задают на 23% собеседований Devops Инженер. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В DevOps-практике процесс сборки (Build) — это не просто компиляция кода, а создание неизменяемого, версионируемого и готового к развертыванию артефакта, чаще всего Docker-образа. Вот как это выглядит в моем опыте.

Цель: Преобразовать исходный код из Git-репозитория в артефакт, который можно предсказуемо развернуть в любом окружении (stage, prod).

Ключевые этапы пайплайна сборки:

  1. Инициализация и получение кода:

    • CI-раннер (GitLab Runner, GitHub Actions runner) запускается на подходящем хосте (с Docker, большим объемом RAM).
    • Клонируется репозиторий с определенным тегом или коммитом. Важно использовать git clone --depth 1 для скорости.
  2. Восстановление зависимостей и кэширование:

    • Устанавливаются зависимости языка (например, go mod download, npm ci, pip install -r requirements.txt).
    • Критически важно: Кэширование зависимостей между запусками пайплайна для значительного ускорения. В GitLab CI это выглядит так:
      cache:
      key: "$CI_COMMIT_REF_SLUG"
      paths:
      - node_modules/
      - .npm/
  3. Статический анализ и проверка качества кода:

    • Линтинг: Запускаются golangci-lint, eslint, hadolint (для Dockerfile).
    • Security Scanning: Инструменты SAST, такие как gosec, bandit (Python), или интеграция с Trivy/Snyk для поиска уязвимостей в зависимостях.
    • Этап "fail fast": если линтер или сканер безопасности находят критичные проблемы, сборка прерывается сразу, экономя время и ресурсы.
  4. Сборка Docker-образа (основной артефакт):

    • Используется многоступенчатая сборка (multi-stage build) для создания минимальных образов.
      
      # Dockerfile
      FROM golang:1.21-alpine AS builder
      WORKDIR /app
      COPY go.mod go.sum ./ 
      RUN go mod download
      COPY . .
      RUN CGO_ENABLED=0 GOOS=linux go build -o /myapp

    FROM gcr.io/distroless/static-debian12 COPY --from=builder /myapp /myapp USER nonroot:nonroot ENTRYPOINT ["/myapp"]

    *   Сборка запускается с явным тегом, включающим хэш коммита и номер сборки.
    ```bash
    docker build -t my-registry.com/myapp:${CI_COMMIT_SHA:0:8} -t my-registry.com/myapp:latest .
  5. Запуск юнит- и интеграционных тестов:

    • Тесты запускаются внутри только что собранного образа или в специальной тестовой среде.
      test:
      stage: test
      script:
      - docker run --rm my-registry.com/myapp:${CI_COMMIT_SHA:0:8} go test ./... -v
  6. Пуш артефакта в registry и создание отчетов:

    • Успешно собранный и протестированный образ пушится в приватный registry (Harbor, GitLab Container Registry, ECR).
    • Генерируются и сохраняются отчеты о тестировании (JUnit XML), покрытии кода (coverage) и сканировании безопасности для отображения в интерфейсе CI/CD.

Пример полной стадии build в .gitlab-ci.yml:

build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_TLS_CERTDIR: ""
  before_script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
  script:
    # Сборка с кэшированием слоев
    - docker build --cache-from $CI_REGISTRY_IMAGE:latest --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    # Также проставляем тег 'latest' для master-ветки (осторожно!)
    - |
      if [[ "$CI_COMMIT_BRANCH" == "main" ]]; then
        docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
        docker push $CI_REGISTRY_IMAGE:latest
      fi
  cache:
    key: docker-layer-cache
    paths:
      - $HOME/.cache/docker
  artifacts:
    reports:
      junit: report.xml
    expire_in: 1 week

Итог: На выходе этапа сборки мы получаем не просто бинарник, а версионированный Docker-образ, прошедший базовые проверки безопасности и качества, готовый для продвижения по следующим стадиям пайплайна (развертывание в staging, нагрузочное тестирование, деплой в production).