
홈랩 Bash를 에이전트로 교체했습니다 — 그리고 DX(개발자 경험)가 이상할 정도로 좋아졌습니다
요약
Bash 스크립트 대신 멀티 에이전트 제어 평면을 구축하여 홈랩 인프라를 관리하는 아키텍처를 소개합니다. 오케스트레이터와 도메인별 에이전트를 분리하여 확장성과 개발자 경험(DX)을 극대화한 것이 핵심입니다.
핵심 포인트
- 시스템 경계당 하나의 에이전트와 하나의 프로토콜 적용
- 도메인 로직을 모르는 가벼운 오케스트레이터 설계
- UI는 하이퍼바이저가 아닌 제어 평면과만 통신
- 인프라 표준화를 통한 조합 가능한 환경 구축
홈랩(Homelabs)은 보통 Bash 스크립트 폴더, 아무도 업데이트하지 않는 위키, 그리고 재부팅하기 두려운 기계 한 대를 의미하곤 했습니다.
저는 다른 것을 원했습니다: 제품처럼 랩(lab)과 대화하고, 인프라(infra)는 지루할 정도로 단순하며 재현 가능하게 유지하며, "내가 집안의 프로덕션을 망가뜨렸나?"라는 질문이 주말을 통째로 날려버리는 상황 대신 복구 가능한 질문이 되도록 만드는 것이었습니다.
그래서 저는 Proxmox 위에 작은 멀티 에이전트 제어 평면(control plane)을 구축했습니다. 단순히 SSH에 붙어 있는 챗봇이 아니라, 오케스트레이터(orchestrator) 뒤에 있는 **별도의 에이전트 서비스(agent services)**이며, Cockpit보다는 Cursor에 더 가까운 느낌을 주는 UI를 갖추고 있습니다.
이것은 에이전트와 인프라, 그리고 이 작업을 할 가치가 있게 만든 개발자 경험(DX)에 대한 이야기입니다.
코드
모든 것은 @shivakadiri 아래의 별도 리포지토리(repos)에 존재하며, 패키지당 하나의 경계를 가집니다:
| 리포지토리 | 역할 |
|---|---|
| homelab-ui | 채팅 / 히스토리 / 대시보드 / 로그 UI |
| ... |
시스템의 형태
높은 수준(high level)에서 보면 다음과 같습니다:
UI → Orchestrator → Agent services
├─ UniFi (JSON-over-HTTP)
├─ Proxmox (A2A)
...
UI는 하이퍼바이저(hypervisors)나 스위치(switches)를 직접 호출하지 않습니다. UI는 **제어 평면(control plane)**과 대화합니다. 제어 평면은 의도(intent)를 적절한 에이전트로 라우팅합니다. 각 에이전트는 하나의 도메인과 하나의 프로토콜을 소유합니다.
이러한 분리는 LLM 브랜드보다 더 중요합니다.
| 에이전트 | 리포지토리 | 프로토콜 | 해당 선택의 이유 |
|---|---|---|---|
| UniFi | homelab-unifi | Langgraph 원격 에이전트 | 기존 REST API를 활용하고 싶음 |
| ... |
오케스트레이터(homelab-orchestrator)는 도메인 로직에 대해 의도적으로 무지하게 설계되었습니다. 이것은 라우터(router), 상태 집계기(health aggregator), 그리고 이벤트 버스(event bus)입니다. VLAN과 ZFS, 그리고 LXC 프리셋을 모두 알고 있는 갓 오브젝트(god-object)가 아닙니다. UI(homelab-ui)는 오직 해당 제어 평면하고만 통신합니다.
이 포스트에서 단 하나의 아키텍처 아이디어만 가져간다면 다음과 같습니다: 시스템 경계당 하나의 에이전트, 에이전트당 하나의 프로토콜, 그리고 이 세 가지 모두인 척하지 않는 하나의 오케스트레이터(Orchestrator).
인프라 우선: 실험실을 앱 플랫폼처럼 느끼게 만들기
기반 플랫폼이 잡동사니 서랍 같다면 에이전트는 무용지물입니다.
저는 게스트(Guest)들을 예측 가능한 IP와 이름을 가진 Proxmox LXC로 표준화했습니다. 이는 다른 모든 것들을 조합 가능(Composable)하게 만드는, 화려하지는 않지만 필수적인 작업입니다:
- Caddy — TLS 종단점 (TLS terminator,
*.klab, 로컬 CA) - AdGuard —
homepage.klab등을 실제로 작동하게 만드는 DNS - Homepage — 실험실의 인간용 지도
- Postgres + pgvector — 상태(State)가 필요한 앱들을 위한 공유 DB
- Ollama + LiteLLM — OpenAI 호환 게이트웨이 뒤에 있는 로컬 모델들
- Hoppscotch — 동일한 서비스들을 대상으로 하는 API 클라이언트
- Authentik — 보기 좋은 HTTPS 인터페이스를 위한 SSO / 포워드 인증 (Forward-auth)
- otel-lgtm — 무언가 잘못되었을 때를 대비한 Grafana의 올인원 OTel 백엔드
이 중 새로운 것은 없습니다. 핵심은 **제품의 형태 (Product shape)**입니다: HTTPS 호스트 이름, 사람이 클릭하는 곳에는 SSO, 기계가 호출하는 곳에는 Bearer 토큰, 그리고 사고 모델(Mental model)과 일치하는 DNS를 갖추는 것입니다.
구체적인 DX(개발자 경험)의 이점: 일단 litellm.klab과 ollama.klab이 존재하면, 에이전트 코드와 API 컬렉션은 더 이상 생(raw) 10.10.15.x 주소를 사용하지 않습니다. 나중에 컨테이너를 옮길 때 미래의 당신이 고마워할 것입니다.
비밀 정보(Secrets)는 .env 코스프레가 아니다
모든 에이전트는 기본적으로 드라이 런 (Dry-run) 모드로 동작합니다. 라이브 모드는 플러그인 가능한 저장소(제 경우 keyring을 통한 macOS Keychain)에서 비밀 정보를 가져옵니다. 테스트 시에는 인메모리(In-memory) 가짜 저장소를 사용합니다.
UI의 Plan/Agent 토글은 Cursor가 사용하는 것과 동일한 개념입니다: 제안(propose) 대 실행(do). 홈랩 운영자에게는 앱 개발자보다 이러한 근육 기억(muscle memory)이 훨씬 더 필요합니다. 실수를 용서해 주는 스테이징 VLAN(staging VLAN) 같은 건 없기 때문입니다.
노트북이 아닌 서비스로서의 에이전트 (Agents as services, not notebooks)
"에이전트"를 Jupyter notebook에 넣거나, 모든 곳에 SSH 접속을 시도하는 단일 FastAPI 파일에 담아두고 싶은 유혹이 생길 수 있습니다.
하지만 그것을 거부하십시오.
각 에이전트는 다음과 같은 기능을 가진 작고 지속적으로 실행되는 서비스(long-running service)여야 합니다:
GET /health/ 메타데이터 (metadata)- 채팅(chat) 또는
run엔트리포인트 (entrypoints) - 모니터링 훅 (monitor hooks)
- 필요한 경우 도메인 RPC (domain RPCs) (예: UniFi는 실시간 클라이언트 인벤토리를 노출하여 Proxmox가 LAN 상의
:8006호스트를 자동 검색 (auto-discover) 할 수 있게 합니다)
Proxmox 검색(discovery)은 공유된 모놀리스(monolith) 없이 에이전트 간 협업이 이루어지는 좋은 사례입니다:
- UniFi 에이전트가 연결된 클라이언트와 IP를 나열합니다 (실시간 모드).
- Proxmox 에이전트가 Proxmox API를 위한 후보군을 조사(probe)합니다.
- 오케스트레이터(Orchestrator)는 SNMP의 복잡한 문제에 직접 관여하지 않습니다.
이것이 바로 유용한 의미에서의 멀티 에이전트(multi-agent)입니다: 하나의 프롬프트 안에서 논쟁하는 페르소나들의 위원회가 아니라, 계약(contracts)을 가진 전문가들인 것입니다.
클라우드 종속(cloud lock-in)을 흉내 내지 않는 로컬 LLM
Ollama 앞에 LiteLLM을 배치하면 LAN 상에 OpenAI 형태의 엔드포인트(endpoint)를 구축할 수 있습니다. 에이전트는 품질이 필요할 때는 클라우드 모델을 사용하고, 오프라인 상태나 CI 친화적인 동작이 필요할 때는 로컬 또는 결정론적 스텁(deterministic stubs)으로 전환할 수 있습니다.
DX(개발자 경험)의 목표는 "Anthropic을 절대 호출하지 않는 것"이 아닙니다. 그것은 손(hands)을 다시 작성하지 않고도 뇌(brain)를 교체하는 것입니다. 즉, 도구, 비밀 정보(secrets), 드라이 런(dry-run), 그리고 프로토콜은 안정적으로 유지됩니다.
개발자 경험이 곧 제품이다
홈랩 프로젝트는 피드백 루프가 "재부팅하고 기도하기(reboot and pray)"가 될 때 사장됩니다.
저의 피드백 루프를 개선한 요소들은 다음과 같습니다:
1. 기본 인터페이스로서의 채팅
UI는 채팅(Chat) / 히스토리(History) / 대시보드(Dashboard) / 로그(Logs)로 구성됩니다. 라우팅은 자동으로 이루어지며, 질문할 때마다 "드롭다운에서 UniFi를 선택"해야 하는 비용(tax)이 발생하지 않습니다. 후속 질문은 session_id와 에이전트를 고정(pin)하므로, 인간 참여형(human-in-the-loop) 명확화 과정이 동일한 전문가와 계속 이어집니다.
2. 사후 처리가 아닌 조기 관측성 (Observability early, not after the fire)
2. 사후 처리가 아닌 조기 관측성 (Observability early, not after the fire)
grafana/otel-lgtm을 LXC에 배포하는 것이 ‘프로덕션 수준의 관측성’은 아닙니다. 하지만 에이전트의 도구 호출(tool call) 실패가 로그만으로는 설명할 수 없는 방식으로 발생했을 때 추적(traces)를 전송할 장소는 됩니다. 홈랩은 OTel 실습에 완벽한 환경입니다. 왜냐하면 폭발 반경(blast radius)이 회사 규정(SLO)이 아니라 당신의 저녁 시간이기 때문입니다.
3. API 컬렉션을 살아있는 문서로 활용하기 (API collections as living docs)
Ollama/LiteLLM용 Hoppscotch 컬렉션은 시간이 지나서 쓸모없어지는 README의 curl 블록보다 훨씬 좋습니다. https://…klab URL을 사용하여 환경(environments)을 가져오세요. 만약 UI는 HTTPS인데 http://10.x와 같은 주소를 유지하면, 브라우저가 알 수 없는 ‘네트워크 오류 / 원인 불명’ 혼합 콘텐츠 실패(mixed-content failures)를 보여줄 것입니다. 제가 어떻게 아는지 물어보세요.
4. 프로비저닝 스크립트를 에피소드 기억처럼 활용하기 (Provisioning scripts as episodic memory)
LXC 프리셋(caddy, authentik, otel-lgtm 등)은 Proxmox 에이전트 옆에 존재합니다. 제가 “homelab02에 X를 추가해 줘”라고 말할 때의 경로는 다음과 같습니다: 알려진 VMID/IP/리소스 → 필요시 Docker 네스팅(nest Docker) → DNS 연결 → Caddy → Homepage 카드. 이 에이전트가 오퍼레이터이고, 스크립트는 에이전트(또는 나 자신)가 재실행할 수 있는 런북(runbook)입니다.
5. 의도적인 프로토콜 다양성 (Protocol diversity on purpose)
하나의 제어 평면(control plane)에서 HTTP + A2A + MCP를 사용하는 것은 복잡하게 들릴 수 있습니다. 하지만 실제로는 깔끔한 클라이언트 어댑터(client adapters)를 강제하고, 네 번째 사설 RPC를 발명하는 것을 막아줍니다. 오케스트레이터의 README는 전설이 아니라 호환성 차트가 됩니다.
과거의 나에게 해주고 싶은 말 (What I would tell past me)
- 모델 선택부터 시작하지 말고, 드라이런(dry-run)과 Keychain부터 시작하세요.
- 에이전트를 구축하기 전에 모든 사용자 대상 서비스에는 호스트 이름(hostname)과 TLS를 부여하세요.
- SSO는 이미 Bearer 토큰을 가진 머신 API 앞이 아니라, UI 앞에 배치하세요 (그렇지 않으면 JSON 대신 Authentik 리디렉트 주소를 디버깅하게 될 것입니다).
- 모든 Ansible 모듈을 붙인 거대한 메가 에이전트보다 작은 에이전트 서비스를 선호하세요.
- DX(개발자 경험)를 인프라로 취급하세요: 계획/에이전트 모드, 이벤트 웹소켓, 상태 확인 엔드포인트(health endpoints), 가져올 수 있는 API 컬렉션 등을 고려해야 합니다.
왜 애쓰는가? (Why bother?)
홈랩은 우리 중 많은 이들이 여전히 스택 전체(full stack): 네트워크 → 하이퍼바이저(hypervisor) → ID(identity) → 모델 → UI를 소유할 수 있는 마지막 장소이기 때문입니다.
제품 엔지니어링(product engineering) 분야에서 에이전트(Agents)가 주목받는 순간을 맞이하고 있습니다. 홈랩(homelab)은 동일한 질문들을 던져볼 수 있는 냉혹한 놀이터입니다:
- 무엇이 안전한 도구인가?
- 세션(session)이란 무엇인가?
- 누가 상태(state)를 변경(mutate)할 수 있는 권한을 갖는가?
- 공유된 두뇌(shared brain) 없이 전문가(specialists)들이 어떻게 협업하는가?
만약 당신의 에이전트가 LXC를 프로비저닝(provision)하지 못하고, dry-run(드라이 런)을 존중하지 않으며, 왜 DNS가 여전히 이전 장비(old box)를 가리키고 있는지 설명하지 못한다면 — 그들은 당신의 본업(day job)을 수행할 준비도 되지 않은 것입니다.
저는 여전히 반복(iteration)하고 있습니다 (언제나 그렇듯). 하지만 실험실은 마침내 컨테이너들의 박물관이라기보다, **확고한 주관을 가진 개발자 플랫폼(developer platform with opinions)**처럼 느껴지기 시작했습니다 — 그리고 그것과 대화하는 것은, 부끄럽게도, 즐겁습니다.
Links
- UI: github.com/shivakadiri/homelab-ui
- Orchestrator: github.com/shivakadiri/homelab-orchestrator
- UniFi agent: github.com/shivakadiri/homelab-unifi
- Proxmox agent: github.com/shivakadiri/homelab-proxmox
- TrueNAS agent: github.com/shivakadiri/homelab-truenas
End State
만약 당신이 이런 형태의 무언가를 구축한다면, 당신은 어떤 프로토콜 분할(protocol split)을 선택했는지 — 그리고 당신의 첫 번째 "계획 vs 에이전트(Plan vs Agent)" 아찔했던 순간(near-miss)은 어떠했는지 꼭 듣고 싶습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기