# Лабораторная 3. Балансировка и отказ API-реплики Запустить несколько взаимозаменяемых экземпляров API за единой точкой входа и сохранить корректность при остановке одного из них. Это **skeleton**, а не готовый сервер. Здесь есть задание, контракт и публичные проверки. Реализацию приложения, Dockerfile и требуемую инфраструктуру пишет студент. Язык и framework свободные; UI не обязателен. [Карта курса](COURSE.md) · [Процесс fork/PR](CONTRIBUTING.md) · [OpenAPI](contracts/openapi.json) · [Семантика API](contracts/README.md) · [Проверки](tests/README.md) · [Отчёт](REPORT.md) · [Примеры JSON](contracts/examples.json) · [Памятка преподавателю](MAINTAINERS.md) ## Откуда начинаем Нужен минимум lab2: PostgreSQL, общие действующие сессии, миграции. Добавляются X-Instance-Id и обязательный Idempotency-Key при отправке сообщения. **Архитектура:** Клиент → LB → API × 2+ → общие PostgreSQL и хранилище сессий. Compose или Swarm — на выбор. ## Что сделать 1. Выберите Compose + Nginx/HAProxy/Traefik либо Docker Swarm. Для выбранного варианта дайте одну последовательность запуска. 2. Уберите локальные сессии/другие данные, от которых зависит корректность, из API. Миграции выполняйте отдельно и согласованно, а не гонкой всех реплик. 3. Настройте балансировщик, проверки готовности, DNS/обновление backend-адресов и graceful shutdown. Не закрепляйте клиента sticky session как способ исправить раздельное состояние. 4. Реализуйте общую идемпотентность отправки сообщения. Проверьте одинаковый ключ одновременно через разные реплики. 5. Подготовьте явный 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 не реплицирована, не заявляйте выживание её узла. ## Как начать и проверить ```sh # Работает прямо в 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/миграции. Все зависимости поднимаются из этого репозитория; ссылка «у меня БД уже установлена» не заменяет воспроизводимость. ```sh # 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 делает её автоматически): ```sh python3 -m venv .venv .venv/bin/python -m pip install -r requirements-dev.txt make validate PYTHON=.venv/bin/python ``` Smoke не выставляет оценку автоматически. Проверку сохранности/отказов запускайте явно по [tests/README.md](tests/README.md), остальные доказательства приведите в отчёте. ## Сценарий защиты 1. Запустить стенд и показать ≥2 разных X-Instance-Id через LB с одной сессией. 2. Запустить make test-failover ACTION_SCRIPT=scripts/stop-one.sh. Скрипт теста выполняет запросы во время action, учитывает ошибки и повторяет сообщения с исходным ключом. 3. Показать, какой контейнер действительно остановлен; заголовок сам по себе не доказательство разных процессов. Повторить для второй реплики после восстановления первой. 4. Проверить restart всего стенда с сохранением volumes и неизменным токеном. Для 4/5 — соответствующие эксперименты. ## Что должно быть в PR compose.yaml или stack.yaml, конфигурация LB, scripts/restart.sh, scripts/stop-one.sh, Dockerfile, миграции, результаты отказов. Заполните `REPORT.md` и [шаблон PR](.github/pull_request_template.md), укажите целевую оценку/трек. Не меняйте обязательный контракт и публичные tests ради зелёного результата. Сохраняйте возможности предыдущих лабораторных в пределах [объявленных переходов](COURSE.md). ## Вопросы на защите - Чем отказ процесса отличается от отказа узла? - Почему depends_on/healthcheck сами по себе не являются балансировщиком? - Какие POST можно повторять после timeout и что защищает Idempotency-Key? - Что сломается при увеличении числа API, если сессии находятся в локальной памяти? ## Первичные материалы - [Compose services](https://docs.docker.com/reference/compose-file/services/) - [Swarm routing mesh](https://docs.docker.com/engine/swarm/ingress/) - [Swarm services](https://docs.docker.com/engine/swarm/services/)