- matrix-gateway: POST /internal/matrix/presence/online endpoint - usePresenceHeartbeat hook with activity tracking - Auto away after 5 min inactivity - Offline on page close/visibility change - Integrated in MatrixChatRoom component
678 lines
20 KiB
Markdown
678 lines
20 KiB
Markdown
# 🌐 Децентралізована мережа нод - Архітектура автоматичного підключення
|
||
|
||
**Дата:** 2025-11-23
|
||
**Версія:** 2.0.0
|
||
**Статус:** 🎯 Production Architecture
|
||
|
||
---
|
||
|
||
## 🎯 Мета
|
||
|
||
Створити повністю автоматизовану систему для додавання нових нод до децентралізованої мережі штучного інтелекту без ручного втручання.
|
||
|
||
---
|
||
|
||
## 🏗️ Архітектура мережі
|
||
|
||
### Поточна структура
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────┐
|
||
│ DAARION AI Network │
|
||
├─────────────────────────────────────────────────────────┤
|
||
│ │
|
||
│ НОДА1 (Hetzner) ◄──────────────────┐ │
|
||
│ 144.76.224.179 │ │
|
||
│ ├─ Node Registry (9205) │ │
|
||
│ ├─ NATS JetStream (4222) │ │
|
||
│ ├─ DAGI Router (9102) │ Discovery │
|
||
│ └─ Gateway (9300) │ & Heartbeat │
|
||
│ │ │
|
||
│ НОДА2 (MacBook M4 Max) ─────────────┘ │
|
||
│ 192.168.1.33 (local) │
|
||
│ ├─ Development Node │
|
||
│ ├─ 50 DAARION Agents │
|
||
│ └─ 8 AI Models │
|
||
│ │
|
||
│ НОДА3-N (Future) ───────────────────┐ │
|
||
│ Any location │ │
|
||
│ └─ Auto-registration ────────────────┘ │
|
||
└─────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
---
|
||
|
||
## 🔧 Компоненти системи
|
||
|
||
### 1. Node Registry Service (Вже існує!)
|
||
|
||
**Місце:** НОДА1 (144.76.224.179:9205)
|
||
**Статус:** ✅ Готовий (потрібна імплементація API)
|
||
|
||
**Призначення:**
|
||
- Центральний реєстр всіх нод
|
||
- Автоматична реєстрація нових нод
|
||
- Heartbeat tracking (keep-alive)
|
||
- Node discovery
|
||
|
||
**База даних:**
|
||
```sql
|
||
-- nodes table
|
||
node_id VARCHAR(255) -- Унікальний ID ноди
|
||
node_name VARCHAR(255) -- Людське ім'я
|
||
ip_address INET -- Публічна IP
|
||
local_ip INET -- Локальна IP
|
||
status VARCHAR(50) -- online, offline, degraded
|
||
last_heartbeat TIMESTAMP -- Останній heartbeat
|
||
capabilities JSONB -- GPU, CPU, RAM, моделі
|
||
```
|
||
|
||
**API Endpoints:**
|
||
```bash
|
||
POST /api/v1/nodes/register # Реєстрація нової ноди
|
||
POST /api/v1/nodes/heartbeat # Heartbeat (keep-alive)
|
||
GET /api/v1/nodes # Список всіх нод
|
||
GET /api/v1/nodes/{node_id} # Інфо про конкретну ноду
|
||
GET /api/v1/nodes/discover # Знайти ноди з певними capabilities
|
||
```
|
||
|
||
---
|
||
|
||
### 2. Bootstrap Agent (Авто-реєстрація)
|
||
|
||
**Файл:** `tools/dagi_node_agent/bootstrap.py`
|
||
**Статус:** ✅ Існує, потрібне покращення
|
||
|
||
**Як працює:**
|
||
|
||
```python
|
||
# Запуск на новій ноді
|
||
python3 -m tools.dagi_node_agent.bootstrap \
|
||
--role production-node \
|
||
--labels gpu,cpu,ollama \
|
||
--registry-url http://144.76.224.179:9205
|
||
```
|
||
|
||
**Що робить:**
|
||
1. ✅ Автоматично визначає hostname, IP
|
||
2. ✅ Генерує унікальний node_id
|
||
3. ✅ Збирає інформацію про систему (CPU, RAM, GPU)
|
||
4. ✅ Реєструється в Node Registry
|
||
5. ✅ Зберігає node_id локально
|
||
6. ✅ Запускає heartbeat loop
|
||
|
||
---
|
||
|
||
### 3. Heartbeat Service (Keep-Alive)
|
||
|
||
**Призначення:** Підтримувати з'єднання з Registry
|
||
|
||
**Механізм:**
|
||
```python
|
||
# Кожні 30 секунд відправляє heartbeat
|
||
while True:
|
||
await send_heartbeat(node_id, metrics)
|
||
await asyncio.sleep(30)
|
||
```
|
||
|
||
**Метрики що відправляються:**
|
||
- CPU usage %
|
||
- RAM usage %
|
||
- GPU usage % (якщо є)
|
||
- Disk usage %
|
||
- Активні сервіси
|
||
- Кількість агентів
|
||
- Статус моделей
|
||
|
||
---
|
||
|
||
### 4. Discovery Service (Пошук нод)
|
||
|
||
**Призначення:** Знайти ноду з потрібними capabilities
|
||
|
||
**API Query:**
|
||
```bash
|
||
# Знайти ноду з GPU
|
||
GET /api/v1/nodes/discover?capability=gpu&status=online
|
||
|
||
# Знайти ноду з конкретною моделлю
|
||
GET /api/v1/nodes/discover?model=deepseek-r1:70b
|
||
|
||
# Знайти найближчу ноду
|
||
GET /api/v1/nodes/discover?latency=min®ion=europe
|
||
```
|
||
|
||
**Response:**
|
||
```json
|
||
{
|
||
"nodes": [
|
||
{
|
||
"node_id": "node-1-hetzner",
|
||
"ip_address": "144.76.224.179",
|
||
"latency_ms": 45,
|
||
"capabilities": {
|
||
"gpu": "NVIDIA RTX 4000",
|
||
"models": ["qwen3:8b", "mistral:7b"]
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 🚀 Автоматичне підключення нової ноди
|
||
|
||
### Сценарій: Додавання НОДА3
|
||
|
||
#### Крок 1: Встановлення на новій ноді
|
||
```bash
|
||
# На НОДА3
|
||
git clone git@github.com:IvanTytar/microdao-daarion.git
|
||
cd microdao-daarion
|
||
pip install -r requirements.txt
|
||
```
|
||
|
||
#### Крок 2: Конфігурація (один файл)
|
||
```bash
|
||
# Створити .env файл
|
||
cat > .env << EOF
|
||
# Node Registry URL
|
||
NODE_REGISTRY_URL=http://144.76.224.179:9205
|
||
|
||
# NATS для комунікації
|
||
NATS_URL=nats://144.76.224.179:4222
|
||
|
||
# Роль ноди
|
||
NODE_ROLE=worker-node
|
||
NODE_LABELS=gpu,inference,home
|
||
|
||
# Ollama (якщо є)
|
||
OLLAMA_BASE_URL=http://localhost:11434
|
||
EOF
|
||
```
|
||
|
||
#### Крок 3: Автоматична реєстрація
|
||
```bash
|
||
# Запустити bootstrap
|
||
python3 -m tools.dagi_node_agent.bootstrap --auto
|
||
```
|
||
|
||
**Що відбудеться автоматично:**
|
||
1. ✅ Визначить hostname та IP
|
||
2. ✅ Згенерує унікальний node_id
|
||
3. ✅ Зсканує систему (CPU, RAM, GPU, диск)
|
||
4. ✅ Перевірить наявність Ollama та моделей
|
||
5. ✅ Зареєструється в Node Registry
|
||
6. ✅ Запустить heartbeat service
|
||
7. ✅ Стане доступною в мережі через 30 секунд
|
||
|
||
#### Крок 4: Перевірка
|
||
```bash
|
||
# На будь-якій ноді
|
||
curl http://144.76.224.179:9205/api/v1/nodes
|
||
|
||
# Побачите НОДА3 в списку!
|
||
```
|
||
|
||
---
|
||
|
||
## 🔐 Безпека та автентифікація
|
||
|
||
### Рівень 1: API Key (Базовий)
|
||
```bash
|
||
# При реєстрації генерується API key
|
||
NODE_API_KEY=sk-node-abc123xyz
|
||
|
||
# Всі запити до Registry потребують ключ
|
||
curl -H "Authorization: Bearer $NODE_API_KEY" \
|
||
http://144.76.224.179:9205/api/v1/nodes
|
||
```
|
||
|
||
### Рівень 2: mTLS (Mutual TLS)
|
||
```bash
|
||
# Кожна нода має свій сертифікат
|
||
NODE_CERT=/etc/dagi/certs/node.crt
|
||
NODE_KEY=/etc/dagi/certs/node.key
|
||
CA_CERT=/etc/dagi/certs/ca.crt
|
||
|
||
# Всі з'єднання через TLS
|
||
```
|
||
|
||
### Рівень 3: Whitelist (IP або node_id)
|
||
```python
|
||
# В Node Registry
|
||
ALLOWED_NODES = [
|
||
"node-1-hetzner",
|
||
"node-2-macbook",
|
||
# Автоматично додаються після верифікації
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## 🌍 Топології мережі
|
||
|
||
### Топологія 1: Централізована (поточна)
|
||
```
|
||
НОДА1 (Registry)
|
||
/ | \
|
||
/ | \
|
||
НОДА2 НОДА3 НОДА4
|
||
```
|
||
|
||
**Плюси:**
|
||
- ✅ Простота
|
||
- ✅ Один point of truth
|
||
- ✅ Легке управління
|
||
|
||
**Мінуси:**
|
||
- ⚠️ Single point of failure
|
||
- ⚠️ Bottleneck при великій кількості нод
|
||
|
||
### Топологія 2: Федеративна (майбутнє)
|
||
```
|
||
НОДА1 (Registry-EU) ◄────► НОДА5 (Registry-US)
|
||
/ | \ / | \
|
||
НОДА2 3 4 НОДА6 7 8
|
||
```
|
||
|
||
**Плюси:**
|
||
- ✅ Відмовостійкість
|
||
- ✅ Географічна близькість
|
||
- ✅ Масштабованість
|
||
|
||
**Мінуси:**
|
||
- ⚠️ Складніша синхронізація
|
||
- ⚠️ Eventual consistency
|
||
|
||
### Топологія 3: P2P (повна децентралізація)
|
||
```
|
||
НОДА1 ◄──► НОДА2
|
||
▲ \ / ▲
|
||
│ \ / │
|
||
│ ◄ │
|
||
└─► НОДА3 ◄─┘
|
||
```
|
||
|
||
**Плюси:**
|
||
- ✅ Повна децентралізація
|
||
- ✅ Максимальна відмовостійкість
|
||
- ✅ Немає central authority
|
||
|
||
**Мінуси:**
|
||
- ⚠️ Складна імплементація
|
||
- ⚠️ Consensus протоколи потрібні
|
||
|
||
---
|
||
|
||
## 📡 Протоколи комунікації
|
||
|
||
### 1. HTTP/REST (Реєстрація та discovery)
|
||
```
|
||
Використання: Реєстрація, heartbeat, queries
|
||
Протокол: HTTP/HTTPS
|
||
Порт: 9205
|
||
```
|
||
|
||
### 2. NATS JetStream (Messaging)
|
||
```
|
||
Використання: Асинхронні повідомлення, events
|
||
Протокол: NATS
|
||
Порт: 4222
|
||
Topics:
|
||
- node.{node_id}.events
|
||
- node.{node_id}.tasks
|
||
- system.broadcast
|
||
```
|
||
|
||
### 3. gRPC (High-performance calls)
|
||
```
|
||
Використання: Швидкі запити між нодами
|
||
Протокол: gRPC/HTTP2
|
||
Порт: 9106 (майбутнє)
|
||
```
|
||
|
||
### 4. WebSocket (Real-time)
|
||
```
|
||
Використання: Real-time dashboard, monitoring
|
||
Протокол: WebSocket
|
||
Порт: 9107 (майбутнє)
|
||
```
|
||
|
||
---
|
||
|
||
## 🔄 Життєвий цикл ноди
|
||
|
||
### 1. Registration (Реєстрація)
|
||
```
|
||
НОДА3 → POST /api/v1/nodes/register → Node Registry
|
||
Response: {node_id, api_key, config}
|
||
```
|
||
|
||
### 2. Active (Працює)
|
||
```
|
||
НОДА3 → POST /api/v1/nodes/heartbeat (every 30s) → Registry
|
||
Registry → Marks node as "online"
|
||
```
|
||
|
||
### 3. Degraded (Проблеми)
|
||
```
|
||
Heartbeat timeout > 90s
|
||
Registry → Marks node as "degraded"
|
||
Alert sent to admin
|
||
```
|
||
|
||
### 4. Offline (Відключена)
|
||
```
|
||
Heartbeat timeout > 5min
|
||
Registry → Marks node as "offline"
|
||
Node removed from active routing
|
||
```
|
||
|
||
### 5. Deregistration (Видалення)
|
||
```
|
||
НОДА3 → DELETE /api/v1/nodes/{node_id} → Registry
|
||
Registry → Removes node from database
|
||
```
|
||
|
||
---
|
||
|
||
## 🛠️ Практична імплементація
|
||
|
||
### Файл 1: `bootstrap.py` (Покращений)
|
||
```python
|
||
#!/usr/bin/env python3
|
||
"""
|
||
Автоматична реєстрація ноди в DAARION Network
|
||
"""
|
||
import socket
|
||
import platform
|
||
import psutil
|
||
import requests
|
||
import uuid
|
||
from pathlib import Path
|
||
|
||
class NodeBootstrap:
|
||
def __init__(self, registry_url):
|
||
self.registry_url = registry_url
|
||
self.node_id = self.generate_node_id()
|
||
|
||
def generate_node_id(self):
|
||
"""Генерує унікальний node_id"""
|
||
hostname = socket.gethostname()
|
||
# Якщо є збережений ID - використати його
|
||
node_id_file = Path.home() / ".dagi" / "node_id"
|
||
if node_id_file.exists():
|
||
return node_id_file.read_text().strip()
|
||
|
||
# Інакше створити новий
|
||
short_uuid = str(uuid.uuid4())[:8]
|
||
node_id = f"node-{hostname}-{short_uuid}"
|
||
|
||
# Зберегти для майбутнього використання
|
||
node_id_file.parent.mkdir(parents=True, exist_ok=True)
|
||
node_id_file.write_text(node_id)
|
||
|
||
return node_id
|
||
|
||
def collect_capabilities(self):
|
||
"""Збирає інформацію про систему"""
|
||
capabilities = {
|
||
"hostname": socket.gethostname(),
|
||
"ip_address": self.get_public_ip(),
|
||
"local_ip": self.get_local_ip(),
|
||
"os": platform.system(),
|
||
"os_version": platform.version(),
|
||
"cpu_cores": psutil.cpu_count(),
|
||
"ram_gb": round(psutil.virtual_memory().total / (1024**3), 2),
|
||
"disk_gb": round(psutil.disk_usage('/').total / (1024**3), 2),
|
||
"gpu": self.detect_gpu(),
|
||
"ollama": self.check_ollama(),
|
||
}
|
||
return capabilities
|
||
|
||
def register(self):
|
||
"""Реєстрація в Node Registry"""
|
||
payload = {
|
||
"node_id": self.node_id,
|
||
"capabilities": self.collect_capabilities(),
|
||
"status": "online"
|
||
}
|
||
|
||
response = requests.post(
|
||
f"{self.registry_url}/api/v1/nodes/register",
|
||
json=payload,
|
||
timeout=10
|
||
)
|
||
response.raise_for_status()
|
||
|
||
return response.json()
|
||
|
||
def start_heartbeat(self):
|
||
"""Запускає heartbeat loop"""
|
||
import asyncio
|
||
asyncio.run(self.heartbeat_loop())
|
||
|
||
async def heartbeat_loop(self):
|
||
"""Безкінечний loop heartbeat"""
|
||
while True:
|
||
try:
|
||
metrics = self.collect_metrics()
|
||
requests.post(
|
||
f"{self.registry_url}/api/v1/nodes/heartbeat",
|
||
json={"node_id": self.node_id, "metrics": metrics},
|
||
timeout=5
|
||
)
|
||
except Exception as e:
|
||
print(f"Heartbeat failed: {e}")
|
||
|
||
await asyncio.sleep(30)
|
||
|
||
# ... (інші методи)
|
||
|
||
if __name__ == "__main__":
|
||
bootstrap = NodeBootstrap("http://144.76.224.179:9205")
|
||
print(f"🔧 Registering node: {bootstrap.node_id}")
|
||
|
||
result = bootstrap.register()
|
||
print(f"✅ Registered successfully!")
|
||
print(f" Node ID: {result['node_id']}")
|
||
print(f" API Key: {result['api_key']}")
|
||
|
||
print("💓 Starting heartbeat...")
|
||
bootstrap.start_heartbeat()
|
||
```
|
||
|
||
### Файл 2: `node_discovery.py` (Пошук нод)
|
||
```python
|
||
"""
|
||
Пошук доступних нод в мережі
|
||
"""
|
||
import requests
|
||
from typing import List, Dict, Optional
|
||
|
||
class NodeDiscovery:
|
||
def __init__(self, registry_url):
|
||
self.registry_url = registry_url
|
||
|
||
def find_nodes(
|
||
self,
|
||
role: Optional[str] = None,
|
||
capability: Optional[str] = None,
|
||
status: str = "online"
|
||
) -> List[Dict]:
|
||
"""Знайти ноди за критеріями"""
|
||
params = {"status": status}
|
||
if role:
|
||
params["role"] = role
|
||
if capability:
|
||
params["capability"] = capability
|
||
|
||
response = requests.get(
|
||
f"{self.registry_url}/api/v1/nodes/discover",
|
||
params=params
|
||
)
|
||
response.raise_for_status()
|
||
|
||
return response.json()["nodes"]
|
||
|
||
def find_best_node_for_model(self, model_name: str) -> Optional[Dict]:
|
||
"""Знайти найкращу ноду для конкретної моделі"""
|
||
nodes = self.find_nodes(capability="ollama")
|
||
|
||
for node in nodes:
|
||
if model_name in node["capabilities"].get("models", []):
|
||
return node
|
||
|
||
return None
|
||
|
||
def get_nearest_node(self, location: str = "auto") -> Dict:
|
||
"""Знайти найближчу ноду"""
|
||
response = requests.get(
|
||
f"{self.registry_url}/api/v1/nodes/discover",
|
||
params={"nearest": location}
|
||
)
|
||
response.raise_for_status()
|
||
|
||
return response.json()["nodes"][0]
|
||
|
||
# Використання
|
||
discovery = NodeDiscovery("http://144.76.224.179:9205")
|
||
|
||
# Знайти всі ноди з GPU
|
||
gpu_nodes = discovery.find_nodes(capability="gpu")
|
||
print(f"Found {len(gpu_nodes)} nodes with GPU")
|
||
|
||
# Знайти ноду з deepseek-r1:70b
|
||
node = discovery.find_best_node_for_model("deepseek-r1:70b")
|
||
if node:
|
||
print(f"Model available on: {node['node_name']}")
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 Переваги цієї архітектури
|
||
|
||
### Для розробників
|
||
- ✅ **Plug and Play** - додали ноду за 5 хвилин
|
||
- ✅ **Нульова конфігурація** - все автоматично
|
||
- ✅ **Discovery API** - легко знайти потрібну ноду
|
||
|
||
### Для системи
|
||
- ✅ **Fault tolerance** - автоматичне виявлення збоїв
|
||
- ✅ **Load balancing** - розподіл навантаження
|
||
- ✅ **Horizontal scaling** - додавай скільки потрібно нод
|
||
|
||
### Для бізнесу
|
||
- ✅ **Швидке масштабування** - від 2 до 1000 нод
|
||
- ✅ **Географічне розподілення** - ноди по всьому світу
|
||
- ✅ **Cost optimization** - використання ресурсів по потребі
|
||
|
||
---
|
||
|
||
## 📈 Roadmap
|
||
|
||
### Phase 1: Базова функціональність (ЗАРАЗ)
|
||
- [x] Node Registry Service infrastructure
|
||
- [ ] Імплементація Node Registry API
|
||
- [ ] Bootstrap agent покращений
|
||
- [ ] Heartbeat service
|
||
- [ ] Discovery API
|
||
|
||
### Phase 2: Безпека та моніторинг
|
||
- [ ] API key authentication
|
||
- [ ] mTLS certificates
|
||
- [ ] Prometheus metrics
|
||
- [ ] Grafana dashboards
|
||
- [ ] Alert system
|
||
|
||
### Phase 3: Федерація
|
||
- [ ] Multi-region registries
|
||
- [ ] Registry replication
|
||
- [ ] Geographic routing
|
||
- [ ] Latency optimization
|
||
|
||
### Phase 4: Повна децентралізація
|
||
- [ ] P2P discovery (DHT)
|
||
- [ ] Consensus protocol
|
||
- [ ] Blockchain registry (optional)
|
||
- [ ] Zero-knowledge proofs
|
||
|
||
---
|
||
|
||
## ✅ Що потрібно зробити ЗАРАЗ
|
||
|
||
### 1. Завершити Node Registry API
|
||
```bash
|
||
# Файл: services/node-registry/app/main.py
|
||
# Імплементувати endpoints:
|
||
POST /api/v1/nodes/register
|
||
POST /api/v1/nodes/heartbeat
|
||
GET /api/v1/nodes
|
||
GET /api/v1/nodes/{node_id}
|
||
GET /api/v1/nodes/discover
|
||
```
|
||
|
||
### 2. Покращити Bootstrap Agent
|
||
```bash
|
||
# Файл: tools/dagi_node_agent/bootstrap.py
|
||
# Додати:
|
||
- Auto-detection capabilities
|
||
- Ollama models scanning
|
||
- GPU detection (NVIDIA, Apple Silicon)
|
||
- Persistent node_id storage
|
||
```
|
||
|
||
### 3. Створити Frontend для моніторингу
|
||
```bash
|
||
# Додати сторінку в React:
|
||
/nodes/registry
|
||
- Список всіх нод
|
||
- Статус (online/offline)
|
||
- Capabilities
|
||
- Real-time heartbeat
|
||
```
|
||
|
||
---
|
||
|
||
## 🎯 Відповідь на ваше питання
|
||
|
||
### Чи можливо автоматизувати додавання нод?
|
||
|
||
**✅ ТАК! Це повністю можливо і вже частково реалізовано!**
|
||
|
||
**Що вже є:**
|
||
- ✅ Node Registry Service (infraструктура готова)
|
||
- ✅ Bootstrap Agent (базова версія)
|
||
- ✅ Database schema для нод
|
||
- ✅ Docker конфігурація
|
||
|
||
**Що треба:**
|
||
- 🔧 Завершити Node Registry API (1-2 дні)
|
||
- 🔧 Покращити Bootstrap Agent (1 день)
|
||
- 🔧 Додати heartbeat service (1 день)
|
||
- 🔧 Створити Discovery API (1 день)
|
||
|
||
**Результат:**
|
||
```bash
|
||
# На будь-якій новій ноді просто запускаєте:
|
||
./scripts/join-network.sh
|
||
|
||
# І нода автоматично:
|
||
# 1. Реєструється
|
||
# 2. Починає heartbeat
|
||
# 3. Стає доступною в мережі
|
||
# 4. З'являється в dashboard
|
||
```
|
||
|
||
---
|
||
|
||
**Статус:** ✅ **Архітектура готова, залишилась імплементація!**
|
||
**Часова оцінка:** 4-5 днів розробки
|
||
**Складність:** Середня (Node Registry API + Bootstrap покращення)
|
||
|
||
**Коли буде готово - у вас буде plug-and-play мережа штучного інтелекту! 🚀**
|
||
|
||
|