Certified Kubernetes Application Developer (CKAD) #10 Kustomize: overlay 패턴, 환경별 매니페스트
#9 Helm에서 차트와 values로 같은 애플리케이션을 환경별로 배포하는 방법을 익혔습니다. Helm이 템플릿 엔진으로 매니페스트를 찍어 내는 도구라면, Kustomize는 정반대 접근입니다. 순수한 YAML을 그대로 두고, 그 위에 변형 규칙만 덧씌워 환경별 매니페스트를 만들어 냅니다. 템플릿 문법이나 {{ }} 같은 치환 표시가 전혀 없어서, base 매니페스트는 그 자체로 kubectl apply가 가능한 정상 YAML 입니다.
Kustomize가 CKAD에서 특히 중요한 이유는 kubectl에 내장되어 있다는 점입니다. 별도 바이너리 설치 없이 kubectl apply -k, kubectl kustomize로 곧장 쓸 수 있어서, 시험 환경에서 추가 셋업이 필요 없습니다. 이번 글에서는 kustomization.yaml의 핵심 필드, base/overlays 구조, 패치와 생성기, 그리고 빌드,적용 흐름을 실기 관점으로 정리하겠습니다.
Kustomize가 푸는 문제 #
같은 애플리케이션을 dev,staging,prod에 배포할 때, 환경마다 다른 것은 보통 일부 필드뿐입니다. replica 수, 이미지 태그, 네임스페이스, ConfigMap 값 정도가 다르고 나머지 구조는 동일합니다. 이 차이를 다루는 방법은 두 가지로 나뉩니다.
- 복사 후 수정. 환경별로 매니페스트를 통째로 복제하면, 공통 부분을 고칠 때마다 모든 사본을 따라 고쳐야 합니다. 누락이 곧 사고로 이어집니다.
- 변형 규칙 분리. 공통 매니페스트(base)를 한 벌만 두고, 환경별 차이는 작은 패치(overlay)로만 기술합니다. 공통 부분은 base 한 곳에서만 관리됩니다.
Kustomize는 후자입니다. base를 그대로 두고 overlay가 그 위에 변형을 덧씌우므로, 환경 간 중복이 사라집니다.
kustomization.yaml의 핵심 필드 #
Kustomize의 모든 동작은 kustomization.yaml 파일 하나에 선언됩니다. 이 파일이 있는 디렉터리가 빌드 단위입니다. 자주 쓰는 필드를 먼저 정리하겠습니다.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# 1) 입력 리소스 (YAML 파일 또는 다른 kustomize 디렉터리)
resources:
- deployment.yaml
- service.yaml
# 2) 모든 리소스에 네임스페이스 일괄 지정
namespace: my-app
# 3) 이름 앞뒤에 접두사/접미사 부착
namePrefix: dev-
nameSuffix: -v1
# 4) 모든 리소스에 라벨/어노테이션 공통 부착
commonLabels:
app: web
env: dev
commonAnnotations:
team: platform
# 5) 이미지 태그/이름 교체 (매니페스트를 건드리지 않고)
images:
- name: nginx
newName: nginx
newTag: "1.27"
# 6) Deployment 등의 replica 수 교체
replicas:
- name: web
count: 4각 필드의 역할은 다음과 같습니다.
| 필드 | 역할 |
|---|---|
resources | 빌드에 포함할 매니페스트 또는 하위 kustomize 디렉터리 |
namespace | 대상 리소스 전체에 네임스페이스 적용 |
namePrefix / nameSuffix | 리소스 이름에 접두사,접미사 부착(참조도 함께 갱신) |
commonLabels | 모든 리소스의 metadata.labels와 selector에 라벨 추가 |
commonAnnotations | 모든 리소스에 어노테이션 추가 |
images | 컨테이너 이미지 이름,태그를 빌드 시점에 교체 |
replicas | 지정한 워크로드의 replica 수 교체 |
commonLabels는 단순히 라벨만 붙이는 것이 아니라 Deployment의 selector.matchLabels와 Pod 템플릿의 라벨까지 함께 맞춰 줍니다. 그래서 직접 selector를 손으로 고칠 때 생기는 불일치 사고를 막아 줍니다.
base와 overlays 구조 #
실무와 시험에서 가장 흔한 형태는 base 디렉터리 하나와 환경별 overlay 디렉터리들로 나누는 구조입니다.
myapp/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ └── service.yaml
└── overlays/
├── dev/
│ ├── kustomization.yaml
│ └── replica-patch.yaml
└── prod/
├── kustomization.yaml
└── replica-patch.yamlbase/kustomization.yaml은 공통 리소스를 모읍니다.
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml각 overlay의 kustomization.yaml은 resources에 파일 대신 base 디렉터리 경로를 적습니다. 그러면 base의 모든 리소스를 끌어온 뒤, 같은 파일에 적힌 변형 규칙과 패치를 그 위에 덧씌웁니다.
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base # base를 통째로 참조
namespace: prod
namePrefix: prod-
replicas:
- name: web
count: 6이렇게 두면 base는 단 한 벌만 유지되고, dev,prod는 각자 필요한 차이만 선언합니다. base의 deployment.yaml을 고치면 두 환경에 동시에 반영됩니다.
패치: 일부 필드만 덮어쓰기 #
namePrefix 나 replicas 같은 전용 필드로 표현되지 않는 변경은 패치로 처리합니다. 패치는 base의 특정 필드만 골라 덮어쓰는 작은 조각입니다. 두 가지 방식이 있습니다.
patchesStrategicMerge (전략적 병합) #
원본과 같은 형태의 부분 YAML을 작성하면, Kustomize가 같은 키를 찾아 병합합니다. 사람이 읽기 쉬워 가장 자주 쓰입니다.
# overlays/prod/resource-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web # 어떤 리소스를 패치할지 식별
spec:
template:
spec:
containers:
- name: web
resources:
limits:
memory: 512Mi# overlays/prod/kustomization.yaml 일부
patchesStrategicMerge:
- resource-patch.yamlmetadata.name과 kind로 대상 리소스를 식별하고, 명시한 필드(여기서는 resources.limits.memory)만 base 위에 덮입니다. 나머지 필드는 base 값을 그대로 유지합니다.
patches (JSON6902) #
배열 요소를 콕 집어 추가,삭제하거나, 특정 경로의 값을 정밀하게 바꿀 때는 JSON6902 패치가 적합합니다. op(add,replace,remove)와 path로 동작을 지정합니다.
# overlays/prod/kustomization.yaml 일부
patches:
- target:
kind: Deployment
name: web
patch: |-
- op: replace
path: /spec/replicas
value: 6
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: LOG_LEVEL
value: infotarget으로 대상을 고르고, patch 안에 연산 목록을 적습니다. path의 /-는 배열 끝에 추가하라는 표기입니다. 전략적 병합이 표현하기 어려운 정밀한 조작에 씁니다.
생성기: ConfigMap과 Secret 자동 생성 #
Kustomize는 ConfigMap과 Secret을 매니페스트로 직접 쓰지 않고 생성기로 만들 수 있습니다. 생성기의 핵심 가치는 내용 해시 접미사입니다.
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=info
- TIMEOUT=30
files:
- app.properties # 파일 내용을 그대로 ConfigMap에 적재
secretGenerator:
- name: app-secret
literals:
- PASSWORD=s3cr3t생성기로 만든 ConfigMap은 app-config-7h8d9f2k4m처럼 내용으로 계산한 해시가 이름 뒤에 붙습니다. 내용이 바뀌면 해시가 바뀌어 이름이 달라지고, 이를 참조하는 Deployment의 Pod 템플릿도 새 이름을 가리키도록 자동으로 갱신됩니다. 그 결과 ConfigMap 값을 바꾸면 Pod가 자동으로 롤링 업데이트 됩니다.
이것은 손으로 작성한 ConfigMap의 고질적 문제를 해결합니다. ConfigMap만 바꾸면 기존 Pod는 변경을 모른 채 옛 값으로 계속 돌아가는데, 생성기는 이름 변경으로 강제 롤링을 유발하기 때문입니다. 해시 접미사를 끄고 싶다면 다음을 추가합니다.
generatorOptions:
disableNameSuffixHash: true빌드와 적용 #
Kustomize의 출력은 kubectl kustomize로 미리 볼 수 있습니다. 클러스터에 아무것도 적용하지 않고 최종 병합 결과 YAML만 표준 출력으로 뽑아냅니다.
# 빌드 결과를 출력만 (적용하지 않음)
k kustomize overlays/prod
# 파일로 저장해 검토
k kustomize overlays/prod > /tmp/prod-rendered.yaml검토가 끝났으면 -k 플래그로 곧장 적용합니다. 디렉터리 경로(kustomization.yaml이 있는 곳)를 인자로 줍니다.
# overlay를 빌드해 클러스터에 적용
k apply -k overlays/prod
# 적용 전 변경 사항 비교
k diff -k overlays/prod
# 삭제도 동일하게 -k로
k delete -k overlays/prod시험에서는 항상 k kustomize로 먼저 결과를 확인한 뒤 k apply -k 하는 순서를 권합니다. 패치가 의도대로 병합됐는지, namePrefix가 selector 참조까지 갱신했는지를 적용 전에 눈으로 검증할 수 있기 때문입니다.
Helm과의 차이 #
#9 Helm과 Kustomize는 같은 문제를 다른 방식으로 풉니다. Helm은 템플릿에 변수를 치환해 매니페스트를 생성하고, Kustomize는 완성된 YAML 위에 오버레이로 변형을 덧씌웁니다. Helm은 패키징,배포,롤백,의존성까지 포함한 패키지 매니저이고, Kustomize는 매니페스트 변형에 집중한 가벼운 도구입니다. 두 도구는 배타적이지 않아서, Helm 차트의 출력을 Kustomize로 한 번 더 변형하는 조합도 가능합니다.
전체 예제: base + dev overlay #
지금까지의 요소를 묶어 base 한 벌과 dev overlay 하나를 완성해 보겠습니다.
먼저 base 파일입니다.
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 1
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27
ports:
- containerPort: 80
envFrom:
- configMapRef:
name: app-config# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yamldev overlay는 base를 참조하면서 namePrefix 부착, replica 수 패치, ConfigMap 생성을 한꺼번에 선언합니다.
# overlays/dev/replica-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: dev
namePrefix: dev-
commonLabels:
env: dev
patchesStrategicMerge:
- replica-patch.yaml
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=debug
- TIMEOUT=10이제 빌드 결과를 확인하고 적용합니다.
# 병합 결과 확인
k kustomize overlays/dev
# 적용
k apply -k overlays/dev빌드 결과에서는 Deployment 이름이 dev-web, ConfigMap 이름이 dev-app-config-<해시>로 바뀌고, envFrom의 참조도 같은 해시 이름을 가리키도록 자동 갱신됩니다. replica는 패치로 2가 되고, 모든 리소스에 env: dev 라벨이 붙습니다. base 매니페스트는 단 한 줄도 직접 고치지 않았습니다.
시험 포인트 #
- kubectl 내장. Kustomize는 별도 설치 없이
k apply -k <디렉터리>,k kustomize <디렉터리>로 곧장 쓴다.-k의 인자는 파일이 아니라 kustomization.yaml이 있는 디렉터리다. - 빌드 미리보기.
k kustomize <디렉터리>는 클러스터를 건드리지 않고 최종 병합 YAML을 출력한다. 적용 전 검증 습관을 들인다. - resources가 base를 가리킨다. overlay의
resources에 base 디렉터리 경로를 넣으면 base 전체를 끌어온 뒤 변형을 덧씌운다. - patchesStrategicMerge vs patches. 같은 형태로 일부 필드를 덮어쓰면 전략적 병합, 배열 요소를 콕 집어 add,remove,replace 하면 JSON6902 다.
- 생성기 해시 롤링. configMapGenerator,secretGenerator는 내용 해시를 이름에 붙여, 값이 바뀌면 Pod가 자동 롤링된다. 막으려면
disableNameSuffixHash: true다. - commonLabels는 selector 까지 갱신한다. 직접 라벨을 손으로 붙일 때와 달리 selector 불일치가 생기지 않는다.
정리 #
이번 글에서 잡은 것:
- Kustomize는 템플릿 없는 오버레이 방식. base의 순수 YAML을 그대로 두고 변형 규칙만 덧씌워 환경별 매니페스트를 만든다.
- kustomization.yaml 핵심 필드. resources, namespace, namePrefix,nameSuffix, commonLabels,commonAnnotations, images, replicas.
- base/overlays 구조. base 한 벌을 두고 overlay가 base를 참조해 환경별 차이만 선언한다.
- 패치 두 종류. patchesStrategicMerge(부분 YAML 병합)와 patches(JSON6902 정밀 조작).
- 생성기. configMapGenerator,secretGenerator의 해시 접미사로 값 변경 시 자동 롤링.
- 빌드,적용.
k kustomize로 미리 보고k apply -k로 적용한다.
다음: Probes #
배포 도구 묶음(Helm,Kustomize)을 끝냈습니다. 다음은 배포한 애플리케이션이 살아 있는지, 트래픽을 받을 준비가 됐는지를 쿠버네티스에 알리는 신호로 넘어갑니다.
#11 Probes: liveness, readiness, startup (exec/HTTP/TCP)에서는 세 종류 probe의 역할 차이, exec,HTTP,TCP 세 가지 검사 방식, initialDelaySeconds,periodSeconds,failureThreshold 같은 타이밍 파라미터, 그리고 시험에서 자주 나오는 “probe 설정이 잘못돼 Pod가 계속 재시작하는” 유형까지 직접 만들어 보며 정리하겠습니다.