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

3.9 KiB
Raw Blame History

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/с — отдельная утечка, не блокирующая данный фикс.