Skip to content

Стандарты CI/CD пайплайна

Обзор

CI/CD пайплайн платформы IWM обеспечивает качество кода, безопасность и надёжные деплои для финансовой системы, обрабатывающей MLM комиссии, платежи и чувствительные данные пользователей.

Цели пайплайна

ЦельРеализация
КорректностьКомплексные наборы тестов для финансовых расчётов
БезопасностьМногоуровневое сканирование (зависимости, секреты, контейнеры)
НадёжностьДеплои без простоя с возможностью отката
СкоростьПараллельные jobs, кэширование Docker слоёв, умное определение изменений
АудируемостьОтслеживание версий, логи деплоев, документирование изменений

Архитектура пайплайна

┌─────────────────────────────────────────────────────────────────────────┐
│                           PR VALIDATION                                  │
│                                                                          │
│   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐ │
│   │    Build    │   │    Lint     │   │    Type     │   │   Secret    │ │
│   │    Check    │   │   Format    │   │    Check    │   │    Scan     │ │
│   └──────┬──────┘   └──────┬──────┘   └──────┬──────┘   └──────┬──────┘ │
│          │                 │                 │                 │        │
│          └─────────────────┴────────┬────────┴─────────────────┘        │
│                                     │                                    │
│                                     ▼                                    │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                        TEST SUITE                                │   │
│   │   ┌───────────┐   ┌───────────┐   ┌───────────┐   ┌───────────┐ │   │
│   │   │   Unit    │   │Integration│   │    E2E    │   │  Contract │ │   │
│   │   │  (80%+)   │   │  (DB/API) │   │ (Critical)│   │   (API)   │ │   │
│   │   └───────────┘   └───────────┘   └───────────┘   └───────────┘ │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                     │                                    │
│                                     ▼                                    │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                      SECURITY GATES                              │   │
│   │   ┌───────────┐   ┌───────────┐   ┌───────────┐   ┌───────────┐ │   │
│   │   │ npm audit │   │  gitleaks │   │   SAST    │   │   Trivy   │ │   │
│   │   │  (high+)  │   │ (secrets) │   │ (CodeQL)  │   │ (container│ │   │
│   │   └───────────┘   └───────────┘   └───────────┘   └───────────┘ │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                     │                                    │
│                                     ▼                                    │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                   DATABASE VALIDATION                            │   │
│   │   ┌─────────────────────┐   ┌─────────────────────────────────┐ │   │
│   │   │  Migration dry-run  │   │  Schema drift detection         │ │   │
│   │   └─────────────────────┘   └─────────────────────────────────┘ │   │
│   └─────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘

                                      │ Merge to main

┌─────────────────────────────────────────────────────────────────────────┐
│                            RELEASE PIPELINE                              │
│                                                                          │
│   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐   ┌─────────────┐ │
│   │  Version    │   │   Build     │   │    Push     │   │  Generate   │ │
│   │   Bump      │──▶│   Images    │──▶│  Registry   │──▶│  Changelog  │ │
│   └─────────────┘   └─────────────┘   └─────────────┘   └─────────────┘ │
│                                                                │         │
│                                                                ▼         │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                     DEPLOY TO STAGING                            │   │
│   │   ┌───────────┐   ┌───────────┐   ┌───────────┐   ┌───────────┐ │   │
│   │   │   Run     │   │  Deploy   │   │  Health   │   │   Smoke   │ │   │
│   │   │Migrations │──▶│  (B/G)    │──▶│   Check   │──▶│   Tests   │ │   │
│   │   └───────────┘   └───────────┘   └───────────┘   └───────────┘ │   │
│   └─────────────────────────────────────────────────────────────────┘   │
│                                     │                                    │
│                              Manual Approval                             │
│                                     │                                    │
│                                     ▼                                    │
│   ┌─────────────────────────────────────────────────────────────────┐   │
│   │                    DEPLOY TO PRODUCTION                          │   │
│   │   ┌───────────┐   ┌───────────┐   ┌───────────┐   ┌───────────┐ │   │
│   │   │   Run     │   │Blue-Green │   │  Health   │   │   Smoke   │ │   │
│   │   │Migrations │──▶│  Deploy   │──▶│   Check   │──▶│  + Notify │ │   │
│   │   └───────────┘   └───────────┘   └───────────┘   └───────────┘ │   │
│   └─────────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────────┘

Workflow валидации PR

Каждый pull request должен пройти все проверки перед разрешением merge.

Этап 1: Сборка и статический анализ

ПроверкаИнструментПорог отказа
Компиляция TypeScripttsc --noEmitЛюбая ошибка
ESLinteslintЛюбая ошибка (warnings допустимы)
Prettierprettier --checkЛюбая проблема форматирования
Stylelint (фронтенд)stylelintЛюбая ошибка

Этап 2: Набор тестов

Тип тестаОхватТребование покрытия
Unit тестыДоменная логика, утилиты, чистые функцииМинимум 80%
Интеграционные тестыОперации с БД, API эндпоинты, RedisМинимум 70%
E2E тестыКритические пользовательские сценарии (см. ниже)Должны проходить
Контрактные тестыВалидация API схемы (см. ниже)Должны проходить

Спецификация контрактных тестов

Контрактные тесты проверяют, что реализация API соответствует спецификации и что изменения не ломают потребителей.

Что валидируем:

АспектИнструментОписание
OpenAPI соответствие@apidevtools/swagger-cli validateСхема - валидный OpenAPI 3.1
Форма ответаКастомные Jest matchersОтветы соответствуют объявленным схемам
Критические измененияopenapi-diffОбнаружение удалённых эндпоинтов, изменённых типов
Валидация запросовClass-validator + ZodВходные DTO соответствуют OpenAPI параметрам

Реализация контрактных тестов:

typescript
// test/contract/api-contract.spec.ts
import { OpenAPIValidator } from 'express-openapi-validator';
import spec from '../openapi.json';

describe('API Contract Tests', () => {
  const validator = new OpenAPIValidator({ spec });

  describe('POST /api/v1/auth/register', () => {
    it('should match request schema', async () => {
      const validRequest = {
        email: 'test@example.com',
        password: 'SecurePass123',
        referralCode: 'ABC123'
      };

      expect(() => validator.validateRequest({
        path: '/api/v1/auth/register',
        method: 'post',
        body: validRequest
      })).not.toThrow();
    });

    it('should match response schema', async () => {
      const response = await request(app)
        .post('/api/v1/auth/register')
        .send(validRequest);

      expect(() => validator.validateResponse({
        path: '/api/v1/auth/register',
        method: 'post',
        statusCode: response.status,
        body: response.body
      })).not.toThrow();
    });
  });
});

Обнаружение критических изменений в CI:

yaml
- name: Check for Breaking API Changes
  run: |
    # Сравнение текущей спецификации с main веткой
    git show origin/main:openapi.json > openapi-main.json

    npx openapi-diff openapi-main.json openapi.json --fail-on-incompatible

    # Несовместимые изменения (вызовут отказ):
    # - Удаление эндпоинтов
    # - Удаление обязательных полей ответа
    # - Добавление обязательных полей запроса
    # - Изменение типов полей

    # Совместимые изменения (разрешены):
    # - Добавление новых эндпоинтов
    # - Добавление опциональных полей запроса
    # - Добавление полей ответа

Критические E2E сценарии (всегда должны тестироваться):

1. Поток аутентификации
   - Регистрация с реферальным кодом
   - Вход с 2FA
   - Сброс пароля
   - Управление сессиями

2. Поток заказа и оплаты
   - Добавить в корзину → Оформление → Оплата → Подтверждение
   - Переходы статуса заказа (полный state machine)
   - Обработка платёжных webhook

3. Поток расчёта комиссий
   - Завершение заказа → Генерация комиссий
   - Многоуровневое распределение (до 10 уровней)
   - Утверждение комиссии → Выплата

4. Операции с MLM-деревом
   - Регистрация партнёра под спонсором
   - Запросы обхода дерева
   - Проверка квалификации ранга

Этап 3: Проверки безопасности

СканированиеИнструментДействие при отказе
Уязвимости зависимостейnpm audit --audit-level=highБлокировать merge
Обнаружение секретовgitleaksБлокировать merge
SASTCodeQL / SonarQubeБлокировать при high/critical
Уязвимости контейнеровTrivyБлокировать при critical

Этап 4: Валидация базы данных

yaml
# Dry-run миграций на тестовой базе данных
- name: Validate Migrations
  run: |
    # Создание временной базы данных
    createdb iwm_migration_test

    # Запуск всех миграций
    npx prisma migrate deploy --preview-feature

    # Проверка соответствия схемы Prisma схеме
    npx prisma db pull --force
    npx prisma validate

    # Очистка
    dropdb iwm_migration_test

Workflow релиза

Запускается при merge в ветку main.

Управление версиями

Семантическое версионирование: MAJOR.MINOR.PATCH

Правила автоматического повышения:
- Merge в main → PATCH инкремент (1.2.3 → 1.2.4)
- Ручное изменение MINOR → Пропустить auto-bump, использовать ручную версию
- Ручное изменение MAJOR → Пропустить auto-bump, использовать ручную версию

Версия хранится в: файл VERSION (корень проекта)

Этап сборки

ШагОписание
Чтение VERSIONПолучить текущую или повышенную версию
Сборка Docker образовBackend + Frontend отдельно
Тегирование образовv{VERSION} + latest
Push в registryGitHub Container Registry (ghcr.io)
Кэширование слоёвtype=gha,mode=max для быстрой пересборки

Кэширование Docker слоёв

yaml
# Каждая инструкция Dockerfile создаёт слой с SHA256 хэшем
# Неизменённые слои переиспользуются из кэша

FROM node:20-alpine          # Layer sha256:a1b2... (кэшируется если без изменений)
COPY package*.json ./        # Layer sha256:c3d4... (кэшируется если package.json тот же)
RUN npm ci                   # Layer sha256:e5f6... (кэшируется если зависимости те же)
COPY . .                     # Layer sha256:g7h8... (меняется при изменении кода)
RUN npm run build            # Layer sha256:i9j0... (пересобирается при изменении кода)

Конфигурация кэша:

yaml
cache-from: type=gha         # Pull из кэша GitHub Actions
cache-to: type=gha,mode=max  # Push ВСЕ слои (не только финальный)

Детали проверок безопасности

Сканирование зависимостей

yaml
# Запуск на каждом PR и еженедельно по расписанию
- name: Dependency Audit
  run: npm audit --audit-level=high

- name: Snyk Scan
  uses: snyk/actions/node@master
  env:
    SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Сканирование секретов

yaml
- name: Gitleaks Scan
  uses: gitleaks/gitleaks-action@v2
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Защищаемые паттерны:

ПаттернПример
API ключиsk_live_*, pk_live_*
URL баз данныхpostgresql://*:*@*
JWT секретыJWT_SECRET=*
Ключи шифрованияENCRYPTION_MASTER_KEY=*
Webhook секреты*_WEBHOOK_SECRET=*

Сканирование контейнеров

yaml
- name: Trivy Scan
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ${{ env.IMAGE }}
    severity: 'CRITICAL,HIGH'
    exit-code: '1'  # Отказ при critical/high

Стратегия миграций базы данных

Валидация миграций (PR)

1. Dry-run на пустой базе данных
2. Dry-run на клоне production (staging)
3. Проверка на деструктивные операции (DROP, TRUNCATE)
4. Оценка продолжительности миграции
5. Флаг миграций, требующих maintenance window

Выполнение миграций (Deploy)

┌─────────────────────────────────────────────────────────────────┐
│                    MIGRATION EXECUTION                           │
│                                                                  │
│   1. Создание backup snapshot                                    │
│      └─▶ pg_dump iwm_production > backup_$(date).sql            │
│                                                                  │
│   2. Запуск миграций                                             │
│      └─▶ npx prisma migrate deploy                              │
│                                                                  │
│   3. Валидация схемы                                             │
│      └─▶ npx prisma validate                                    │
│                                                                  │
│   4. Health check                                                │
│      └─▶ curl /health/ready                                     │
│                                                                  │
│   5. При ошибке: Восстановление из backup                        │
│      └─▶ psql < backup_$(date).sql                              │
└─────────────────────────────────────────────────────────────────┘

Важно: Prisma migrate deploy запускает каждый файл миграции в своей транзакции автоматически. НЕ оборачивайте это в BEGIN/COMMIT вручную.

Ограничения транзакций DDL

Некоторые DDL операции PostgreSQL не могут выполняться внутри транзакции. Они требуют специальной обработки:

ОперацияПоддержка транзакцийОбработка
CREATE INDEX CONCURRENTLYНетОтдельный файл миграции, выполнять вручную
ALTER TYPE (enum add value)Нет (PG < 12)Отдельная миграция, требует downtime на старом PG
DROP INDEX CONCURRENTLYНетОтдельный файл миграции
REINDEX CONCURRENTLYНетВыполнять во время maintenance window

Для нетранзакционных миграций:

sql
-- migrations/20240115_add_index_concurrently.sql
-- @non-transactional

CREATE INDEX CONCURRENTLY idx_orders_created
ON product.orders(created_at);
yaml
# Пайплайн обрабатывает нетранзакционные миграции отдельно
- name: Run Non-Transactional Migrations
  run: |
    for file in prisma/migrations/*_non_transactional.sql; do
      psql $DATABASE_URL -f "$file" || exit 1
    done

Политика деструктивных миграций

ОперацияТребование
DROP TABLEТребует ручного утверждения + проверки backup
DROP COLUMNДолжна предшествовать удалению кода в предыдущем релизе
TRUNCATEЗапрещено в production миграциях
ALTER TYPEТребует maintenance window

Тестирование верификации backup

Backup бесполезны если их нельзя восстановить. Регулярная верификация обеспечивает реальную возможность восстановления.

Расписание верификации:

ОкружениеЧастотаМетод
ProductionЕженедельноПолное восстановление на изолированный инстанс
StagingЕжемесячноТест полного восстановления
После миграцииНемедленноSpot check критических таблиц

Автоматизированная job верификации backup:

yaml
# .github/workflows/backup-verification.yml
name: Backup Verification

on:
  schedule:
    - cron: '0 4 * * 0'  # Еженедельно воскресенье 4AM
  workflow_dispatch:

jobs:
  verify-backup:
    runs-on: ubuntu-latest
    steps:
      - name: Create Fresh Backup
        run: |
          pg_dump $PRODUCTION_URL \
            --format=custom \
            --file=backup-$(date +%Y%m%d).dump

      - name: Spin Up Isolated Instance
        run: |
          docker run -d \
            --name pg-verify \
            -e POSTGRES_PASSWORD=verify_test \
            -p 5433:5432 \
            postgres:15

          sleep 10  # Ожидание запуска

      - name: Restore Backup
        run: |
          pg_restore \
            --host=localhost \
            --port=5433 \
            --username=postgres \
            --dbname=postgres \
            --clean \
            --if-exists \
            backup-$(date +%Y%m%d).dump

      - name: Verify Data Integrity
        run: |
          PGPASSWORD=verify_test psql \
            -h localhost -p 5433 -U postgres -d postgres \
            -f scripts/verify-backup-integrity.sql

      - name: Verify Row Counts
        run: |
          # Сравнение количества строк с production
          node scripts/compare-backup-counts.js

      - name: Cleanup
        if: always()
        run: docker rm -f pg-verify

      - name: Report Results
        run: |
          if [ "${{ job.status }}" == "success" ]; then
            curl -X POST $SLACK_WEBHOOK \
              -d '{"text": ":white_check_mark: Backup verification passed"}'
          else
            curl -X POST $SLACK_WEBHOOK \
              -d '{"text": ":x: Backup verification FAILED - investigate immediately"}'
          fi

Скрипт проверки целостности:

sql
-- scripts/verify-backup-integrity.sql

-- Проверка существования критических таблиц и наличия данных
DO $$
DECLARE
    v_count INT;
BEGIN
    -- Таблица users
    SELECT COUNT(*) INTO v_count FROM core.users;
    IF v_count = 0 THEN
        RAISE EXCEPTION 'CRITICAL: users table is empty';
    END IF;
    RAISE NOTICE 'users: % rows', v_count;

    -- Таблица partners
    SELECT COUNT(*) INTO v_count FROM mlm.partners;
    RAISE NOTICE 'partners: % rows', v_count;

    -- Commission transactions
    SELECT COUNT(*) INTO v_count FROM mlm.commission_transactions;
    RAISE NOTICE 'commission_transactions: % rows', v_count;

    -- Таблица orders
    SELECT COUNT(*) INTO v_count FROM product.orders;
    RAISE NOTICE 'orders: % rows', v_count;

    -- Проверка целостности внешних ключей
    SELECT COUNT(*) INTO v_count
    FROM mlm.partners p
    LEFT JOIN core.users u ON p.user_id = u.id
    WHERE u.id IS NULL;

    IF v_count > 0 THEN
        RAISE EXCEPTION 'CRITICAL: % orphaned partner records', v_count;
    END IF;

    -- Проверка целостности дерева
    SELECT COUNT(*) INTO v_count
    FROM mlm.partner_tree_paths ptp
    LEFT JOIN mlm.partners p ON ptp.ancestor_id = p.id
    WHERE p.id IS NULL;

    IF v_count > 0 THEN
        RAISE EXCEPTION 'CRITICAL: % orphaned tree path records', v_count;
    END IF;

    RAISE NOTICE 'All integrity checks passed';
END $$;

Отслеживание времени восстановления:

МетрикаЦельОповещение если
Время создания backup< 30 мин> 1 час
Время восстановления< 1 час> 2 часа
Время верификации< 15 мин> 30 мин
Общее RTO< 2 часа> 4 часа

Стратегия окружений

Матрица окружений

ОкружениеНазначениеТриггер деплояДанные
DevelopmentЛокальная разработкаРучнойSeed data
CIТестирование в пайплайнеКаждый PRСвежие на каждый запуск
StagingPre-production валидацияMerge в mainКлон production (анонимизированный)
ProductionLive системаРучное утверждениеРеальные данные

Анонимизация данных Staging

Данные production, клонированные в staging, должны быть анонимизированы для защиты приватности пользователей и соответствия GDPR/требованиям защиты данных.

Правила анонимизации по типам данных:

Категория данныхПолеМетод анонимизации
PIIemailfaker.email() с сохранением оригинального домена
PIIphone+7900${random7digits}
PIIfirst_name, last_namefaker.name()
PIIaddress fieldsfaker.address()
Финансовыеbank_account****${last4} (маскирование)
Финансовыеcard_numberПолностью удаляется
Финансовыеbalance amountsСохраняются (не PII)
KYCpassport_numberXX${random8digits}
KYCtax_id${random12digits}
KYCdocument_urlsЗаменяются placeholder изображениями
Authpassword_hashУстанавливается известный тестовый хэш пароля
Authsession tokensУдаляются
Auth2fa_secretsУдаляются
Auditip_address192.168.x.x (приватный диапазон)
Audituser_agentСохраняется (не PII)

Скрипт анонимизации:

sql
-- scripts/anonymize-staging.sql
-- Запускать после клонирования production в staging

BEGIN;

-- Таблица users
UPDATE core.users SET
    email = 'user_' || id::text || '@staging.iwm.local',
    phone = '+7900' || LPAD(FLOOR(RANDOM() * 10000000)::TEXT, 7, '0'),
    password_hash = '$2b$10$staging.password.hash.for.testing';  -- Пароль: "staging123"

-- Профили пользователей
UPDATE core.user_profiles SET
    first_name = 'Test',
    last_name = 'User_' || SUBSTRING(user_id::text, 1, 8),
    middle_name = NULL;

-- Адреса
UPDATE product.addresses SET
    first_name = 'Test',
    last_name = 'User',
    address_line1 = FLOOR(RANDOM() * 100)::TEXT || ' Test Street',
    address_line2 = 'Apt ' || FLOOR(RANDOM() * 100)::TEXT,
    city = 'Test City',
    phone = '+7900' || LPAD(FLOOR(RANDOM() * 10000000)::TEXT, 7, '0');

-- KYC документы
UPDATE core.kyc_verifications SET
    document_number = 'XX' || LPAD(FLOOR(RANDOM() * 100000000)::TEXT, 8, '0');

UPDATE core.kyc_documents SET
    file_url = 'https://staging-assets.iwm.local/placeholder-document.pdf',
    file_name = 'anonymized_document.pdf';

-- Детали выплат (чувствительные банковские данные)
UPDATE mlm.payout_requests SET
    payout_details = jsonb_set(
        payout_details,
        '{account_number}',
        '"****' || RIGHT(payout_details->>'account_number', 4) || '"'
    )
WHERE payout_details ? 'account_number';

-- Удаление чувствительных auth данных
DELETE FROM core.sessions;
DELETE FROM core.two_factor_auth;

-- Audit логи - анонимизация IP
UPDATE core.audit_log SET
    ip_address = ('192.168.' || (RANDOM() * 255)::INT || '.' || (RANDOM() * 255)::INT)::INET;

-- Реферальные ссылки партнёров - обновление домена
UPDATE mlm.referral_links SET
    full_url = REPLACE(full_url, 'iwm.com', 'staging.iwm.local');

COMMIT;

-- Проверка отсутствия утечки production данных
DO $$
BEGIN
    -- Проверка отсутствия реальных email
    IF EXISTS (SELECT 1 FROM core.users WHERE email NOT LIKE '%@staging.iwm.local') THEN
        RAISE EXCEPTION 'Anonymization failed: real emails found';
    END IF;

    -- Проверка отсутствия реальных номеров телефонов
    IF EXISTS (SELECT 1 FROM core.users WHERE phone NOT LIKE '+7900%') THEN
        RAISE EXCEPTION 'Anonymization failed: real phone numbers found';
    END IF;
END $$;

Автоматизированное обновление staging:

yaml
# .github/workflows/staging-refresh.yml
name: Refresh Staging Data

on:
  schedule:
    - cron: '0 3 * * 0'  # Еженедельно воскресенье 3AM
  workflow_dispatch:      # Ручной запуск

jobs:
  refresh-staging:
    runs-on: ubuntu-latest
    steps:
      - name: Create Production Snapshot
        run: |
          pg_dump $PRODUCTION_URL \
            --no-owner \
            --no-privileges \
            > production-snapshot.sql

      - name: Restore to Staging
        run: |
          psql $STAGING_URL -c "DROP SCHEMA IF EXISTS public CASCADE; CREATE SCHEMA public;"
          psql $STAGING_URL < production-snapshot.sql

      - name: Run Anonymization
        run: psql $STAGING_URL < scripts/anonymize-staging.sql

      - name: Verify Anonymization
        run: node scripts/verify-anonymization.js

      - name: Notify Team
        run: |
          curl -X POST $SLACK_WEBHOOK \
            -d '{"text": "Staging environment refreshed with anonymized production data"}'

Контроль доступа к staging:

РольДоступ к StagingЧто видит
DeveloperПолный доступТолько анонимизированные данные
QAПолный доступТолько анонимизированные данные
SupportНет доступа
Внешний подрядчикНет доступа

Паритет окружений

yaml
# Все окружения используют идентичные:
- Docker образы (тот же SHA)
- Схему базы данных (те же миграции)
- Структуру переменных окружения (разные значения)
- Конфигурацию инфраструктуры (масштабируется по-разному)

Валидация переменных окружения

typescript
// Валидируется при запуске приложения с помощью Zod
// CI должен проверять определение всех обязательных переменных

const envSchema = z.object({
  NODE_ENV: z.enum(['development', 'staging', 'production']),
  DATABASE_URL: z.string().url(),
  REDIS_URL: z.string().url(),
  JWT_SECRET: z.string().min(32),
  ENCRYPTION_MASTER_KEY: z.string().min(32),
  // ... все остальные обязательные переменные
});

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

Blue-Green деплой

┌─────────────────────────────────────────────────────────────────┐
│                      BLUE-GREEN DEPLOY                           │
│                                                                  │
│   Текущее состояние:                                             │
│   ┌─────────────┐                                               │
│   │    BLUE     │ ◀── Load Balancer ◀── Traffic                │
│   │   (v1.2.3)  │                                               │
│   └─────────────┘                                               │
│   ┌─────────────┐                                               │
│   │   GREEN     │     (idle)                                    │
│   │   (v1.2.3)  │                                               │
│   └─────────────┘                                               │
│                                                                  │
│   Деплой v1.2.4:                                                 │
│   1. Деплой в GREEN                                              │
│   2. Запуск health checks на GREEN                               │
│   3. Запуск smoke tests на GREEN                                 │
│   4. Переключение трафика на GREEN                               │
│   5. Мониторинг ошибок                                           │
│   6. При ошибках: Переключение обратно на BLUE (откат)           │
│   7. При стабильности: Обновление BLUE до v1.2.4 (синхронизация) │
└─────────────────────────────────────────────────────────────────┘

Процедура отката

yaml
# Автоматические триггеры отката:
- Отказ health check (3 подряд)
- Процент ошибок > 5% (по сравнению с baseline)
- Время ответа > 2x baseline

# Шаги отката:
1. Переключить load balancer на предыдущую версию
2. Оповестить дежурного инженера
3. Сохранить логи для расследования
4. НЕ запускать обратные миграции автоматически

Окно отката

ФазаДлительностьСтатус BLUEДействие при проблемах
Немедленная0-15 минРаботает, без трафикаМгновенное переключение обратно
Краткосрочная15 мин - 2 часаРаботает, тёплый резервБыстрый откат (< 1 мин)
Среднесрочная2-24 часаОстановлен, образ сохранёнПерезапуск BLUE, переключение трафика
Долгосрочная> 24 часовТерминированРедеплой предыдущей версии

Учёт расхождения данных:

  • Откат в течение 2 часов: Минимальное расхождение данных, откат безопасен
  • Откат после 2 часов: Аудит новых созданных данных, может потребоваться ручная сверка
  • Откат после 24 часов: Требуется план миграции данных, не автоматический
yaml
# Конфигурация окна отката
rollback:
  instant_window: 15m      # BLUE работает
  warm_standby: 2h         # BLUE остановлен но не терминирован
  image_retention: 24h     # Предыдущий образ сохраняется в registry
  max_auto_rollback: 2h    # После этого требуется ручное утверждение

Управление соединениями с БД во время деплоя

Во время blue-green деплоя обе версии могут работать одновременно. Это требует аккуратного управления пулом соединений.

┌─────────────────────────────────────────────────────────────────┐
│                 CONNECTION POOL DURING DEPLOY                    │
│                                                                  │
│   Database Pool Limit: 100 connections                           │
│                                                                  │
│   Нормальная работа:                                             │
│   ┌─────────────┐                                               │
│   │    BLUE     │ ─── 50 connections ───▶ ┌──────────────┐     │
│   │  (active)   │                         │              │     │
│   └─────────────┘                         │  PostgreSQL  │     │
│                                           │              │     │
│   Во время деплоя (оба работают):         │  Pool: 100   │     │
│   ┌─────────────┐                         │              │     │
│   │    BLUE     │ ─── 40 connections ───▶ │              │     │
│   │  (draining) │                         │              │     │
│   └─────────────┘                         │              │     │
│   ┌─────────────┐                         │              │     │
│   │   GREEN     │ ─── 40 connections ───▶ │              │     │
│   │  (starting) │                         └──────────────┘     │
│   └─────────────┘                                               │
│                                                                  │
│   Reserve: 20 connections для миграций и админских операций      │
└─────────────────────────────────────────────────────────────────┘

Конфигурация пула соединений:

typescript
// config/database.ts
const poolConfig = {
  // Нормальная работа
  default: {
    min: 5,
    max: 50,
  },
  // Во время деплоя (определяется через DEPLOY_MODE env)
  deployment: {
    min: 2,
    max: 40,  // Уменьшено для допуска перекрытия
  },
};

Последовательность деплоя для предотвращения исчерпания соединений:

yaml
deploy_steps:
  1. Установить GREEN pool в режим deployment (max: 40)
  2. Запустить GREEN инстансы
  3. Дождаться health check GREEN
  4. Запустить миграции (используют зарезервированные соединения)
  5. Постепенно переключать трафик (10% → 50% → 100%)
  6. Установить BLUE в режим drain (прекратить приём новых соединений)
  7. Дождаться закрытия соединений BLUE (max 30s)
  8. Остановить BLUE инстансы
  9. Установить GREEN pool в нормальный режим (max: 50)

PgBouncer рекомендуется для production:

ini
# pgbouncer.ini
[pgbouncer]
pool_mode = transaction
max_client_conn = 200
default_pool_size = 50
reserve_pool_size = 10
reserve_pool_timeout = 3

Health Checks

typescript
// /health/live - Работает ли процесс?
// Возвращает 200 если процесс жив

// /health/ready - Может ли обрабатывать запросы?
// Проверки:
// - Соединение с базой данных
// - Соединение с Redis
// - Доступность необходимых сервисов

// /health/startup - Завершена ли инициализация?
// Используется Kubernetes чтобы знать когда отправлять трафик

Требования к тестам

Пороги покрытия

json
{
  "coverageThreshold": {
    "global": {
      "branches": 75,
      "functions": 80,
      "lines": 80,
      "statements": 80
    },
    "src/modules/mlm/domain/**": {
      "branches": 90,
      "functions": 95,
      "lines": 95
    },
    "src/modules/payment/domain/**": {
      "branches": 90,
      "functions": 95,
      "lines": 95
    }
  }
}

Наборы тестов для платформы

НаборФокусТриггер
Commission TestsМногоуровневые расчёты, edge cases, округлениеКаждый PR
Tree Operation TestsINSERT, MOVE, лимиты глубины, предотвращение цикловКаждый PR
Payment IntegrationОбработка webhook, идемпотентность, возвратыКаждый PR
State Machine TestsПереходы заказов, предотвращение невалидных состоянийКаждый PR
Encryption TestsЦикл encrypt/decrypt, ротация ключейКаждый PR
Load TestsРасчёт комиссий под нагрузкойЕженедельно / Pre-release

Спецификации нагрузочных тестов

Нагрузочные тесты проверяют производительность системы в реалистичных и стрессовых условиях.

Целевые метрики:

СценарийЦельПорог (отказ если)
Расчёт комиссий100 заказов/сек< 50 заказов/сек
Конкурентные запросы выплат50 запросов/сек< 25 запросов/сек
Обход дерева (10 уровней)< 50ms p95> 200ms p95
Время ответа API (p95)< 200ms> 500ms
Соединения с БДСтабильно на max poolИсчерпание пула
Использование памяти< 512MB на инстанс> 1GB

Сценарии нагрузочных тестов:

typescript
// load-tests/k6/commission-load.js
import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  scenarios: {
    // Sustained load: Нормальная работа
    sustained: {
      executor: 'constant-arrival-rate',
      rate: 100,              // 100 заказов в секунду
      timeUnit: '1s',
      duration: '5m',
      preAllocatedVUs: 50,
    },
    // Spike: Симуляция flash sale
    spike: {
      executor: 'ramping-arrival-rate',
      startRate: 100,
      timeUnit: '1s',
      stages: [
        { duration: '1m', target: 100 },   // Нормальный
        { duration: '30s', target: 500 },  // Всплеск до 5x
        { duration: '2m', target: 500 },   // Поддержание всплеска
        { duration: '30s', target: 100 },  // Возврат к нормальному
      ],
      preAllocatedVUs: 200,
    },
    // Stress: Поиск точки отказа
    stress: {
      executor: 'ramping-arrival-rate',
      startRate: 50,
      timeUnit: '1s',
      stages: [
        { duration: '2m', target: 200 },
        { duration: '2m', target: 400 },
        { duration: '2m', target: 600 },
        { duration: '2m', target: 800 },   // Поиск где ломается
      ],
      preAllocatedVUs: 300,
    },
  },
  thresholds: {
    http_req_duration: ['p(95)<500'],      // 95% запросов < 500ms
    http_req_failed: ['rate<0.01'],        // Процент ошибок < 1%
    'commission_calculated': ['rate>50'],  // Минимум 50/s обработано
  },
};

export default function () {
  const orderPayload = {
    userId: `user_${__VU}_${__ITER}`,
    amount: Math.floor(Math.random() * 10000) + 1000,
    referringPartnerId: 'partner_test_001',
  };

  const res = http.post(
    `${__ENV.API_URL}/api/v1/orders`,
    JSON.stringify(orderPayload),
    { headers: { 'Content-Type': 'application/json' } }
  );

  check(res, {
    'order created': (r) => r.status === 201,
    'commission triggered': (r) => r.json('commissionJobId') !== null,
  });

  sleep(0.1);
}

Поведение соединений с БД под нагрузкой:

yaml
# Мониторинг во время нагрузочных тестов
metrics:
  - pg_stat_activity.active_connections
  - pg_stat_activity.waiting_connections
  - pg_stat_user_tables.seq_scan (не должен резко расти)
  - pg_stat_user_tables.idx_scan (должен справляться с нагрузкой)
  - pg_locks.blocked_queries (должен быть ноль)

Интеграция в CI:

yaml
- name: Run Load Tests (Pre-release)
  if: github.event_name == 'release'
  run: |
    k6 run load-tests/k6/commission-load.js \
      --env API_URL=${{ secrets.STAGING_URL }} \
      --out json=load-test-results.json

    # Отказ релиза если пороги не пройдены
    node scripts/validate-load-test.js load-test-results.json

Мониторинг и уведомления

Уведомления о деплое

yaml
# Уведомлять о:
- Деплой начат (staging/production)
- Деплой успешен
- Деплой провален
- Откат инициирован
- Требуется ручное утверждение

# Каналы:
- Slack / Telegram
- Email (для провалов)
- PagerDuty (для production провалов)

Post-Deploy верификация

yaml
# Smoke tests запускаются сразу после деплоя:
1. GET /health/ready → 200
2. POST /api/v1/auth/login (тестовый пользователь) → 200 + JWT
3. GET /api/v1/products → 200 + валидный ответ
4. GET /api/v1/mlm/ranks → 200 + валидный ответ

# При любом отказе → инициировать откат

Метрики для мониторинга после деплоя

МетрикаСравнение с baselineПорог оповещения
Процент ошибокvs. предыдущий час> 2x baseline
Время ответа (p95)vs. предыдущий час> 1.5x baseline
Время запросов к БДvs. предыдущий час> 2x baseline
Использование памятиvs. предыдущий деплой> 120%
Использование CPUvs. предыдущий деплой> 150%

Процедура Hotfix

Когда production сломан и требуется быстрое исправление, процедура hotfix позволяет обойти нормальный поток сохраняя безопасность.

Когда использовать Hotfix

СитуацияИспользовать Hotfix?Нормальный деплой OK?
Production не работает / 500 ошибкиДаНет
Критическая уязвимость безопасностиДаНет
Обработка платежей сломанаДаНет
Расчёт комиссий неправильныйДаНет
Мелкий баг, пользователи не затронутыНетДа
Деградация производительности (< 2x)НетДа
Фича не работает как ожидалосьНетДа

Поток Hotfix

┌─────────────────────────────────────────────────────────────────┐
│                       HOTFIX PROCEDURE                           │
│                                                                  │
│   1. ОЦЕНКА (5 мин max)                                          │
│      └─▶ Подтвердить серьёзность, определить root cause         │
│      └─▶ Решение: Hotfix vs Откат vs Ожидание                   │
│                                                                  │
│   2. ВЕТКА                                                       │
│      └─▶ git checkout -b hotfix/ISSUE-ID main                   │
│      └─▶ НЕ от feature ветки                                    │
│                                                                  │
│   3. ИСПРАВЛЕНИЕ                                                 │
│      └─▶ Только минимальное изменение                           │
│      └─▶ Без рефакторинга                                       │
│      └─▶ Без "раз уж мы тут" дополнений                         │
│                                                                  │
│   4. ВАЛИДАЦИЯ (Сокращённая)                                     │
│      └─▶ Unit тесты для изменённого кода                        │
│      └─▶ Type check                                             │
│      └─▶ Ручной smoke test                                      │
│      └─▶ Пропустить: Full E2E, Load tests, SAST                 │
│                                                                  │
│   5. УТВЕРЖДЕНИЕ                                                 │
│      └─▶ Один reviewer (senior engineer)                        │
│      └─▶ PR не требуется (прямой push с утверждением)           │
│                                                                  │
│   6. ДЕПЛОЙ                                                      │
│      └─▶ Прямо в production (пропустить staging)                │
│      └─▶ Наблюдать за метриками 15 минут                        │
│                                                                  │
│   7. ПОСЛЕДУЮЩИЕ ДЕЙСТВИЯ (в течение 24 часов)                   │
│      └─▶ Создать правильный PR backport в main                  │
│      └─▶ Добавить регрессионный тест                            │
│      └─▶ Написать incident report                               │
└─────────────────────────────────────────────────────────────────┘

Команды Hotfix

bash
# 1. Создать hotfix ветку от production тега
git fetch --tags
git checkout -b hotfix/IWM-123-fix-commission-calc v1.2.3

# 2. Сделать исправление
# ... изменения кода ...

# 3. Запустить сокращённые тесты
npm run test:unit -- --testPathPattern="commission"
npm run type-check

# 4. Деплой напрямую (требует HOTFIX_APPROVED=true)
HOTFIX_APPROVED=true npm run deploy:production

# 5. Тегировать hotfix
git tag -a v1.2.3-hotfix.1 -m "Hotfix: Commission calculation overflow"
git push origin v1.2.3-hotfix.1

# 6. Backport в main
git checkout main
git cherry-pick <hotfix-commit-sha>
git push origin main

Матрица утверждения Hotfix

Тип исправленияТребуемый утверждающийМожет утвердить сам
Логическое исправление (без БД)1 senior engineerНет
Изменение конфигурации1 engineerДа (если дежурный)
Исправление БД2 engineers + DBAНет
Исправление безопасности1 security + 1 engineerНет
Откат к предыдущей версии1 engineerДа (если дежурный)

Дежурство и эскалация

Путь эскалации

┌─────────────────────────────────────────────────────────────────┐
│                      ESCALATION LADDER                           │
│                                                                  │
│   Level 0: Автоматизация                                         │
│   └─▶ Health check отказ → Авто-откат                           │
│   └─▶ Процент ошибок > 5% → Авто-откат                          │
│   └─▶ Оповещение в #alerts канал                                │
│                                                                  │
│   Level 1: Дежурный инженер (0-15 мин)                          │
│   └─▶ Получить PagerDuty alert                                  │
│   └─▶ Acknowledge в течение 5 минут                             │
│   └─▶ Оценка: Можно исправить самому? Нужна эскалация?          │
│                                                                  │
│   Level 2: Senior Engineer (15-30 мин)                          │
│   └─▶ Авто-эскалация если L1 не acknowledge                     │
│   └─▶ Присоединиться к incident call                            │
│   └─▶ Решение: Hotfix vs Откат vs Внешняя помощь                │
│                                                                  │
│   Level 3: Engineering Lead + Команда (30+ мин)                 │
│   └─▶ Несколько инженеров на связи                              │
│   └─▶ Координация со стейкхолдерами                             │
│   └─▶ Коммуникация с клиентами если нужно                       │
│                                                                  │
│   Level 4: Executive (Major Incident)                            │
│   └─▶ Продолжительный outage (> 1 часа)                         │
│   └─▶ Утечка данных / Инцидент безопасности                     │
│   └─▶ Финансовое влияние (платежи затронуты)                    │
└─────────────────────────────────────────────────────────────────┘

Конфигурация PagerDuty

yaml
# pagerduty-config.yml
services:
  - name: IWM Production
    escalation_policy: iwm-production
    alert_creation: create_alerts_and_incidents

escalation_policies:
  - name: iwm-production
    rules:
      - escalation_delay_minutes: 5
        targets:
          - type: schedule_reference
            id: primary-oncall

      - escalation_delay_minutes: 15
        targets:
          - type: schedule_reference
            id: senior-oncall

      - escalation_delay_minutes: 30
        targets:
          - type: user_reference
            id: engineering-lead
          - type: user_reference
            id: cto

schedules:
  - name: primary-oncall
    rotation: weekly
    users: [engineer_1, engineer_2, engineer_3, engineer_4]

  - name: senior-oncall
    rotation: weekly
    users: [senior_1, senior_2]

Уровни серьёзности оповещений

СерьёзностьВремя реагированияПримеры
P1 - Критический5 минProduction не работает, платежи не проходят, утечка данных
P2 - Высокий15 минВажная фича сломана, процент ошибок > 5%
P3 - Средний1 часДеградация производительности, некритические ошибки
P4 - НизкийСледующий рабочий деньМелкие баги, оповещения мониторинга

Коммуникация при инциденте

yaml
# Во время инцидента:
channels:
  - "#incident-active"      # Обновления в реальном времени (только инженеры)
  - "#engineering"          # Обновления статуса (каждые 30 мин)
  - "#general"              # Статус для клиентов (если нужно)

templates:
  initial_alert: |
    :rotating_light: **INCIDENT DETECTED**
    Severity: {severity}
    Service: {service}
    Description: {description}
    On-call: @{oncall_user}
    Incident channel: #incident-{id}

  status_update: |
    **Incident Update** ({time} с начала)
    Status: {investigating|identified|fixing|monitoring|resolved}
    Impact: {impact_description}
    Следующее обновление: {eta}

  resolution: |
    :white_check_mark: **INCIDENT RESOLVED**
    Duration: {duration}
    Root cause: {root_cause}
    Fix applied: {fix_description}
    Follow-up: {follow_up_ticket}

Ротация секретов

Расписание ротации

СекретЧастота ротацииАвто-ротацияТребуется downtime
JWT_SECRET90 днейНетНет (период двойного ключа)
ENCRYPTION_MASTER_KEY180 днейНетДа (перешифрование)
Пароль БД90 днейДа (через cloud)Нет
API ключи (внешние)365 днейВарьируетсяНет
Webhook секреты180 днейНетТребуется координация

Ротация JWT Secret (Zero Downtime)

typescript
// Поддержка двойных JWT секретов во время ротации
const jwtConfig = {
  // Текущий секрет (для подписи новых токенов)
  current: process.env.JWT_SECRET,

  // Предыдущий секрет (для валидации старых токенов во время ротации)
  previous: process.env.JWT_SECRET_PREVIOUS || null,

  // Окно ротации (как долго принимать старые токены)
  rotationWindowDays: 7,
};

// Валидация принимает оба секрета
function validateToken(token: string): JwtPayload {
  try {
    return jwt.verify(token, jwtConfig.current);
  } catch (e) {
    if (jwtConfig.previous) {
      return jwt.verify(token, jwtConfig.previous);
    }
    throw e;
  }
}

Процедура ротации:

yaml
jwt_rotation_steps:
  1. Сгенерировать новый JWT_SECRET
  2. Установить JWT_SECRET_PREVIOUS = текущий JWT_SECRET
  3. Установить JWT_SECRET = новый секрет
  4. Деплой (оба секрета теперь валидны)
  5. Ждать 7 дней (токены истекают, refresh использует новый секрет)
  6. Удалить JWT_SECRET_PREVIOUS
  7. Деплой финальной конфигурации

Ротация ключа шифрования

Ротация ключа шифрования требует перешифрования существующих данных.

typescript
// Версионирование ключей в зашифрованных данных
interface EncryptedField {
  version: string;      // 'v1', 'v2', и т.д.
  ciphertext: string;
  iv: string;
}

// Расшифровка с поддержкой версий
async function decrypt(field: EncryptedField): Promise<string> {
  const key = getKeyByVersion(field.version);
  return decryptWithKey(field.ciphertext, field.iv, key);
}

// Фоновая job перешифрования
async function reEncryptAllData(fromVersion: string, toVersion: string) {
  const batchSize = 1000;
  let offset = 0;

  while (true) {
    const records = await db.taxIdentification.findMany({
      where: { encryptedData: { path: ['version'], equals: fromVersion } },
      take: batchSize,
      skip: offset,
    });

    if (records.length === 0) break;

    for (const record of records) {
      const decrypted = await decrypt(record.encryptedData);
      const reEncrypted = await encrypt(decrypted, toVersion);

      await db.taxIdentification.update({
        where: { id: record.id },
        data: { encryptedData: reEncrypted },
      });
    }

    offset += batchSize;
    logger.info(`Re-encrypted ${offset} records`);
  }
}

Процедура ротации:

yaml
encryption_key_rotation:
  1. Сгенерировать новый ключ (ENCRYPTION_MASTER_KEY_V2)
  2. Деплой с обоими ключами доступными
  3. Новые шифрования используют v2
  4. Запустить фоновую job перешифрования
  5. Мониторить завершение job (может занять часы)
  6. Проверить что все записи v2
  7. Удалить старый ключ из конфигурации
  8. Безопасно удалить старый ключ из менеджера секретов

Ротация Webhook секретов

Внешние webhook (платёжные провайдеры) требуют координации.

yaml
webhook_rotation:
  stripe:
    1. Сгенерировать новый webhook в Stripe dashboard
    2. Добавить новый STRIPE_WEBHOOK_SECRET_V2 в конфигурацию
    3. Деплой (принимать оба секрета)
    4. Отключить старый webhook в Stripe dashboard
    5. Удалить старый секрет из конфигурации
    coordination: Self-service, без downtime

  payment_provider:
    1. Связаться с поддержкой провайдера
    2. Запланировать окно ротации
    3. Провайдер отправляет тестовый webhook с новым секретом
    4. Подтвердить получение
    5. Провайдер переключается на новый секрет
    6. Обновить конфигурацию
    coordination: Зависит от провайдера, может требовать окно

Приоритет реализации

Обязательно (Фаза 1)

КомпонентПричина
Build + Type checkБазовая корректность
Unit тесты (80%)Точность финансовых расчётов
Интеграционные тестыКорректность операций с БД
npm auditСоответствие OWASP A06
Сканирование секретовПредотвращение утечки credentials
Валидация миграцийЦелостность базы данных
Health checksВерификация деплоя
Staging окружениеPre-production валидация

Желательно (Фаза 2)

КомпонентПричина
E2E тесты (критические сценарии)Валидация пользовательских путей
Сканирование контейнеров (Trivy)Безопасность инфраструктуры
SAST (CodeQL)Уязвимости на уровне кода
Blue-green деплойРелизы без downtime
Автоматический откатБыстрое восстановление
Уведомления о деплоеОсведомлённость команды
Coverage gatesПредотвращение регрессии

Желательно (Фаза 3)

КомпонентПричина
Preview environmentsТестирование PR
Нагрузочное тестированиеВалидация производительности
Визуальная регрессияКонсистентность UI
Авто-changelogДокументация релизов
Авто-обновление зависимостейАвтоматизация поддержки
Feature flagsПостепенные rollouts
Chaos testingВерификация устойчивости

Связанные документы