opencode-config/docs/decisions/059-pr-132-fix-docker-opencode-limits.md
Sergey e72d435b82
fix(docker): increase opencode limits and add tini reaper + healthcheck (#132)
* fix(docker): increase opencode memory and cpu limits

* fix(docker): add tini init for zombie reaping

* fix(docker): add healthcheck for hang auto-restart

* test(docker): assert opencode limits, init and healthcheck

* docs(handoff): add handoff and ADR for opencode limits fix

* docs(handoff): set PR number

* docs(project-map): update docker-compose and test descriptions for PR#132

---------

Co-authored-by: opencode-agent <agent@opencode.local>
2026-07-29 21:19:01 +03:00

30 lines
No EOL
3.9 KiB
Markdown
Raw Permalink 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.

# ADR-059: Increase opencode container limits, add tini reaper and healthcheck
## Статус
Accepted (2026-07-29)
## Контекст
`opencode serve` (веб-UI на `:4096`, проксирован через NPM как `opencode.slaid098.dev`) зависал при активной работе агента — `504 Gateway Time-out`, контейнер не отвечал по IP. Живая диагностика cgroup v2 внутри контейнера:
- `memory.peak` = 5.72 GB из лимита 6 GB (95%), `oom_kill` = 0 → ядро не убивает, а делает direct reclaim (синхронный scan/free страниц в event loop процесса) → Node.js event loop блокируется → serve зависает.
- CPU: `nr_throttled` = 879, `throttled_usec` = 104.7 с — упирался в лимит 3 ядра (`cpu.max=300000/100000`); `cpu.pressure avg300` = 0.11 (11% устойчивый).
- >120 зомби-процессов (`<defunct>`: `[git]`, `[gh]`, `[node]`); opencode — PID 1, не вызывает `wait()` для reaping детей.
- Swap на хосте linux-1 отключён (`Swap: 0B`) — нет буфера при пиках.
Все корневые причины устранимы изменениями в `docker-compose.yml` (область репо). Сервис `dind` использовал 32 MB из 4 GB (0.78%), без троттлинга — не трогаем.
## Решение
Три изменения в сервисе `opencode` (dind — без изменений):
1. **Увеличить лимиты ресурсов** (`deploy.resources.limits`):
- `memory`: 6G → **8G** (+2.3 GB над наблюдённым пиком 5.72 GB — устраняет direct-reclaim блокировку event loop).
- `cpus`: '3' → **'4'** (убирает 879 троттлинг-событий; на хосте 6 ядер, запас есть).
- `pids`: 1024 → **2048** (буфер для зомби; основной фикс — `init: true`, это страховка).
2. **`init: true`** на верхнем уровне сервиса: Docker подставит **tini** как PID 1, который авто-reap'ит осиротевших зомби (процессы, чей родитель умер — переподчиняются init). Прямых детей живого opencode tini не заберёт, но основную массу зомби уберёт. Best practice для контейнеров, порождающих подпроцессы.
3. **`healthcheck`** (после `deploy`): `CMD-SHELL` с `curl` к `localhost:4096`, допускает 200|401 как healthy (`serve` требует basic auth → 401 без credentials). `interval: 30s`, `timeout: 10s`, `retries: 3`, `start_period: 30s`. Независание (HTTP не отвечает) → Docker детектит unhealthy и авто-рестартует через уже существующий `restart: unless-stopped` (НЕ дублировать). Используем `CMD-SHELL` (не `CMD` exec-form) — нужен pipe в grep; `$$` экранирует `$` для shell.
## Альтернативы
- **Swap на хосте** — рассмотрена, но вне репо (ops-задача на linux-1). Даёт буфер при пиках, но не решает direct-reclaim при 95% утилизации и не убирает зомби/троттлинг.
- **cron-рестарт контейнера** — вне репо, реактивный workaround, не детектит зависание (только по расписанию). Healthcheck proactive.
- **Откат версии opencode** — отвергнута: диагностика показала resource-проблемы (память/CPU/зомби), а не регрессию версии. Живой рост RSS ~11 MB/с — отдельная утечка, не блокирующая данный фикс.