Helm이란?
Helm은 Kubernetes의 패키지 매니저입니다. YAML 매니페스트를 직접 관리하는 대신, Chart라는 템플릿 패키지로 애플리케이션을 정의하고 배포합니다. 하지만 기본적인 helm install만으로는 실전 운영이 어렵습니다. 이 글에서는 Helm 템플릿의 Go template 문법, 헬퍼 함수, 조건부 렌더링, 서브차트 의존성까지 심화 패턴을 다룹니다.
Chart 디렉터리 구조
Helm Chart의 표준 구조를 먼저 이해해야 합니다.
my-app/
├── Chart.yaml # 차트 메타데이터 (이름, 버전, 의존성)
├── values.yaml # 기본 설정값
├── values-prod.yaml # 환경별 오버라이드
├── templates/
│ ├── _helpers.tpl # 재사용 헬퍼 함수
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── serviceaccount.yaml
│ ├── NOTES.txt # 설치 후 안내 메시지
│ └── tests/
│ └── test-connection.yaml
└── charts/ # 서브차트 (의존성)
Go Template 핵심 문법
Helm 템플릿은 Go의 text/template 엔진을 기반으로 합니다. 핵심 문법을 정리합니다.
# 1. 값 참조 — .Values, .Release, .Chart
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}
labels:
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
spec:
replicas: {{ .Values.replicaCount }}
# 2. 파이프라인 — 값을 함수에 전달
# quote: 문자열을 따옴표로 감싸기
# upper/lower: 대소문자 변환
# default: 기본값 설정
annotations:
description: {{ .Values.description | default "No description" | quote }}
env: {{ .Values.env | upper }}
# 3. 공백 제어 — {{- 와 -}} 로 앞뒤 공백 제거
metadata:
labels:
{{- range $key, $val := .Values.labels }}
{{ $key }}: {{ $val | quote }}
{{- end }}
조건부 렌더링과 반복
환경에 따라 리소스를 선택적으로 생성하거나, 동적 목록을 렌더링하는 패턴입니다. K8s ResourceQuota 같은 리소스도 조건부로 생성할 수 있습니다.
# if/else — 조건부 리소스 생성
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "my-app.fullname" . }}
{{- with .Values.ingress.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
{{- if .Values.ingress.tls }}
tls:
{{- range .Values.ingress.tls }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
{{- range .Values.ingress.hosts }}
- host: {{ .host | quote }}
http:
paths:
{{- range .paths }}
- path: {{ .path }}
pathType: {{ .pathType }}
backend:
service:
name: {{ include "my-app.fullname" $ }}
port:
number: {{ .port | default 80 }}
{{- end }}
{{- end }}
{{- end }}
# with — 스코프 변경으로 반복 참조 줄이기
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 2 }}
{{- end }}
# range + 인덱스
env:
{{- range $i, $env := .Values.extraEnv }}
- name: {{ $env.name }}
value: {{ $env.value | quote }}
{{- end }}
_helpers.tpl 헬퍼 함수 설계
재사용 가능한 템플릿 조각을 define으로 정의하고 include로 호출합니다. 이것이 Helm 차트의 DRY 원칙 핵심입니다.
# templates/_helpers.tpl
# 차트 이름 (63자 제한, DNS 호환)
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
# 풀네임: release-chart (릴리스 충돌 방지)
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
# 공통 레이블 — 모든 리소스에 일관 적용
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
{{- end }}
# 셀렉터 레이블 — Deployment/Service 매칭용
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
# ServiceAccount 이름
{{- define "my-app.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "my-app.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
# 이미지 전체 경로 (레지스트리 + 리포 + 태그)
{{- define "my-app.image" -}}
{{- $registry := .Values.image.registry | default "docker.io" -}}
{{- $repo := .Values.image.repository -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion -}}
{{- printf "%s/%s:%s" $registry $repo $tag -}}
{{- end }}
values.yaml 설계 패턴
values.yaml은 차트의 인터페이스입니다. 구조적으로 잘 설계해야 사용자가 쉽게 커스터마이징할 수 있습니다.
# values.yaml — 구조적 설계 예시
replicaCount: 2
image:
registry: docker.io
repository: myorg/my-app
tag: "" # 빈 문자열 → Chart.AppVersion 사용
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 3000
ingress:
enabled: false
className: nginx
annotations: {}
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls: []
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilization: 70
targetMemoryUtilization: 80
# 환경 변수 주입
env:
NODE_ENV: production
LOG_LEVEL: info
# 시크릿 참조
envFromSecrets: []
# - secretName: db-credentials
# key: DATABASE_URL
# envName: DATABASE_URL
# 프로브 설정
probes:
liveness:
path: /health
initialDelaySeconds: 30
periodSeconds: 10
readiness:
path: /ready
initialDelaySeconds: 5
periodSeconds: 5
serviceAccount:
create: true
name: ""
annotations: {}
환경별 values 오버라이드
기본 values.yaml 위에 환경별 파일을 겹쳐서 적용하는 것이 운영의 핵심입니다.
# values-prod.yaml — 프로덕션 오버라이드
replicaCount: 4
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
autoscaling:
enabled: true
minReplicas: 4
maxReplicas: 20
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
hosts:
- host: api.myservice.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: api-tls
hosts:
- api.myservice.com
# 배포 명령:
# helm upgrade --install my-app ./my-app
# -f values.yaml
# -f values-prod.yaml
# --namespace production
# --set image.tag=v1.5.2
Deployment 템플릿 완성본
위의 모든 패턴을 결합한 실전 Deployment 템플릿입니다. K8s Drain·PDB와 함께 사용하면 무중단 배포가 가능합니다.
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 }}
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
annotations:
# ConfigMap 변경 시 자동 롤아웃 트리거
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "my-app.serviceAccountName" . }}
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
containers:
- name: {{ .Chart.Name }}
image: {{ include "my-app.image" . }}
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
# 환경 변수: values.env 맵에서 자동 생성
env:
{{- range $key, $val := .Values.env }}
- name: {{ $key }}
value: {{ $val | quote }}
{{- end }}
{{- range .Values.envFromSecrets }}
- name: {{ .envName }}
valueFrom:
secretKeyRef:
name: {{ .secretName }}
key: {{ .key }}
{{- end }}
# 프로브
livenessProbe:
httpGet:
path: {{ .Values.probes.liveness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.liveness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
readinessProbe:
httpGet:
path: {{ .Values.probes.readiness.path }}
port: http
initialDelaySeconds: {{ .Values.probes.readiness.initialDelaySeconds }}
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
{{- with .Values.resources }}
resources:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
서브차트와 의존성 관리
Chart.yaml의 dependencies로 Redis, PostgreSQL 같은 인프라 컴포넌트를 서브차트로 포함할 수 있습니다.
# Chart.yaml
apiVersion: v2
name: my-app
version: 1.0.0
appVersion: "2.3.1"
dependencies:
- name: redis
version: "18.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled # values에서 on/off
- name: postgresql
version: "13.x.x"
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabled
# values.yaml에서 서브차트 설정
redis:
enabled: true
architecture: standalone
auth:
password: "my-redis-pass"
master:
resources:
requests:
cpu: 100m
memory: 128Mi
postgresql:
enabled: false # 외부 DB 사용 시 비활성화
# 의존성 설치
# helm dependency update ./my-app
# helm dependency build ./my-app
helm template로 디버깅
배포 전 렌더링 결과를 확인하는 습관은 필수입니다. 실수로 잘못된 YAML이 클러스터에 적용되는 것을 방지합니다.
# 전체 렌더링 확인
helm template my-release ./my-app -f values-prod.yaml
# 특정 템플릿만 확인
helm template my-release ./my-app -s templates/deployment.yaml
# 문법 검증
helm lint ./my-app -f values-prod.yaml
# dry-run으로 서버 사이드 검증 (클러스터 필요)
helm install my-release ./my-app --dry-run --debug
# 특정 값 오버라이드 테스트
helm template my-release ./my-app
--set replicaCount=5
--set image.tag=v2.0.0
--set ingress.enabled=true
실전 팁: ConfigMap 변경 자동 롤아웃
ConfigMap을 수정해도 Deployment가 자동으로 재시작되지 않는 것은 Kubernetes의 유명한 함정입니다. Helm 템플릿에서 sha256sum을 활용하면 이 문제를 해결할 수 있습니다.
# deployment.yaml의 pod template annotations
template:
metadata:
annotations:
# ConfigMap 내용이 바뀌면 해시가 달라져서 롤아웃 트리거
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "my-app.fullname" . }}
data:
{{- range $key, $val := .Values.env }}
{{ $key }}: {{ $val | quote }}
{{- end }}
{{- if .Values.configFiles }}
{{- range $filename, $content := .Values.configFiles }}
{{ $filename }}: |
{{- $content | nindent 4 }}
{{- end }}
{{- end }}
Helm 테스트 작성
배포 후 자동 검증을 위한 테스트 Pod를 정의할 수 있습니다.
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "my-app.fullname" . }}-test"
labels:
{{- include "my-app.labels" . | nindent 4 }}
annotations:
"helm.sh/hook": test
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
restartPolicy: Never
containers:
- name: wget
image: busybox:1.36
command: ['wget']
args:
- '--spider'
- '--timeout=5'
- 'http://{{ include "my-app.fullname" . }}:{{ .Values.service.port }}/health'
# 테스트 실행
# helm test my-release --namespace production
정리
Helm 템플릿은 단순한 YAML 생성기가 아니라, Kubernetes 배포의 추상화 레이어입니다. _helpers.tpl로 DRY 원칙을 지키고, 조건부 렌더링으로 환경별 차이를 흡수하며, 서브차트로 의존성을 관리합니다. helm template과 helm lint로 배포 전 검증을 습관화하고, sha256sum 트릭으로 ConfigMap 변경을 자동 감지하세요. 잘 설계된 Helm Chart는 팀 전체의 배포 생산성을 끌어올립니다.