<!-- canonical: https://docs.axelabs.ai/services/matrix -->
<!-- source: content/services/matrix.mdx -->

---
title: Matrix
description: AXE Labs 인프라 모니터링 MCP. Rust + native MCP (no Python SDK). Docker 컨테이너 + HTTP endpoint + Cloudflare tunnel 의 health 를 1분 주기로 수집하여 상태 보드 + alert + Blueprint 에이전트 도구로 노출.
---

# Matrix

**한 줄 소개**: AXE Labs 내부 인프라 (Docker 컨테이너 · HTTP 엔드포인트 · Cloudflare tunnel · 디스크) 의 health 를 1 분 주기로 수집하여 (a) `axe.axelabs.ai/matrix` 상태 보드, (b) `acked_at IS NULL` alert 큐, (c) Blueprint 에이전트가 MCP 5 도구로 조회 — 의 3 가지 surface 로 노출하는 **첫 Rust 서비스** ([D-matrix-1](/ops/decisions)).

## 기술 스택

| 항목 | 값 |
|---|---|
| 언어 | Rust 2024 edition (toolchain `rust:1.95-slim`) |
| HTTP framework | axum 0.8 + tower-http (CORS) |
| Async runtime | tokio 1 (full) |
| DB | PostgreSQL 16 (alpine, ICU `ko-KR`) + sqlx 0.8 (`runtime-tokio` + `tls-rustls`) |
| Docker API | bollard 0.18 (container health 조회) |
| HTTP client | reqwest 0.12 (rustls, 5s timeout) — endpoint probe |
| 인증 | jsonwebtoken 9 (HS256 JWT, `MATRIX_JWT_SECRET`) — **현재 router 미부착, [known-gaps](/ops/known-gaps#matrix-mcp-auth-not-enforced) 참조** |
| 스케줄링 | tokio-cron-scheduler 0.13 + tokio sleep loop |
| 로깅 | tracing + tracing-subscriber (`env-filter` + json formatter) |
| Migration | 코드 인-라인 (db.rs `migrate()`, `CREATE TABLE IF NOT EXISTS`) |

소스: `/Users/axe/matrix/src/` (~600 LOC, 6 modules: api · auth · collector · config · db · mcp)

## 포트

| 포트 | 용도 |
|---|---|
| 3901 | PostgreSQL 16 (`matrix-postgres`) |
| 3910 | MCP HTTP blue (`matrix-mcp-blue`, alias `matrix-mcp` active) |
| 3911 | MCP HTTP green (`matrix-mcp-green`, passive) |
| 3912 | `axe-matrix-proxy` (Caddy blue/green selector, 127.0.0.1 bind only) |

cloudflared origin → 외부 `https://axe.axelabs.ai/matrix` + `https://axelabs.ai/matrix` (둘 다 같은 path 라우팅). `matrix.axelabs.ai` 서브도메인은 **미등록** — DNS resolve 실패. 정식 경로는 `axe.axelabs.ai/matrix`.

## 3 가지 surface

1. **MCP** (`POST /matrix/mcp`) — JSON-RPC 2.0 over HTTP, 5 tools. Blueprint / Claude 에이전트가 도구로 호출
2. **REST** (`GET /matrix`, `/matrix/api/status`, `/matrix/api/alerts`) — 상태 보드 (인증 없음, 의도된 공개)
3. **Health probe** (`GET /health`, `/health/ready`) — Docker healthcheck + 외부 모니터링

> `/matrix/mcp` 는 코드상 `auth.rs::auth_middleware` 정의되어 있으나 `api.rs::router()` 에 부착되지 않음. 현재 anonymous 호출 가능. [B-matrix-mcp-auth-enforce](/ops/backlog) 참조.

## MCP Tools (5 개)

`src/mcp.rs:91-150` 의 `handle_tools_list()` 기준. `protocolVersion: 2025-03-26`, `serverInfo.name: "matrix"`, `serverInfo.version: env!("CARGO_PKG_VERSION")` (현재 `0.1.0`).

| Tool | 입력 | 설명 |
|---|---|---|
| `get_status` | — | 가장 최근 collector cycle 의 전체 health 보고 (in-memory `last_check`). 한 번도 안 돈 직후엔 `"no check results yet"` |
| `get_service_history` | `service` (string, required), `hours` (int, default 24) | 특정 서비스의 시계열 — `check_results.report` JSONB 에서 해당 `name` 의 status/detail 추출 (최근 100 rows) |
| `list_alerts` | `include_acked` (bool, default false) | unacked alert 50 개 (or 전체 50). UUID + service + severity + message + created_at + acked_at/by |
| `acknowledge_alert` | `alert_id` (UUID, required), `acked_by` (string, default `"operator"`) | `UPDATE alerts SET acked_at = now(), acked_by = $1 WHERE id = $2 AND acked_at IS NULL` — 이미 ack 된 항목은 no-op |
| `get_uptime_report` | `days` (int, default 7) | 기간 내 모든 cycle 의 per-service pass/total 카운트 + uptime% (소수점 2 자리) |

## REST endpoints

| 메서드 | 경로 | 인증 | 설명 |
|---|---|---|---|
| GET | `/health` | — | `{"status":"ok","service":"matrix"}` (DB 미점검) |
| GET | `/health/ready` | — | `{"status":"ok","db":"connected"}` 또는 `503 + {"db":"<err>"}`. Docker healthcheck 가 사용 |
| GET | `/matrix` | — | landing (상태 보드, 미구현 placeholder 가능성) |
| GET | `/matrix/api/status` | — | 최근 cycle 의 JSON 전체 — `get_status` MCP 와 동일 데이터 |
| GET | `/matrix/api/alerts` | — | unacked 20 개 alert |
| POST | `/matrix/mcp` | — (의도는 JWT) | MCP JSON-RPC. `initialize` / `tools/list` / `tools/call` 처리 |

## 데이터 모델

`src/db.rs:14-39` 의 인-라인 migration (`CREATE TABLE IF NOT EXISTS`). 3 테이블 + 2 인덱스.

| 테이블 | 컬럼 | 역할 |
|---|---|---|
| `check_results` | `id` UUID PK · `ts` TIMESTAMPTZ · `duration_ms` INT · `overall` TEXT (`pass`/`fail`) · `report` JSONB | collector cycle 의 전체 보고서 (모든 service check 결과 array 포함) |
| `services` | `name` PK · `svc_type` · `config` JSONB · `created_at` | (현재 미사용 — 향후 동적 등록용) |
| `alerts` | `id` UUID PK · `service` · `severity` (default `error`) · `message` · `created_at` · `acked_at` · `acked_by` | failure 1 회 발생 시 INSERT, 동일 서비스의 unacked alert 이 있으면 no-op (중복 방지) |

인덱스: `idx_check_results_ts` (DESC), `idx_alerts_unacked` (partial WHERE `acked_at IS NULL`).

## Collector — 무엇을 보는가

`src/collector.rs::run_loop()` — `MATRIX_CHECK_INTERVAL` 초마다 (default 30, [D-matrix-2](/ops/decisions) 에서 60→30), `run_check()` 가 5 종류 점검 병합:

| 종류 | 함수 | 대상 | 판정 |
|---|---|---|---|
| Docker container | `check_docker` | bollard 로 `all=false` (실행 중) 컨테이너 list. `status` 문자열에 `healthy` / `unhealthy` 포함 검사. `matrix-` 접두는 자가 모니터링 회피로 skip | unhealthy = fail, healthy or running = pass |
| HTTP endpoint | `check_http` | 2 개 — `blueprint-http` (host:3100/api/health) + `frame-mcp-http` (host:3710/health, alive-only). cortex-fe/cortex-be 는 개발 중이라 제거 (commit `96cf758`, 재가동 시 재활성) | 상태코드 일치 = pass; frame 만 응답 있으면 pass |
| Cloudflare tunnel | `check_tunnels` | 3 개 — `tunnel-artemis`, `tunnel-blueprint`, `tunnel-mysrt` (`*.axellc.com`) | 200/301/302 = pass, 그 외 = warn, unreachable = fail |
| Disk | `check_disk` | `df -h /` 의 root 디스크 사용률 | ≥90% = warn |
| **WAN/인터넷** | `check_wan` | 3 프로브 동시(`tokio::join!`) — `wan-gateway` (공유기 ICMP), `wan-internet` (`MATRIX_WAN_TARGET` ICMP, RTT+손실%), `wan-dns` (`MATRIX_DNS_HOST` 해석 시간). [D-matrix-2](/ops/decisions) | unreachable = fail. **귀책 판별**: gateway ↑ + internet ↓ ⇒ ISP/WAN fault (warning 발생), gateway ↓ + internet ↓ ⇒ 댁내 링크/WiFi |

호스트 도달은 모두 `host.docker.internal` 경유. failure 시 `create_alert()` 가 동일 서비스의 unacked alert 가 없을 때만 INSERT (중복 방지). cycle 결과는 항상 `check_results` 에 INSERT.

> **WAN 프로브 (D-matrix-2)**: `wan-*` 결과는 기존 `check_results` JSONB 에 그대로 들어가 별도 스키마 변경 없이 `get_service_history` / `get_uptime_report` / 상태 보드 / alert 에 자동 노출. ICMP 는 컨테이너에 `cap_add: NET_RAW` + 런타임 이미지의 `iputils-ping` 으로 권한상승 없이 가능 (Docker Desktop NAT 통과 + 공유기/외부 도달 검증 2026-06-03). 게이트웨이 IP 는 노드별로 다르므로 `MATRIX_LAN_GATEWAY` env 로 주입.

> **함정 (2026-05-26 발견 → 같은 날 fix)**: `blueprint-http` URL 이 `:3110` (미 listening) 으로 25 분+ 연속 FAILED 누적. 정정: `:3100/api/health` (Blueprint Next.js dev). 동일 세션에서 `cortex-fe`/`cortex-be` 도 검증 — cortex backend (`:3210`) 는 **실제 down**, cortex frontend (`:3200`) 는 listening 하지만 root path 빈 응답 → 두 항목은 valid alert (cortex 측 점검 별도 필요). [B-matrix-collector-port-fix](/ops/backlog) ✅

## 환경 변수

`src/config.rs::Config::from_env()` 기준. `.env.local` (env_file) 으로 vault → 컨테이너 dump (D-ops-18 패턴).

```bash
MATRIX_PORT=3910                                            # default 3910
MATRIX_DATABASE_URL=postgres://matrix:<pw>@postgres:5432/matrix
MATRIX_DB_PASSWORD=                                          # compose 가 ${MATRIX_DB_PASSWORD:?required}
MATRIX_JWT_SECRET=                                          # default "dev-secret-change-me" — production 미설정 시 위험
MATRIX_CHECK_INTERVAL=30                                    # 초 (D-matrix-2: 60→30, 끊김 해상도 향상)
MATRIX_CUSTOMER_ID=axe                                       # multi-tenant 마커
MATRIX_DEPLOY_COLOR=blue|green                              # blue/green 식별
MATRIX_LAN_GATEWAY=192.168.55.1                            # wan-gateway 프로브 대상 (공유기 IP, 노드별 상이)
MATRIX_WAN_TARGET=1.1.1.1                                   # wan-internet 프로브 대상 (공인 anycast)
MATRIX_DNS_HOST=google.com                                  # wan-dns 해석 테스트 호스트
```

> `MATRIX_LAN_GATEWAY` / `MATRIX_WAN_TARGET` / `MATRIX_DNS_HOST` 는 비밀이 아니라 노드별 설정값 — secrets 표/vault 등재 불필요 (default 존재). 단 `MATRIX_LAN_GATEWAY` 는 DHCP 로 바뀔 수 있어 노드 셋업 시 확인 ([D-matrix-2](/ops/decisions)).

[/architecture/secrets](/architecture/secrets) 의 service secrets 표 등재 (matrix `.env.local` = DB password + JWT secret + console API token 3 종).

## Blue/green 운영

- `matrix-mcp-blue` 와 `matrix-mcp-green` 가 동일 image (compose 의 `build: .`) 로 동시 실행. blue 만 `matrix-mcp` network alias 보유 → `axe-matrix-proxy` (Caddyfile 의 `reverse_proxy matrix-mcp:3910`) 가 항상 blue 로 forward.
- swap = compose 의 alias 를 green 으로 옮긴 후 `docker compose up -d --no-deps matrix-mcp-green` → 검증 → blue 도 업데이트. frame · hive 와 동일 패턴.
- **현재 상태** (2026-05-26): 두 컨테이너 동일 코드 (build 동시), 둘 다 healthy, 3 일 가동.

## 운영 노트 · 알려진 함정

- ~~**Git 미커밋**~~ ✅ — `f73492d` (initial) + `48a301b` (fix) 두 commit 으로 history 회복 (2026-05-26).
- ~~**Stale build (2026-05-23)**~~ ✅ — `matrix_info` 함수 미정의 (E0425) 였던 게 root cause. 같은 날 stub 추가 + 무중단 rebuild.
- ~~**JWT 미적용**~~ ✅ — `/matrix/mcp` 에 `middleware::from_fn(auth_middleware)` 부착 (commit `96cf758`). REST + health 는 anonymous 유지 (의도된 공개 status board). 검증: no-auth POST → 401 "missing bearer token", REST → 200. production 호출 client 0 (access log 0 + Blueprint repo 미참조) 확인 후 안전 부착.
- **자가 모니터링 회피**: collector 가 `matrix-` 접두 컨테이너를 skip. matrix-postgres 도 skip 됨 — 의도된 단순화, 그러나 DB 가 죽으면 `/health/ready` 자체가 503 떨어지므로 외부 healthcheck 로 보완.
- **39xx 범위 공유**: `axelabs` 회사 홈 (3900) 과 matrix (3901/3910/3911/3912) 가 같은 범위에서 공존. CLAUDE.md / [/ops/inventory#포트-할당](/ops/inventory#포트-할당) 에 명시.
- ~~**부수 발견 — cortex 측 valid alert**~~ ✅ resolved by removal — cortex 가 개발 중이라 collector endpoint list 에서 cortex-fe / cortex-be 제거 (commit `96cf758`, src/collector.rs:181). 재가동 시 다시 활성화. 누적 unacked alert 2 row (`acked_by='cortex-removed-from-collector-2026-05-26'`) ack 처리.

## 관련 문서

- [D-matrix-1](/ops/decisions) — Rust + native MCP 채택 결정
- [/ops/backlog](/ops/backlog) — `B-matrix-*` 시리즈 (deploy, blueprint connector, console retire, rebuild, git init, auth enforce, collector port fix)
- [/architecture/secrets](/architecture/secrets) — matrix `.env.local` 3 secrets
- [/architecture/artifacts](/architecture/artifacts) — `matrix.health@1.0` schema 노출
- [/services/blueprint](/services/blueprint) — matrix MCP 의 client
