Skip to content

마이크로서비스 운영기: 5개 서비스를 한 사람이 관리하는 법

역사적 기록

당시 운영 단위는 Gateway, Router, Scanner, Memory, Runtime이었다. 현재 공개 포트폴리오의 Core는 Gateway, Router, Runtime, Contracts다. 현재 맵은 Architecture다.

GoVail은 초기 구상 단계에서 Gateway 하나로 시작했지만, 요구사항이 구체화되면서 현재 5개의 마이크로서비스(Gateway, Router, Scanner, Memory, Runtime)를 가진 시스템으로 성장했습니다.

흥미로운 점은 이 모든 컴포넌트의 개발과 운영을 한 명의 개발자가 주도하고 있다는 것입니다. "어떻게 1인 개발자가 이 많은 서비스를 감당할 수 있을까?" 이 질문에 답하기 위해, 제가 시스템을 유지 보수하며 얻은 몇 가지 운영 레슨을 공유하고자 합니다.

Docker Compose 기반 로컬 개발 환경의 명암

마이크로서비스 아키텍처(MSA)를 선택하면 자연스럽게 개발 환경 세팅의 복잡도가 증가합니다. 저는 쿠버네티스(Kubernetes)를 로컬에 올리는 대신, 훨씬 가볍고 직관적인 Docker Compose를 표준 개발 환경으로 채택했습니다.

장점: 모든 것을 하나의 명령어로

docker-compose up -d 명령어 하나만 실행하면, Gateway부터 SurrealDB, Temporal 서버, 그리고 관측 스택(Prometheus, Grafana 등)까지 모두 한 번에 기동됩니다. 환경 변수와 볼륨 마운트가 명확히 정의되어 있어, 새로운 contributor가 프로젝트에 합류할 때도 "이거 실행해 보세요" 한 마디면 충분했습니다.

단점: 늘어나는 리소스와 속도

서비스가 추가될 때마다 노트북의 RAM 점유율이 서서히 올라갑니다. 또한, 개발 도중 코드를 수정하면 이미지를 다시 빌드해야 하므로, 빠른 이터레이션(iteration) 사이클을 유지하기 위해 특정 서비스만 로컬 머신에서 직접 실행(Host-run)하고 나머지는 Docker 네트워크에 연결하는 하이브리드 방식을 병행하게 되었습니다.

서비스 간 의존성 관리와 버전 호환성 전략

여러 서비스가 얽혀있다 보니 한쪽의 API 변경이 다른 쪽에 장애를 유발할 위험이 항상 존재합니다. 특히 Gateway와 Router, Runtime 간의 통신이 빈번하기 때문에 버전 호환성을 맞추는 것이 핵심 과제였습니다.

저는 이 문제를 해결하기 위해 Schema-First Contracts 방식을 도입했습니다. 모든 서비스 간의 통신 규약과 메시지 포맷은 JSON Schema로 정의하여 중앙 저장소인 govail-contracts에서 관리합니다.

json
// govail-contracts/schemas/audit-event.schema.json 예시
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "AuditEvent",
  "type": "object",
  "properties": {
    "request_id": { "type": "string" },
    "status": { "enum": ["allowed", "blocked", "pending"] }
  },
  "required": ["request_id", "status"]
}

각 서비스(Rust, Python 등)는 이 Schema를 기반으로 코드를 자동 생성하거나 런타임에 유효성을 검사합니다. 덕분에 한 서비스에서 무심코 필드명을 바꾸더라도, 컴파일 타임 혹은 CI 과정에서 계약 위반을 즉각 발견할 수 있었습니다.

모노레포 vs 멀티레포 결정의 실제 트레이드오프

GoVail 프로젝트는 처음부터 멀티레포(Multi-repo) 구조를 채택했습니다. govail-gateway, govail-router 등 서비스별로 리포지토리를 분리한 것입니다.

트레이드오프 회고

  • 자율성: 각 서비스별로 언어(Rust vs Python)와 빌드 파이프라인이 달랐기 때문에, 레포를 분리하여 CI/CD 설정을 깔끔하게 유지할 수 있었습니다.
  • 복잡성: 여러 레포에 걸친 기능을 동시에 업데이트해야 할 때(예: 새로운 인증 방식 도입), 여러 개의 PR을 띄우고 순서를 맞춰 머지해야 하는 번거로움이 있었습니다.

이러한 복잡성을 완화해준 것이 바로 앞서 언급한 govail-contracts입니다. 공통 인터페이스가 단일 레포에서 꽉 잡고 있으니, 각 구현체 리포지토리는 그 스펙에 맞춰 독립적으로 진화할 수 있었습니다. 1인 개발 환경에서도 "어디를 고치면 어디가 깨지는지" 명확히 알 수 있다는 점이 심리적 안정감을 주었습니다.

결론: 복잡도를 통제하는 도구들

1인 개발자가 5개의 서비스를 안정적으로 다루기 위해서는 뛰어난 기억력보다는 시스템적인 강제력이 필요합니다.

  • 인프라는 Docker Compose로 묶고
  • 계약은 **JSON Schema(Contracts)**로 고정하며
  • 코드 분리는 멀티레포로 명확히 하는 것

이 세 가지 기둥이 없었다면 GoVail은 거대한 진흙 덩어리(Big Ball of Mud)가 되었을 것입니다. 다음 포스트에서는 이 시스템 내에서 장기 실행 비동기 작업을 어떻게 안정적으로 처리하는지, Temporal을 활용한 경험을 이야기해보겠습니다.

Released under the Apache 2.0 License.