Files
multi-agent-mux/PRIVATE_SERVER.md
T

16 KiB

🔒 MAM 개인 전용 브로커(Private Broker) 구축 및 연동 가이드 (PRIVATE_SERVER.md)

  • 작성일: 2026-08-20 (Rev.2)
  • 문서 목적: MAM(Multi-Agent Mux)의 공개 브로커 의존성 및 보안 결함(A-2)을 해소하기 위한 개인 전용 브로커(NATS / Mosquitto) 구축, 운영, 다능성 활용 및 MAM 클라이언트 연동 가이드.
  • 연계 문서: NATS_REPORT.md, implementation_plan.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.shwait_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 })
• JetStream 엔진 내장 (이벤트 영속화 및 복구)
• NKey/JWT 기반 계정 및 Subject별 세분화된 ACL
• WebSocket 및 NATS 네이티브 프로토콜 동시 서빙
• 가장 널리 쓰이는 표준 경량 MQTT 브로커
• 낮은 메모리 점유율 (~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)

# 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 실행:

# 호스트에 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):

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)를 기본 스토리지로 사용합니다.

# 설정 및 데이터 디렉터리 생성 (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 실행:

docker run -d \
  --name mam-mosquitto \
  --restart unless-stopped \
  -p 1883:1883 \
  -v ./mosquitto.conf:/mosquitto/config/mosquitto.conf \
  eclipse-mosquitto:latest

기본 mosquitto.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_agemax_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 파일에 개인 서버 주소를 등록합니다.

Note

MAM 코드(mqtt_common.py)는 MQTT_* 접두사의 환경변수를 읽습니다. 이전 비공식 문서의 MAM_MQTT_* 변수는 무효하므로 반드시 아래의 표준 변수명을 사용해야 합니다.

# ==============================================================================
# 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 상태 확인

# 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는 레지스트리에 등록된 잡에 대해서만 발행을 수행하므로, 임시 잡을 등록하고 발행한 후 완료 처리합니다.

# 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. 단위 회귀 테스트 검증

.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.pyjob_subscriber.py의 로컬 디스크 폴백(B-14, B-15)을 먼저 적용하여 브로커 다운 시에도 루프가 멈추지 않는 방탄 구조를 확립합니다.
  2. Phase 2 (개인 브로커 가동): 개인 서버에 nats-server -c nats.conf를 구동하고 .mam.envMQTT_BROKER를 연결합니다.
  3. Phase 3 (A-2 보안 완전 종결): 워크스페이스 지문 토픽(mam/<sha256[:12]>/jobs/...) 및 무조건 토큰 발급을 적용하여 공개 브로커 위험을 완전히 영구 폐기합니다.