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>
20 KiB
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, дочекатися 30–60 с, перевірити 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)
- Що саме перевіряє 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}}'
- Логи контейнера:
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 aliasnatsдля контейнераdagi-nats-node1у docker-compose; (2) змінитиNATS_URLнаnats://dagi-nats-node1:4222у воркері; (3) аліас через composenetworks: … 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-го падіння" (щоб знайти повторювану причину)
- Час останнього падіння memory-service (з
docker logs --since ...). docker inspect ...Health.Logдля memory-service та postgres-backup.- Латентність Qdrant /collections та чи проходить nc з контейнера memory-service на
dagi-qdrant-node1:6333. 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-й раз)
- Закріпити правила DOCKER-USER одним із способів 0.4 A або B (A краще — iptables-persistent).
- Додати в runbook перевірку DOCKER-USER як перший крок при будь-яких таймаутах між контейнерами (див. 0.5).
- Окремо запланувати 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
natsvs реальний контейнер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), рекомендація.