Kubernetes 개발·운영 실전 5편 — Helm 실전 관리
Helm Chart 구조 설계, values 파일로 환경별 설정 분리, 안전한 배포와 롤백 전략. 프로덕션에서 Helm을 체계적으로 관리하는 실전 가이드.
TestForge Team ·
Helm이 필요한 이유
Kubernetes 매니페스트를 그냥 kubectl apply -f로 관리하면 한계가 옵니다.
- dev/staging/prod 환경마다 이미지 태그, replicas, 도메인이 다름
- 여러 파일에 걸쳐 반복되는 설정
- 어떤 버전이 클러스터에 배포됐는지 추적이 어려움
- 롤백 시 이전 상태로 돌아가는 방법이 불명확
Helm은 이 문제를 Chart(템플릿) + Values(환경별 설정) + Release(배포 이력) 구조로 해결합니다.
1. Chart 구조
my-app/
├── Chart.yaml # Chart 메타데이터
├── values.yaml # 기본값
├── values-dev.yaml # 개발 환경 오버라이드
├── values-staging.yaml # 스테이징 오버라이드
├── values-prod.yaml # 프로덕션 오버라이드
└── templates/
├── _helpers.tpl # 공통 helper 함수
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── configmap.yaml
├── hpa.yaml
└── NOTES.txt # 배포 후 출력 메시지
Chart.yaml
apiVersion: v2
name: my-app
description: My Application Helm Chart
type: application
version: 0.3.1 # Chart 버전 (SemVer)
appVersion: "1.2.3" # 앱 버전
dependencies:
- name: redis
version: "19.x.x"
repository: https://charts.bitnami.com/bitnami
condition: redis.enabled
2. values.yaml — 기본값 설계
# values.yaml
replicaCount: 1
image:
repository: my-registry/my-app
tag: "" # Chart.yaml의 appVersion 사용 (비워두면 자동)
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: false
className: nginx
host: ""
tls: false
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 70
env: {}
# DATABASE_URL: ""
# REDIS_URL: ""
redis:
enabled: false
nodeSelector: {}
tolerations: []
affinity: {}
values-prod.yaml — 프로덕션 오버라이드
replicaCount: 3
image:
pullPolicy: Always
ingress:
enabled: true
host: api.example.com
tls: true
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 2Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
redis:
enabled: true
auth:
enabled: true
3. 템플릿 작성 실전
_helpers.tpl — 공통 이름 함수
{{/* 앱 이름 */}}
{{- define "my-app.name" -}}
{{- .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/* 전체 이름 (릴리즈명-차트명) */}}
{{- 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: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
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:
app.kubernetes.io/name: {{ include "my-app.name" . }}
template:
metadata:
labels:
{{- include "my-app.labels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.targetPort }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- if .Values.env }}
env:
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
{{- end }}
4. 배포 명령 실전
설치
# 기본 설치
helm install my-release ./my-app
# 환경별 values 적용
helm install my-release ./my-app \
-f values.yaml \
-f values-prod.yaml \
--namespace production \
--create-namespace
# 이미지 태그 동적 주입 (CI/CD에서 사용)
helm install my-release ./my-app \
-f values-prod.yaml \
--set image.tag=1.2.3 \
--namespace production
업그레이드
# 업그레이드 (없으면 설치)
helm upgrade --install my-release ./my-app \
-f values-prod.yaml \
--set image.tag=$NEW_TAG \
--namespace production \
--atomic \ # 실패 시 자동 롤백
--timeout 5m \
--wait # 모든 Pod Ready 대기
배포 이력 확인 및 롤백
# 배포 이력 확인
helm history my-release -n production
REVISION STATUS CHART APP VERSION DESCRIPTION
1 superseded my-app-0.3.0 1.2.1 Initial install
2 superseded my-app-0.3.1 1.2.2 Upgrade complete
3 deployed my-app-0.3.1 1.2.3 Upgrade complete
# 이전 버전으로 롤백
helm rollback my-release 2 -n production
# 한 단계 전으로 롤백
helm rollback my-release -n production
5. Helm Secrets — 민감 정보 관리
Helm values에 직접 비밀번호를 넣는 것은 위험합니다. 두 가지 방법을 사용합니다.
방법 1: 외부 Secret 참조
# values.yaml에는 Secret 이름만 기록
database:
secretName: db-credentials # 실제 값은 Kubernetes Secret에 저장
# deployment.yaml 템플릿
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: {{ .Values.database.secretName }}
key: url
방법 2: helm-secrets 플러그인 (SOPS)
helm plugin install https://github.com/jkroepke/helm-secrets
# 암호화된 values 파일 사용
helm secrets upgrade my-release ./my-app \
-f values-prod.yaml \
-f secrets.prod.yaml # SOPS로 암호화된 파일
6. Chart 테스트
# 템플릿 렌더링만 확인 (실제 배포 없음)
helm template my-release ./my-app -f values-prod.yaml
# 린트 검사
helm lint ./my-app -f values-prod.yaml
# 드라이런 (서버 검증 포함)
helm upgrade --install my-release ./my-app \
-f values-prod.yaml \
--dry-run \
--namespace production
7. Helm Repository 관리
# 공식 Charts 레포 추가
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
# 사용 가능한 버전 확인
helm search repo bitnami/redis --versions | head -10
# OCI 기반 레포 (최신 방식)
helm push my-app-0.3.1.tgz oci://registry.example.com/charts
helm install my-release oci://registry.example.com/charts/my-app --version 0.3.1
정리
| 단계 | 명령 | 설명 |
|---|---|---|
| 검증 | helm lint / helm template | 배포 전 문법/렌더링 확인 |
| 배포 | helm upgrade --install --atomic | 실패 시 자동 롤백 |
| 이력 | helm history | 버전별 배포 상태 |
| 롤백 | helm rollback | 특정 revision으로 복구 |
다음 편에서는 멀티 환경 배포 전략을 다룹니다. Kustomize overlay로 환경별 설정을 분리하고 ArgoCD App of Apps 패턴으로 전체 클러스터를 GitOps로 관리하는 방법을 정리합니다.