Cloud Run을 버리고 집으로 돌아오다: Single Node k3s와 Argo CD로 구축한 온프레미스 GitOps

곽태욱·2026년 2월 10일
post-thumbnail

요약: 월 $100의 고정 비용이 발생하는 GCP Cloud Run 환경을 단일 노드 온프레미스 k3s 클러스터로 이관하여 비용을 90% 이상 절감하고, Argo CD를 통해 클라우드 네이티브 수준의 배포 경험(DX)을 유지한 경험을 공유합니다.

1. 서버리스의 달콤함과 청구서의 쓴맛

초기 스타트업이나 사이드 프로젝트에서 Google Cloud Run은 매력적인 선택지입니다. 인프라 관리 없이 컨테이너만 던지면 알아서 스케일링되고, 트래픽이 없으면 비용이 0이 되는 'Scale to Zero'는 환상적입니다.

하지만 서비스가 성장하고 Always-on 워크로드가 늘어나면 이야기가 달라집니다.

  • 백그라운드 잡(Job)이 주기적으로 실행되어야 하고,
  • 응답 속도를 위해 min-instances를 설정해야 하며,
  • DB 연결을 위한 VPC Connector 비용이 추가됩니다.

결국 "편리함"의 대가로 지불하는 프리미엄이 실제 하드웨어 비용 대비 과도하게 느껴지는 시점이 옵니다. 저는 이 지점에서 "로컬 온프레미스(Local On-premise)"로 돌아가려고 결심했습니다. 단, Kubernetes의 강력한 오케스트레이션 능력은 유지한 채로요.

2. 아키텍처: 왜 k3s인가?

"집에서 쿠버네티스를 돌린다"고 하면 대부분 오버엔지니어링이라고 생각합니다. 하지만 k3s라면 이야기가 다릅니다.

  • Lightweight: k3s는 불필요한 레거시 기능을 걷어내고 단일 바이너리(<100MB)로 패키징된 경량화 k8s입니다.
  • Production Ready: CNCF 인증을 받았으며, 실제 프로덕션 환경에서도 충분히 사용 가능합니다.
  • Single Node Cluster: 마스터(Control Plane)와 워커 노드를 하나의 머신에서 구동하여 리소스 효율을 극대화했습니다.

Tech Stack Transformation

구분기존 (GCP Cloud Run)변경 (On-Premise k3s)
ComputeCloud Run (Serverless)Mac Mini (Single Node)
OrchestratorGoogle Borg (Managed)k3s
DeliveryCloud Build + gcloud deployArgo CD (GitOps)
IngressGlobal Load BalancerCloudflare Tunnel (Gateway)
MonitoringCloud MonitoringPrometheus + Grafana (via Helm)

3. 핵심 구현 전략

단순히 kubectl apply로 배포하는 것은 지속 가능하지 않습니다. 클라우드 환경에서 누리던 자동화와 안정성을 온프레미스에 이식하기 위해 다음과 같은 전략을 사용했습니다.

3.1. GitOps: Argo CD와 App of Apps 패턴

인프라의 상태를 코드로 관리(IaC)하기 위해 Argo CD를 도입했습니다. 특히 App of Apps 패턴을 적용하여 클러스터 전체를 부트스트랩합니다.

# k8s/bootstrap/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: platform-bootstrap
spec:
  source:
    repoURL: 'https://github.com/gwak2837/....git'
    path: k8s/platform
    targetRevision: HEAD
  # ...

디렉토리 구조는 다음과 같이 계층화했습니다.

  • k8s/platform/: 클러스터 전역 유틸리티 (Cert-manager, Ingress Controller, Monitoring)
  • k8s/apps/: 실제 비즈니스 애플리케이션 (Next.js, Hono API, Redis)

이제 git push 한 번이면, Argo CD가 변경 사항을 감지하고 로컬 클러스터의 상태를 동기화(Sync)합니다.

3.2. 네트워킹: 포트 포워딩 없는 안전한 노출 (Cloudflare Tunnel)

로컬 서버를 외부에 노출할 때 가장 큰 보안 위협은 방화벽 구멍(Port Forwarding)입니다. 이를 해결하기 위해 Cloudflare Tunnel (cloudflared)을 k8s 내부에 배포했습니다.

  • Ingress Controller 불필요: cloudflared 파드가 아웃바운드 전용 터널을 생성하여 Cloudflare 엣지와 연결됩니다.
  • Zero Trust: 공인 IP를 노출하지 않고도 외부 트래픽을 안전하게 클러스터 내부 서비스(Service)로 라우팅합니다.
# cloudflared 설정 예시
ingress:
  - hostname: api.litomi.com
    service: http://litomi-api-service:8080
  - hostname: monitor.litomi.com
    service: http://kube-prometheus-stack-grafana:80

3.3. 모니터링: 데이터 주권 확보

GCP에서는 클릭 몇 번으로 보던 메트릭을 직접 구축해야 합니다. kube-prometheus-stack Helm 차트를 커스터마이징하여 배포했습니다.

  • Node Exporter: 호스트 머신의 CPU, 메모리, 디스크 I/O 모니터링
  • Service Monitor: Next.js 및 Hono API 애플리케이션의 커스텀 메트릭 수집
  • Alertmanager: 장애 발생 시 Slack/Discord로 알림 발송

이 모든 설정 역시 k8s/platform/monitoring/values.yaml 파일로 버전 관리됩니다.

4. 마이그레이션 중 겪은 트레이드오프

물론 모든 것이 장점만 있는 것은 아닙니다. Serverless의 무한한 확장성을 포기하고 고정된 하드웨어로 돌아오면서 겪은 기술적 과제들이 있었습니다.

4.1. 유한한 리소스

Cloud Run에서는 돈만 내면 CPU와 메모리가 마법처럼 늘어났지만, 단일 노드 환경은 물리적 상한선(예: 16GB RAM)이 명확합니다. 배치 작업이 돌 때 웹 서버의 응답 속도가 느려지는 '이웃 소음(Noisy Neighbor)' 문제가 발생할 수 있습니다.

이를 해결하기 위해 Kubernetes의 QoS(Quality of Service) 클래스를 적극적으로 활용해야 했습니다.

  • Guaranteed: 핵심 웹 서비스에는 requestslimits를 동일하게 설정하여 리소스를 독점적으로 보장.
  • BestEffort: 리소스를 많이 먹는 배치 작업은 남는 자원만 쓰도록 설정하여, 시스템 부하 시 가장 먼저 종료(Evict)되도록 설계.

4.2. 가용성

단일 노드이기에 하드웨어 장애 시 서비스가 중단됩니다. 하지만 현재 서비스 규모에서는 "99.99%의 가용성"보다 "비용 효율성"이 더 중요하다고 판단했습니다. (필요 시 물리 노드 추가로 쉽게 확장 가능)

5. 결론: 기술적 자유와 비용 절감

이 마이그레이션을 통해 얻은 성과는 명확합니다.

  1. 비용 절감: 월 클라우드 비용이 전기세 수준으로 수렴했습니다.
  2. 기술 내재화: Managed Service 뒤에 숨겨져 있던 인프라의 동작 원리(네트워크 패킷 흐름, 스토리지 프로비저닝 등)를 깊이 이해하게 되었습니다.
  3. 확장성: 이제 더 많은 마이크로서비스를 띄워도 추가 비용 걱정 없이 실험해 볼 수 있는 "놀이터"가 생겼습니다.

Cloud Run은 훌륭하지만, 엔지니어에게 "나만의 클라우드"를 구축해 보는 것만큼 값진 경험은 없습니다. k3s와 Argo CD는 그 여정을 위한 좋은 도구입니다.

profile
이유와 방법을 알려주는 메모장 겸 블로그 (Frontend, AI, 경제, 책)

0개의 댓글