Kustomize 환경별 배포 운영 가이드

Kustomize란? — 템플릿 없이 YAML을 커스터마이징

Kubernetes 매니페스트를 환경별(dev/staging/prod)로 관리할 때, 같은 Deployment를 복사해서 replica 수와 이미지 태그만 바꾸는 일이 반복된다. Helm은 Go 템플릿으로 이 문제를 해결하지만, 학습 곡선이 높고 템플릿이 복잡해진다. Kustomize는 원본 YAML을 수정하지 않고 overlay(덧씌우기)로 환경별 차이만 선언하는 도구다.

kubectl v1.14부터 kubectl apply -k로 내장되어 별도 설치가 불필요하다. 이 글에서는 base/overlay 구조, patches, configMapGenerator, 이미지 태그 오버라이드, components, 그리고 Helm vs Kustomize 선택 기준까지 정리한다.

1. 디렉터리 구조 — base와 overlays

k8s/
├── base/                          # 공통 매니페스트
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   └── hpa.yaml
└── overlays/
    ├── dev/                       # 개발 환경 오버레이
    │   ├── kustomization.yaml
    │   └── patch-replicas.yaml
    ├── staging/
    │   ├── kustomization.yaml
    │   └── patch-resources.yaml
    └── prod/
        ├── kustomization.yaml
        ├── patch-replicas.yaml
        └── patch-resources.yaml

base/kustomization.yaml

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml
  - hpa.yaml

commonLabels:
  app: order-service
  managed-by: kustomize

base/deployment.yaml

# base/deployment.yaml — 환경 무관한 기본 설정
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 1                      # 기본값 — overlay에서 오버라이드
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      containers:
        - name: order
          image: order-service:latest
          ports:
            - containerPort: 8080
              name: http
          resources:
            requests:
              cpu: 250m
              memory: 256Mi
            limits:
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: http
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: http
            initialDelaySeconds: 30

2. Overlay — 환경별 차이를 덧씌우기

# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base                     # base 참조

namespace: production              # 네임스페이스 오버라이드

namePrefix: prod-                  # 리소스 이름에 prefix 추가
nameSuffix: ""

commonLabels:
  env: production

commonAnnotations:
  team: backend

# 이미지 태그 오버라이드 — 가장 많이 쓰는 기능
images:
  - name: order-service
    newName: registry.example.com/order-service
    newTag: v2.3.1

# 패치 적용
patches:
  - path: patch-replicas.yaml
  - path: patch-resources.yaml
# overlays/prod/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  replicas: 6                      # prod: 6개 replica
# overlays/prod/patch-resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
spec:
  template:
    spec:
      containers:
        - name: order
          resources:
            requests:
              cpu: "1"
              memory: 1Gi
            limits:
              memory: 2Gi
# 빌드 및 적용
kubectl kustomize overlays/prod        # 최종 YAML 미리보기
kubectl apply -k overlays/prod         # 클러스터에 적용
kubectl diff -k overlays/prod          # 변경사항 diff

3. Patch 전략 — Strategic Merge vs JSON 6902

3-1. Strategic Merge Patch (기본)

Kubernetes의 Strategic Merge Patch를 사용한다. 지정한 필드만 덮어쓰고 나머지는 유지한다.

# 위에서 본 patch-replicas.yaml이 Strategic Merge Patch
# metadata.name으로 대상을 매칭하고, spec.replicas만 오버라이드

3-2. JSON 6902 Patch

더 정밀한 제어가 필요할 때 사용한다. 배열의 특정 인덱스를 수정하거나, 필드를 삭제할 수 있다.

# overlays/prod/kustomization.yaml
patches:
  - target:
      kind: Deployment
      name: order-service
    patch: |
      - op: replace
        path: /spec/replicas
        value: 6
      - op: add
        path: /spec/template/spec/containers/0/env/-
        value:
          name: SPRING_PROFILES_ACTIVE
          value: prod
      - op: remove
        path: /spec/template/spec/containers/0/resources/limits/cpu

3-3. 인라인 Patch

# kustomization.yaml에 직접 패치 내용 작성
patches:
  - target:
      kind: Deployment
      name: order-service
    patch: |
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: order-service
      spec:
        replicas: 6
        template:
          spec:
            tolerations:
              - key: "dedicated"
                operator: "Equal"
                value: "app"
                effect: "NoSchedule"
패치 타입 장점 사용 시점
Strategic Merge 직관적, YAML 형식 그대로 필드 추가/수정 (대부분의 경우)
JSON 6902 배열 인덱스 제어, 필드 삭제 가능 정밀한 수정, 필드 삭제
인라인 별도 파일 불필요 간단한 1~2줄 패치

4. ConfigMap·Secret 자동 생성 — Generator

Kustomize의 Generator는 파일이나 리터럴에서 ConfigMap/Secret을 자동 생성하고, 내용이 바뀌면 이름에 해시 suffix를 붙여 Deployment가 자동으로 롤아웃된다. Terraform ConfigMap 체크섬 패턴과 동일한 효과를 Kustomize가 기본 제공한다.

# kustomization.yaml
configMapGenerator:
  # 파일에서 생성
  - name: app-config
    files:
      - application.yml
      - logback-spring.xml
    options:
      disableNameSuffixHash: false   # 해시 suffix 활성화 (기본값)

  # 리터럴에서 생성
  - name: app-env
    literals:
      - DATABASE_HOST=db.production.svc.cluster.local
      - DATABASE_PORT=5432
      - REDIS_HOST=redis.production.svc.cluster.local

  # .env 파일에서 생성
  - name: app-dotenv
    envs:
      - .env.production

secretGenerator:
  - name: db-credentials
    literals:
      - DB_USERNAME=order_svc
      - DB_PASSWORD=s3cur3p@ss
    type: Opaque

  # 파일에서 TLS Secret
  - name: tls-cert
    files:
      - tls.crt=certs/server.crt
      - tls.key=certs/server.key
    type: kubernetes.io/tls

해시 suffix 동작:

# 생성된 ConfigMap 이름: app-config-k5hm2f8
# application.yml 내용이 바뀌면: app-config-d9h3k7g (새 해시)
# → Deployment의 configMapRef도 자동으로 새 이름으로 교체
# → Pod이 자동 롤아웃 (재시작)

# 해시 suffix 비활성화 (비권장 — 변경 감지 불가)
generatorOptions:
  disableNameSuffixHash: true

5. Components — 재사용 가능한 기능 단위

Kustomize v3.7+에서 도입된 Components는 여러 overlay에서 공통으로 쓰는 패치 세트를 모듈화한다.

k8s/
├── base/
├── components/
│   ├── monitoring/                # Prometheus 사이드카 추가
│   │   └── kustomization.yaml
│   ├── istio-sidecar/             # Istio 어노테이션 추가
│   │   └── kustomization.yaml
│   └── high-availability/         # PDB + topology spread 추가
│       ├── kustomization.yaml
│       └── pdb.yaml
└── overlays/
    ├── dev/
    │   └── kustomization.yaml     # components 없음
    └── prod/
        └── kustomization.yaml     # 3개 component 모두 사용
# components/high-availability/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

resources:
  - pdb.yaml

patches:
  - target:
      kind: Deployment
    patch: |
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: not-used              # Component에서는 target으로 매칭
      spec:
        template:
          spec:
            topologySpreadConstraints:
              - maxSkew: 1
                topologyKey: topology.kubernetes.io/zone
                whenUnsatisfiable: DoNotSchedule
                labelSelector:
                  matchLabels:
                    app: order-service
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

components:
  - ../../components/monitoring
  - ../../components/istio-sidecar
  - ../../components/high-availability

images:
  - name: order-service
    newTag: v2.3.1

6. CI/CD에서 이미지 태그 주입

CI 파이프라인에서 빌드된 이미지 태그를 Kustomize에 주입하는 패턴이다.

# GitHub Actions 예시
- name: Build & Push Image
  run: |
    IMAGE_TAG=${{ github.sha }}
    docker build -t registry.example.com/order-service:$IMAGE_TAG .
    docker push registry.example.com/order-service:$IMAGE_TAG

- name: Update Kustomize Image Tag
  run: |
    cd k8s/overlays/prod
    kustomize edit set image 
      order-service=registry.example.com/order-service:${{ github.sha }}

- name: Deploy
  run: |
    kubectl apply -k k8s/overlays/prod
# ArgoCD + Kustomize — GitOps 방식
# ArgoCD Application에서 kustomize를 소스로 지정
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: order-service-prod
spec:
  source:
    repoURL: https://github.com/example/k8s-manifests.git
    targetRevision: main
    path: k8s/overlays/prod
    kustomize:
      images:
        - order-service=registry.example.com/order-service:v2.3.1

7. Helm vs Kustomize — 선택 기준

기준 Kustomize Helm
접근 방식 패치(overlay) — 원본 유지 템플릿(Go template) — 값 주입
학습 곡선 낮음 (순수 YAML) 높음 (Go template 문법)
패키지 배포 없음 (Git 기반) Chart Repository 지원
릴리스 관리 없음 (kubectl 기반) helm install/upgrade/rollback
조건부 리소스 Component로 부분 가능 if/else로 자유롭게
kubectl 내장 예 (v1.14+) 아니오 (별도 설치)
추천 사용처 자체 서비스 환경별 배포 공유 차트, 복잡한 조건부 로직

실무 가이드:

  • 자체 서비스 배포: Kustomize — base/overlay로 환경별 관리가 간단
  • 오픈소스 설치(nginx-ingress, prometheus): Helm — 공식 Chart 사용
  • 둘 다 쓰기: Helm으로 설치한 Chart를 Kustomize로 후처리 가능 (helm template | kustomize)

8. 실전 트러블슈팅

# 최종 결과물 미리보기 (적용 전 필수)
kubectl kustomize overlays/prod

# 특정 리소스만 확인
kubectl kustomize overlays/prod | grep -A 20 "kind: Deployment"

# dry-run으로 서버 측 검증
kubectl apply -k overlays/prod --dry-run=server

# diff로 변경사항 확인
kubectl diff -k overlays/prod

# 흔한 실수: base의 리소스가 resources에 누락
# Error: accumulating resources: ... not found
# → kustomization.yaml의 resources에 파일 추가 확인

# 흔한 실수: patch target이 매칭 안 됨
# → metadata.name이 base의 리소스 이름과 정확히 일치하는지 확인
# → namePrefix/nameSuffix 적용 전 이름으로 매칭

9. 운영 체크리스트

항목 권장 사항 위반 시 증상
디렉터리 구조 base/ + overlays/{env}/ 분리 환경별 YAML 복사 → 동기화 실패
ConfigMap 해시 Generator + suffix hash 활성화 설정 변경해도 Pod이 재시작 안 됨
이미지 태그 images 필드로 오버라이드 base에 :latest가 prod에 배포됨
적용 전 검증 kubectl diff -k로 변경사항 확인 의도치 않은 리소스 삭제/변경
공통 패치 Components로 모듈화 같은 패치를 여러 overlay에 복사
Secret 관리 secretGenerator + 외부 시크릿 관리 평문 Secret이 Git에 커밋

마무리 — Kustomize는 YAML의 Git이다

Kustomize의 철학은 단순하다. 원본 YAML은 건드리지 않고, 환경별 차이만 overlay로 선언한다. base가 Git의 main 브랜치라면, overlay는 환경별 브랜치의 diff와 같다.

핵심은 세 가지다. 첫째, base에는 환경 무관한 기본값만 두고 overlay에서 덮어써라. 둘째, configMapGenerator의 해시 suffix로 설정 변경 시 자동 롤아웃을 보장하라. 셋째, Components로 재사용 가능한 패치 세트를 모듈화하여 overlay 간 중복을 제거하라. kubectl에 내장된 도구만으로 환경별 배포를 깔끔하게 관리할 수 있다.

위로 스크롤
WordPress Appliance - Powered by TurnKey Linux