Files
microdao-daarion/docs/NODA1-MEMORY-RUNBOOK.md
Apple ef3473db21 snapshot: NODE1 production state 2026-02-09
Complete snapshot of /opt/microdao-daarion/ from NODE1 (144.76.224.179).
This represents the actual running production code that has diverged
significantly from the previous main branch.

Key changes from old main:
- Gateway (http_api.py): expanded from ~40KB to 164KB with full agent support
- Router: new /v1/agents/{id}/infer endpoint with vision + DeepSeek routing
- Behavior Policy: SOWA v2.2 (3-level: FULL/ACK/SILENT)
- Agent Registry: config/agent_registry.yml as single source of truth
- 13 agents configured (was 3)
- Memory service integration
- CrewAI teams and roles

Excluded from snapshot: venv/, .env, data/, backups, .tgz archives

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 08:46:46 -08:00

20 KiB
Raw Permalink Blame History

NODA1 — Runbook: Memory Stack (діагностика та відновлення)

Контекст: Проблеми зі стеком пам'яті на NODA1 трапляються регулярно. Цей runbook — для швидкої діагностики та безпечного перезапуску без змін конфігурації.

Нода: NODA1 — node1-daarion (144.76.224.179)
Сервіси: dagi-memory-service-node1, dagi-qdrant-node1, postgres-backup-node1, render-pdf-worker-node1, dagi-nats-node1, Caddy


1) Швидка фіксація стану (read-only, ~1 хв)

# Загальний стан контейнерів (імена/статуси/порти)
docker ps -a --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}' | egrep 'dagi-memory-service-node1|dagi-qdrant-node1|postgres-backup-node1|render-pdf-worker-node1|dagi-nats-node1| nats '

# Health-статус детально (memory + postgres-backup)
docker inspect dagi-memory-service-node1 --format '{{json .State.Health}}' | jq .
docker inspect postgres-backup-node1     --format '{{json .State.Health}}' | jq . 2>/dev/null || true

# Останні 100/200 рядків логів (таймаути, Qdrant)
docker logs --tail 100 dagi-memory-service-node1
docker logs --tail 200 dagi-qdrant-node1
docker logs --tail 200 postgres-backup-node1

2) Діагностика мережі/доступності memory-service ↔ Qdrant (read-only)

2.1 Qdrant "живий" з хоста

curl -s -o /dev/null -w "Qdrant /healthz HTTP=%{http_code}\n" --connect-timeout 3 http://127.0.0.1:6333/healthz
curl -s -o /dev/null -w "Qdrant /collections HTTP=%{http_code}\n" --connect-timeout 3 http://127.0.0.1:6333/collections

2.2 Qdrant доступний з контейнера memory-service (критично для міжконтейнерної мережі)

# DNS + TCP + HTTP всередині контейнера
docker exec dagi-memory-service-node1 sh -lc '
  echo "== DNS ==";
  getent hosts dagi-qdrant-node1 || true;
  echo "== TCP 6333 ==";
  (nc -vz -w 2 dagi-qdrant-node1 6333 && echo OK) || echo FAIL;
  echo "== HTTP /healthz ==";
  curl -s -o /dev/null -w "HTTP=%{http_code}\n" --connect-timeout 5 http://dagi-qdrant-node1:6333/healthz || echo FAIL
' 2>/dev/null || echo "docker exec failed"

2.3 Перевірка "де саме" таймаут: /collections і латентність

# /collections інколи повільний при навантаженні/IO
docker exec dagi-memory-service-node1 sh -lc '
  echo "== /collections latency ==";
  time curl -s -o /dev/null --connect-timeout 5 http://dagi-qdrant-node1:6333/collections || true
' 2>/dev/null

Якщо healthz швидкий, а /collections "висить" або дає таймаути — проблема частіше навантаження/IO Qdrant, а не DNS.


0) Довести причину: bind vs iptables (read-only)

0.1 Bind-порти всередині Qdrant

docker exec dagi-qdrant-node1 sh -c '
  ss -ltnp | egrep ":(6333|6334)\s" || netstat -ltnp | egrep ":(6333|6334)\s" || cat /proc/net/tcp | head -5
'
  • 127.0.0.1:6333 → причина (A): bind на localhost; фікс: QDRANT__SERVICE__HOST=0.0.0.0.
  • 0.0.0.0:6333 (або в /proc/net/tcp: 00000000:18BD) → причина (B): мережеве блокування; див. 0.3.

0.2 Одна мережа для Qdrant і memory

docker network inspect dagi-network --format '{{json .Containers}}' | jq 'keys'
docker inspect dagi-qdrant-node1 --format '{{json .NetworkSettings.Networks}}' | jq .
docker inspect dagi-memory-service-node1 --format '{{json .NetworkSettings.Networks}}' | jq .

0.3 iptables DOCKER-USER (часта причина таймаутів між контейнерами)

iptables -S DOCKER-USER
iptables -L DOCKER-USER -n -v

Перевірено 2025-01-28: У DOCKER-USER є правило ! -s 127.0.0.1/32 -p tcp --dport 6333 -j DROP (і аналогічно для 8000, 9500, …). Трафік з контейнерів (172.18.x.x) на порт 6333/8000 DROP-иться; з хоста (127.0.0.1) — працює. Qdrant при цьому слухає на 0.0.0.0:6333 (/proc/net/tcp: 00000000:18BD). Висновок: причина (B) iptables, не bind.

Фікс (дозволити контейнер→контейнер для 6333 і 8000): вставити ACCEPT перед DROP (наприклад у rc.local або окремому скрипті, щоб правило пережило перезавантаження):

# Дозволити трафік з мережі контейнерів (dagi-network 172.18.0.0/16) на Qdrant і memory-service
iptables -I DOCKER-USER 1 -s 172.18.0.0/16 -p tcp -m tcp --dport 6333 -j ACCEPT
iptables -I DOCKER-USER 1 -s 172.18.0.0/16 -p tcp -m tcp --dport 8000 -j ACCEPT

Після цього: docker restart dagi-memory-service-node1, дочекатися 3060 с, перевірити health.


0.4 Збереження правил DOCKER-USER після reboot

A) iptables-persistent (рекомендовано, Ubuntu/Debian)

A0) Зафіксувати поточні правила:

iptables -S DOCKER-USER
iptables -L DOCKER-USER -n -v --line-numbers

A1) Встановити пакети:

apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent netfilter-persistent

A2) Зберегти правила:

iptables-save > /etc/iptables/rules.v4
# (IPv6 за потреби) ip6tables-save > /etc/iptables/rules.v6

A3) Увімкнути відновлення на старті:

systemctl enable netfilter-persistent
systemctl restart netfilter-persistent
systemctl status netfilter-persistent --no-pager -l

A3.1) Готовність без reboot (read-only) — переконатися, що netfilter-persistent підхопив rules.v4 і що Docker не перетре ланцюги:

# 1) netfilter-persistent читає rules.v4
sudo netfilter-persistent status
sudo systemctl status netfilter-persistent --no-pager -l

# 2) Завантажені правила і звірка з файлом
sudo iptables-save | grep -n "^\*filter"
sudo iptables-save | egrep "DOCKER-USER|172\.18\.0\.0/16|dport (6333|8000)" -n

# 3) У файлі правила є
sudo grep -n "DOCKER-USER" /etc/iptables/rules.v4
sudo egrep -n "172\.18\.0\.0/16.*dport (6333|8000).*ACCEPT" /etc/iptables/rules.v4 || echo "MISSING_IN_FILE"

# 4) ACCEPT стоять ПЕРЕД DROP
sudo iptables -L DOCKER-USER -n --line-numbers | head -n 25

Очікувано: iptables -L DOCKER-USER --line-numbers показує ACCEPT для 172.18.0.0/16 на 6333 і 8000 вище за DROP.

A4) Після reboot — валідація:

iptables -S DOCKER-USER | egrep '172\.18\.0\.0/16.*dport (6333|8000).*ACCEPT' || echo "MISSING"
docker ps --format 'table {{.Names}}\t{{.Status}}' | egrep 'dagi-qdrant-node1|dagi-memory-service-node1'
curl -s -o /dev/null -w "memory /health HTTP=%{http_code}\n" http://127.0.0.1:8000/health

A4.1) Після reboot — ~30 секунд перевірок:

sudo iptables -S DOCKER-USER | egrep '172\.18\.0\.0/16.*dport (6333|8000).*ACCEPT' || echo "MISSING"

docker ps --format 'table {{.Names}}\t{{.Status}}' | egrep 'dagi-qdrant-node1|dagi-memory-service-node1'

curl -s -o /dev/null -w "memory /health HTTP=%{http_code}\n" http://127.0.0.1:8000/health

B) Альтернатива без пакетів: systemd unit на boot

B1) Створити unit /etc/systemd/system/dagi-iptables-allow-dagi-network.service:

[Unit]
Description=Allow dagi-network container traffic to Qdrant and Memory ports in DOCKER-USER
After=network-online.target docker.service
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/bin/sh -c '\
  iptables -C DOCKER-USER -s 172.18.0.0/16 -p tcp --dport 6333 -j ACCEPT 2>/dev/null || \
  iptables -I DOCKER-USER 1 -s 172.18.0.0/16 -p tcp --dport 6333 -j ACCEPT; \
  iptables -C DOCKER-USER -s 172.18.0.0/16 -p tcp --dport 8000 -j ACCEPT 2>/dev/null || \
  iptables -I DOCKER-USER 1 -s 172.18.0.0/16 -p tcp --dport 8000 -j ACCEPT; \
  iptables -S DOCKER-USER'
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

B2) Увімкнути і запустити:

systemctl daemon-reload
systemctl enable --now dagi-iptables-allow-dagi-network.service
systemctl status dagi-iptables-allow-dagi-network.service --no-pager -l

Страхувальний варіант (без конфлікту з A): залишити A як є і додати B-unit лише як fallback. Unit ідемпотентний (iptables -C перед вставкою). Корисно, якщо на хості є UFW/інші інструменти, що переупорядковують правила після boot.

Якщо після reboot правила зникнуть

1) iptables-nft vs iptables-legacy або netfilter-persistent тягне не той бекенд:

iptables -V
update-alternatives --display iptables 2>/dev/null || true
sudo journalctl -u netfilter-persistent -b --no-pager | tail -200

2) Docker/ufw змінюють форвардинг або ланцюги після старту:

sudo journalctl -u docker -b --no-pager | tail -200
sudo iptables -S DOCKER-USER

Якщо змішування legacy/nft — стандартизувати backend (окремий крок).


0.5 Перший крок при таймаутах між контейнерами

При будь-яких таймаутах memory↔Qdrant або інших контейнер↔контейнер спочатку перевірити DOCKER-USER:

iptables -S DOCKER-USER
iptables -L DOCKER-USER -n -v --line-numbers

Якщо є ! -s 127.0.0.1/32 ... --dport 6333 -j DROP без попереднього ACCEPT для 172.18.0.0/16 — застосувати фікс з 0.3 і зберегти правила (0.4).


3) Діагностика самого memory-service (read-only)

# Health endpoint з хоста
curl -s -i --max-time 5 http://127.0.0.1:8000/health | head

# Ресурси контейнера (CPU throttling / RAM pressure)
docker stats --no-stream dagi-memory-service-node1 dagi-qdrant-node1 2>/dev/null

# Env (тільки читання) — чи не з'їхав QDRANT_HOST/PORT
docker exec dagi-memory-service-node1 sh -lc 'env | egrep "QDRANT|MEM|VECTOR|COLLECTION|TIMEOUT" | sort' 2>/dev/null || true

4) postgres-backup-node1 unhealthy — мінімальна діагностика (read-only)

  1. Що саме перевіряє healthcheck і останній output:
docker inspect postgres-backup-node1 --format '{{json .Config.Healthcheck}}' | jq .
docker inspect postgres-backup-node1 --format '{{range .State.Health.Log}}{{println .End " exit=" .ExitCode " " .Output}}{{end}}'
  1. Логи контейнера:
docker logs --tail 200 postgres-backup-node1

Типові причини "Up, але unhealthy": healthcheck звертається до Postgres по hostname, який не резолвиться/не доступний; немає прав/пароля; або backup-процес завис і healthcheck очікує lock.

Перевірено 2025-01-28: Логи показують pg_dump version mismatch: Postgres сервер 16.11, pg_dump у контейнері 15.14. Healthcheck повертає 503, бо внутрішній HTTP-сервіс бекапу відповідає 503 при невдалому бекапі.

Наступний фікс (зміна образу): оновити образ postgres-backup так, щоб pg_dump був 16.x (базуватись на postgres:16 або встановити postgresql-client-16 у backup image). Після оновлення образу — перезапуск контейнера backup.


4.1 render-pdf-worker: NATS timeout

  • Перевірити env: docker inspect render-pdf-worker-node1 --format '{{json .Config.Env}}' | jq -r '.[]' | egrep -i NATS'.
  • Перевірено 2025-01-28: NATS_URL=nats://nats:4222 — hostname nats, тоді як контейнер у мережі називається dagi-nats-node1. Якщо аліасу nats немає, воркер не з’єднається з NATS. Наступний фікс (один із варіантів): (1) додати network alias nats для контейнера dagi-nats-node1 у docker-compose; (2) змінити NATS_URL на nats://dagi-nats-node1:4222 у воркері; (3) аліас через compose networks: … aliases: [nats].

4.2 Caddy failed: конфлікт порту

  • Перевірено 2025-01-28: journalctl -u caddy: "listen tcp :443: bind: address already in use". Порти 80/443 зайняті nginx. Caddy не стартує, поки nginx тримає ці порти.

Наступний фікс (архітектура): це не поломка, а конфлікт. Варіанти: (1) залишити nginx єдиним reverse proxy і вимкнути Caddy; (2) перейти на Caddy (вимкнути nginx); (3) Caddy слухає інші порти і nginx проксує до нього (рідко має сенс).


5) Точковий перезапуск БЕЗ зміни конфігурації

5.1 Перезапуск лише memory-service

docker restart dagi-memory-service-node1

# Дати прогрітись і перевірити health
sleep 30
docker inspect dagi-memory-service-node1 --format '{{.State.Health.Status}}'
curl -s -o /dev/null -w "memory /health HTTP=%{http_code}\n" --max-time 5 http://127.0.0.1:8000/health
docker logs --tail 80 dagi-memory-service-node1

5.2 Якщо знову таймаут до Qdrant — перезапуск Qdrant → потім memory-service

docker restart dagi-qdrant-node1
sleep 20
docker restart dagi-memory-service-node1

sleep 40
docker inspect dagi-memory-service-node1 --format '{{.State.Health.Status}}'

5.3 render-pdf-worker-node1 (Exited 1) — запустити і глянути причину падіння

docker start render-pdf-worker-node1
sleep 2
docker ps -a --filter name=render-pdf-worker-node1 --format 'table {{.Names}}\t{{.Status}}'
docker logs --tail 200 render-pdf-worker-node1

5.4 postgres-backup (unhealthy) — лише restart за потреби

docker restart postgres-backup-node1
docker logs --tail 200 postgres-backup-node1

6) Caddy failed (read-only + точковий restart за потреби)

systemctl status caddy --no-pager -l
journalctl -u caddy -n 200 --no-pager

Якщо треба просто підняти сервіс без змін конфігурації:

systemctl restart caddy
systemctl status caddy --no-pager -l

7) Що саме зібрати після "4-го падіння" (щоб знайти повторювану причину)

  1. Час останнього падіння memory-service (з docker logs --since ...).
  2. docker inspect ...Health.Log для memory-service та postgres-backup.
  3. Латентність Qdrant /collections та чи проходить nc з контейнера memory-service на dagi-qdrant-node1:6333.
  4. docker stats --no-stream для memory + qdrant у момент проблеми.

Якщо надаси ці 4 блоки в одному повідомленні — можна точніше локалізувати: мережа між контейнерами, Qdrant під навантаженням/IO, або healthcheck memory-service надто "агресивний" відносно реального часу відповіді Qdrant.


8) Однією командою (копіпаста на NODA1)

Швидка діагностика (read-only):

echo "=== Qdrant from host ===" && curl -s -o /dev/null -w "%{http_code}\n" --connect-timeout 3 http://127.0.0.1:6333/healthz
echo "=== Qdrant from memory container ===" && docker exec dagi-memory-service-node1 sh -c 'curl -s -o /dev/null -w "%{http_code}\n" --connect-timeout 5 http://dagi-qdrant-node1:6333/healthz' 2>/dev/null || echo "fail"
echo "=== Memory /health ===" && curl -s -o /dev/null -w "%{http_code}\n" --max-time 5 http://127.0.0.1:8000/health
docker ps -a --format 'table {{.Names}}\t{{.Status}}' | egrep 'dagi-memory-service-node1|dagi-qdrant-node1|postgres-backup-node1|render-pdf-worker-node1'

Тільки перезапуск memory + render-pdf-worker (без Qdrant):

docker restart dagi-memory-service-node1
docker start render-pdf-worker-node1
echo "Waiting 45s for memory health..."
sleep 45
docker ps --filter name=dagi-memory-service-node1 --filter name=render-pdf-worker-node1 --format "table {{.Names}}\t{{.Status}}"

Рекомендація для вашого кейсу (щоб не ловити це 5-й раз)

  1. Закріпити правила DOCKER-USER одним із способів 0.4 A або B (A краще — iptables-persistent).
  2. Додати в runbook перевірку DOCKER-USER як перший крок при будь-яких таймаутах між контейнерами (див. 0.5).
  3. Окремо запланувати 3 техборги: (1) backup image з pg16; (2) NATS alias/URL для render-pdf-worker; (3) reverse-proxy single-owner (nginx або Caddy).

Статус по інших знайдених проблемах (куди рухатись далі)

  • postgres-backup: pg_dump version mismatch (сервер 16.x, клієнт 15.x) → потрібен образ з pg16.
  • render-pdf-worker: NATS hostname nats vs реальний контейнер dagi-nats-node1 → alias або зміна NATS_URL.
  • Caddy: конфлікт портів з nginx (80/443) → один reverse proxy (nginx-only або міграція на Caddy).
Проблема Що потрібно
postgres-backup Образ з pg_dump 16.x (зміна образу неминуча).
render-pdf-worker Або network alias nats для dagi-nats-node1, або зміна NATS_URL на nats://dagi-nats-node1:4222 (мінімальна правка compose).
Caddy Конфлікт портів з nginx — вибір одного reverse proxy.

Готові патчі: див. node2/docs/NODA1-TECHBORGS-PATCHES.md — зібрані YAML-блоки з NODA1, точні diff, порядок застосування та acceptance checks для патчів #1 (postgres-backup pg16), #2 (render-pdf-worker NATS alias), #3 (Caddy vs nginx).


Скрипт діагностики (на NODA1): node2/scripts/noda1-memory-diagnose.sh
Запуск на сервері: скопіювати скрипт на NODA1 і виконати bash noda1-memory-diagnose.sh.
Або з локальної машини: ssh root@144.76.224.179 'bash -s' < node2/scripts/noda1-memory-diagnose.sh

Останнє оновлення: 2025-01-28
Історія: 4-й випадок; додано постійне збереження DOCKER-USER (A/B), перший крок при таймаутах (0.5), наступні фікси (backup pg16, NATS alias, Caddy vs nginx), рекомендація.