* 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>
3.9 KiB
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 — без изменений):
-
Увеличить лимиты ресурсов (
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, это страховка).
-
init: trueна верхнем уровне сервиса: Docker подставит tini как PID 1, который авто-reap'ит осиротевших зомби (процессы, чей родитель умер — переподчиняются init). Прямых детей живого opencode tini не заберёт, но основную массу зомби уберёт. Best practice для контейнеров, порождающих подпроцессы. -
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(неCMDexec-form) — нужен pipe в grep;$$экранирует$для shell.
Альтернативы
- Swap на хосте — рассмотрена, но вне репо (ops-задача на linux-1). Даёт буфер при пиках, но не решает direct-reclaim при 95% утилизации и не убирает зомби/троттлинг.
- cron-рестарт контейнера — вне репо, реактивный workaround, не детектит зависание (только по расписанию). Healthcheck proactive.
- Откат версии opencode — отвергнута: диагностика показала resource-проблемы (память/CPU/зомби), а не регрессию версии. Живой рост RSS ~11 MB/с — отдельная утечка, не блокирующая данный фикс.