목차
- Helm Chart를 제대로 쓰고 있는 게 맞나요?
- Helm이 뭔지 다시 한번 짚고 가기
- Chart 구조를 제대로 잡는 것부터 시작
- 실전 배포 전략: 이것만 지켜도 반은 성공
- 1. Chart.yaml 버전 관리를 철저하게
- 2. _helpers.tpl로 중복 제거하기
- 3. 시크릿 관리 — 절대 Chart에 넣지 마세요
- 4. PodDisruptionBudget과 HPA는 프로덕션 필수
- ⚠️ 실제로 겪었던 트러블슈팅 사례
- 문제 1: helm upgrade 후 롤백이 안 되는 상황
- 문제 2: helm diff 없이 배포했다가 낭패
- 문제 3: 네임스페이스 간 의존성 충돌
- 배포 결과 검증하기
- Helm Chart 관리 전략 정리
- 마무리: Helm은 도구, 전략은 여러분 몫
Helm Chart를 제대로 쓰고 있는 게 맞나요?
솔직히 말씀드리면, 저도 처음 Helm을 쓰기 시작했을 때는 그냥 helm install 하나로 모든 게 해결된다고 생각했거든요. 차트 가져다 쓰고, values 파일 조금 수정하고, 배포하면 끝. 근데 이게 개발 환경에서는 통했는데, 프로덕션에 올리는 순간 문제가 터지기 시작하더라고요.
롤백이 안 된다, 시크릿이 차트에 하드코딩돼 있다, 팀원이 values 파일을 잘못 수정해서 서비스가 내려갔다... 이런 일들을 겪으면서 "아, Helm Chart 베스트 프랙티스라는 게 괜히 있는 게 아니구나" 싶었습니다. 그래서 오늘은 13년 동안 쿠버네티스 인프라를 운영하면서 직접 삽질하며 정리한 Helm 배포 전략과 관리 노하우를 공유해드리려고 해요.
쿠버네티스 Helm을 처음 시작하신 분들도, 이미 쓰고 계신데 뭔가 찜찜한 분들도 도움이 될 거예요.
▲ Helm Chart가 쿠버네티스 클러스터에 배포되는 전체 흐름 — Chart Repository부터 Release 관리까지 한눈에 볼 수 있습니다.
Helm이 뭔지 다시 한번 짚고 가기
아마 대부분 아시겠지만, 한 번 정리하고 넘어갈게요. Helm(헬름)은 쿠버네티스의 패키지 매니저입니다. 쉽게 말해, apt나 yum처럼 쿠버네티스 애플리케이션을 패키징하고 배포하는 도구예요.
핵심 개념 세 가지만 기억하시면 됩니다.
- Chart(차트): 쿠버네티스 리소스를 정의하는 파일 묶음. npm의 package.json 같은 개념이에요.
- Release(릴리스): 클러스터에 설치된 Chart의 인스턴스. 같은 Chart를 여러 번 설치하면 각각 다른 Release가 됩니다.
- Repository(레포지토리): Chart를 저장하고 공유하는 저장소. Docker Hub의 Chart 버전이라고 보시면 돼요.
Helm 3 기준으로 설명드릴 거예요. Helm 2는 Tiller(틸러)라는 서버 컴포넌트가 있었는데, 보안 문제로 Helm 3에서 완전히 제거됐거든요. 혹시 아직 Helm 2를 쓰시는 분 계시면, 진짜 빨리 마이그레이션하세요.
Chart 구조를 제대로 잡는 것부터 시작
프로덕션 Helm 관리의 첫 번째 원칙은 Chart 디렉토리 구조를 일관성 있게 가져가는 것입니다. 처음부터 잘 잡아두지 않으면 나중에 수습하기가 정말 힘들어요.
my-app/
├── Chart.yaml # 차트 메타데이터 (이름, 버전, 의존성)
├── values.yaml # 기본 설정값
├── values-dev.yaml # 개발 환경 오버라이드
├── values-staging.yaml # 스테이징 환경 오버라이드
├── values-prod.yaml # 프로덕션 환경 오버라이드
├── templates/
│ ├── _helpers.tpl # 재사용 가능한 템플릿 함수
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── hpa.yaml # HorizontalPodAutoscaler
│ ├── pdb.yaml # PodDisruptionBudget
│ └── NOTES.txt # 설치 후 출력되는 안내 메시지
└── charts/ # 의존 차트들 (서브차트)
여기서 핵심은 환경별 values 파일을 분리하는 거예요. 하나의 values.yaml에 모든 환경 설정을 때려넣는 분들이 많은데, 그러면 관리가 안 됩니다. 제가 실제로 운영하는 방식은 기본값은 values.yaml에 두고, 환경별 차이점만 오버라이드 파일에 담아두는 거죠.
# values.yaml (기본값)
replicaCount: 1
image:
repository: my-registry/my-app
tag: "latest" # CI/CD에서 덮어씁니다
pullPolicy: IfNotPresent
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 10
targetCPUUtilizationPercentage: 70
podDisruptionBudget:
enabled: false
minAvailable: 1
# values-prod.yaml (프로덕션 오버라이드)
replicaCount: 3
image:
pullPolicy: Always
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: 2000m
memory: 2Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20
podDisruptionBudget:
enabled: true
minAvailable: 2
배포할 때는 이렇게 쓰면 됩니다.
# 프로덕션 배포
helm upgrade --install my-app ./my-app \
-f values.yaml \
-f values-prod.yaml \
--namespace production \
--create-namespace \
--set image.tag=${IMAGE_TAG}
💡 팁: --install 플래그를 함께 쓰면 없으면 설치하고, 있으면 업그레이드합니다. CI/CD 파이프라인에서 정말 유용하게 쓰이는 옵션이더라고요.
실전 배포 전략: 이것만 지켜도 반은 성공
▲ GitOps 기반 Helm 배포 파이프라인 — Git push부터 프로덕션 릴리스까지의 자동화 흐름을 보여줍니다.
1. Chart.yaml 버전 관리를 철저하게
Chart.yaml에서 version과 appVersion을 구분하는 게 중요합니다. 처음엔 저도 이걸 같은 거라고 생각했는데, 아니더라고요.
apiVersion: v2
name: my-app
description: My Application Helm Chart
type: application
version: 1.3.0 # Chart 자체의 버전 (Chart 구조가 바뀌면 올림)
appVersion: "2.1.4" # 실제 애플리케이션 버전
dependencies:
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
SemVer(시맨틱 버저닝)를 반드시 지켜주세요. Chart 구조가 바뀌면 version을, 앱 소스만 바뀌면 appVersion만 올리는 습관을 들이면 나중에 롤백할 때 정말 편합니다.
2. _helpers.tpl로 중복 제거하기
템플릿 파일에서 같은 라벨, 같은 셀렉터를 매번 복붙하고 계신 분 계신가요? 저 예전에 그랬거든요. 나중에 앱 이름 하나 바꾸려고 파일을 10개 수정한 적이 있었는데, 그때 _helpers.tpl의 소중함을 알았습니다.
# templates/_helpers.tpl
{{/*
공통 라벨 정의
*/}}
{{- define "my-app.labels" -}}
helm.sh/chart: {{ include "my-app.chart" . }}
{{ include "my-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
셀렉터 라벨
*/}}
{{- 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 }}
3. 시크릿 관리 — 절대 Chart에 넣지 마세요
⚠️ 경고: 이게 진짜 중요합니다. DB 패스워드, API 키, 인증서 같은 민감한 정보를 values.yaml이나 Chart에 직접 넣으면 안 돼요. Git에 올라가는 순간 끝입니다.
제가 추천하는 방법은 두 가지예요.
방법 1: 외부 시크릿 참조
# templates/deployment.yaml
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-app-secrets # 별도로 생성된 Secret
key: db-password
방법 2: Helm Secrets 플러그인 활용
# helm-secrets 플러그인 설치
helm plugin install https://github.com/jkroepke/helm-secrets
# secrets.yaml을 암호화 (SOPS + AWS KMS 또는 GPG 활용)
helm secrets encrypt secrets.yaml
# 배포 시 복호화하여 사용
helm secrets upgrade --install my-app ./my-app \
-f values.yaml \
-f secrets.yaml
저는 현재 AWS Secrets Manager와 External Secrets Operator를 조합해서 쓰고 있는데, 이 조합이 가장 깔끔하더라고요. 나중에 이 주제로 별도 글 하나 써볼게요.
4. PodDisruptionBudget과 HPA는 프로덕션 필수
롤링 업데이트 중에 서비스가 잠깐 내려간 경험 있으신가요? 저 처음에 그거 때문에 새벽에 전화 받았거든요. PDB(PodDisruptionBudget)를 설정하면 노드 드레인이나 업그레이드 중에도 최소 파드 수를 보장해줍니다.
# templates/pdb.yaml
{{- if .Values.podDisruptionBudget.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
minAvailable: {{ .Values.podDisruptionBudget.minAvailable }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
{{- end }}
# templates/hpa.yaml
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "my-app.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}
⚠️ 실제로 겪었던 트러블슈팅 사례
문제 1: helm upgrade 후 롤백이 안 되는 상황
배포했는데 문제가 생겨서 롤백하려고 했더니 이전 릴리스 히스토리가 없는 거예요. 알고 보니 --history-max 옵션을 설정 안 해서 히스토리가 날아가 있었고, 심지어 ConfigMap이 Helm 외부에서 직접 수정돼 있어서 상태가 꼬여 있었습니다.
# 릴리스 히스토리 최대 10개 유지 (기본값 10)
helm upgrade --install my-app ./my-app \
--history-max 10 \
--atomic \
--timeout 5m0s
# 롤백 방법
helm history my-app -n production # 히스토리 확인
helm rollback my-app 3 -n production # 3번 리비전으로 롤백
💡 팁: --atomic 플래그를 쓰면 배포 실패 시 자동으로 이전 상태로 롤백해줍니다. CI/CD에서 정말 유용하더라고요.
문제 2: helm diff 없이 배포했다가 낭패
values 파일을 수정하고 바로 배포했다가 예상치 못한 리소스가 변경된 적이 있었어요. 이후로는 반드시 helm-diff 플러그인을 써서 변경 사항을 먼저 확인합니다.
# helm-diff 플러그인 설치
helm plugin install https://github.com/databus23/helm-diff
# 배포 전 변경 사항 미리 확인
helm diff upgrade my-app ./my-app \
-f values.yaml \
-f values-prod.yaml \
-n production
문제 3: 네임스페이스 간 의존성 충돌
서브차트를 쓰다 보면 의존성 버전이 충돌하는 경우가 있어요. helm dependency update를 주기적으로 실행하고, Chart.lock 파일을 Git에 함께 커밋하는 걸 습관화하세요.
# 의존성 업데이트
helm dependency update ./my-app
# 의존성 목록 확인
helm dependency list ./my-app
배포 결과 검증하기
▲ Helm 릴리스 상태와 쿠버네티스 리소스 헬스를 한눈에 확인할 수 있는 모니터링 대시보드 예시입니다.
배포가 끝났다고 끝이 아닙니다. 제대로 됐는지 확인하는 과정이 중요해요.
# 릴리스 상태 확인
helm status my-app -n production
# 실제 렌더링된 매니페스트 확인
helm get manifest my-app -n production
# 적용된 values 확인
helm get values my-app -n production
# 전체 릴리스 목록
helm list -A
# 배포된 리소스 상태 확인
kubectl get all -l app.kubernetes.io/instance=my-app -n production
# 파드 로그 확인
kubectl logs -l app.kubernetes.io/name=my-app -n production --tail=100
저는 배포 후 항상 이 체크리스트를 확인합니다.
- 모든 파드가 Running 상태인지 확인
- Readiness Probe가 통과했는지 확인
- HPA가 올바르게 연결됐는지 확인
- Ingress가 정상 응답하는지 확인
- 에러 로그가 없는지 확인
Helm Chart 관리 전략 정리
▲ 프로덕션 Helm Chart 관리를 위한 핵심 베스트 프랙티스를 한눈에 정리한 요약 가이드입니다.
| 항목 | 나쁜 예 | 좋은 예 |
|---|---|---|
| 시크릿 관리 | values.yaml에 패스워드 직접 기입 | External Secrets 또는 helm-secrets 활용 |
| 환경 분리 | 단일 values.yaml에 모든 환경 설정 | 환경별 values 파일 분리 + 오버라이드 |
| 배포 전 검증 | 바로 helm upgrade 실행 | helm diff로 변경 사항 먼저 확인 |
| 롤백 준비 | 히스토리 관리 없이 배포 | --history-max 설정 + --atomic 플래그 |
| 가용성 보장 | PDB 없이 운영 | PodDisruptionBudget + HPA 설정 |
| 차트 버전 | version과 appVersion 혼용 | SemVer 기반으로 명확히 분리 |
| 중복 코드 | 각 템플릿에 라벨 직접 기입 | _helpers.tpl로 공통 템플릿 관리 |
마무리: Helm은 도구, 전략은 여러분 몫
Helm Chart 베스트 프랙티스를 정리하다 보니 꽤 길어졌네요. 핵심만 다시 짚어드리면 이렇습니다.
- ✅ 환경별 values 파일 분리는 선택이 아닌 필수
- ✅ 시크릿은 절대 Chart에 직접 넣지 말 것
- ✅
--atomic+--history-max로 안전망 확보 - ✅ helm-diff로 배포 전 반드시 변경 사항 확인
- ✅ PDB와 HPA는 프로덕션 환경에서 필수 설정
- ✅ _helpers.tpl로 중복 템플릿 코드 제거
사실 이 모든 게 처음부터 완벽하게 되지는 않아요. 저도 수많은 삽질을 거쳐서 지금의 방식에 안착했거든요. 중요한 건 조금씩 개선해나가는 거예요.
다음 글에서는 Helm과 ArgoCD를 연동한 GitOps 배포 전략을 다뤄볼 예정입니다. Helm만으로는 아쉬운 부분들을 GitOps가 어떻게 채워주는지 실제 경험 기반으로 써볼게요. 기대해 주세요!
궁금한 점이나 다른 경험 있으시면 댓글로 편하게 남겨주세요. 같이 고민해봐요. 🎉
'IT > k8s' 카테고리의 다른 글
| [k8s] 쿠버네티스 Pod 트러블슈팅: CrashLoopBackOff, OOMKilled 완벽 해결 (0) | 2026.05.04 |
|---|---|
| [k8s] ArgoCD 프로덕션 환경 GitOps 베스트 프랙티스: 안정적인 배포와 보안 강화 (0) | 2026.04.30 |
| [k8s] 쿠버네티스 영구 스토리지: Longhorn vs Rook Ceph 비교 및 선택 가이드 (0) | 2026.04.27 |
| [k8s] 쿠버네티스 Ingress Controller 비교: Nginx, Traefik, HAProxy 선택 가이드 (0) | 2026.04.20 |
| [k8s] ArgoCD 멀티 클러스터 GitOps 배포 및 관리 전략 (1) | 2026.04.20 |
| [K8s] 쿠버네티스 Ingress Controller 비교: Nginx, Traefik, Gateway API 선택 가이드 (0) | 2026.04.18 |