Лабораторная 3. Балансировка и отказ API-реплики
Запустить несколько взаимозаменяемых экземпляров API за единой точкой входа и сохранить корректность при остановке одного из них.
Это skeleton, а не готовый сервер. Здесь есть задание, контракт и публичные проверки. Реализацию приложения, Dockerfile и требуемую инфраструктуру пишет студент. Язык и framework свободные; UI не обязателен.
Карта курса · Процесс fork/PR · OpenAPI · Семантика API · Проверки · Отчёт · Примеры JSON · Памятка преподавателю
Откуда начинаем
Нужен минимум lab2: PostgreSQL, общие действующие сессии, миграции. Добавляются X-Instance-Id и обязательный Idempotency-Key при отправке сообщения.
Архитектура: Клиент → LB → API × 2+ → общие PostgreSQL и хранилище сессий. Compose или Swarm — на выбор.
Что сделать
- Выберите Compose + Nginx/HAProxy/Traefik либо Docker Swarm. Для выбранного варианта дайте одну последовательность запуска.
- Уберите локальные сессии/другие данные, от которых зависит корректность, из API. Миграции выполняйте отдельно и согласованно, а не гонкой всех реплик.
- Настройте балансировщик, проверки готовности, DNS/обновление backend-адресов и graceful shutdown. Не закрепляйте клиента sticky session как способ исправить раздельное состояние.
- Реализуйте общую идемпотентность отправки сообщения. Проверьте одинаковый ключ одновременно через разные реплики.
- Подготовьте явный action script, останавливающий одну выбранную API-реплику, и продемонстрируйте чтение/запись во время отказа.
Оценка «3»: работающий минимум
- Весь стенд описан в compose.yaml или stack.yaml, запускается документированной командой. Не менее двух API-реплик, одна публичная точка входа, одна общая БД и общее хранилище сессий.
- Разные запросы одного пользователя обслуживаются разными репликами без повторного входа. Это видно по X-Instance-Id. Доступ через LB — основной проверяемый маршрут.
- Можно остановить любую одну API-реплику при работающей другой: не позднее 10 секунд продолжаются новые чтения/записи, подтверждённые сообщения доступны. Краткие ошибки/оборванные запросы во время обнаружения отказа измеряются и допустимы.
- Повтор POST сообщения с тем же Idempotency-Key не создаёт дубль, включая конкурентный повтор и переключение реплики. Неподтверждённую запись клиент может безопасно повторить с тем же ключом.
- make test, persistence и failover-сценарий проходят; для failover проверяется наличие ≥2 наблюдаемых реплик. Для Swarm сборка образа и его доступность узлам описаны отдельно.
Оценка «4»: уровень стажёра/джуна
Весь уровень 3 и все пункты ниже:
- Health/readiness, timeouts и прекращение приёма при SIGTERM настроены согласованно; LB исключает недоступный backend. Укажите период проверок и максимальное время обнаружения отказа.
- Последовательно остановите каждую API-реплику (восстанавливая предыдущую); соберите RPS, ошибки и время восстановления. Общая идемпотентность проверяется параллельными запросами с одним ключом.
- Контейнеры имеют CPU/RAM-лимиты, API не публикуют порты наружу в обход LB; секреты и хранилища общие только там, где нужно. Нет container_name, мешающего масштабированию одного Compose-service.
- Rolling update или эквивалентная поочерёдная замена проверена под нагрузкой. Миграции запускаются однократно и совместимы с работающей версией либо ограничение честно описано.
Оценка «5»: исследование и доказанный результат
Весь уровень 4 и один законченный трек на выбор. Приведите гипотезу, повторяемую методику, результаты и ограничения.
Трек 1. Цена масштабирования. При фиксированном суммарном CPU/RAM сравните 1/2/4 API-реплики на одном профиле нагрузки; затем проведите серию с ростом ресурсов. Найдите saturation point БД/LB/пула и покажите, почему рост реплик перестаёт помогать. Дайте исходные данные, графики и одно подтверждённое улучшение.
Трек 2. Отказ узла. Расширьте стенд до нескольких узлов, покажите перенос API при потере узла и разберите оставшиеся SPOF (LB, DB, storage). Для заявленной защищённой зависимости покажите реальный failover и сохранность данных. Если DB не реплицирована, не заявляйте выживание её узла.
Как начать и проверить
# Работает прямо в skeleton, Python 3.9+; приложение не запускается:
make check
# После реализации запустите свой сервер/стенд по REPORT.md.
# По умолчанию тест использует http://localhost:8080.
make test
.env.example, compose.example.yaml и contracts/infrastructure.json описывают настройки и роли компонентов. compose.example.yaml — список ролей с пустым services, не запускаемый стенд. Реализуйте свой compose.yaml/stack.yaml, закрепите версии образов, заполните локальный .env и опишите bootstrap/миграции. Все зависимости поднимаются из этого репозитория; ссылка «у меня БД уже установлена» не заменяет воспроизводимость.
# Compose-вариант после реализации:
docker compose --env-file .env config --quiet
docker compose --env-file .env up -d --build
# Дождитесь /health/ready; затем make test.
# Для Swarm укажите эквивалентные build/push/deploy-команды в REPORT.md.
Примеры запросов доступны в examples/requests.http, примеры JSON — в contracts/examples.json. Для полной проверки схемы (по желанию локально; CI делает её автоматически):
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements-dev.txt
make validate PYTHON=.venv/bin/python
Smoke не выставляет оценку автоматически. Проверку сохранности/отказов запускайте явно по tests/README.md, остальные доказательства приведите в отчёте.
Сценарий защиты
- Запустить стенд и показать ≥2 разных X-Instance-Id через LB с одной сессией.
- Запустить make test-failover ACTION_SCRIPT=scripts/stop-one.sh. Скрипт теста выполняет запросы во время action, учитывает ошибки и повторяет сообщения с исходным ключом.
- Показать, какой контейнер действительно остановлен; заголовок сам по себе не доказательство разных процессов. Повторить для второй реплики после восстановления первой.
- Проверить restart всего стенда с сохранением volumes и неизменным токеном. Для 4/5 — соответствующие эксперименты.
Что должно быть в PR
compose.yaml или stack.yaml, конфигурация LB, scripts/restart.sh, scripts/stop-one.sh, Dockerfile, миграции, результаты отказов.
Заполните REPORT.md и шаблон PR, укажите целевую оценку/трек. Не меняйте обязательный контракт и публичные tests ради зелёного результата. Сохраняйте возможности предыдущих лабораторных в пределах объявленных переходов.
Вопросы на защите
- Чем отказ процесса отличается от отказа узла?
- Почему depends_on/healthcheck сами по себе не являются балансировщиком?
- Какие POST можно повторять после timeout и что защищает Idempotency-Key?
- Что сломается при увеличении числа API, если сессии находятся в локальной памяти?