Как написать CI/CD Pipeline с нуля?

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

Ответ

При создании пайплайна с нуля я начинаю с определения этапов (stages) и стремлюсь к максимальной скорости обратной связи и надежности.

1. Проектирование этапов (Stages): Базовый пайплайн включает: test -> build -> deploy (в staging) -> production. Для монолита это может быть линейно, для микросервисов — параллельно.

2. Пример пайплайна для контейнеризованного приложения (GitLab CI):

# .gitlab-ci.yml
stages:
  - lint              # Статический анализ
  - test              # Unit/Integration тесты
  - build             # Сборка артефакта (Docker образ)
  - security-scan     # Сканирование образа
  - deploy-staging    # Развертывание в тестовое окружение
  - integration-test  # E2E тесты в staging
  - deploy-prod       # Развертывание в prod (ручное)

variables:
  DOCKER_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

# 1. Линтинг и проверка кода
code-quality:
  stage: lint
  image: node:18-alpine
  script:
    - npm ci
    - npm run lint
    - npm run type-check
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/

# 2. Запуск тестов с покрытием
unit-test:
  stage: test
  image: node:18-alpine
  script:
    - npm ci
    - npm test -- --coverage
  artifacts:
    reports:
      junit: junit.xml          # Отчет для GitLab
    paths:
      - coverage/               # Данные о покрытии

# 3. Сборка Docker-образа с BuildKit и multi-stage
docker-build:
  stage: build
  image: docker:24.0
  services:
    - docker:24.0-dind          # Docker-in-Docker для сборки
  variables:
    DOCKER_BUILDKIT: 1          # Использую BuildKit для скорости и безопасности
  script:
    - echo $CI_JOB_TOKEN | docker login -u gitlab-ci-token --password-stdin $CI_REGISTRY
    - docker build --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $CI_REGISTRY_IMAGE:latest -t $DOCKER_TAG -t $CI_REGISTRY_IMAGE:latest .
    - docker push $DOCKER_TAG
    - docker push $CI_REGISTRY_IMAGE:latest

# 4. Сканирование образа на уязвимости (Trivy)
container-scan:
  stage: security-scan
  image: aquasec/trivy:latest
  dependencies: []              # Не загружаем артефакты предыдущих шагов
  script:
    - trivy image --exit-code 1 --severity HIGH,CRITICAL $DOCKER_TAG

# 5. Развертывание в staging с помощью Helm
deploy-to-staging:
  stage: deploy-staging
  image: alpine/helm:3.12
  environment:
    name: staging
    url: https://staging.myapp.com
  script:
    - helm upgrade --install my-app ./charts/my-app --namespace staging --set image.tag=$CI_COMMIT_SHORT_SHA --wait

# 6. Запуск интеграционных тестов в staging
e2e-tests:
  stage: integration-test
  image: cypress/included:12.0
  environment:
    name: staging
    url: https://staging.myapp.com
  script:
    - npm run e2e:ci

# 7. Развертывание в production (требует ручного подтверждения)
deploy-to-production:
  stage: deploy-prod
  image: alpine/helm:3.12
  environment:
    name: production
    url: https://myapp.com
  script:
    - helm upgrade --install my-app ./charts/my-app --namespace production --set image.tag=$CI_COMMIT_SHORT_SHA --wait
  when: manual                  # Ручной запуск
  only:
    - main                      # Только из главной ветки

3. Ключевые принципы:

  • Кэширование: Кэширую зависимости (node_modules, .gradle/caches) для ускорения сборок.
  • Артефакты: Передаю отчеты о тестах (JUnit, coverage) между этапами для визуализации.
  • Environments: Использую встроенное понятие окружений для отслеживания развертываний.
  • Ручные гейты: Критические этапы (продакшн-деплой) требуют ручного подтверждения.
  • Инфраструктура как код (IaC): Отдельный пайплайн (например, в Terraform) управляет созданием и обновлением инфраструктуры (K8s кластер, сети), в который этот application-пайплайн интегрируется.