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>
413 lines
20 KiB
Markdown
413 lines
20 KiB
Markdown
# 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 хв)
|
||
|
||
```bash
|
||
# Загальний стан контейнерів (імена/статуси/порти)
|
||
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 "живий" з хоста
|
||
|
||
```bash
|
||
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 (критично для міжконтейнерної мережі)
|
||
|
||
```bash
|
||
# 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` і латентність
|
||
|
||
```bash
|
||
# /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
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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 (часта причина таймаутів між контейнерами)
|
||
|
||
```bash
|
||
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` або окремому скрипті, щоб правило пережило перезавантаження):
|
||
|
||
```bash
|
||
# Дозволити трафік з мережі контейнерів (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) Зафіксувати поточні правила:**
|
||
```bash
|
||
iptables -S DOCKER-USER
|
||
iptables -L DOCKER-USER -n -v --line-numbers
|
||
```
|
||
|
||
**A1) Встановити пакети:**
|
||
```bash
|
||
apt-get update
|
||
DEBIAN_FRONTEND=noninteractive apt-get install -y iptables-persistent netfilter-persistent
|
||
```
|
||
|
||
**A2) Зберегти правила:**
|
||
```bash
|
||
iptables-save > /etc/iptables/rules.v4
|
||
# (IPv6 за потреби) ip6tables-save > /etc/iptables/rules.v6
|
||
```
|
||
|
||
**A3) Увімкнути відновлення на старті:**
|
||
```bash
|
||
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 не перетре ланцюги:
|
||
|
||
```bash
|
||
# 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 — валідація:**
|
||
```bash
|
||
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 секунд перевірок:**
|
||
```bash
|
||
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`:
|
||
```ini
|
||
[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) Увімкнути і запустити:**
|
||
```bash
|
||
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 тягне не той бекенд:**
|
||
```bash
|
||
iptables -V
|
||
update-alternatives --display iptables 2>/dev/null || true
|
||
sudo journalctl -u netfilter-persistent -b --no-pager | tail -200
|
||
```
|
||
|
||
**2) Docker/ufw змінюють форвардинг або ланцюги після старту:**
|
||
```bash
|
||
sudo journalctl -u docker -b --no-pager | tail -200
|
||
sudo iptables -S DOCKER-USER
|
||
```
|
||
|
||
Якщо змішування legacy/nft — стандартизувати backend (окремий крок).
|
||
|
||
---
|
||
|
||
## 0.5 Перший крок при таймаутах між контейнерами
|
||
|
||
При будь-яких таймаутах memory↔Qdrant або інших контейнер↔контейнер **спочатку** перевірити DOCKER-USER:
|
||
|
||
```bash
|
||
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)
|
||
|
||
```bash
|
||
# 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:
|
||
|
||
```bash
|
||
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}}'
|
||
```
|
||
|
||
2. Логи контейнера:
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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
|
||
|
||
```bash
|
||
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) — запустити і глянути причину падіння
|
||
|
||
```bash
|
||
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 за потреби
|
||
|
||
```bash
|
||
docker restart postgres-backup-node1
|
||
docker logs --tail 200 postgres-backup-node1
|
||
```
|
||
|
||
---
|
||
|
||
## 6) Caddy failed (read-only + точковий restart за потреби)
|
||
|
||
```bash
|
||
systemctl status caddy --no-pager -l
|
||
journalctl -u caddy -n 200 --no-pager
|
||
```
|
||
|
||
Якщо треба просто підняти сервіс без змін конфігурації:
|
||
|
||
```bash
|
||
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):**
|
||
|
||
```bash
|
||
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):**
|
||
|
||
```bash
|
||
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), рекомендація.
|