GitNexus([email protected], 레포를 그래프로 인덱싱해서 AI 에이전트가 MCP로 질의하게 해주는 npm 도구)를 tools 네임스페이스에 상시 서비스로 띄운 기록. 설계 논의부터 디스크 장애·VM 리사이즈까지, 실제로 겪은 삽질을 순서대로 남긴다.
c4i(C4I 앱)도 monitoring(관측 도구)도 성격이 안 맞아서 새로 tools 생성.docs/Public-Access-via-Tailscale-Funnel.md를 다시 보면, 이 클러스터의 Traefik Ingress는 전부 tailscale funnel 80을 통해 공인 인터넷에 통째로 노출된다. 즉 Ingress를 쓰면 "내부 전용"이 아니라 "전 세계 공개"가 되는 구조다. 진짜 "내부 전용(VPN 안에서만)" 패턴은 Headlamp/Grafana/Prometheus가 쓰는 NodePort였다 — 이게 GitNexus에도 맞는 선택.k3s-master는 CPU 2코어/메모리 여유가 더 많아서, 인덱싱 시 메모리를 꽤 쓰는 GitNexus를 의도적으로 컨트롤 플레인에 배치(평소 "컨트롤 플레인은 피한다"는 컨벤션을 깸).GitNexus 저장소의 Dockerfile.cli를 그대로 쓰려고 했는데, 주석을 읽어보니 의도적으로 web UI 번들을 뺀 API 전용 이미지였다(Render의 분리형 아키텍처용 — API 서버 + 별도 web 프록시 컨테이너). 반면 npm 패키지 자체(registry.npmjs.org/gitnexus/-/gitnexus-1.6.12.tgz)를 까보면 package/web/에 115개 파일(빌드된 UI 번들)이 그대로 들어있다는 걸 확인했다 — 즉 npm install -g [email protected]만으로 웹 UI까지 다 포함된다. 처음 요청받은 대로 단순한 Dockerfile을 직접 작성했다(node:22-bookworm-slim + 빌드 도구 + 전역 설치).
serve는 기본적으로 인증이 없다실측으로 확인한 중요한 사실 두 가지:
① GITNEXUS_PUBLIC_ORIGIN을 설정하면 서버가 시작 자체를 거부한다. 로컬에서 직접 컨테이너를 돌려 확인:
Failed to start GitNexus server:
GITNEXUS_PUBLIC_ORIGIN is set (...), but 'gitnexus serve' has no
authentication yet. ... reach the server through a proxy that
authenticates for it.
공식 README에도 같은 내용이 있었지만, 문서를 그대로 믿지 않고 실제로 컨테이너를 띄워서 재현했다. 그래서 이 값은 아예 안 넣는다.
② 결과적으로 쓰기 라우트(POST /api/analyze, DELETE /api/repo)는 loopback 오리진만 허용. 와일드카드 바인드(--host 0.0.0.0)에서 서버 자체 로그가 이렇게 경고한다:
[gitnexus serve] Bound to a wildcard address (0.0.0.0); browser write
routes accept only loopback origins (localhost/127.0.0.1/[::1]).
GITNEXUS_MCP_AUTH_TOKEN은 /api/mcp 라우트만 보호하는 별개 메커니즘이고(로그로 "Bearer authentication enabled for serve /api/mcp" 확인), 이 쓰기 제약과는 무관하다.
추가로 나중에 밝혀진 것(10번 항목): 이 단계에서는 "읽기는 되고 쓰기만 막힌다"고 판단했는데, 실제로는 번들 UI가 페이지 로드 시점에 읽기/쓰기 구분 없이 자기 자신의 localhost만 확인하도록 돼 있어서 NodePort로는 읽기 열람조차 안 됐다. 초기 판단이 틀렸던 부분 — 10번 항목에 정정 기록.
이 제약을 발견하고 실제로 나온 질문. 답은: GitNexus는 애초에 웹 UI가 메인 용도가 아니다 — README 자체가 "CLI + MCP(권장) vs 웹 UI(데모/일회성 탐색용)"로 구분해놓았다. 그래서:
auto-sync**는 파드 안에서 도는 CLI 백그라운드 작업이라 역시 무관