Mid📖Теория3 min

Тестирование и деплой микросервисов

Contract testing, consumer-driven contracts, Canary deployment, Blue-Green, тестовая пирамида и CI/CD для микросервисов

Тестирование и деплой микросервисов

Сложность тестирования микросервисов

В монолите интеграционный тест запускается в одном процессе. В микросервисах каждый тест должен учитывать сетевые вызовы, eventual consistency и независимый деплой.

Монолит: один процесс, один тест
┌──────────────────────────────────┐
│  Test Runner                     │
│  ┌────────┐ ┌────────┐ ┌──────┐ │
│  │ Orders │ │ Users  │ │ Pay  │ │
│  │ (in    │ │ (in    │ │ (in  │ │
│  │ memory)│ │ memory)│ │ mem) │ │
│  └────────┘ └────────┘ └──────┘ │
│                                  │
│  Всё в одном процессе -- просто  │
└──────────────────────────────────┘

Микросервисы: N процессов, N проблем
┌──────────┐  ┌──────────┐  ┌──────────┐
│  Orders  │  │  Users   │  │ Payments │
│  (test)  │──│  (mock?) │──│  (mock?) │
│          │  │  (real?) │  │  (real?) │
└──────────┘  └──────────┘  └──────────┘

Запускать всё? Дорого и медленно.
Мокать всё? Тесты не отражают реальность.

Тестовая пирамида для микросервисов

                    /\
                   /  \
                  / E2E \        < 5% -- Smoke tests (happy path)
                 /  Tests\       Медленные, хрупкие, дорогие
                /──────────\
               / Contract   \    ~15% -- Контрактные тесты
              /   Tests      \   Проверяют API-совместимость
             /────────────────\
            / Integration      \  ~30% -- Тесты с реальной БД
           /   Tests            \ и внешними зависимостями
          /──────────────────────\
         /    Unit Tests          \ ~50% -- Быстрые, изолированные
        /      (domain logic)      \ Ядро бизнес-логики
       /────────────────────────────\

Unit Tests

Тестируют бизнес-логику сервиса в изоляции. Внешние зависимости -- моки.

Что тестировать:
  ✅ Доменная логика (расчёт цены, валидация, state machine)
  ✅ Бизнес-правила (скидки, лимиты, workflow)
  ✅ Чистые функции (преобразования данных)

Чего НЕ тестировать unit-тестами:
  ❌ HTTP endpoints (это интеграционные тесты)
  ❌ SQL запросы (это интеграционные тесты)
  ❌ Внешние API (это контрактные тесты)

Integration Tests

Тестируют взаимодействие с инфраструктурой (БД, Redis, Kafka) внутри одного сервиса.

┌──────────────────────────────────────┐
│  Integration Test                    │
│                                      │
│  ┌──────────────┐                    │
│  │ Order Service│                    │
│  │  (real code) │                    │
│  └──────┬───────┘                    │
│         │                            │
│  ┌──────┴───────┐  ┌──────────────┐  │
│  │ PostgreSQL   │  │ Redis        │  │
│  │ (Testcontainer)│ (Testcontainer)│ │
│  └──────────────┘  └──────────────┘  │
│                                      │
│  Внешние сервисы замокированы:       │
│  User Service   -> WireMock          │
│  Payment Service -> WireMock         │
└──────────────────────────────────────┘

Testcontainers: реальная БД в Docker для тестов
WireMock: HTTP-сервер, имитирующий внешний API

Contract Testing

Проблема

Order Service вызывает User Service по HTTP. Как убедиться, что API совместимы, не запуская оба сервиса?

Без Contract Tests:

  Order Service ожидает:
    GET /users/123 -> { "id": 123, "name": "Ivan" }

  User Service возвращает (после рефакторинга):
    GET /users/123 -> { "id": 123, "fullName": "Ivan" }

  Баг! "name" переименовали в "fullName".
  Узнаём только на staging или production.

Consumer-Driven Contract Testing

┌──────────────────────────────────────────────────────┐
│         Consumer-Driven Contract Testing             │
│                                                      │
│  1. Consumer (Order Svc) определяет ожидания:        │
│     "Мне нужно GET /users/{id} с полями id, name"    │
│                                                      │
│  2. Contract сохраняется как файл (Pact):            │
│     { "consumer": "OrderService",                    │
│       "provider": "UserService",                     │
│       "interactions": [{                             │
│         "request": { "method": "GET", "path":...},   │
│         "response": { "body": { "id": 123,           │
│           "name": "Ivan" } }                         │
│       }]                                             │
│     }                                                │
│                                                      │
│  3. Provider (User Svc) верифицирует контракт:       │
│     - Запускает свой API                             │
│     - Прогоняет запросы из контракта                  │
│     - Проверяет что ответы соответствуют              │
│                                                      │
│  4. Если контракт нарушен -- тест падает ДО деплоя   │
└──────────────────────────────────────────────────────┘

Workflow Contract Testing

             Consumer                    Provider
             (Order Svc)                 (User Svc)
                │                           │
  1. Написать   │                           │
     Pact тест  │                           │
                │                           │
  2. Запуск ────│────> Pact Broker ─────────│
     генерирует │   (хранилище контрактов)  │
     контракт   │                           │
                │                           │
                │                    3. CI/CD Pipeline
                │                       │
                │                    4. Загрузить контракт
                │                       из Pact Broker
                │                       │
                │                    5. Верифицировать
                │                       контракт
                │                       │
                │               Успех:  │ Контракт ОК
                │               Провал: │ Breaking change!
                │                       │ Блокировать деплой

Инструменты Contract Testing

Инструмент Протокол Подход
Pact HTTP, messaging Consumer-driven
Spring Cloud Contract HTTP, messaging Provider-driven
Protovalidate gRPC/Protobuf Schema validation
Schemathesis OpenAPI Property-based

CI/CD для микросервисов

Один репозиторий vs Mono-repo

Polyrepo (один репозиторий = один сервис):
  github.com/company/order-service
  github.com/company/user-service
  github.com/company/payment-service

  + Независимые CI/CD пайплайны
  + Чёткое владение (одна команда = один repo)
  + Изоляция зависимостей
  - Сложно делать cross-service refactoring
  - Дублирование CI/CD конфигураций

Monorepo (все сервисы в одном репозитории):
  github.com/company/platform
    /services/order/
    /services/user/
    /services/payment/
    /shared/libs/

  + Atomic cross-service commits
  + Переиспользование CI конфигов
  + Единый code review
  - Нужен Bazel/Nx для инкрементальных билдов
  - Права доступа сложнее

CI Pipeline для микросервиса

┌──────────────────────────────────────────────────────┐
│  CI Pipeline (per service)                           │
│                                                      │
│  ┌─────────┐  ┌────────┐  ┌──────────┐  ┌────────┐  │
│  │  Build  │─>│  Unit  │─>│ Integr.  │─>│Contract│  │
│  │ & Lint  │  │ Tests  │  │  Tests   │  │ Tests  │  │
│  └─────────┘  └────────┘  └──────────┘  └────┬───┘  │
│                                               │      │
│  ┌──────────────┐  ┌───────────────┐         │      │
│  │ Security Scan│  │ Docker Build  │<────────┘      │
│  │ (SAST, deps) │  │ & Push        │                │
│  └──────────────┘  └───────┬───────┘                │
│                            │                         │
│                     ┌──────┴──────┐                  │
│                     │  Artifact   │                  │
│                     │  Registry   │                  │
│                     └─────────────┘                  │
└──────────────────────────────────────────────────────┘

Стратегии деплоя

Blue-Green Deployment

┌─────────────────────────────────────────────────────┐
│  Blue-Green Deployment                              │
│                                                     │
│  Текущее состояние:                                 │
│  ┌──────────┐         ┌──────────────┐              │
│  │  Router  │────────>│  BLUE (v1)   │ ← Live      │
│  │  (LB)   │         │  3 pods      │              │
│  └──────────┘         └──────────────┘              │
│                       ┌──────────────┐              │
│                       │  GREEN (v2)  │ ← Idle       │
│                       │  3 pods      │              │
│                       └──────────────┘              │
│                                                     │
│  Деплой v2:                                         │
│  1. Деплоить v2 в GREEN                             │
│  2. Прогнать smoke tests на GREEN                   │
│  3. Переключить Router: BLUE -> GREEN               │
│  4. GREEN стал Live, BLUE стал Idle                 │
│                                                     │
│  Откат: переключить Router обратно на BLUE (секунды)│
│                                                     │
│  + Мгновенный откат                                 │
│  + Zero downtime                                    │
│  - Двойные ресурсы                                  │
│  - БД миграции нужно делать backward-compatible      │
└─────────────────────────────────────────────────────┘

Canary Deployment

┌─────────────────────────────────────────────────────┐
│  Canary Deployment                                  │
│                                                     │
│  Этап 1: 5% трафика на canary                       │
│  ┌──────────┐    95%   ┌──────────────┐             │
│  │  Router  │─────────>│  Stable (v1) │             │
│  │          │    5%    │  10 pods     │             │
│  │          │─────────>┌──────────────┐             │
│  └──────────┘          │ Canary (v2)  │             │
│                        │  1 pod       │             │
│                        └──────────────┘             │
│                                                     │
│  Мониторинг метрик canary:                          │
│  - Error rate < 0.1%? ✅                            │
│  - P99 latency < 200ms? ✅                          │
│  - CPU/Memory нормальные? ✅                        │
│                                                     │
│  Этап 2: 25% трафика                                │
│  Этап 3: 50% трафика                                │
│  Этап 4: 100% -- canary стал stable                 │
│                                                     │
│  Если метрики плохие на любом этапе:                │
│  -> Автоматический rollback (0% на canary)          │
│                                                     │
│  + Постепенный rollout, ранее обнаружение проблем   │
│  + Минимальный blast radius                         │
│  - Сложнее настроить                                │
│  - Нужна хорошая observability                      │
└─────────────────────────────────────────────────────┘

Rolling Update

┌─────────────────────────────────────────────────────┐
│  Rolling Update (Kubernetes default)                │
│                                                     │
│  Начало:  [v1] [v1] [v1] [v1]                      │
│                                                     │
│  Шаг 1:   [v1] [v1] [v1] [v2] ← новый pod          │
│  Шаг 2:   [v1] [v1] [v2] [v2]                      │
│  Шаг 3:   [v1] [v2] [v2] [v2]                      │
│  Шаг 4:   [v2] [v2] [v2] [v2] ← все обновлены      │
│                                                     │
│  Kubernetes: maxSurge=1, maxUnavailable=0           │
│  Гарантия: всегда есть 4 готовых пода               │
│                                                     │
│  + Не нужны двойные ресурсы                         │
│  + Встроено в Kubernetes                            │
│  - Нет чистого rollback (нужно откатить деплой)     │
│  - Во время rollout -- разные версии одновременно   │
└─────────────────────────────────────────────────────┘

Сравнение стратегий

Стратегия Downtime Rollback Ресурсы Сложность
Blue-Green Нет Мгновенный 2x Средняя
Canary Нет Быстрый 1.05-1.5x Высокая
Rolling Нет Медленный 1.25x Низкая
Recreate Да Медленный 1x Минимальная

Feature Flags

Деплой кода =/= включение фичи. Feature Flags позволяют деплоить код и включать функциональность отдельно.

┌──────────────────────────────────────────────────┐
│  Feature Flags                                   │
│                                                  │
│  Деплой: код v2 содержит новый алгоритм поиска   │
│  Флаг: new_search_algorithm = false               │
│                                                  │
│  Включение:                                      │
│  1. new_search_algorithm = true (для 1% юзеров)  │
│  2. Мониторинг метрик                            │
│  3. new_search_algorithm = true (для 10%)        │
│  4. Если ОК -> 100%                              │
│  5. Удалить старый код и флаг                    │
│                                                  │
│  Откат: new_search_algorithm = false              │
│  Мгновенно, без деплоя!                          │
└──────────────────────────────────────────────────┘

Инструменты: LaunchDarkly, Unleash, Flagsmith, самописный

Database Migrations

Backward-Compatible Migrations

При Blue-Green и Canary одновременно работают две версии кода. Миграции БД должны быть совместимы с обеими.

Добавление колонки (безопасно):
  v1: SELECT id, name FROM users
  Migration: ALTER TABLE users ADD COLUMN email VARCHAR
  v2: SELECT id, name, email FROM users
  v1 продолжает работать (игнорирует email)

Удаление колонки (ОПАСНО -- 3 шага):
  Шаг 1 (деплой v2): перестать читать колонку old_field
  Шаг 2 (миграция): ALTER TABLE DROP COLUMN old_field
  Шаг 3: убедиться что всё ОК

Переименование колонки (ОПАСНО -- expand/contract):
  Шаг 1: добавить новую колонку, дублировать данные
  Шаг 2: переключить код на новую колонку
  Шаг 3: удалить старую колонку

Observability в CI/CD

Deployment Metrics

Ключевые метрики после деплоя:

  DORA Metrics:
  1. Deployment Frequency      -- как часто деплоим
  2. Lead Time for Changes     -- от коммита до production
  3. Change Failure Rate       -- % деплоев с проблемами
  4. Mean Time to Recovery     -- время восстановления

  Canary Metrics:
  - Error rate (5xx)
  - Latency (p50, p95, p99)
  - Saturation (CPU, Memory)
  - Traffic (requests per second)

Реальные примеры

Amazon -- Deployment Pipeline

Amazon деплоит каждые 11.7 секунд (данные 2015). Их pipeline:

  1. Разработчик пушит код
  2. Автоматические тесты (unit, integration, contract)
  3. Canary deployment в одном регионе
  4. Мониторинг 15-30 минут
  5. Автоматическое расширение на все регионы
  6. Автоматический rollback при аномалиях

Google -- Borg/Kubernetes

Google обрабатывает 2 миллиарда контейнеров в неделю. Rolling updates с health checks -- стандарт. Каждый сервис проходит через canary (1% pods) перед полным rollout.

Spotify -- Squad-based CI/CD

Каждый Squad (команда из 6-8 человек) владеет полным CI/CD пайплайном для своих сервисов. Они сами решают когда и как деплоить. Автономия команд -- ключевой принцип.

Проверь себя

🧪

Почему миграции БД при Canary/Blue-Green должны быть backward-compatible?

🧪

В чём главное преимущество Blue-Green Deployment?

🧪

Что такое Feature Flags?

🧪

Как Canary Deployment обнаруживает проблемы?

🧪

Что проверяют Consumer-Driven Contract Tests?