무색
기술블로그
에세이
연구
프로덕트
소개

무색

소프트웨어로 비즈니스의 가능성을 만듭니다. 웹·앱 개발, 음성 AI, 자동화 콘텐츠 제작까지 — 기술이 필요한 곳에 무색이 있습니다.

연락처

[email protected]

사업자 정보

상호: 무색

대표: 배성재

사업자등록번호: 577-58-00836

인천광역시 연수구 인천타워대로 323, 에이동 8층 801-802호 AB-132 (송도동, 송도 센트로드)

© 2026 무색. All rights reserved.
개인정보처리방침·이용약관
INCHEON, KR
K8s 배포 패턴 비교 키 비주얼
크로스커팅
2026. 1. 23.

K8s 배포 패턴 비교: 수동 매니페스트에서 Helm까지

kuberneteskustomizehelmargocdgitops

홈랩 K8s 클러스터를 운영하면서 배포 방식이 세 번 바뀌었다. 처음엔 YAML 파일을 직접 써서 kubectl apply를 치고, 그 다음엔 Kustomize로 환경을 나누고, 마지막엔 Helm 차트까지 끌어왔다. 배포할 서비스가 하나일 땐 아무거나 써도 되지만 열 개가 넘어가면 얘기가 달라진다. 각 방식을 언제 쓰고 언제 넘어가야 하는지, 실제 프로젝트에서 겪은 걸 바탕으로 정리해본다.

수동 매니페스트: kubectl apply의 시대

무색(museck) 홈페이지를 처음 K8s에 올릴 때 선택한 방법이다. Deployment, Service, PVC, ConfigMap, IngressRoute를 각각 YAML로 작성하고 앱 코드 레포의 k8s/ 디렉토리에 넣어뒀다.

# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: museck-app
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: app
          image: gitea.xssh.org/admin/museck:abc123
          ports:
            - containerPort: 3000

이 방식의 장점은 진입 장벽이 거의 없다는 거다. YAML 문법만 알면 바로 쓸 수 있고 디버깅도 직관적이다. 뭐가 배포되는지 파일 하나하나 눈으로 확인할 수 있으니까.

문제는 환경이 둘로 나뉘는 순간 터진다. staging과 production에서 다른 건 이미지 태그, 도메인, 리소스 제한 정도인데, 이걸 분리하려면 YAML 파일을 통째로 복사해야 한다. 복사한 파일 10개 중 하나만 수정을 빠뜨려도 장애가 난다. 실제로 museck을 production으로 올릴 때 이 복사 지옥을 경험하고 Kustomize로 넘어갔다.

Kustomize: base/overlay로 환경 분리

Kustomize는 kubectl에 내장되어 있어서 별도 설치가 필요 없다. 핵심 아이디어는 간단하다. 공통 리소스를 base에 두고 환경별 차이만 overlay에서 덮어쓰는 것.

homelab/
└── services/museck/
    ├── base/
    │   ├── deployment.yaml
    │   ├── service.yaml
    │   └── kustomization.yaml
    └── overlays/
        ├── staging/
        │   └── kustomization.yaml  # 이미지 태그, 도메인
        └── production/
            └── kustomization.yaml  # 이미지 태그, 도메인, 리소스 제한

museck의 경우 staging overlay에서 바꾸는 건 이미지 태그와 도메인 두 가지뿐이다.

# overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - ../../base
images:
  - name: gitea.xssh.org/admin/museck
    newTag: "abc123"  # CI가 커밋 SHA로 업데이트

CI 파이프라인이 Docker 이미지를 빌드하고 레지스트리에 푸시한 뒤, 이 kustomization.yaml의 newTag를 커밋 SHA로 바꿔서 커밋한다. ArgoCD가 변경을 감지하면 자동으로 롤링 업데이트가 진행된다. production 배포는 별도의 workflow_dispatch 트리거로 수동 제어한다. 빈 태그를 입력하면 현재 staging에 배포된 태그를 자동으로 가져오는 기능도 넣었는데, 매번 SHA를 복사해서 붙여넣는 번거로움이 사라졌다.

Kustomize는 내가 직접 작성한 매니페스트를 관리하는 데 적합하다. base를 한 번 잘 만들어두면 overlay에서 이미지 태그와 설정값만 바꿔도 되니까 파일 복사가 필요 없다. museck 같은 자체 개발 앱에는 딱 맞는 도구다.

Helm: 복잡한 외부 앱은 차트로

Kustomize로 잘 돌아가던 구조에 균열이 생긴 건 Postiz를 배포할 때였다. Postiz는 소셜 미디어 자동화 도구인데 단독으로 돌아가지 않는다. PostgreSQL, Redis, Temporal 워크플로우 엔진이 전부 필요하다. 이걸 수동으로 매니페스트를 쓰면 Bitnami PostgreSQL의 StatefulSet, PVC, 서비스 이름 규칙을 전부 직접 관리해야 한다.

Helm 차트는 이런 복잡한 의존성을 패키지로 묶어준다. Postiz의 공식 Helm 차트는 OCI 레지스트리(ghcr.io)에서 가져오고, values.yaml 하나로 PostgreSQL 버전부터 Redis 설정까지 전부 제어할 수 있다.

# ArgoCD multi-source: OCI Helm + Git values + Git manifests
spec:
  sources:
    - repoURL: ghcr.io/gitroomhq/postiz-helmchart/charts/postiz-app
      chart: postiz-app
      targetRevision: 1.0.5
      helm:
        valueFiles:
          - $values/services/postiz/postiz-values.yaml
    - repoURL: https://gitea.xssh.org/homelab/homelab.git
      targetRevision: master
      ref: values
    - repoURL: https://gitea.xssh.org/homelab/homelab.git
      targetRevision: master
      path: services/postiz/manifests  # Helm 외 추가 리소스

ArgoCD의 multi-source 기능이 여기서 빛을 발한다. 소스 세 개를 하나의 Application으로 묶을 수 있다. OCI Helm 차트(외부), values 파일(Git), Helm으로 관리 안 되는 추가 매니페스트(Temporal 등)를 조합한다.

다만 Helm이 만능은 아니다. Postiz 배포에서 13번 커밋을 찍은 건 Helm subchart의 서비스 이름 규칙, Bitnami 이미지 태그 삭제, Temporal ConfigMap 마운트 충돌 같은 문제가 연쇄적으로 터졌기 때문이다. Helm은 복잡도를 숨겨주지만 숨겨진 복잡도가 문제를 일으키면 디버깅이 어렵다.

ArgoCD와 결합하기

세 가지 방식 모두 ArgoCD와 잘 붙는다. ArgoCD는 Git 레포를 감시하다가 변경이 생기면 클러스터에 자동 적용하는 GitOps 컨트롤러다. 핵심은 App of Apps 패턴이다.

# ArgoCD root Application (App of Apps 진입점)
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: root
  namespace: argocd
spec:
  source:
    repoURL: https://gitea.xssh.org/homelab/homelab.git
    path: argocd/apps  # 이 디렉토리 안의 Application YAML 자동 감지
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

root Application이 apps/ 디렉토리를 감시한다. 새 서비스를 배포하고 싶으면 이 디렉토리에 Application YAML 파일 하나만 추가하면 된다. ArgoCD UI를 건드릴 필요가 없다. museck-staging, museck-production, postiz, syncthing 등이 전부 이 방식으로 등록되어 있다.

배포 방식에 따라 ArgoCD Application의 source 설정이 달라진다.

  • 수동 매니페스트: source.path에 매니페스트 디렉토리를 지정하면 끝
  • Kustomize: source.path에 overlay 디렉토리를 지정. ArgoCD가 kustomize build를 자동 실행
  • Helm: sources(복수)에 차트 URL과 values 레퍼런스를 분리. multi-source로 외부 차트 + 내부 values 조합

어떤 걸 쓸까: 선택 기준

실제로 프로젝트를 여러 개 운영하면서 자리 잡은 기준이다.

수동 매니페스트는 환경이 하나뿐이고 서비스가 단순할 때 쓴다. Cloudflare Tunnel의 cloudflared Deployment가 좋은 예다. 컨테이너 하나에 Secret 참조 하나면 끝이라 굳이 Kustomize를 쓸 이유가 없었다.

Kustomize는 직접 만든 앱을 staging/production으로 나눠 배포할 때 적합하다. museck처럼 내가 Deployment부터 IngressRoute까지 직접 작성한 매니페스트가 있고 환경별로 이미지 태그와 설정만 다르면 Kustomize가 가장 깔끔하다.

Helm은 외부 오픈소스 앱을 가져다 쓸 때 빛난다. PostgreSQL, Redis 같은 subchart 의존성이 있고 공식 차트가 제공되면 Helm이 맞다. Postiz, ArgoCD 자체도 Helm으로 배포했다.

혼용도 괜찮다. 한 클러스터에서 세 가지 방식을 동시에 쓰고 있다. ArgoCD는 어떤 방식이든 Application YAML만 잘 써주면 동일하게 auto-sync를 건다.

삽질에서 배운 것

배포 방식을 바꾸면서 몇 가지 교훈이 남았다.

앱 코드와 매니페스트는 분리하자. museck 초기에는 앱 레포에 k8s/ 디렉토리를 뒀다. CI가 같은 레포에서 이미지 빌드도 하고 매니페스트도 수정하니까 워크플로우가 꼬였다. 매니페스트를 homelab 레포로 옮기고 나서야 CI는 이미지만 빌드하고, 매니페스트 변경은 별도로 관리하는 깔끔한 구조가 됐다.

Helm subchart의 서비스 이름을 반드시 확인하자. Bitnami PostgreSQL subchart는 릴리스 이름 기반으로 서비스 이름을 만든다. fullnameOverride가 아니면 postiz-postgresql 같은 이름이 된다. DATABASE_URL에 이걸 제대로 안 넣으면 ECONNREFUSED부터 만난다.

CI/CD 워크플로우도 UX를 생각하자. production 배포 워크플로우에서 이미지 태그를 수동 입력하게 만들었더니 staging 태그를 복사하는 작업이 매번 귀찮았다. 빈 입력이면 staging 태그를 자동으로 가져오게 바꿨더니 실수도 줄고 편의성도 높아졌다.

정리

수동 매니페스트에서 시작해서 Kustomize를 거쳐 Helm까지 오는 데 2주 정도 걸렸다. 중간에 수동으로 kubectl apply 치던 시절로 되돌아가고 싶은 적도 있었지만 서비스가 열 개를 넘기면서 GitOps 없이는 운영이 불가능하다는 걸 체감했다.

세 가지 방식 중 하나만 정답인 건 아니다. 서비스마다 복잡도가 다르고 적합한 도구도 다르다. 한 클러스터 안에서 셋을 섞어 쓰더라도 ArgoCD가 통일된 배포 인터페이스를 제공하니까 관리 부담은 크지 않다. 홈랩이든 프로덕션이든 결국 중요한 건 Git에 있는 것이 곧 클러스터의 상태라는 GitOps 원칙 자체다. 그걸 어떤 도구로 구현하느냐는 프로젝트 규모에 맞춰 선택하면 된다.

자주 묻는 질문

Kustomize와 Helm 중 어떤 걸 써야 하나요?
직접 만든 앱을 staging/production으로 나눠 배포할 때는 Kustomize가 적합하고, PostgreSQL이나 Redis 같은 외부 오픈소스 앱을 의존성과 함께 배포할 때는 Helm이 맞습니다.
ArgoCD App of Apps 패턴이란?
root Application 하나가 apps/ 디렉토리를 감시하면서 새 Application YAML이 추가되면 자동으로 등록하는 패턴입니다. ArgoCD UI를 건드릴 필요 없이 Git만으로 서비스를 관리할 수 있습니다.
K8s 매니페스트를 앱 레포에 두면 안 되나요?
초기에는 괜찮지만 CI가 이미지 빌드와 매니페스트 수정을 한 레포에서 하면 워크플로우가 꼬입니다. 매니페스트를 별도 인프라 레포로 분리하면 CI는 이미지만, GitOps는 배포만 담당하는 깔끔한 구조가 됩니다.
크로스커팅(5/18)
Prev

MCP 서버 3종 비교: AI 에이전트는 외부 시스템을 어떻게 다루는가

Next

Claude Code 헤드리스 모드: 서브프로세스로 AI 삽입하기