328 lines
16 KiB
Markdown
328 lines
16 KiB
Markdown
# 🔒 MAM 개인 전용 브로커(Private Broker) 구축 및 연동 가이드 (`PRIVATE_SERVER.md`)
|
|
|
|
- **작성일**: 2026-08-20 (Rev.2)
|
|
- **문서 목적**: MAM(Multi-Agent Mux)의 공개 브로커 의존성 및 보안 결함(A-2)을 해소하기 위한 개인 전용 브로커(NATS / Mosquitto) 구축, 운영, 다능성 활용 및 MAM 클라이언트 연동 가이드.
|
|
- **연계 문서**: [`NATS_REPORT.md`](NATS_REPORT.md), [`implementation_plan.md`](implementation_plan.md), [`IMPROVEMENTS.md`](IMPROVEMENTS.md)
|
|
|
|
---
|
|
|
|
## 1. 개요 및 도입 배경
|
|
|
|
현재 MAM 프레임워크의 기본 메시징 브로커는 공개 서버(`broker.hivemq.com:1883`)로 설정되어 있습니다. 개인 전용 브로커(Private Broker)를 구축하여 연결하면 **클라이언트 코드 변경 없이(0줄 변경)** 보안 위험을 원천 차단하고 네트워크 안정성을 대폭 향상시킬 수 있습니다.
|
|
|
|
```
|
|
[MAM Orchestrator / Agents]
|
|
│
|
|
▼ (MQTT 3.1.1 / TLS)
|
|
[Private Dedicated Broker] ───> 사설망/개인 서버 (NATS Server / Mosquitto)
|
|
• 외부 불법 트래픽 100% 차단 (A-2 보안 해소)
|
|
• JetStream 영속성 및 NKey/JWT ACL 지원
|
|
• 초저지연 (<1ms) 및 무제한 대역폭
|
|
```
|
|
|
|
MAM의 제어 평면(`run_loop.sh`의 `wait_for_job` 파일시스템 폴링)은 브로커와 100% 독립적으로 작동하므로, 브로커는 **비동기 관측(observability) 사이드카** 역할을 수행합니다.
|
|
|
|
---
|
|
|
|
## 2. 해결 영역 매트릭스 (브로커 전환 vs 코드 패치)
|
|
|
|
전용 브로커 구축으로 즉시 해결되는 영역과, 로컬 코드 패치(Track 0)가 병행되어야 하는 영역의 명확한 구분입니다.
|
|
|
|
| 구분 | 당면 과제 | 개인 브로커 구축 시 | 로컬 코드 패치 필요 여부 (Track 0) |
|
|
|---|---|:---:|:---:|
|
|
| **보안 (A-2)** | 공개 브로커 노출 및 외부 악의적 이벤트 수신 위협 | 🟢 **100% 즉시 해소** (사설망/ACL 격리) | Track 2에서 토큰 발급 강제 |
|
|
| **안정성** | 공개 브로커의 예고 없는 순단 및 속도 제한(Rate-limit) | 🟢 **100% 즉시 해소** (전용 리소스) | — |
|
|
| **내결함성 (B-14)** | 브로커 일시 장애 시 65분 루프 정지(Hang) 결함 | ⚠️ 브로커 점검/순단 시 여전히 위험 | 🔴 **필수 (Track 0 Step 1 선행 패치)** |
|
|
| **지연/오판 (B-15)** | 브로커 다운 시 120초 지연 및 정상 작업의 에러 오판정 | ⚠️ 브로커 점검/순단 시 여전히 위험 | 🔴 **필수 (Track 0 Step 2 & 3 선행 패치)** |
|
|
|
|
> [!IMPORTANT]
|
|
> **방탄 아키텍처 원칙:**
|
|
> "Track 0(`B-14`, `B-15`) 패치를 통해 브로커가 다운되어도 루프가 100% 정상 완주하도록 로컬 디스크 내결함성을 먼저 확보하고, 개인 브로커를 연결하여 A-2 보안과 성능을 완결합니다."
|
|
|
|
---
|
|
|
|
## 3. 전용 브로커 추천 및 비교
|
|
|
|
MAM 클라이언트는 표준 `paho-mqtt`를 사용하므로, MQTT 3.1.1을 지원하는 모든 브로커와 100% 호환됩니다.
|
|
|
|
| 비교 항목 | 🏆 `nats-server` (강력 권장) | `eclipse-mosquitto` (대안) |
|
|
|---|---|---|
|
|
| **아키텍처** | Go 단일 정적 바이너리 (Zero Dependency) | C 기반 경량 오픈소스 브로커 |
|
|
| **주요 특징** | • 내장 MQTT 3.1.1 리스너 (`mqtt { port: 1883 }`)<br>• JetStream 엔진 내장 (이벤트 영속화 및 복구)<br>• NKey/JWT 기반 계정 및 Subject별 세분화된 ACL<br>• WebSocket 및 NATS 네이티브 프로토콜 동시 서빙 | • 가장 널리 쓰이는 표준 경량 MQTT 브로커<br>• 낮은 메모리 점유율 (~10MB) |
|
|
| **추천 용도** | 모던 인프라, 확장성, 감사 로그 영속화, 홈랩 통합 | 정통 초경량 임베디드/단일 목적 환경 |
|
|
| **배포 난이도** | 🟢 바이너리 1개 실행 또는 Docker 1줄 | 🟢 패키지 매니저 (`apt`, `brew`) 또는 Docker |
|
|
|
|
---
|
|
|
|
## 4. 개인 서버 브로커 배포 가이드
|
|
|
|
### 4.1 `nats-server` 배포 (권장)
|
|
|
|
`nats-server`에서 MQTT를 활성화하려면 설정 파일(`nats.conf`)에 `mqtt { port: 1883 }` 블록과 `jetstream { }` 블록이 반드시 포함되어야 합니다.
|
|
|
|
> [!NOTE]
|
|
> `nats-server`의 `-m` 플래그는 HTTP 모니터링 포트(`--http_port`)를 지정하는 옵션이며, MQTT를 켜는 플래그가 아닙니다. MQTT 활성화는 반드시 `-c nats.conf` 설정 파일을 통해 구성해야 합니다.
|
|
|
|
#### 1) 공통 설정 파일 (`nats.conf`)
|
|
```conf
|
|
# nats.conf
|
|
server_name: mam-hub
|
|
|
|
# JetStream 영속 스토리지 (MQTT QoS 1 및 Retained 메시지 처리에 필수)
|
|
jetstream {
|
|
store_dir: "~/.local/share/nats/data" # Docker 환경에서는 "/data"로 매핑
|
|
max_file: 10G # 홈랩 디스크 상한 설정
|
|
}
|
|
|
|
# HTTP 모니터링 엔드포인트 (/varz, /jsz 대시보드)
|
|
http_port: 8222
|
|
|
|
# 평면 A: MAM MQTT 3.1.1 프로토콜 리스너
|
|
mqtt {
|
|
port: 1883
|
|
}
|
|
|
|
# 평면 B: 홈랩/웹 브라우저 대시보드용 WebSocket 리스너 (선택 사항)
|
|
websocket {
|
|
port: 8080
|
|
no_tls: true # 내부 사설망 한정
|
|
}
|
|
```
|
|
|
|
#### 2) 배포 방법 A. Docker / Docker Compose (권장)
|
|
|
|
**단일 Docker 실행:**
|
|
```bash
|
|
# 호스트에 nats.conf 생성 후 실행
|
|
docker run -d \
|
|
--name mam-nats \
|
|
--restart unless-stopped \
|
|
-p 1883:1883 \
|
|
-p 4222:4222 \
|
|
-p 8222:8222 \
|
|
-p 8080:8080 \
|
|
-v ./nats.conf:/etc/nats/nats.conf:ro \
|
|
-v nats-data:/data \
|
|
nats:latest \
|
|
-c /etc/nats/nats.conf
|
|
```
|
|
|
|
**Docker Compose (`docker-compose.yml`):**
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
nats:
|
|
image: nats:latest
|
|
container_name: mam-nats
|
|
restart: unless-stopped
|
|
command: ["-c", "/etc/nats/nats.conf"]
|
|
ports:
|
|
- "1883:1883" # MQTT 3.1.1 포트 (평면 A: MAM)
|
|
- "4222:4222" # NATS 기본 포트 (평면 B)
|
|
- "8222:8222" # HTTP 모니터링 (/varz, /jsz)
|
|
- "8080:8080" # WebSocket (평면 B)
|
|
volumes:
|
|
- ./nats.conf:/etc/nats/nats.conf:ro
|
|
- nats-data:/data
|
|
|
|
volumes:
|
|
nats-data:
|
|
```
|
|
|
|
#### 3) 배포 방법 B. 네이티브 바이너리 설치 (macOS / Linux — 비루트 사용자 공간)
|
|
|
|
macOS의 sealed APFS 루트 볼륨(`/data`) 권한 문제를 방지하기 위해 사용자 홈 디렉터리(`~/.config/nats/`, `~/.local/share/nats/data`)를 기본 스토리지로 사용합니다.
|
|
|
|
```bash
|
|
# 설정 및 데이터 디렉터리 생성 (sudo 불필요)
|
|
mkdir -p ~/.config/nats ~/.local/share/nats/data
|
|
|
|
# 설정 파일 작성
|
|
cat <<'EOF' > ~/.config/nats/nats.conf
|
|
server_name: mam-hub
|
|
jetstream {
|
|
store_dir: "~/.local/share/nats/data"
|
|
max_file: 10G
|
|
}
|
|
http_port: 8222
|
|
mqtt {
|
|
port: 1883
|
|
}
|
|
websocket {
|
|
port: 8080
|
|
no_tls: true
|
|
}
|
|
EOF
|
|
|
|
# macOS (Homebrew 설치 및 실행)
|
|
brew install nats-server
|
|
nats-server -c ~/.config/nats/nats.conf &
|
|
|
|
# Linux (x86_64 단일 바이너리 설치 및 실행)
|
|
curl -L https://github.com/nats-io/nats-server/releases/download/v2.10.20/nats-server-v2.10.20-linux-amd64.tar.gz | tar xz
|
|
sudo mv nats-server-v2.10.20-linux-amd64/nats-server /usr/local/bin/
|
|
nats-server -c ~/.config/nats/nats.conf &
|
|
```
|
|
|
|
---
|
|
|
|
### 4.2 `mosquitto` 배포 (대안)
|
|
|
|
#### Docker 실행:
|
|
```bash
|
|
docker run -d \
|
|
--name mam-mosquitto \
|
|
--restart unless-stopped \
|
|
-p 1883:1883 \
|
|
-v ./mosquitto.conf:/mosquitto/config/mosquitto.conf \
|
|
eclipse-mosquitto:latest
|
|
```
|
|
|
|
**기본 `mosquitto.conf` 설정 파일 예시:**
|
|
```conf
|
|
listener 1883
|
|
allow_anonymous true
|
|
persistence true
|
|
persistence_location /mosquitto/data/
|
|
```
|
|
|
|
---
|
|
|
|
## 5. 하나의 서버로 여러 프로젝트 — `nats-server` 다능성 (Versatility)
|
|
|
|
`nats-server`의 다능성은 **MAM을 네이티브 NATS로 이관할 이유가 아니라, MAM 코드를 한 줄도 바꾸지 않고도 얻을 수 있는 부가적 이득**입니다.
|
|
|
|
### 5.1 두 개의 소비 평면 (Two Consumption Planes)
|
|
|
|
`nats-server`는 단일 프로세스 내에서 여러 프로토콜 리스너를 동시에 구동하므로, MAM의 단순성과 개인 홈랩의 확장성을 완벽히 양립시킵니다.
|
|
|
|
```
|
|
┌───────────────────────────────────────────────────────────┐
|
|
│ nats-server (단일 인스턴스) │
|
|
├─────────────────────────────┬─────────────────────────────┤
|
|
│ 평면 A: MAM 워크로드 │ 평면 B: 홈랩/개인 프로젝트 │
|
|
├─────────────────────────────┼─────────────────────────────┤
|
|
프로토콜 │ MQTT 3.1.1 (포트 1883) │ NATS(4222), WebSocket(8080) │
|
|
클라이언트 │ paho-mqtt (코드 변경 0줄) │ nats-py, nats.js, CLI 등 자유 │
|
|
사용 기능 │ QoS 1, Retain, 와일드카드, TLS│ JetStream 리플레이, KV, Object│
|
|
설계 원칙 │ 초경량 동기 CLI 핫패스 보존 │ 고급 비동기 이벤트 스트리밍 │
|
|
공유 자원 │ └───── 단일 정적 바이너리 / JetStream 스토리지 / ACL ─────┘│
|
|
└───────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 5.2 교차 프로토콜 브리징 (Cross-Protocol Bridging)
|
|
- `nats-server`는 내부적으로 MQTT 토픽(`/`)을 NATS Subject(`.`)로 실시간 자동 변환합니다.
|
|
- MAM 에이전트가 MQTT 토픽 `python/mqtt/jobs/<job_id>/events`로 이벤트를 발행하면, 웹 브라우저나 타 프로젝트의 NATS 구독자는 NATS Subject `python.mqtt.jobs.<job_id>.events` 또는 `python.mqtt.jobs.*.events`로 즉시 실시간 수신할 수 있습니다.
|
|
- **실용적 이점**: MAM 소스 코드를 단 1줄도 수정하지 않고도 React/Vue 웹 대시보드나 터미널 모니터링 툴을 즉시 부착할 수 있습니다.
|
|
- **주의 사항**: 토픽 레벨 내에 마침표(`.`)가 포함되면 NATS 계층에서 토큰이 분리될 수 있으나, MAM의 `job_id`는 8자리 hex, 워크스페이스 지문은 12자리 hex이므로 안전합니다.
|
|
|
|
### 5.3 JetStream 이벤트 리플레이 (Event Replay)
|
|
- `python.mqtt.jobs.>` Subject를 구독하는 JetStream 스트림을 생성하면, 지난 작업의 이벤트 스트림 전체를 시점 지정(Time-based) 또는 시퀀스 지정(Sequence-based)으로 사후 리플레이할 수 있습니다.
|
|
- **주의 사항**: 이 기능은 옵트인(Opt-in)이며, MQTT QoS 1 처리를 위한 내부 시스템 스트림(`$MQTT_*`)과 별개로 관리됩니다. 디스크 용량 관리를 위해 `max_age`나 `max_bytes` 상한을 반드시 설정해야 합니다.
|
|
|
|
### 5.4 내장 Key-Value (KV) 및 Object Store
|
|
- 홈랩 및 개인 프로젝트에서 Redis나 MinIO 같은 별도 인프라를 띄우지 않고도 `nats-server` 내장 KV 및 Object Store를 즉시 사용할 수 있습니다.
|
|
- **금지 사항 (Non-Goal)**: MAM의 로컬 레지스트리(`.mam/jobs/*.json`)를 JetStream KV로 대체해서는 안 됩니다 (`wait_for_job`의 fcntl 및 파일시스템 폴링 계약 유지).
|
|
|
|
### 5.5 멀티테넌트 계정 분리 및 보안
|
|
- 단일 서버 내에서 `MAM` 전용 계정과 `HOME` 개인 계정을 분리하여 리소스 쿼터와 권한을 완벽히 격리할 수 있습니다.
|
|
- **권고 배치**: MAM과 이를 관측하는 대시보드는 동일한 계정(`MAM`)에 배치하고, 무관한 홈랩 서비스는 별도 계정(`HOME`)에 배치합니다.
|
|
|
|
---
|
|
|
|
## 6. MAM 클라이언트 연동 설정 (`.mam.env`)
|
|
|
|
개인 서버 브로커가 구동되면, MAM 저장소 루트의 [`.mam.env`](file:///.mam.env) 파일에 개인 서버 주소를 등록합니다.
|
|
|
|
> [!NOTE]
|
|
> MAM 코드(`mqtt_common.py`)는 `MQTT_*` 접두사의 환경변수를 읽습니다. 이전 비공식 문서의 `MAM_MQTT_*` 변수는 무효하므로 반드시 아래의 표준 변수명을 사용해야 합니다.
|
|
|
|
```bash
|
|
# ==============================================================================
|
|
# MAM Private MQTT Broker Configuration (.mam.env)
|
|
# ==============================================================================
|
|
|
|
# 개인 서버 IP 또는 도메인
|
|
MQTT_BROKER="192.168.1.100" # 예: 10.0.0.5, mqtt.my-domain.com 등
|
|
|
|
# MQTT 기본 포트 (평문 TCP: 1883, TLS 암호화: 8883)
|
|
MQTT_PORT=1883
|
|
|
|
# TLS 암호화 활성화 여부 (0: 평문 TCP, 1: TLS 암호화)
|
|
MQTT_TLS=0
|
|
|
|
# 인증 설정 (익명 브로커는 주석 처리 또는 빈 문자열 유지)
|
|
# MQTT_USERNAME=my_agent_user
|
|
# MQTT_PASSWORD=my_secure_password
|
|
|
|
# TLS 인증서 경로 (MQTT_TLS=1 설정 시 사용)
|
|
# MQTT_CA_CERTS=/path/to/ca.crt
|
|
# MQTT_CERTFILE=/path/to/client.crt
|
|
# MQTT_KEYFILE=/path/to/client.key
|
|
```
|
|
|
|
*참고: OS 환경변수에 동일한 이름이 이미 `export`되어 있는 경우 OS 환경변수가 `.mam.env` 파일 설정보다 우선합니다.*
|
|
|
|
---
|
|
|
|
## 7. 연동 및 동작 검증 테스트 (4-Step Verification)
|
|
|
|
개인 서버 브로커와의 연동 상태를 정확하게 검증하는 4단계 절차입니다.
|
|
|
|
### Step 1. 브로커 리스너 및 JetStream 상태 확인
|
|
```bash
|
|
# MQTT 리스너 활성화 확인
|
|
curl -s http://192.168.1.100:8222/varz | grep -i mqtt
|
|
|
|
# JetStream 엔진 정상 구동 확인
|
|
curl -s http://192.168.1.100:8222/jsz
|
|
```
|
|
|
|
### Step 2. 임시 잡 등록 및 연결 검증 이벤트 발행
|
|
`publish_event.py`는 레지스트리에 등록된 잡에 대해서만 발행을 수행하므로, 임시 잡을 등록하고 발행한 후 완료 처리합니다.
|
|
|
|
```bash
|
|
# 1) 임시 잡 등록 (자동 채번된 JID 캡처)
|
|
JID=$(.venv/bin/python .agents/skills/multi-agent-mux-delegate-job/scripts/registry.py \
|
|
--registry-dir .mam/jobs \
|
|
register \
|
|
--prompt "Private broker connectivity test" \
|
|
--agent-session "herdr:test")
|
|
echo "registered test job: $JID"
|
|
|
|
# 2) 이벤트 발행 (상세 로그 출력 및 rc=0 단언)
|
|
.venv/bin/python .agents/skills/multi-agent-mux-delegate-job/scripts/publish_event.py \
|
|
--registry-dir .mam/jobs \
|
|
--job "$JID" \
|
|
--event progress \
|
|
--detail "Private broker connection verified" -v
|
|
|
|
# 3) 테스트 잡 종결 처리 (미종결 시 --wait-any 유령 잡 잔존 방지)
|
|
.venv/bin/python .agents/skills/multi-agent-mux-delegate-job/scripts/registry.py \
|
|
--registry-dir .mam/jobs status --job "$JID" --set completed
|
|
```
|
|
|
|
### Step 3. 접속 대상 브로커 IP 단언
|
|
Step 2의 `-v` 출력 로그 또는 `.mam/delegate_job_logs/$JID/events.ndjson` 파일에서 실제 접속 호스트가 개인 브로커 IP로 나타나고 `broker.hivemq.com`이 포함되지 않았는지 확인합니다.
|
|
|
|
### Step 4. 단위 회귀 테스트 검증
|
|
```bash
|
|
.venv/bin/python -m pytest tests/ -q
|
|
```
|
|
*참고: MAM의 기본 단위/컴포넌트 테스트 스위트는 모의(Mock) 객체를 사용하므로 브로커 연결 여부와 무관하게 100% 통과합니다. 실제 네트워크 연동 검증은 Step 1~3이 담당합니다.*
|
|
|
|
---
|
|
|
|
## 8. 권장 실행 순서
|
|
|
|
```
|
|
[Phase 1: 내결함성 확보] ──> [Phase 2: 개인 브로커 가동] ──> [Phase 3: A-2 보안 완전 종결]
|
|
Track 0 (B-14, B-15) nats-server (nats.conf) 지문 토픽 및 인증 토큰 발급
|
|
로컬 디스크 폴백 패치 .mam.env 환경변수 연동 외부 간섭 100% 차단
|
|
```
|
|
|
|
1. **Phase 1 (Track 0 선행 패치)**: `publish_event.py`와 `job_subscriber.py`의 로컬 디스크 폴백(`B-14`, `B-15`)을 먼저 적용하여 브로커 다운 시에도 루프가 멈추지 않는 방탄 구조를 확립합니다.
|
|
2. **Phase 2 (개인 브로커 가동)**: 개인 서버에 `nats-server -c nats.conf`를 구동하고 `.mam.env`에 `MQTT_BROKER`를 연결합니다.
|
|
3. **Phase 3 (A-2 보안 완전 종결)**: 워크스페이스 지문 토픽(`mam/<sha256[:12]>/jobs/...`) 및 무조건 토큰 발급을 적용하여 공개 브로커 위험을 완전히 영구 폐기합니다.
|