* 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>
26 lines
No EOL
3.3 KiB
Markdown
26 lines
No EOL
3.3 KiB
Markdown
---
|
||
pr: 132
|
||
title: fix(docker): increase opencode limits and add tini reaper + healthcheck
|
||
---
|
||
|
||
## Что сделано
|
||
- `docker-compose.yml` сервис `opencode`: `limits.memory` 6G → 8G, `limits.cpus` '3' → '4', `limits.pids` 1024 → 2048
|
||
- Добавлен `init: true` на уровне сервиса (tini как PID 1 — reaping осиротевших зомби)
|
||
- Добавлен `healthcheck`: `CMD-SHELL` curl к `localhost:4096`, допускает 200|401 как healthy (serve требует basic auth → 401 без credentials), `interval: 30s`, `timeout: 10s`, `retries: 3`, `start_period: 30s`. Независание → Docker рестартует через существующий `restart: unless-stopped`
|
||
- `restart: unless-stopped` НЕ дублирован (уже был на строке 25)
|
||
- Сервис `dind` — БЕЗ ИЗМЕНЕНИЙ
|
||
- `tests/test_docker_compose.py`: +8 raw-text assertions (memory/cpu/pids limits, init:true, healthcheck block + timing, restart-not-duplicated, dind-unchanged). Итого 13 passed (5 baseline + 8 новых)
|
||
- ADR-059 создан
|
||
|
||
## Почему
|
||
Живая диагностика cgroup v2 внутри зависшего контейнера: `memory.peak` 5.72 GB из 6 GB (95%), `oom_kill`=0 → ядро делало direct reclaim (синхронный scan/free страниц в event loop) → Node.js event loop блокировался → `opencode serve` зависал (504 + не отвечал по IP). CPU: `nr_throttled`=879, `throttled_usec`=104.7s при лимите 3 ядра. >120 зомби-процессов (`<defunct>`), opencode — PID 1, не вызывает `wait()`. Swap на хосте отключён (`Swap: 0B`). 8G даёт +2.3 GB над пиком 5.72 GB; 4 ядра убирают троттлинг (на хосте 6 ядер); tini reaping зомби; healthcheck → авто-рестарт при зависании.
|
||
|
||
## Pending
|
||
— Развёртывание на проде и наблюдение: подтвердить что direct-reclaim stalls и троттлинг исчезли, RSS-рост не упирается в новый 8G лимит. Если упрётся — копать в сторону утечки MCP-серверов (5 дубликатных групп puppeteer+context7, ~30 процессов, ~500 MB) — это вне scope данного issue.
|
||
— Swap на хосте linux-1 остаётся отключённым — ops-задача (вне репо).
|
||
|
||
## Watch out
|
||
— Healthcheck использует `CMD-SHELL` (не `CMD`) — нужен pipe `| grep -qE '^(200|401)$'` для допуска 401. В exec-форме (`CMD`) pipe не работает. `$$` в compose экранирует `$` для shell.
|
||
— `curl -sf` трактует 401 как не-2xx → ложный unhealthy. Поэтому grep на 200|401, а не `-sf`.
|
||
— Docker недоступен на CI-хосте — `docker compose config` не запускается; YAML валидирован через PyYAML.
|
||
— `init: true` reaping'ит только осиротевших зомби (родитель умер). Прямых детей живого opencode tini не заберёт — но это всё равно убирает основную массу. |