--- title: 내부 운영 에이전트 (Ops Agents) updated: 2026-04-10 tags: [agent, ops, claude-code] --- kappa가 혼자 쓰는 **내부 인프라·운영 자동화 Claude Code 에이전트** 집합. 고객 대상 OpenClaw 에이전트(jp1 `agents` 프로젝트, anvil/stamp/flux 등)와는 완전히 분리된 영역. 외부 클라우드 BM 프로비저닝(jp1 `infra-tool` 의 Tofu API)과도 무관. **명명 규칙**: 북유럽 신화 (Heimdall = 파수꾼, Syn = 문지기, …). OpenClaw 쪽은 대장간/중세 기능 네이밍(anvil/forge/smelt/stamp/…)이라 자연스럽게 분리됨. ## 에이전트 목록 | 이름 | 호스트 | Incus 프로젝트 | IP | 역할 | 비고 | |------|--------|---------------|-----|------|------| | **[[heimdall]]** | kr1 | `ops` | 10.100.3.108 | 인프라 전반 (K3s, Incus, Longhorn, 스토리지, 네트워크, 일반 서비스) | tofu 관리 (`kaffa/ops-agents-tofu/heimdall`) | | **[[syn]]** | hp2 | `ops` | 10.100.2.173 | 엣지 레이어 전담 (BunnyCDN, SafeLine WAF, APISIX, Cloudflare 엣지) | tofu 관리 (`kaffa/ops-agents-tofu/syn`) | | **[[skuld]]** | hp2 | `ops` | 10.100.2.45 | 플래너 (작업 분해, 우선순위, 일정 조율, 의사결정 지원) | tofu 관리 (`kaffa/ops-agents-tofu/skuld`) | ## 공통 원칙 ### 통신 프로토콜 (ssh + tmux send-keys 체인) 모든 내부 에이전트는 **한 줄 태그 포맷**으로 서로와 kappa에 신호 전송: | 태그 | 의미 | |------|------| | `[SYN DONE] ...` | 작업 완료 | | `[SYN FAIL] ...` | 실패 (원인 한 줄) | | `[SYN ASK] ...` | 결정/입력 필요 | | `[SYN PROGRESS] ...` | 중간 이정표 (드물게) | | `[SKULD DONE] ...` | 작업 완료 | | `[SKULD FAIL] ...` | 실패 (원인 한 줄) | | `[SKULD ASK] ...` | 결정/입력 필요 | | `[SYN→HEIMDALL] ...` / `[HEIMDALL→SYN] ...` | 피어 투 피어 | | `[SKULD→HEIMDALL] ...` / `[SKULD→SYN] ...` | Skuld 피어 통신 | | `[SYN CC] ...` / `[HEIMDALL CC] ...` / `[SKULD CC] ...` | 피어 대화를 kappa 에 CC | 핵심 규칙: **한 줄만**. 상세는 자기 tmux 창 또는 Outline `agent-qna` 문서에 남기고, 신호에는 포인터만. ### 통신 경로 - **kappa Mac → Syn**: `tmux send-keys -t 0:0 '...' Enter` (window 0 이 syn-login, ssh 체인 유지) - **kappa Mac → Heimdall**: `ssh kaffa@10.100.3.108 'tmux send-keys -t heimdall ...'` - **Syn → kappa Mac**: `ssh kaffa@100.106.245.27 '/opt/homebrew/bin/tmux send-keys -t 0:1 ...'` - **Syn → Heimdall**: `ssh kaffa@10.100.3.108 'tmux send-keys -t heimdall ...'` - **Heimdall → Syn**: `ssh kaffa@100.106.245.27 '/opt/homebrew/bin/tmux send-keys -t 0:0 ...'` (경유 필요) - **kappa Mac → Skuld**: `ssh kaffa@10.100.2.45 'tmux send-keys -t skuld ...'` - **Skuld → kappa Mac**: `ssh kaffa@100.106.245.27 '/opt/homebrew/bin/tmux send-keys -t 0:1 ...'` - **Skuld → Heimdall**: `ssh kaffa@10.100.3.108 'tmux send-keys -t heimdall ...'` - **Skuld → Syn**: `ssh kaffa@10.100.2.173 'tmux send-keys -t syn ...'` Syn, Skuld 컨테이너는 hp2 subnet routing 덕분에 **Tailscale 설치 없이** tailnet의 다른 노드(kappa Mac 100.106.245.27, heimdall 10.100.3.108 등)에 직접 reachable. 호스트의 tailscale0 interface가 container bridge 로 투명하게 열려있음 — 2026-04-09 실측 확인. heimdall(kr1)의 incusbr0 역시 동일하게 tailnet 노출됨. ### SSH identity 모든 ops 에이전트는 **공유 ed25519 키 "ops-agents@kaffa"** 를 사용 (Vault `secret/apps/ops-agents-ssh` · private_key / public_key). 이 키는: - `/home/kaffa/.ssh/id_ed25519` 에 각 컨테이너 cloud-init 시 자동 배치 (tofu `ssh_private_key` 변수 → `base64encode` → tftpl write_files) - `authorized_keys` 기본값에 포함되어 모든 에이전트가 피어로부터 inbound SSH 허용 - kappa Mac `~/.ssh/authorized_keys` 에도 추가되어 에이전트 → kappa CC 채널 지원 보안 tradeoff: 공유 키라서 1개 에이전트 compromise = 전체 ops-agents compromise. 이미 동일 blast radius (모두 kappa 사설 인프라, 상호 완전 신뢰) 라서 수용. 개별 키 관리 부담(신규 provisioning 마다 수동 pubkey 배포) 대비 운영 단순성이 우선. fingerprint: `SHA256:eBCIglGmK/FnDxJLqxT0CJvRGFEGaIKRWnZ3ZpTaugU` ## 역할 분담 원칙 **Heimdall 전담 (Syn 손대지 말 것)**: - K3s 클러스터 전반 (노드, Longhorn, 일반 워크로드) - Incus 호스트 관리 - 스토리지, 네트워크 (Tailscale/OpenWrt/HAProxy) - 일반 서비스 파드 (Outline/Gitea/Vault 본체 등) - Obsidian 정본 전체 쓰기 권한 **Syn 전담 (Heimdall 손대지 말 것)**: - BunnyCDN: 풀존, Shield/WAF, Rate Limit, MonthlyBandwidthLimit, 캐시 정책 (Edge Script 64811 `crowdsec-bouncer-middleware`는 2026-04-26 폐기) - SafeLine WAF: K3s `safeline` ns, Traefik middleware, 탐지 규칙, APISIX 통합 - APISIX: 모든 인스턴스 (K3s 서울 / osaka-gw / sandbox-tokyo / zlambda), 라우트, 플러그인, Admin API - Cloudflare 엣지 관련: Turnstile 위젯, Worker 라우트 (`cs-cf-worker-bouncer` / `cfb-manager`는 2026-04-26 폐기) - Obsidian 중 엣지 범위 파일 쓰기: `infra/network/apisix.md`, `infra/security/crowdsec-safeline.md` 의 엣지 섹션, `services/bunnycdn*.md` **공동/협업**: - CrowdSec 시나리오: Heimdall 소유, Syn은 APISIX/SafeLine용 시나리오 파라미터 튜닝 제안만 - 두 에이전트 모두 OpenMemory·Outline 읽기·쓰기 OK (MCP 경유). **Vault는 kappa 게이트웨이 ASK-only** (아래 섹션). ## Vault 접근 — kappa 게이트웨이 프로토콜 2026-04-09 도입. 에이전트는 Vault에 직접 접근할 수 없다. 중앙 집중 감사·블라스트 래디우스 제한이 목적. **구현**: - Heimdall `~/.mcp.json` · Syn `~/syn/.mcp.json` 에서 `vault` 항목 완전 제거 - 두 에이전트 모두 `mcp__vault__*` 도구를 물리적으로 호출 불가 - CLAUDE.md 에 규칙·포맷 명시 **프로토콜**: | 단계 | 행위자 | 포맷 | |------|-------|------| | 1 | 에이전트 | `[SYN ASK] vault: 필요 (용도: <한줄>)` → kappa tmux 0:1 에 주입 | | 2 | kappa | Vault 조회 (MCP `mcp__vault__read_secret`) | | 3a | kappa | `[KAPPA->SYN] = ` → 에이전트 tmux 에 주입 (성공) | | 3b | kappa | `[KAPPA->SYN] DENIED: <사유/정정힌트>` → 에이전트 tmux 에 주입 (거절) | | 4 | 에이전트 | 값 사용·즉시 폐기, 파일/OpenMemory/Obsidian 평문 저장 금지 | | 5 | 에이전트 | `[SYN DONE] vault ... 수신 확인, 미저장 폐기` CC | **규칙**: - ASK 한 번에 여러 키를 동시 요청 가능 (같은 작업 범위 내) - 예외 없음 — bunnycdn/openmemory/nocodb 등 다른 MCP는 그대로 동작 - 환경변수 잔존(`$VAULT_TOKEN`, `$VAULT_ADDR`) 신뢰 금지 - 검증: Syn + Heimdall 양쪽에서 e2e 테스트 완료 (DENIED 경로 포함) **이점**: - 기술적 격리 (에이전트가 규칙을 잊거나 혼동해도 Vault 도달 불가) - 중앙 감사 로그 (kappa 세션 대화 히스토리에 모든 ASK/응답 기록) - 동기 승인 (kappa 부재 시 자동 차단) **한계**: - kappa 세션 활성 상태 필요 (에이전트 자율 작업 중 kappa 부재 시 시크릿 요구 작업 blocking) - kappa 세션 대화 히스토리에 시크릿 평문 잔존 (`/clear` 또는 세션 종료 시 사라짐) - kappa 본인의 MCP 권한 남용 방지책은 별도 필요 (vault audit log on Vault 서버 사이드 병행 권장) ## 배포 관리 ### Heimdall - OpenTofu 관리: [`kaffa/ops-agents-tofu/heimdall`](https://gitea.inouter.com/kaffa/ops-agents-tofu) (2026-04-09 재생성) - 변경 플로우: 로컬 clone → `heimdall/` 에서 `tofu plan` → `tofu apply` - State: 로컬 `terraform.tfstate` (gitignore, 수동 백업) - Secrets (`terraform.tfvars`): Vault `secret/apps/gitea` 참조, 커밋 금지 ### Syn - OpenTofu 관리: [`kaffa/ops-agents-tofu`](https://gitea.inouter.com/kaffa/ops-agents-tofu) (2026-04-09 신규) - 변경 플로우: 로컬 clone → `syn/` 에서 `tofu plan` → `tofu apply` - State: 로컬 `terraform.tfstate` (gitignore, 수동 백업). 향후 R2 s3 backend 이주 검토. ### Skuld - OpenTofu 관리: [`kaffa/ops-agents-tofu/skuld`](https://gitea.inouter.com/kaffa/ops-agents-tofu) (2026-04-10 신규) - 변경 플로우: 로컬 clone → `skuld/` 에서 `tofu plan` → `tofu apply` - State: 로컬 `terraform.tfstate` (gitignore, 수동 백업) - Secrets (`terraform.tfvars`): Vault 참조, 커밋 금지 ### 런타임 설정 (heimdall · syn 공통) 두 에이전트 모두 동일한 워크스페이스 패턴: - 베이스 이미지: Debian 13 cloud (incus `images:debian/13/cloud`) - 유저: `kaffa` (uid 1000, NOPASSWD sudo) - 셸: `fish` - Claude Code 공식 인스톨러 (`curl -fsSL https://claude.ai/install.sh | bash`), npm 미사용 - `uv` / `uvx` (MCP 서버·Python 실행) - 리소스 limit: heimdall 8 GiB / 4 vCPU · syn 4 GiB / 4 vCPU · skuld 4 GiB / 4 vCPU #### 파일 레이아웃 (컨테이너 내부) | 경로 | 레벨 | 내용 | |---|---|---| | `/home/kaffa//CLAUDE.md` | workspace | 에이전트 persona / 범위 / 프로토콜 | | `/home/kaffa//.mcp.json` | workspace | 프로젝트 MCP 서버 목록 | | `/home/kaffa//{runbooks 하위}` | workspace | 레이어별 운영 런북 (syn: bunnycdn/cloudflare/safeline/apisix) | | `/home/kaffa/.claude/settings.json` | user-global | allowlist, enabledPlugins | | `/home/kaffa/.claude/.credentials.json` | user-global | Claude Code OAuth | | `/home/kaffa/.claude/plugins/` | user-global | 플러그인 marketplace | | `/home/kaffa/.claude.json` | user-global | 프로젝트 상태, trust 플래그 | | `/home/kaffa/obsidian/` | user-global | Obsidian vault clone (별도 git) | | `/home/kaffa/.ssh/id_ed25519` | user-global | 공유 `ops-agents@kaffa` 키 | #### systemd 서비스 | 에이전트 | 서비스 | WorkingDirectory | ExecStart | |---|---|---|---| | heimdall | `heimdall-tmux.service` | `/home/kaffa/heimdall` | `tmux new-session -d -s heimdall "claude"` | | syn | `syn-tmux.service` | `/home/kaffa/syn` | `tmux new-session -d -s syn "claude --dangerously-skip-permissions"` | | skuld | `skuld-tmux.service` | `/home/kaffa/skuld` | `tmux new-session -d -s skuld "claude --dangerously-skip-permissions"` | heimdall 은 `--dangerously-skip-permissions` 플래그를 **사용하지 않음** — `settings.json` allowlist 로 대체. syn, skuld 는 플래그 유지. #### tofu repo 워크스페이스 정본 변경 시 `kaffa/ops-agents-tofu` 하위 워크스페이스 디렉토리에서 작업 → commit/push: - `heimdall-workspace/{CLAUDE.md, mcp.json, runbooks/{k3s,incus,longhorn,network,patroni,storage}}` → `/home/kaffa/heimdall/{CLAUDE.md, .mcp.json, runbooks/}` - `syn-workspace/{CLAUDE.md, mcp.json, runbooks/{bunnycdn,cloudflare,safeline,apisix}}` → `/home/kaffa/syn/{CLAUDE.md, .mcp.json, <런북들 top-level>}` - `skuld-workspace/{CLAUDE.md, mcp.json}` → `/home/kaffa/skuld/{CLAUDE.md, .mcp.json}` **자동 전개**: cloud-init runcmd 가 부팅 시 ops-agents-tofu repo 를 `git clone --depth=1` 로 가져와 위 매핑대로 `/home/kaffa//` 에 복사 + clone 정리. 신규 컨테이너 provisioning 시 manual scp 단계 0. 라이브 컨테이너에 워크스페이스 변경 반영 시: - 직접 수정 + repo 에 commit/push (정합성) - 또는 repo 변경 후 `scp` 로 sync #### 시크릿 환경변수 (heimdall) heimdall 은 `.mcp.json` 안에 토큰을 평문으로 두지 않는다. 패턴: 1. Vault `secret/apps/` 에 토큰 저장 2. tofu 변수 `_mcp_token` (sensitive) 에 주입 (terraform.tfvars 는 gitignore) 3. cloud-init 이 `/etc/heimdall/env` (root:kaffa 0640) 에 `KEY=value` 형식으로 작성 4. systemd `heimdall-tmux.service` 가 `EnvironmentFile=/etc/heimdall/env` 로 로드 5. `.mcp.json` 의 stdio 서버는 `command: "sh", args: ["-c", "exec npx ... --header \"X: $KEY\""]` 로 expansion 현재 적용 대상: - `NOCODB_MCP_TOKEN` (Vault `secret/apps/nocodb.mcp_token`) - `NOCODB_MCP_URL` (Vault `secret/apps/nocodb.mcp_url`) syn 은 현재 토큰 필요한 MCP 가 없어 env file 미사용 (필요 시 동일 패턴 적용 가능). - systemd `syn-tmux.service` (enabled, 첫 로그인 후 수동 start) ## 향후 확장 후보 내부 에이전트를 더 찍어낸다면 (Tofu 템플릿화 완료 후): | 이름 (북유럽) | 역할 후보 | |---|---| | **Mimir** | 지식·메모리 관리 (Obsidian 정합성, OpenMemory 큐레이션) | | **Forseti** | 분쟁 조정 — CI/CD, PR 리뷰, 코드 품질 | | **Bragi** | 프론트엔드·UX·카피 | | **Eir** | 헬스체크·복구·장애 대응 | | **Vör** | 관찰성 (VictoriaMetrics/Logs/Grafana) | 이름 예약만. 실제 생성은 필요 시점에. ## 참조 - [[infra-hosts]] — 전체 인프라 토폴로지 - [[../openclaw/openclaw-agents|openclaw-agents]] — 분리된 OpenClaw 시스템 (고객용) - [`kaffa/ops-agents-tofu`](https://gitea.inouter.com/kaffa/ops-agents-tofu) — Tofu IaC 정본