Документация Архитектура масштабирования

Архитектура масштабирования

Mockarty изначально спроектирован как распределённая система. Независимо от того, запускаете ли вы один экземпляр для локальной разработки или разворачиваете десятки узлов в нескольких дата-центрах, архитектура остаётся той же – вы просто добавляете новые компоненты.

Аналогия: Представьте Mockarty как сеть пиццерий. Одна точка (один Admin Node) справляется со всем. Но по мере роста заказов вы добавляете курьеров (Mock Resolver), чтобы быстрее обслуживать клиентов, а основная кухня (Admin Node) занимается управлением меню и координацией. Если нужно тестировать новые рецепты (запускать тесты), вы организуете тестовую кухню (Runner Agent), чтобы не замедлять основную работу.

В этом руководстве описано, как компоненты взаимодействуют друг с другом, как их масштабировать и за чем наблюдать после запуска.

Примечание о Docker-образах: Официальные образы публикуются в Docker Hub как mockarty/mockarty (admin), mockarty/resolver, mockarty/runner, mockarty/generator и mockarty/cli. В сниппетах ниже могут встречаться другие реестры (например, ghcr.io/...) для иллюстрации — заменяйте их на реестр, который зеркалит ваша организация.

URL-адреса в примерах: Во всех примерах используется localhost:5770 как адрес Mockarty по умолчанию. Если ваш экземпляр работает на удалённом сервере, замените localhost:5770 на реальный адрес (например, https://mockarty.company.com или http://192.168.1.50:5770). Подробнее — в разделе Полезные функции и советы.


Обзор архитектуры

Mockarty состоит из четырёх типов компонентов, которые взаимодействуют по HTTP и gRPC:

ADMIN NODE порт 5770 Web UI REST API Координатор gRPC :5773 Composite Repository PostgreSQL (обязателен) Redis (опционален) gRPC регистрация + heartbeat MOCK RESOLVER #1 порт 5780 Разрешение моков (HTTP) MOCK RESOLVER #2 порт 5781 ...ещё резолверы RUNNER AGENT #1 порт 6770 api_test, performance RUNNER AGENT #2 порт 6771

Роли компонентов

Компонент Порт по умолчанию Роль
Admin Node 5770 (HTTP), 5773 (gRPC) Центральный узел. Управляет моками, обслуживает UI, координирует раннеров, выполняет миграции. В каждом развёртывании ровно один Admin Node.
Mock Resolver 5780+ Лёгкие узлы, обрабатывающие входящие запросы к мокам. Читают определения моков из базы данных (с кешированием), но никогда не пишут. Можно запускать сколько угодно.
Runner Agent 6770+ Распределённые воркеры для выполнения API-тестов и нагрузочных тестов. Регистрируются у Координатора по gRPC и забирают задачи из очереди.
Coordinator 5773 (gRPC, встроен в Admin) gRPC-сервис, встроенный в Admin Node. Раннеры и резолверы регистрируются здесь, получают задачи и отправляют heartbeat-сигналы.

Ключевая идея: Admin Node – единственный компонент, который пишет в базу данных. Резолверы только читают. Такое разделение позволяет масштабировать обработку запросов к мокам (чтение) независимо от нагрузки на Admin Node.

Dashboard Admin Node с подключёнными резолверами и раннерами


Как работает разрешение моков

Когда клиент отправляет запрос на узел-резолвер, происходит следующее:

Клиент MOCK RESOLVER 1. Сопоставление маршрута 2. Проверка кеша 3. Чтение из БД 4. Оценка условий 5. Рендеринг ответа Faker / JsonPath 6. Возврат ответа Попадание в кеш? Вернуть сразу Промах? Запрос к PostgreSQL Ответ

Паттерн Composite Repository

Каждый узел (Admin и резолвер) использует Composite Repository, объединяющий три уровня хранения:

  1. Ristretto (кеш в памяти) — поиск за микросекунды. Всегда доступен, без внешних зависимостей. Хранит недавно запрошенные моки в ограниченном LRU-кеше.
  2. Redis (опционально, только admin-нода) — общий кеш на admin-ноде. Поиск за доли миллисекунды. Резолверы не используют Redis — они работают только с Ristretto.
  3. PostgreSQL (обязателен) — источник истины. Все записи сначала идут сюда. Чтение проваливается до PostgreSQL, если кеши не содержат нужных данных.

Путь чтения следует паттерну read-through:

Запрос Ristretto L1 в памяти промах Redis L2 общий промах PostgreSQL L3 персистентный попадание? Вернуть готово попадание? Вернуть обновить Ristretto авторитетный Вернуть обновить Redis и Ristretto

Запись всегда идёт сначала в PostgreSQL, затем кеши обновляются синхронно, чтобы избежать устаревших данных сразу после записи. Composite-слой также обрабатывает конфликты сериализации с автоматическими повторами (до 3 попыток) для сценариев с высокой конкурентностью.


Горизонтальное масштабирование с помощью резолверов

Зачем нужны резолверы?

Admin Node выполняет множество задач: обслуживает UI, запускает фоновые процессы (очистка, бэкапы, планирование), координирует раннеров и обрабатывает запросы к мокам. При высокой нагрузке разрешение моков — самая частая операция — может «вытеснить» административные функции.

Резолверы решают эту проблему, перенося обработку моков на выделенные лёгкие процессы. Каждый резолвер:

  • Обрабатывает только запросы к мокам (HTTP, gRPC, GraphQL, SOAP, SSE, WebSocket)
  • Подключается напрямую к PostgreSQL (только чтение, требуется DB_DSN)
  • Имеет собственный кеш Ristretto в памяти (без поддержки Redis)
  • Регистрируется у Координатора для отслеживания состояния

Практическая ценность: добавление 3 резолверов позволяет обработать примерно в 4 раза больше трафика моков, не затрагивая Admin Node. Admin остаётся отзывчивым для работы с UI, управления API и оркестрации тестов.

Когда добавлять резолверы

Симптом Действие
Задержка ответа моков растёт под нагрузкой Добавьте узлы-резолверы за балансировщиком нагрузки
UI Admin становится медленным во время нагрузочных тестов Перенаправьте трафик моков на резолверы, оставьте Admin для UI/API
Нужна географическая распределённость Разверните резолверы ближе к потребляющим сервисам
Нужно обновление моков без простоя Резолверы подхватывают изменения из БД; перезапускайте их без остановки Admin

Пример: 3 резолвера за Nginx

# docker-compose.scaling.yml
version: "3.8"

services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: mockarty
      POSTGRES_USER: mockarty
      POSTGRES_PASSWORD: secret
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  admin:
    image: mockarty/admin:latest
    environment:
      DB_DSN: "postgres://mockarty:secret@postgres:5432/mockarty?sslmode=disable"
      CACHE_TYPE: redis
      REPO_REDIS_HOST: redis
      REPO_REDIS_PORT: "6379"
      HTTP_PORT: "5770"
      RUNNER_GRPC_PORT: "5773"
    ports:
      - "5770:5770"
      - "5773:5773"
    depends_on:
      - postgres
      - redis

  resolver-1:
    image: mockarty/resolver:latest
    environment:
      DB_DSN: "postgres://mockarty:secret@postgres:5432/mockarty?sslmode=disable"
      HTTP_PORT: "5780"
      GRPC_PORT: "4780"
      COORDINATOR_ADDR: admin:5773
      API_TOKEN: "${RESOLVER_TOKEN}"
    depends_on:
      - admin

  resolver-2:
    image: mockarty/resolver:latest
    environment:
      DB_DSN: "postgres://mockarty:secret@postgres:5432/mockarty?sslmode=disable"
      HTTP_PORT: "5780"
      GRPC_PORT: "4780"
      COORDINATOR_ADDR: admin:5773
      API_TOKEN: "${RESOLVER_TOKEN}"
    depends_on:
      - admin

  resolver-3:
    image: mockarty/resolver:latest
    environment:
      DB_DSN: "postgres://mockarty:secret@postgres:5432/mockarty?sslmode=disable"
      HTTP_PORT: "5780"
      GRPC_PORT: "4780"
      COORDINATOR_ADDR: admin:5773
      API_TOKEN: "${RESOLVER_TOKEN}"
    depends_on:
      - admin

  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - resolver-1
      - resolver-2
      - resolver-3

volumes:
  pgdata:

nginx.conf для балансировки нагрузки:

events {
    worker_connections 1024;
}

http {
    upstream resolvers {
        least_conn;
        server resolver-1:5780;
        server resolver-2:5780;
        server resolver-3:5780;
    }

    server {
        listen 80;

        # Трафик разрешения моков → резолверы
        location / {
            proxy_pass http://resolvers;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_connect_timeout 5s;
            proxy_read_timeout 30s;
        }

        # Проверка работоспособности
        location /health {
            proxy_pass http://resolvers;
        }
    }
}

Ваши потребляющие сервисы обращаются к nginx:8080 для разрешения моков, а разработчики заходят на admin:5770 для работы с UI и управления API.


Архитектура Runner Agent

Runner Agent — это распределённые воркеры, выполняющие долгие задачи: коллекции API-тестов и нагрузочные тесты.

Возможности (Capabilities)

Каждый раннер объявляет свои возможности при регистрации:

Возможность Что выполняет
api_test Коллекции API-тестов, запланированные тестовые наборы
performance Скрипты нагрузочного/перформанс-тестирования

Раннер может иметь несколько возможностей. Задаются через переменную окружения CAPABILITIES:

CAPABILITIES="api_test,performance"

Общие и пространственные раннеры

Раннеры могут работать в двух режимах:

  • Общие раннеры (scope: admin) — принимают задачи из любого пространства имён. Создаются с токенами интеграции административного уровня. Подходят для общей инфраструктуры.
  • Пространственные раннеры — принимают задачи только из назначенного пространства имён. Создаются с токенами интеграции, привязанными к пространству имён. Подходят для изоляции команд.
Admin Node (Координатор) Общий Runner A ВСЕ пространства Общий Runner B ВСЕ пространства Runner Team-Alpha только "alpha" Runner Team-Beta только "beta"

Процесс распределения задач

1. Пользователь запускает тест (через UI или API) 2. Admin Node создаёт задачу в БД 3. Координатор назначает задачу раннеру (по возможностям + области видимости) 4. Раннер забирает задачу через gRPC-поток 5. Раннер выполняет задачу, отправляя обновления (в реальном времени: gRPC -> SSE в браузер) 6. Раннер возвращает результаты Координатору 7. Результаты сохраняются в БД и отображаются в UI

Настройка Runner Agent

# Обязательные
COORDINATOR_ADDR=mockarty:5773      # gRPC-адрес Координатора
API_TOKEN=mki_xxxxx                 # Токен интеграции (формат mki_*)
RUNNER_NAME=runner-1                # Уникальное имя раннера

# Опциональные
CAPABILITIES=api_test,performance   # Что умеет этот раннер
SHARED=true                         # Принимать задачи из всех пространств имён
NAMESPACE=team-alpha                # Только если SHARED=false
MAX_CONCURRENT_TASKS=3              # Макс. параллельных задач (по умолчанию: 3)

Heartbeat-сигналы и отказоустойчивость

Раннеры отправляют heartbeat-сигналы Координатору каждые несколько секунд (настраивается через RUNNER_HEARTBEAT_TIMEOUT, по умолчанию 30 секунд). Если раннер перестаёт отвечать:

  1. Координатор помечает его как офлайн после истечения таймаута heartbeat
  2. Все выполняемые задачи возвращаются в очередь для других раннеров
  3. Когда раннер возвращается, он автоматически перерегистрируется

Таймаут задачи по умолчанию — 30 минут (RUNNER_TASK_TIMEOUT), что предотвращает блокировку очереди зависшими задачами.


Уровни базы данных и кеширования

PostgreSQL — источник истины

PostgreSQL обязателен для любого продуктивного развёртывания. В нём хранятся:

  • Все определения моков и их условия
  • Данные хранилищ (Global, Chain, Mock stores)
  • Коллекции API-тестов, результаты и расписания
  • Учётные записи пользователей, сессии, политики RBAC
  • Журналы аудита и конфигурации вебхуков
  • Очередь задач раннеров и результаты

Рекомендуемая версия: PostgreSQL 14+ (улучшенная производительность JSON и оптимизация запросов).

SQLite поддерживается как альтернатива для одноузловых развёртываний (разработка, desktop, встраиваемые инсталляции). SQLite нельзя использовать при CLUSTER_MODE=true — advisory locks для выбора лидера требуют PostgreSQL.

MySQL не поддерживается. Константа DB_USE=mysql существует в коде как placeholder для будущего драйвера, но миграции и bootstrap реализованы только для PostgreSQL и SQLite. Не пытайтесь запускать Mockarty поверх MySQL — процесс завершится с ошибкой при применении миграций.

Redis — общий слой кеширования

Redis опционален и доступен только на admin-ноде. При включении (CACHE_TYPE=redis):

  • Admin-нода использует Redis как общий слой кеширования наряду с Ristretto
  • Инвалидация кеша работает через систему уведомлений об изменениях в БД
  • Задержка разрешения моков на admin-ноде снижается до долей миллисекунды для закешированных моков

Примечание: Резолверы не поддерживают Redis. Они используют исключительно кеш Ristretto в памяти, а данные загружаются напрямую из PostgreSQL (требуется DB_DSN).

Настройка:

CACHE_TYPE=redis
REPO_REDIS_HOST=redis
REPO_REDIS_PORT=6379
REPO_REDIS_PASSWORD=secret    # если включена аутентификация

Ristretto — кеш в памяти

На каждом узле (admin и резолвер) всегда используется кеш Ristretto в памяти. Это основной слой кеширования для резолверов. Он обеспечивает:

  • Поиск с нулевой задержкой для часто используемых моков (микросекунды)
  • Отсутствие внешних зависимостей
  • Ограниченное потребление памяти с вытеснением по LRU

На admin-ноде, когда Redis также настроен, Ristretto работает как L1-кеш, а Redis как L2:

Запрос Ristretto L1 · в процессе Redis L2 · общий PostgreSQL L3 · персистентный

Выбор стратегии кеширования

Развёртывание CACHE_TYPE Почему
Один узел, разработка/тестирование inmemory (по умолчанию) Redis не нужен. Ristretto справляется сам.
Продуктивная admin-нода redis Admin-нода получает преимущество от Redis как L2-кеша наряду с Ristretto.
Несколько резолверов inmemory Резолверы прогревают кеш Ristretto из PostgreSQL при запуске и периодически обновляют.

Паттерны развёртывания

Паттерн 1: один узел (разработка / небольшие команды)

Admin Node SQLite или PostgreSQL порт 5770 UI + API + Моки
# Минимальный запуск с SQLite
DB_USE=sqlite ./mockarty
  • Когда: локальная разработка, демонстрации, небольшие команды (< 5 человек), < 100 моков
  • Плюсы: нулевая инфраструктура, один бинарник, мгновенный запуск
  • Минусы: нет горизонтального масштабирования, ограничения SQLite при конкурентной записи

Паттерн 2: небольшая команда (PostgreSQL, Admin + 1 резолвер)

Admin :5770 Резолвер :5780 PostgreSQL источник истины
  • Когда: команда из 5-20 человек, умеренный трафик моков, нужно сохранить отзывчивость UI
  • Плюсы: Admin разгружен от трафика моков, простая настройка
  • Минусы: резолвер — единая точка отказа для разрешения моков

Паттерн 3: средний (PostgreSQL + Redis, 2 резолвера, 1 раннер)

Nginx :8080 Резолвер #1 :5780 Резолвер #2 :5781 PostgreSQL источник истины Admin :5770 Redis кеш admin Runner Agent api_test + performance
  • Когда: 20-100 пользователей, сотни моков, автоматизированные тестовые пайплайны
  • Плюсы: резервирование резолверов, общий Redis-кеш, распределённое выполнение тестов
  • Рекомендуемые ресурсы: 2 CPU / 4 ГБ RAM на резолвер, 4 CPU / 8 ГБ RAM для Admin

Паттерн 4: крупный / корпоративный

Балансировщик L7 / L4 Резолвер #1 :5780 Резолвер #2 :5781 Резолвер #N ...N резолверов Redis кеш admin PostgreSQL Primary Read Replicas (для резолверов) Admin :5770 / :5773 координатор Runner #1 общий Runner #M ns:beta ...M раннеров
  • Когда: 100+ пользователей, тысячи моков, многокомандные пространства имён, требования к SLA
  • Инфраструктура: PostgreSQL HA (primary + реплики), Redis Sentinel или Cluster, N резолверов за L7-балансировщиком, M раннеров (общие + по пространствам имён)
  • Плюсы: отказоустойчивость, независимое масштабирование каждого уровня, изоляция пространств имён

Сетевая топология

Порты и протоколы

Компонент Порт Протокол Направление Назначение
Admin Node 5770 HTTP/HTTPS Входящий Web UI, REST API, разрешение моков
Coordinator 5773 gRPC Входящий Регистрация раннеров/резолверов, распределение задач
Resolver 5780+ HTTP/HTTPS Входящий Только разрешение моков
Runner Agent 6770+ HTTP Входящий (опционально) Панель мониторинга раннера
PostgreSQL 5432 TCP Внутренний Подключения к БД от Admin и резолверов
Redis 6379 TCP Внутренний Подключения к кешу только от Admin

TLS между компонентами

Координатор (gRPC) поддерживает TLS для связи раннер-Admin:

# Admin Node (TLS Координатора)
RUNNER_GRPC_TLS_ENABLED=true
RUNNER_GRPC_TLS_CERT_FILE=/path/to/server.crt
RUNNER_GRPC_TLS_KEY_FILE=/path/to/server.key
RUNNER_GRPC_TLS_DIR=.mockarty/tls

# Опционально: mTLS (взаимный TLS) — требовать клиентские сертификаты
RUNNER_GRPC_TLS_CLIENT_CA_CERT=/path/to/ca.crt

Если RUNNER_GRPC_TLS_ENABLED=true, но файлы сертификата/ключа не указаны, Mockarty автоматически генерирует самоподписанный сертификат в директории TLS.

Для продуктивных развёртываний используйте сертификаты, подписанные центром сертификации вашей организации, особенно если раннеры взаимодействуют через недоверенные сети.

Правила межсетевого экрана (минимум)

Admin  → PostgreSQL :5432  (обязательно)
Admin  → Redis      :6379  (если CACHE_TYPE=redis)

Resolver → PostgreSQL :5432  (обязательно, только чтение)
Resolver → Admin      :5773  (gRPC-регистрация)

Runner → Admin :5773  (gRPC, распределение задач + heartbeat)

Clients → Resolver :5780  (запросы к мокам, через балансировщик)
Developers → Admin :5770  (UI и управление API)

⚠️ Критично: Load Balancer обязателен для интеграций в кластерном режиме

ВАЖНОЕ ОГРАНИЧЕНИЕ: При запуске нескольких Admin-нод в кластерном режиме Runner Agent и Mock Resolver подключаются к координатору через gRPC по конкретному адресу ноды. При смене лидера (failover) интеграция теряет соединение и не может автоматически обнаружить нового лидера.

Проблема

Runner Agent и Mock Resolver настраиваются со статическим COORDINATOR_ADDR (например, mockarty-1:5773). При смене лидера новый координатор запускается на другой ноде (например, mockarty-2:5773). Runner/Resolver продолжает бесконечно пытаться подключиться к старому адресу.

Обязательное решение

Установите TCP/gRPC балансировщик нагрузки перед gRPC-портами всех Admin-нод (по умолчанию: 5773):

Runner Agent → Load Balancer :5773 → Admin Node 1 :5773 (лидер)
                                   → Admin Node 2 :5773 (follower)
                                   → Admin Node 3 :5773 (follower)

Пример конфигурации Nginx для gRPC:

upstream mockarty_coordinator {
    server mockarty-1:5773;
    server mockarty-2:5773;
    server mockarty-3:5773;
}

server {
    listen 5773 http2;
    location / {
        grpc_pass grpc://mockarty_coordinator;
        grpc_next_upstream error timeout;
    }
}

Без балансировщика

Сценарий Результат
1 Admin-нода ✅ Работает — failover не нужен
Несколько нод, без LB ⚠️ Интеграции отключаются при смене лидера
Несколько нод, с LB ✅ Автоматический failover через балансировщик

По этой же причине Web UI (HTTP) также должен быть за балансировщиком при нескольких Admin-нодах.

Задачи, выполняемые только лидером

В кластерном режиме (CLUSTER_MODE=true) перечисленные ниже задачи запускаются только на лидере. Подчинённые ноды остаются живыми, обслуживают API-чтения и готовы перехватить лидерство при потере advisory lock в PostgreSQL, но не выполняют эти задачи:

Задача Почему только лидер
gRPC Coordinator (раннеры и резолверы) Единая точка диспатча для очереди задач и heartbeat
Обработчик очереди задач Исключает двойное назначение задачи разным раннерам
Монитор heartbeat раннеров Единый источник истины о живости раннеров
Запланированные прогоны API Tester Предотвращает дублирующиеся запланированные прогоны
Расписания фаззинга и performance-тестов Аналогично — расписание должно срабатывать ровно один раз
Планировщик обслуживания БД VACUUM / ANALYZE / retention cleanup
Планировщик очистки (устаревших прогонов и результатов) Идемпотентен, но дешевле как singleton
Планировщик глобальной очистки Межнеймспейсная очистка
Планировщик бэкапов Только одна нода пишет артефакты бэкапа

Каждая из этих задач обёрнута в leaderElector.RunWhileLeader(start, stop) — при смене лидера вызывается stop() на старом лидере и start() на новом. Это значит, что задержка failover = TTL advisory-lock + время раскрутки задачи на новом лидере. Учитывайте это при планировании ёмкости: одна хорошо размеренная admin-нода должна уметь нести все эти нагрузки, потому что в кластере из 3 нод только одна выполняет их в каждый момент времени.

Видимость для операторов: endpoint /health на лидере показывает эти планировщики как OK; на подчинённых нодах — как standby. Это нормальное поведение.


Планирование ёмкости

Ниже приведены ориентировочные значения для типичных нагрузок с моками. Реальные цифры зависят от сложности моков (количество условий, функции Faker, обращения к хранилищам, размер ответа).

Пропускная способность разрешения моков

Конфигурация Прибл. запросов/сек Примечания
1 Admin Node (без резолвера) ~2 000 Достаточно для разработки и небольших команд
1 резолвер ~5 000 Кеш Ristretto обрабатывает большинство чтений
3 резолвера + Nginx ~15 000 Почти линейное масштабирование с least_conn
10 резолверов + LB ~50 000+ Продуктивный уровень для крупных организаций

Рекомендации по ресурсам

Компонент CPU RAM Диск Примечания
Admin Node 2-4 ядра 4-8 ГБ 20 ГБ Больше CPU при большом числе фоновых задач
Resolver 1-2 ядра 2-4 ГБ Минимум Без состояния; масштабируется горизонтально
Runner Agent 2-4 ядра 4-8 ГБ 10 ГБ Больше для нагрузочных тестов
PostgreSQL 2-8 ядер 8-32 ГБ SSD Размер зависит от количества моков и истории

Когда масштабировать

Метрика Действие
p95 ответа моков > 100 мс Добавить больше резолверов
Ответ UI Admin > 2 сек Перенести трафик моков на резолверы
Исчерпание пула соединений БД Добавить PgBouncer или увеличить max_connections
Очередь задач раннеров растёт Добавить Runner Agent

Мониторинг состояния

Эндпоинт /health

Каждый компонент предоставляет эндпоинт /health, возвращающий подробный статус:

curl -s http://localhost:5770/health | jq .
{
  "status": "pass",
  "releaseId": "1.2.3",
  "uptime": "72h15m30s",
  "system": {
    "goVersion": "go1.24.1",
    "goroutines": 142,
    "cpus": 4,
    "memAllocMb": "85.3",
    "memSysMb": "210.7"
  },
  "components": {
    "database": {
      "status": "up",
      "latency": "1.2ms"
    },
    "redis": {
      "status": "up",
      "latency": "0.3ms"
    },
    "scheduler": {
      "status": "up"
    },
    "coordinator": {
      "status": "up"
    }
  }
}

Поле status содержит "pass", когда все критические компоненты работоспособны, или "fail", если какой-либо обязательный компонент (например, база данных) недоступен. Некритические компоненты (например, Redis) возвращают "not_configured", если отключены.

Метрики Prometheus

Mockarty предоставляет метрики в формате Prometheus по адресу /metrics:

curl http://localhost:5770/metrics

Ключевые метрики для мониторинга:

Метрика Что показывает
mockarty_http_request_duration_seconds Распределение задержки разрешения моков
mockarty_http_requests_total Количество запросов по методу, эндпоинту, коду статуса
mockarty_mock_requests_total Количество запросов к мокам по ID мока, неймспейсу, протоколу
mockarty_db_query_duration_seconds Производительность запросов к БД
mockarty_cache_hits_total / mockarty_cache_misses_total Эффективность кеширования по типу кеша
mockarty_errors_total Количество ошибок по типу и компоненту

Рекомендации по алертам

# Пример правил алертинга Prometheus
groups:
  - name: mockarty
    rules:
      - alert: MockartyDown
        expr: up{job="mockarty-admin"} == 0
        for: 1m
        labels:
          severity: critical

      - alert: HighMockLatency
        expr: histogram_quantile(0.95, http_request_duration_seconds_bucket) > 0.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "p95 ответа моков превышает 500 мс — рассмотрите добавление резолверов"

      - alert: DatabaseSlow
        expr: db_query_duration_seconds{quantile="0.99"} > 1
        for: 5m
        labels:
          severity: warning


Лучшие практики

1. Разделяйте трафик моков и административный трафик

Самое важное решение при масштабировании: направляйте трафик тестируемых сервисов на выделенные резолверы, а не на Admin. Admin должен обрабатывать только доступ к UI и управление API.

2. Начинайте просто, масштабируйте по необходимости

Разработка Один узел с SQLite Стейджинг Admin + PostgreSQL + 1 резолвер Продакшен Admin + PostgreSQL + Redis + 2+ резолвера + балансировщик

Не усложняйте заранее. Один Admin Node с PostgreSQL обрабатывает тысячи запросов в секунду. Добавляйте резолверы только при обнаружении проблем с задержкой или пропускной способностью.

3. Масштабируйте резолверы горизонтально

Каждый резолвер прогревает свой кеш Ristretto независимо из PostgreSQL. Координатор отправляет обновления моков всем подключённым резолверам в реальном времени, поэтому кеши остаются консистентными. Добавляйте больше резолверов для обработки возросшего трафика.

4. Синхронизируйте версии резолверов с версией Admin

Все компоненты используют единую версию. При обновлении сначала обновляйте Admin Node (он выполняет миграции), затем накатывайте резолверы и раннеры. Никогда не запускайте резолверы с версией новее, чем у Admin.

5. Используйте пространственные раннеры для изоляции команд

В многокомандных средах выделяйте каждой команде раннер с привязкой к пространству имён. Это предотвращает ситуацию, когда дорогой нагрузочный тест одной команды блокирует набор API-тестов другой команды.

6. Мониторьте очередь задач

Растущая очередь задач означает, что раннеры не справляются с нагрузкой. Либо добавьте больше Runner Agent, либо проверьте, не зависают ли тесты (проверьте RUNNER_TASK_TIMEOUT).

7. Используйте пул соединений для PostgreSQL

В крупных развёртываниях с множеством резолверов каждый резолвер открывает собственный пул соединений. Используйте PgBouncer между резолверами и PostgreSQL для мультиплексирования соединений и предотвращения исчерпания max_connections.

8. Масштабируйте резолверы, а не CPU Admin

Если проблема в задержке моков, добавление CPU к Admin Node даёт убывающую отдачу, поскольку Admin выполняет множество задач. Выделенный резолвер использует все ресурсы для разрешения моков. Два небольших резолвера превосходят один мощный Admin Node по обработке трафика моков.


Частые ошибки

  • Запуск резолверов на более новой версии, чем Admin Node. Всегда обновляйте Admin Node первым (он выполняет миграции БД), затем обновляйте резолверы и раннеры. Резолвер на более новой версии может ожидать изменения схемы БД, которые ещё не применены.
  • Забыли DB_DSN для резолверов. Резолверы читают данные моков напрямую из PostgreSQL. Без валидного DB_DSN они не могут разрешать моки. Это отличается от Admin Node, где DB_DSN очевидно обязателен.
  • Использование mki_* токенов для REST API вызовов. Токены интеграции предназначены только для регистрации у gRPC-координатора (resolver/runner). Для автоматизации REST API (CI/CD, скрипты) используйте пользовательские API-токены (mk_*).
  • Направление трафика моков на Admin Node в продакшене. Admin Node обрабатывает UI, фоновые задачи, координацию И разрешение моков. Под нагрузкой разрешение моков вытеснит административные функции. Всегда используйте выделенные резолверы для продуктивного трафика моков.
  • Отсутствие health check на балансировщике нагрузки. Без health check мёртвый резолвер продолжит получать трафик. Настройте балансировщик на проверку GET /health на каждом резолвере.

Kubernetes Operator и конфигурация на основе CRD

Kubernetes-оператор Mockarty управляет полным жизненным циклом кластера через пользовательский CRD MockartyCluster. Admin Node записывает желаемое состояние в CR, оператор приводит его в соответствие с ресурсами Kubernetes (Deployments, Services, ConfigMaps, NetworkPolicies), а стандартные контроллеры Kubernetes делают всё остальное.

Спецификация CRD MockartyCluster

CRD поддерживает следующие секции верхнего уровня:

Секция Назначение
adminNode Развёртывание Admin Node (реплики, образ, ресурсы, переменные окружения)
resolverNodes Пул Mock Resolver (реплики, образ, ресурсы, конфигурация HPA)
runnerAgents Пул Runner Agent (реплики, образ, ресурсы)
orchestrator Оркестратор Server Generator (опционально, реплики, образ)
database Подключение к PostgreSQL (ссылка на секрет с DSN, размер пула)
cache Подключение к Redis (хост, порт, ссылка на секрет) или in-memory
tokenBootstrap Автоматическое создание токенов интеграции (включено/выключено)

Минимальный пример

apiVersion: mockarty.io/v1alpha1
kind: MockartyCluster
metadata:
  name: mockarty
  namespace: mockarty
spec:
  adminNode:
    replicas: 1
    image: ghcr.io/mockarty/mockarty:latest
  resolverNodes:
    replicas: 2
    image: ghcr.io/mockarty/mockarty-resolver:latest
  database:
    dsnSecretRef:
      name: mockarty-db
      key: dsn
  tokenBootstrap:
    enabled: true

Полный пример с HPA и сетевыми политиками

apiVersion: mockarty.io/v1alpha1
kind: MockartyCluster
metadata:
  name: mockarty-production
  namespace: mockarty
spec:
  adminNode:
    replicas: 1
    image: ghcr.io/mockarty/mockarty:1.3.0
    resources:
      requests:
        cpu: "500m"
        memory: "512Mi"
      limits:
        cpu: "2"
        memory: "2Gi"
    env:
      - name: LOG_LEVEL
        value: "info"
      - name: COOKIE_SECURE
        value: "true"

  resolverNodes:
    replicas: 3
    image: ghcr.io/mockarty/mockarty-resolver:1.3.0
    resources:
      requests:
        cpu: "250m"
        memory: "256Mi"
      limits:
        cpu: "1"
        memory: "1Gi"
    hpa:
      enabled: true
      minReplicas: 2
      maxReplicas: 10
      targetCPUUtilization: 70

  runnerAgents:
    replicas: 2
    image: ghcr.io/mockarty/mockarty-runner:1.3.0
    resources:
      requests:
        cpu: "250m"
        memory: "256Mi"
      limits:
        cpu: "1"
        memory: "1Gi"

  orchestrator:
    replicas: 1
    image: ghcr.io/mockarty/orchestrator:1.3.0

  database:
    dsnSecretRef:
      name: mockarty-db
      key: dsn
    maxOpenConns: 25

  cache:
    type: redis
    host: redis-master.mockarty.svc.cluster.local
    port: 6379
    passwordSecretRef:
      name: mockarty-redis
      key: password

  tokenBootstrap:
    enabled: true

  networkPolicy:
    enabled: true
    allowIngressFrom:
      - namespaceSelector:
          matchLabels:
            mockarty-access: "true"

Архитектурный поток

  1. Admin Node – пользователь настраивает кластер через Admin UI или REST API. Admin Node обновляет CR MockartyCluster в Kubernetes, записывая желаемое состояние.
  2. Оператор – отслеживает ресурсы MockartyCluster. При каждом изменении приводит желаемое состояние в соответствие с конкретными объектами Kubernetes: Deployments, Services, ConfigMaps, HPA и NetworkPolicies.
  3. Kubernetes – стандартные контроллеры (Deployment controller, HPA controller) управляют планированием, масштабированием и проверками здоровья.

Такое разделение означает, что Admin Node никогда не обращается к Kubernetes API для повседневных операций – он только записывает желаемое состояние. Оператор берёт на себя все императивные взаимодействия с Kubernetes.

Автоматическое создание токенов (Token Bootstrap)

Когда tokenBootstrap.enabled: true, оператор автоматически:

  1. Создаёт токены интеграции через API Admin Node после того, как Admin Node станет готов
  2. Сохраняет токены как Kubernetes Secrets (mockarty-resolver-token, mockarty-runner-token)
  3. Монтирует секреты в поды Resolver и Runner как переменные окружения

Для ручного управления токенами установите tokenBootstrap.enabled: false и создайте токены через Admin UI, затем укажите их в собственных Secrets.

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

Секция hpa в resolverNodes создаёт HorizontalPodAutoscaler:

  • minReplicas / maxReplicas – границы масштабирования
  • targetCPUUtilization – порог для масштабирования вверх (в процентах)

Резолверы – основная цель масштабирования, так как обрабатывают весь продуктивный трафик моков. Runner-агенты обычно масштабируются вручную в зависимости от тестовой нагрузки.

Сетевые политики

Когда networkPolicy.enabled: true, оператор создаёт ресурсы NetworkPolicy, которые:

  • Разрешают входящий трафик к Admin Node только из указанных пространств имён или pod-селекторов
  • Разрешают входящий трафик к резолверам от любого пода в кластере (трафик моков)
  • Ограничивают исходящий трафик резолверов и раннеров до порта координатора Admin Node (5773) и базы данных
  • Разрешают всем подам доступ к кешу (Redis)

Используйте allowIngressFrom, чтобы указать, какие пространства имён могут отправлять трафик моков к резолверам.


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