# ТЗ: Healthcheck enduro-trails-app — заменить curl на python-проверку **Work Item:** ET-015 **Базовый документ:** [01-brd.md](01-brd.md) **Версия:** 1.0 **Дата:** 2026-06-05 ## 1. Постановка Заменить в `docker-compose.yml` (сервис `app`) healthcheck-команду так, чтобы она: - использовала средства, **уже доступные** в образе `python:3.12-slim` (т.е. интерпретатор `python3`), без установки дополнительных пакетов; - обращалась к `http://localhost:5556/api/health` и трактовала HTTP-код **2xx** как «healthy», любой иной отклик и любую ошибку соединения — как «unhealthy»; - укладывалась в `timeout: 5s`. ## 2. Текущее состояние `docker-compose.yml`, строки 22–26: ```yaml healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5556/api/health"] interval: 30s timeout: 5s retries: 3 ``` `Dockerfile`: базовый образ `python:3.12-slim`. `curl` отсутствует. Установлен `pip`, доступен `python3` (и алиас `python`). `src/api/main.py:1224`: ```python @app.get("/api/health") async def health(): return { "status": "ok", "db_path": DATA_PATH, "db_exists": os.path.exists(DATA_PATH), } ``` Возвращает HTTP 200 + JSON. Менять не требуется. ## 3. Целевое состояние ### 3.1. Изменение в `docker-compose.yml` Секция `healthcheck` сервиса `app` приводится к виду: ```yaml healthcheck: test: - "CMD" - "python" - "-c" - "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:5556/api/health', timeout=3).status == 200 else 1)" interval: 30s timeout: 5s retries: 3 start_period: 20s ``` Пояснения: - `CMD` (а не `CMD-SHELL`) — никакого shell-парсинга, аргументы передаются как массив, экранирование не нужно. - `python` — алиас, имеющийся в `python:3.12-slim` (есть и `python3`, оба указывают на один интерпретатор). - `urllib.request.urlopen(..., timeout=3)` — стандартная библиотека, без зависимостей; внутренний `timeout=3` короче внешнего `timeout: 5s`, остаётся запас на старт интерпретатора. - `sys.exit(0 if ... == 200 else 1)` — корректное преобразование статуса HTTP в exit code. Любой raise (URLError, HTTPError, timeout) пробросится наверх, процесс завершится ненулевым кодом → `unhealthy`. - `start_period: 20s` — добавляется, чтобы Docker не считал ранние ошибки запуска приложения «провалом» healthcheck в окне старта. Uvicorn поднимается за < 2 c, 20 с — комфортный запас. ### 3.2. Изменения в `Dockerfile` **Не требуются.** Добавлять `curl` через `apt-get` нельзя — раздувает образ и противоречит выбранному подходу. ### 3.3. Изменения в `src/api/main.py` **Не требуются.** Эндпоинт `/api/health` существует и отдаёт 200. ## 4. Альтернативы (рассмотрены и отклонены) | Вариант | Плюсы | Минусы | Решение | |---------|-------|--------|---------| | `apt-get install curl` в Dockerfile | Привычная команда | +~10 МБ к образу, новый APT-слой, противоречит slim-философии | Отклонено | | `wget --spider` | Однострочник | `wget` тоже отсутствует в `python:3.12-slim` (проверено: пакет `wget` не входит в slim) | Отклонено | | HEALTHCHECK в Dockerfile | Декларативно | Дублирует compose, при изменении нужно пересобирать образ | Отклонено, держим в compose | | Отдельный health-скрипт `scripts/healthcheck.py` | Чище YAML | Лишний файл для одной строки, мутит образ | Отклонено | Принятый вариант: **inline python one-liner** через `urllib.request`. ## 5. Реализационные требования ### R-1. Изменение `docker-compose.yml` - В сервисе `app` секция `healthcheck` заменяется на конструкцию из п. 3.1. - Остальные параметры сервиса (ports, volumes, environment) не затрагиваются. ### R-2. Идемпотентность пересборки - Изменения не требуют ребилда образа (`docker compose build`). Достаточно `docker compose up -d app` для пересоздания контейнера с новой healthcheck-командой. - Допускается ребилд при необходимости — это не должно ломать сборку. ### R-3. Обратная совместимость - Никаких ENV-переменных, влияющих на путь healthcheck, не вводится. Адрес `http://localhost:5556/api/health` зашит в строку. (Локальный — `localhost` внутри контейнера; порт всегда 5556, как ENV `PORT` в Dockerfile.) ### R-4. Документация - В `docs/work-items/ET-015/06-adr/healthcheck-via-python.md` зафиксировать решение «использовать python-one-liner вместо curl». Автор ADR — следующий этап (Architecture), не Анализ. - Обновить `CHANGELOG.md` в секции «Unreleased» строкой формата `fix(infra): use python urllib for container healthcheck (ET-015)`. ### R-5. Линт и форматирование - YAML-валидность `docker-compose.yml` проверяется `make lint`. - Длина строки python one-liner допустима в YAML (нет лимита 120 для строковых значений multi-line array). ## 6. Тестирование См. [04-test-plan.yaml](04-test-plan.yaml). Кратко: - **integration-1**: после `docker compose up -d app` контейнер должен выйти в `healthy` за ≤ 120 с. - **integration-2**: при остановке uvicorn (или искусственном блоке порта) — за ≤ 120 с переходит в `unhealthy`. - **unit-1** (опционально): smoke-тест python-one-liner вне Docker через `python -c "..."` против поднятого локально `make dev`. ## 7. Деплой и откат - Деплой: `make deploy-test` (как обычно). При деплое compose пересоздаст контейнер `enduro-trails-app-1`. - Проверка: `docker inspect enduro-trails-app-1 --format '{{.State.Health.Status}}'` → `healthy` в течение нескольких циклов (`interval=30s × 3 = 90s` плюс `start_period=20s`). - Откат: `git revert` коммита; `docker compose up -d app`. Старая (поломанная) healthcheck-команда вернётся, но сам сервис продолжит работать. ## 8. Риски | Риск | Вероятность | Митигация | |------|-------------|-----------| | Python one-liner крэшится на каком-то Docker-движке из-за квотинга | низкая | YAML-массив `["CMD", "python", "-c", "..."]` — без shell, без экранирования | | Длинная строка усложняет редактирование | средняя | Использовать YAML block-scalar (`>-` или `|`) при необходимости, но в текущей форме строка читаемая | | Эндпоинт `/api/health` в будущем сделают «дорогим» и timeout=3s не хватит | низкая | Эндпоинт сейчас отдаёт ~7 мс; при изменении — пересмотр timeout | | На prod-среде iptables/сеть отличаются и localhost внутри контейнера ведёт себя иначе | очень низкая | `localhost` в network namespace контейнера = loopback контейнера, не зависит от хоста | ## 9. Definition of Ready (для следующего этапа) - BRD прочитан, ТЗ согласовано. - Доступ к тестовой среде mva154 для проверки `docker inspect`. - `make deploy-test` и `docker compose` доступны из ветки.