안녕하세요, 자바파커입니다.
"Helm 템플릿 문법 괄호 속에서 버벅이는 게 지친다. 그냥 YAML 몇 줄만 바꿔서 환경별로 배포하고 싶다."
솔직히 저도 그랬습니다. {{- if .Values.ingress.enabled }} 같은 템플릿 안에서 들여쓰기 한 칸 틀리면 렌더링 전체가 깨지고, 디버깅은 helm template을 수십 번 돌려보며 YAML을 다시 맞추는 작업이 됩니다. 소규모 팀·단일 앱에서는 이게 배보다 배꼽이 큰 느낌이 들기 시작합니다.
결론부터 말씀드리면 — Kustomize는 템플릿 문법 없이 "원본 YAML을 그대로 두고, 환경별 변경점만 patch로 쌓아 올리는" 매니페스트 관리 도구입니다. Go 템플릿을 배울 필요가 없고, kubectl에 이미 내장되어 있어 별도 설치가 필요 없습니다. Helm 편에서 예고한 대로, 오늘은 이 kustomize의 구조·변환기·patch 전략·Helm과의 비교·조합 패턴·ArgoCD 연동까지 정리하겠습니다.
이전 편을 안 보셨다면 K8S에 대한 이해 · kubectl 실전 명령어 · k9s 완벽 가이드 · Helm 완벽 가이드 · ArgoCD GitOps 가이드부터 읽으시면 흐름이 이어집니다.
Kustomize란? — 한 줄 정의
Kustomize는 K8S 매니페스트를 "원본(base) + 변경점(overlay)"으로 분리해서 관리하는 선언적 변환 도구입니다. K8S SIG(Special Interest Group)가 유지보수하며, kubectl 1.14+부터 내장되어 있습니다.
핵심 철학:
| 철학 | 의미 |
|---|---|
| Template-free | Go 템플릿·변수 치환 없음. 순수 YAML만 다룸 |
| Composition-based | 변환·patch를 쌓아 올려 최종 YAML 생성 |
| Declarative all the way | kustomization.yaml 파일 자체가 선언형 설정 |
| Reusable bases | 공통 base를 여러 환경 overlay가 재사용 |
비유: Git의 diff/patch 모델을 K8S YAML에 적용한 것. 원본을 수정하지 않고 patch 파일을 얹는 방식이라 base가 항상 "순수"하게 유지됩니다.
3대 핵심 개념 — Base, Overlay, Kustomization
Kustomize의 모든 것은 이 세 단어로 정리됩니다.
| 개념 | 역할 |
|---|---|
| Base | 공통 매니페스트 묶음 (변경 없는 원본 YAML 모음) |
| Overlay | 환경별 변경점 묶음 (base를 참조하고 그 위에 patch 적용) |
| Kustomization | kustomization.yaml — base든 overlay든 각 디렉토리의 설계도 |
전형적인 디렉토리 구조
my-app/
├── base/
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── configmap.yaml
│ └── kustomization.yaml ← base 설계도
└── overlays/
├── dev/
│ ├── patch.yaml
│ └── kustomization.yaml ← base를 참조 + dev patch
├── staging/
│ ├── patch.yaml
│ └── kustomization.yaml
└── prod/
├── patch.yaml
└── kustomization.yaml핵심 규칙: base는 절대 환경별 정보를 가지지 않습니다. replicas: 1 같은 기본값도 overlay가 덮어씁니다.
설치 — kubectl만 있으면 끝
kubectl apply -k <dir> 로 바로 사용 가능. 별도 설치 불필요.
# 현재 kubectl 버전 확인 (1.14+ 권장)
kubectl version --client
# kustomize 렌더링만 (apply 없이)
kubectl kustomize overlays/prod
# 렌더링 + 적용
kubectl apply -k overlays/prod더 최신 기능(Helm 통합·Component 등)이 필요하면 standalone CLI 설치:
# macOS
brew install kustomize
# Linux / Windows
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
# 확인
kustomize version팁:
kubectl내장 버전은 보통 최신보다 뒤처집니다. 신기능 쓰려면 standalonekustomizeCLI 권장.
실전 10분 — nginx를 base + 2개 overlay로 분리
Helm 편에서 만들었던 nginx 앱을, 이번엔 kustomize로 똑같이 구성해보겠습니다.
1단계: Base 만들기
base/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80base/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: nginx
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80base/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app.kubernetes.io/part-of: my-app2단계: Dev overlay
overlays/dev/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
namePrefix: dev-
resources:
- ../../base
images:
- name: nginx
newTag: 1.27-alpine # 더 가벼운 이미지
replicas:
- name: nginx
count: 1
commonAnnotations:
environment: development3단계: Prod overlay
overlays/prod/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: production
namePrefix: prod-
resources:
- ../../base
images:
- name: nginx
newTag: 1.27 # 안정 태그
replicas:
- name: nginx
count: 3
patches:
- path: resources-patch.yaml
target:
kind: Deployment
name: nginxoverlays/prod/resources-patch.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi4단계: 렌더링 · 배포
# 렌더링해서 눈으로 확인
kubectl kustomize overlays/prod
# 바로 배포
kubectl apply -k overlays/prod
# 제거
kubectl delete -k overlays/prod한 번도 템플릿 문법을 쓰지 않았고, 모든 파일이 그 자체로 valid한 K8S YAML입니다. 에디터 자동완성·검증이 전부 그대로 동작합니다.
주요 변환기(Transformers) — 자주 쓰는 6가지
kustomization.yaml에서 쓸 수 있는 "내장 조작 기능"들입니다.
1) commonLabels / commonAnnotations
모든 리소스에 공통 라벨·어노테이션을 붙입니다.
commonLabels:
app.kubernetes.io/managed-by: kustomize
team: platform
commonAnnotations:
contact: ops@example.com2) namePrefix / nameSuffix
리소스 이름 앞·뒤에 접두/접미사. 같은 클러스터에 여러 환경 띄울 때 필수.
namePrefix: prod-
nameSuffix: -v2
# nginx → prod-nginx-v23) namespace
모든 리소스의 네임스페이스를 일괄 지정.
namespace: production4) images
이미지 태그·레지스트리를 교체. 배포 파이프라인에서 가장 많이 쓰는 기능.
images:
- name: nginx
newName: myregistry.io/nginx # 레지스트리 교체
newTag: v1.2.4 # 태그 교체CI에서 이 newTag만 sed·yq로 수정하면 CD가 이어지는 패턴이 흔합니다.
5) replicas
replica 개수 오버라이드.
replicas:
- name: nginx
count: 36) configMapGenerator / secretGenerator
ConfigMap·Secret을 파일 또는 literal에서 자동 생성. 해시 suffix가 자동으로 붙어 변경 시 Pod 재시작을 자연스럽게 유도.
configMapGenerator:
- name: app-config
files:
- configs/app.conf
literals:
- LOG_LEVEL=info
secretGenerator:
- name: db-creds
envs:
- .env.prod이 기능 하나만으로도 "ConfigMap 바꾸고 Pod가 안 새로고침되는" 고질적 이슈가 사라집니다.
Patch 전략 — Strategic Merge vs JSON 6902
복잡한 변경은 patch 파일로 처리합니다. 두 가지 방식.
Strategic Merge Patch (추천, 대부분의 경우)
YAML 부분 덮어쓰기. K8S 리소스의 "전략적 병합" 규칙을 따름 (배열은 name 기반 병합 등).
# overlays/prod/kustomization.yaml
patches:
- path: patch-resources.yaml
target:
kind: Deployment
name: nginx# overlays/prod/patch-resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
template:
spec:
containers:
- name: nginx
env:
- name: NODE_ENV
value: production
resources:
limits:
cpu: 1000m장점: YAML 그대로 읽힘. 리뷰·머지 쉬움. 단점: 배열 요소를 "이름 없이" 추가할 때 제약 있음.
JSON 6902 Patch (정교한 제어)
JSON Pointer 경로로 명령형 변경. add/remove/replace 연산.
patches:
- target:
kind: Deployment
name: nginx
patch: |
- op: replace
path: /spec/replicas
value: 5
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: LOG_LEVEL
value: debug언제 쓰나: 배열 요소 삭제, 정확한 인덱스 지정, Strategic Merge로 표현 불가능한 변경.
실전 기준: 평소엔 Strategic Merge, "이 한 줄을 정확히 지워야 한다" 같은 경우에만 JSON 6902. 섞어 쓸 수 있음.
Components — 재사용 가능한 기능 묶음
여러 overlay에서 공통으로 얹고 싶은 변경점(예: 모니터링 사이드카, 보안 정책)을 Component로 추출.
components/
└── monitoring/
├── sidecar-patch.yaml
└── kustomization.yaml# components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
patches:
- path: sidecar-patch.yaml
target:
kind: Deploymentoverlay에서 쓰기:
# overlays/prod/kustomization.yaml
resources:
- ../../base
components:
- ../../components/monitoring
- ../../components/istio-injectionHelm의 subchart보다 훨씬 가볍고 선언적입니다. "기능 단위 레이어"를 섞어 쓰는 감각.
Helm vs Kustomize — 언제 뭘 쓰나?
가장 자주 받는 질문. 실전 기준으로 정리.
| 항목 | Helm | Kustomize |
|---|---|---|
| 학습 곡선 | Go 템플릿 문법 필요 | YAML만 알면 됨 |
| 설치 | 별도 바이너리 | kubectl 내장 |
| 환경 분리 | values 파일 | overlay 디렉토리 |
| 버전 관리 | helm history·rollback 내장 |
없음 (Git·ArgoCD에 의존) |
| 패키지 공유 | 레포지토리 생태계 풍부 | 제한적 (Git 기반) |
| 복잡한 로직 | 템플릿 함수로 표현 가능 | 제한적 (변환기·patch 조합) |
| 검증·자동완성 | 렌더링 후에만 가능 | 원본 YAML이 그대로 valid |
| 외부 Chart 사용 | 네이티브 | helmCharts로 래핑 가능 |
| CD 통합 | ArgoCD·Flux 네이티브 지원 | ArgoCD·Flux 네이티브 지원 |
선택 기준
Helm이 유리한 경우:
- 외부 Chart(ingress-nginx·cert-manager·prometheus 등) 설치
- 패키지를 만들어 여러 사용자에게 배포
- 조건 분기·반복이 많은 복잡한 템플릿
Kustomize가 유리한 경우:
- 사내 마이크로서비스 YAML 관리 (학습 비용 최소)
- 외부 Chart에 파일 몇 개만 덧붙이는 상황
- 엄격한 YAML 검증·리뷰 문화
- 팀에 Go 템플릿 경험이 없는 경우
둘 다 쓰는 게 현실: ingress-nginx는 Helm으로 설치하고, 우리 앱은 kustomize로 관리하는 식. ArgoCD는 둘 다 네이티브 지원하므로 CD 레벨에서 통합됩니다.
조합 패턴 — Helm 위에 Kustomize
Helm Chart를 base로 두고 kustomize overlay로 사내 규약을 얹는 패턴.
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
helmCharts:
- name: nginx
repo: https://charts.bitnami.com/bitnami
version: 15.4.4
releaseName: my-nginx
valuesFile: values-prod.yaml
patches:
- path: common-labels-patch.yaml # 사내 라벨 규약 강제실행 시 --enable-helm 플래그 필요:
kustomize build --enable-helm overlays/prod이 패턴의 가치: 외부 Chart의 기본 구조를 존중하면서, 사내 보안·관측·네이밍 규약을 일관되게 덧붙일 수 있습니다. 매우 실용적입니다.
ArgoCD 연동 — 5편 이어서
ArgoCD는 kustomization.yaml을 자동 감지합니다. Application에 path만 지정하면 끝.
# Application 매니페스트
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-prod
spec:
source:
repoURL: https://github.com/javapark/k8s-manifests.git
targetRevision: main
path: apps/my-app/overlays/prod
kustomize:
namePrefix: prod- # ArgoCD 수준에서 추가 오버라이드 가능
images:
- nginx=nginx:1.27.1
destination:
server: https://kubernetes.default.svc
namespace: productionCI 파이프라인에서 이미지 태그만 kustomize edit set image로 갱신 → Git push → ArgoCD 자동 sync. ArgoCD 편의 Self-Heal까지 붙이면 재현 가능한 배포 파이프라인 완성.
# CI에서 쓰는 패턴
cd apps/my-app/overlays/prod
kustomize edit set image nginx=nginx:v1.2.4
git commit -am "chore: bump nginx to v1.2.4"
git push자주 겪는 이슈 3가지
1) "patch가 안 먹힌다"
대부분 target 불일치입니다. kind·name·(필요시) namespace가 정확히 일치하는지 확인. labelSelector로 여러 리소스를 타겟할 수도 있습니다.
patches:
- path: add-label.yaml
target:
kind: Deployment
labelSelector: "tier=backend"2) "commonLabels가 Selector까지 바꿔서 배포가 깨진다"
commonLabels는 Selector에도 적용됩니다. 이미 배포된 Deployment의 Selector는 불변 — 새 라벨 추가 시 롤아웃이 실패합니다. 팀 라벨 같은 고정값은 처음부터 넣거나, labels 변환기(v4.5+)로 Selector 제외 가능:
labels:
- pairs:
app.kubernetes.io/part-of: my-app
includeSelectors: false # Selector는 건드리지 않음3) "configMapGenerator 해시 때문에 Deployment가 매번 재배포된다"
원래 의도된 동작입니다 (ConfigMap 내용 변경 시 Pod 새로고침). ConfigMap 해시를 원하지 않으면:
generatorOptions:
disableNameSuffixHash: true단, 이러면 ConfigMap 변경이 Pod에 자동 반영되지 않으니 rollout restart를 별도로 해야 합니다.
FAQ
Q. Helm을 이미 쓰고 있는데 kustomize로 갈아탈 가치가 있나요?
작은 팀·단순 앱이면 Yes. 템플릿 디버깅 시간이 많이 줄어듭니다. 단, 외부 Chart를 많이 쓰고 있다면 Helm을 유지하면서 일부만 kustomize로 관리하는 하이브리드가 현실적입니다.
Q. kubectl apply -k 랑 kustomize build | kubectl apply -f - 차이는?
내장 버전 vs 최신 버전의 차이입니다. kubectl 내장 kustomize는 보통 standalone CLI보다 몇 버전 뒤처집니다. 신기능(Component, helmCharts 등)이 필요하면 standalone kustomize build를 쓰는 게 안전.
Q. 환경 수가 10개 넘어가면 overlay가 폭발하지 않나요?
Component로 공통 기능을 뽑아내고, overlay는 최소한의 차이만 담으세요. 같은 환경 그룹(예: dev 클러스터 3개)은 공통 overlay + 작은 patch로 처리. 정말 환경이 수십 개면 ArgoCD ApplicationSet으로 자동 생성하는 게 맞습니다.
Q. Helm Chart를 kustomize base로 쓰려면?
helmCharts 필드를 쓰고 kustomize build --enable-helm으로 빌드. ArgoCD는 Application의 kustomize.helmCharts로 직접 지원합니다.
마무리 — 다음 단계
kustomize의 진짜 가치는 "YAML을 YAML답게 다루는 경험"입니다. 파일 하나하나가 그 자체로 valid하고, 에디터·lint·리뷰 도구가 모두 그대로 동작합니다. Helm이 주는 "패키지성"과는 다른 축의 가치라 둘 다 쓰는 게 정답이라는 게 오늘의 결론.
오늘 정리 핵심:
- Kustomize = Base + Overlay, 템플릿 없이 patch로 조합
kubectl내장 — 설치 필요 없음- 가장 많이 쓰는 건
namespace·namePrefix·images·replicas·configMapGenerator - Patch는 Strategic Merge 기본 / 정교한 경우 JSON 6902
- Helm과 대립이 아니라 조합 — 외부 Chart는 Helm, 사내 앱은 Kustomize
- ArgoCD가 둘 다 네이티브 지원하므로 CD 레벨에서 자유롭게 섞어 쓰기
시리즈 다음 주제
- HPA·VPA로 오토스케일링 구성
- Ingress + cert-manager로 HTTPS 자동화
- Prometheus + Grafana 모니터링 스택
- ArgoCD ApplicationSet 심화 — 멀티 클러스터 대규모 운영
여러분은 Helm과 Kustomize 중 주력으로 쓰는 건 어느 쪽인가요? 또는 이 둘을 어떻게 조합해 쓰고 계신지 댓글로 공유해주세요. 다음 편에 실전 패턴으로 반영하겠습니다.