Files
enduro-trails/docs/work-items/ET-015/02-trz.md
claude-bot c2cf8280ca
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 9s
CI / build (push) Successful in 3s
analyst(ET): auto-commit from analyst run_id=101
2026-06-05 15:11:28 +00:00

170 lines
9.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ТЗ: 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`, строки 2226:
```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` доступны из ветки.