9.0 KiB
9.0 KiB
ТЗ: Healthcheck enduro-trails-app — заменить curl на python-проверку
Work Item: ET-015 Базовый документ: 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:
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:
@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 приводится к виду:
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, как ENVPORTв 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. Кратко:
- 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доступны из ветки.