Helm 심화 — Chart 구조, 템플릿 문법, 릴리즈 관리까지
Deployment, Service, ConfigMap, Ingress... 리소스마다 YAML을 작성하고 환경별로 관리하려면 파일이 끝없이 늘어나는데, 더 좋은 방법은 없을까요?
Helm은 Kubernetes의 패키지 매니저입니다. 여러 리소스를 하나의 Chart로 묶고, 환경별 설정은 values로 분리하며, 버전 관리와 롤백까지 제공합니다. 복잡한 애플리케이션을 한 줄의 명령어로 설치하고 관리할 수 있게 해줍니다.
Chart 구조
my-app/
├── Chart.yaml # Chart 메타데이터
├── values.yaml # 기본 설정값
├── charts/ # 의존성 Chart (서브차트)
├── templates/ # Kubernetes 매니페스트 템플릿
│ ├── _helpers.tpl # 재사용 템플릿 함수
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── hpa.yaml
│ └── NOTES.txt # 설치 후 출력 메시지
└── .helmignore # 패키징 제외 파일
Chart.yaml
apiVersion: v2
name: my-app
description: 나의 웹 애플리케이션
version: 1.2.0 # Chart 버전
appVersion: "2.0.0" # 애플리케이션 버전
type: application # application 또는 library
dependencies:
- name: postgresql
version: "12.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
values.yaml
# 기본 설정값 — 템플릿에서 .Values로 참조
replicaCount: 3
image:
repository: my-app
tag: "2.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
hostname: app.example.com
tls: true
리소스 제한, 오토스케일링, 서브차트 설정도 같은 values.yaml에 정의하여 환경별로 오버라이드할 수 있습니다.
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilization: 70
postgresql:
enabled: true
auth:
database: mydb
Go 템플릿 문법
Helm 템플릿은 Go의 text/template 패키지를 사용합니다.
기본 문법
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template 섹션에서는 .Values와 .Chart 객체를 활용해 이미지, 포트, 리소스 등을 동적으로 생성합니다.
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 8080
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
자주 쓰는 문법
| 문법 | 설명 | 예제 |
|---|---|---|
{{ .Values.x }} | values 참조 | {{ .Values.replicaCount }} |
{{ .Release.Name }} | 릴리즈 이름 | my-release |
{{ .Chart.Name }} | Chart 이름 | my-app |
{{- if }}...{{- end }} | 조건문 | {{- if .Values.ingress.enabled }} |
{{- range }}...{{- end }} | 반복문 | {{- range .Values.env }} |
{{- with }}...{{- end }} | 스코프 변경 | {{- with .Values.resources }} |
{{ include "x" . }} | 헬퍼 함수 호출 | {{ include "my-app.labels" . }} |
{{ toYaml . }} | YAML 변환 | {{ toYaml .Values.resources }} |
| ` | nindent N` | N칸 들여쓰기 |
| ` | default "x"` | 기본값 |
| ` | quote` | 따옴표 추가 |
_helpers.tpl
재사용 가능한 템플릿을 정의합니다.
# templates/_helpers.tpl
{{- define "my-app.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
핵심 명령어
# 설치
helm install my-release ./my-app
helm install my-release ./my-app -f values-prod.yaml
helm install my-release ./my-app --set replicaCount=5
# 업그레이드
helm upgrade my-release ./my-app -f values-prod.yaml
# 설치 또는 업그레이드 (CI/CD에서 자주 사용)
helm upgrade --install my-release ./my-app -f values-prod.yaml
# 릴리즈 목록
helm list -n production
# 릴리즈 이력
helm history my-release
릴리즈 관리와 디버깅에 사용하는 명령어들입니다.
# 롤백
helm rollback my-release 2 # revision 2로 롤백
# 삭제
helm uninstall my-release
# 템플릿 확인 (설치 없이)
helm template my-release ./my-app -f values-prod.yaml
# 문법 검증
helm lint ./my-app
# Chart 패키징
helm package ./my-app
Values 오버라이드
# 파일로 오버라이드
helm install my-release ./my-app -f values-prod.yaml -f values-secret.yaml
# 명령줄로 오버라이드 (최우선)
helm install my-release ./my-app --set image.tag=v2.1.0
# 우선순위: --set > 마지막 -f 파일 > 이전 -f 파일 > values.yaml
환경별 values 파일 패턴
my-app/
├── values.yaml # 기본값
├── values-dev.yaml # 개발 환경 오버라이드
├── values-staging.yaml # 스테이징 환경
└── values-prod.yaml # 프로덕션 환경
# values-prod.yaml
replicaCount: 5
image:
tag: "2.0.0"
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
memory: 1Gi
ingress:
hostname: app.production.example.com
Helm Hooks
릴리즈 라이프사이클의 특정 시점에 Job 등을 실행합니다.
# templates/db-migrate.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-app.fullname" . }}-migrate
annotations:
"helm.sh/hook": pre-upgrade # 업그레이드 전에 실행
"helm.sh/hook-weight": "0" # 실행 순서 (작은 수 먼저)
"helm.sh/hook-delete-policy": hook-succeeded # 성공 시 Job 삭제
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["./migrate", "--up"]
Hook 타입
| Hook | 실행 시점 |
|---|---|
pre-install | 설치 전 |
post-install | 설치 후 |
pre-upgrade | 업그레이드 전 |
post-upgrade | 업그레이드 후 |
pre-delete | 삭제 전 |
post-delete | 삭제 후 |
pre-rollback | 롤백 전 |
post-rollback | 롤백 후 |
test | helm test 실행 시 |
Chart 리포지토리
# 리포지토리 추가
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
# Chart 검색
helm search repo nginx
# 리포지토리에서 설치
helm install my-nginx bitnami/nginx
# OCI 레지스트리 (Helm 3.8+)
helm push my-app-1.0.0.tgz oci://myregistry.io/charts
helm install my-release oci://myregistry.io/charts/my-app --version 1.0.0
실무 팁
- **
helm template으로 먼저 확인 **: 설치 전에 생성될 YAML을 반드시 검토하세요 - **
helm diff플러그인 **: 업그레이드 전 변경사항을 diff로 확인합니다 - **Chart 버전과 앱 버전을 분리 **: Chart.yaml의 version과 appVersion을 구분하세요
- ** 시크릿은 values에 넣지 마세요 **: Sealed Secrets이나 ESO와 조합하세요
# helm-diff 플러그인 설치
helm plugin install https://github.com/databus23/helm-diff
# 업그레이드 전 변경 확인
helm diff upgrade my-release ./my-app -f values-prod.yaml
주의할 점
1. helm upgrade --install 시 values 파일을 빠뜨리면 기존 설정이 기본값으로 덮어씌워진다
Helm은 upgrade할 때 이전 릴리즈의 values를 자동으로 유지하지 않습니다. -f values-prod.yaml을 빼먹으면 chart의 기본 values로 배포되어 프로덕션 설정(replica 수, 리소스, 환경변수 등)이 날아갑니다. --reuse-values 플래그를 쓸 수도 있지만, chart 버전이 바뀌면 새 기본값이 적용되지 않는 부작용이 있으므로 항상 명시적으로 values 파일을 지정하세요.
2. Chart 의존성 업데이트 없이 배포하면 구 버전 서브차트가 사용된다
Chart.yaml에서 의존성 버전을 올렸더라도 helm dependency update를 실행하지 않으면 charts/ 디렉토리에 캐시된 구 버전이 그대로 사용됩니다. CI 파이프라인에서 helm dependency build를 빌드 스텝에 반드시 포함하세요.
3. 템플릿의 들여쓰기 오류가 런타임에서야 발견된다
Helm 템플릿에서 {{ .Values.config | nindent 4 }}의 들여쓰기를 잘못 설정하면 유효하지 않은 YAML이 생성됩니다. helm template이나 helm install --dry-run으로 문법은 통과하지만, 실제 Kubernetes가 적용할 때 파싱 에러가 납니다. 배포 전에 helm template | kubectl apply --dry-run=server -f -로 서버 측 검증까지 하세요.
정리
Helm은 복잡한 Kubernetes 리소스를 Chart로 패키징하고, values로 환경별 설정을 분리하며, 릴리즈 관리와 롤백을 제공합니다. Go 템플릿 문법으로 유연한 YAML 생성이 가능하고, hooks로 배포 전후의 작업을 자동화할 수 있습니다. helm template과 helm diff를 활용하면 배포 실수를 크게 줄일 수 있습니다.