Split View: Helm Chart 작성과 배포 자동화 완벽 가이드
Helm Chart 작성과 배포 자동화 완벽 가이드
- 1. Helm이란?
- 2. Chart 디렉토리 구조
- 3. values.yaml 설계 패턴
- 4. Templates 작성
- 5. Helm Hooks
- 6. CI/CD 파이프라인 연동
- 7. Chart 테스트
- 8. 퀴즈

1. Helm이란?
Helm은 Kubernetes의 패키지 매니저입니다. 복잡한 Kubernetes 리소스를 하나의 Chart로 패키징하여 설치, 업그레이드, 롤백을 간편하게 관리할 수 있습니다.
왜 Helm인가?
- 반복적인 YAML 작성 제거
- 환경별 설정 분리 (dev/staging/production)
- 버전 관리와 롤백
- 의존성 관리
2. Chart 디렉토리 구조
mychart/
├── Chart.yaml # Chart 메타데이터
├── Chart.lock # 의존성 잠금 파일
├── values.yaml # 기본 설정값
├── values-prod.yaml # 프로덕션 오버라이드
├── templates/ # Kubernetes 매니페스트 템플릿
│ ├── _helpers.tpl # 공통 헬퍼 함수
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── serviceaccount.yaml
│ ├── NOTES.txt # 설치 후 안내 메시지
│ └── tests/
│ └── test-connection.yaml
├── charts/ # 의존성 Chart
└── .helmignore # 패키징 제외 파일
Chart.yaml 작성
apiVersion: v2
name: my-web-app
description: A production-ready web application
type: application
version: 1.2.0 # Chart 버전 (SemVer)
appVersion: '3.1.0' # 앱 버전
maintainers:
- name: youngjukim
email: fjvbn2003@gmail.com
dependencies:
- name: postgresql
version: '12.x.x'
repository: 'https://charts.bitnami.com/bitnami'
condition: postgresql.enabled
- name: redis
version: '17.x.x'
repository: 'https://charts.bitnami.com/bitnami'
condition: redis.enabled
3. values.yaml 설계 패턴
# values.yaml - 기본 설정
replicaCount: 2
image:
repository: myregistry.io/my-web-app
tag: '' # Chart appVersion 사용
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: app-tls
hosts:
- app.example.com
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
postgresql:
enabled: true
auth:
database: myapp
username: appuser
redis:
enabled: false
4. Templates 작성
_helpers.tpl - 공통 헬퍼
{{- define "mychart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "mychart.labels" -}}
helm.sh/chart: {{ include "mychart.chart" . }}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
envFrom:
- configMapRef:
name: {{ include "mychart.fullname" . }}-config
5. Helm Hooks
Hook을 사용하면 릴리스 라이프사이클의 특정 시점에 작업을 실행할 수 있습니다:
# templates/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-db-migrate
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command: ["python", "manage.py", "migrate"]
envFrom:
- secretRef:
name: {{ include "mychart.fullname" . }}-db-secret
backoffLimit: 3
Hook 종류
| Hook | 실행 시점 |
|---|---|
| pre-install | 설치 전 |
| post-install | 설치 후 |
| pre-upgrade | 업그레이드 전 |
| post-upgrade | 업그레이드 후 |
| pre-rollback | 롤백 전 |
| pre-delete | 삭제 전 |
| test | helm test 실행 시 |
6. CI/CD 파이프라인 연동
GitHub Actions + Helm
# .github/workflows/deploy.yml
name: Deploy Helm Chart
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ap-northeast-2
- name: Update kubeconfig
run: aws eks update-kubeconfig --name my-cluster
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.14.0
- name: Helm lint
run: helm lint ./charts/my-web-app
- name: Helm upgrade
run: |
helm upgrade --install my-app ./charts/my-web-app \
--namespace production \
--create-namespace \
-f ./charts/my-web-app/values-prod.yaml \
--set image.tag=${{ github.sha }} \
--wait --timeout 5m
helm template로 로컬 검증
# 렌더링된 매니페스트 확인
helm template my-release ./mychart -f values-prod.yaml
# 특정 값 오버라이드
helm template my-release ./mychart --set replicaCount=3
# kubeval로 유효성 검증
helm template my-release ./mychart | kubeval --strict
7. Chart 테스트
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: curl
image: curlimages/curl:8.5.0
command: ['curl']
args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}/healthz']
restartPolicy: Never
# 테스트 실행
helm test my-release -n production
8. 퀴즈
Q1: Chart.yaml에서 version과 appVersion의 차이는?
- version: Chart 자체의 버전입니다. Chart 구조, 템플릿, values가 변경될 때 올립니다. SemVer를 따릅니다.
- appVersion: Chart가 배포하는 애플리케이션의 버전입니다. 앱 코드가 변경될 때 올립니다.
예: Chart version 1.2.0이 appVersion 3.1.0을 배포할 수 있습니다.
Q2: Helm Hook의 hook-weight는 어떤 역할을 하나요?
hook-weight는 같은 타입의 hook이 여러 개 있을 때 실행 순서를 결정합니다. 낮은 숫자가 먼저 실행됩니다.
예: weight -5인 DB 마이그레이션이 weight 0인 캐시 초기화보다 먼저 실행됩니다.
Q3: deployment.yaml에서 checksum/config 어노테이션의 용도는?
ConfigMap이 변경되면 sha256sum이 바뀌어 Pod의 annotation이 달라집니다. 이로 인해 Deployment가 rolling update를 트리거합니다.
ConfigMap만 변경하면 기본적으로 Pod가 재시작되지 않는 문제를 해결하는 패턴입니다.
The Complete Guide to Helm Chart Authoring and Deployment Automation
- 1. What is Helm?
- 2. Chart Directory Structure
- 3. values.yaml Design Patterns
- 4. Writing Templates
- 5. Helm Hooks
- 6. CI/CD Pipeline Integration
- 7. Chart Testing
- 8. Quiz
- Quiz

1. What is Helm?
Helm is a package manager for Kubernetes. It packages complex Kubernetes resources into a single Chart, making installation, upgrades, and rollbacks easy to manage.
Why Helm?
- Eliminates repetitive YAML writing
- Separates configuration by environment (dev/staging/production)
- Version management and rollback
- Dependency management
2. Chart Directory Structure
mychart/
├── Chart.yaml # Chart metadata
├── Chart.lock # Dependency lock file
├── values.yaml # Default configuration values
├── values-prod.yaml # Production overrides
├── templates/ # Kubernetes manifest templates
│ ├── _helpers.tpl # Common helper functions
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── hpa.yaml
│ ├── configmap.yaml
│ ├── secret.yaml
│ ├── serviceaccount.yaml
│ ├── NOTES.txt # Post-install instructions
│ └── tests/
│ └── test-connection.yaml
├── charts/ # Dependency charts
└── .helmignore # Files to exclude from packaging
Writing Chart.yaml
apiVersion: v2
name: my-web-app
description: A production-ready web application
type: application
version: 1.2.0 # Chart version (SemVer)
appVersion: '3.1.0' # Application version
maintainers:
- name: youngjukim
email: fjvbn2003@gmail.com
dependencies:
- name: postgresql
version: '12.x.x'
repository: 'https://charts.bitnami.com/bitnami'
condition: postgresql.enabled
- name: redis
version: '17.x.x'
repository: 'https://charts.bitnami.com/bitnami'
condition: redis.enabled
3. values.yaml Design Patterns
# values.yaml - Default configuration
replicaCount: 2
image:
repository: myregistry.io/my-web-app
tag: '' # Uses Chart appVersion
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: true
className: nginx
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: app.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: app-tls
hosts:
- app.example.com
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
autoscaling:
enabled: false
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 80
postgresql:
enabled: true
auth:
database: myapp
username: appuser
redis:
enabled: false
4. Writing Templates
_helpers.tpl - Common Helpers
{{- define "mychart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "mychart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{- define "mychart.labels" -}}
helm.sh/chart: {{ include "mychart.chart" . }}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{- define "mychart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "mychart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "mychart.fullname" . }}
labels:
{{- include "mychart.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "mychart.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "mychart.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: http
initialDelaySeconds: 15
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}
envFrom:
- configMapRef:
name: {{ include "mychart.fullname" . }}-config
5. Helm Hooks
Hooks allow you to execute tasks at specific points in the release lifecycle:
# templates/pre-install-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "mychart.fullname" . }}-db-migrate
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-weight": "-5"
"helm.sh/hook-delete-policy": before-hook-creation
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
command: ["python", "manage.py", "migrate"]
envFrom:
- secretRef:
name: {{ include "mychart.fullname" . }}-db-secret
backoffLimit: 3
Hook Types
| Hook | Execution Timing |
|---|---|
| pre-install | Before installation |
| post-install | After installation |
| pre-upgrade | Before upgrade |
| post-upgrade | After upgrade |
| pre-rollback | Before rollback |
| pre-delete | Before deletion |
| test | When helm test is run |
6. CI/CD Pipeline Integration
GitHub Actions + Helm
# .github/workflows/deploy.yml
name: Deploy Helm Chart
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
aws-region: ap-northeast-2
- name: Update kubeconfig
run: aws eks update-kubeconfig --name my-cluster
- name: Install Helm
uses: azure/setup-helm@v3
with:
version: v3.14.0
- name: Helm lint
run: helm lint ./charts/my-web-app
- name: Helm upgrade
run: |
helm upgrade --install my-app ./charts/my-web-app \
--namespace production \
--create-namespace \
-f ./charts/my-web-app/values-prod.yaml \
--set image.tag=${{ github.sha }} \
--wait --timeout 5m
Local Validation with helm template
# Check rendered manifests
helm template my-release ./mychart -f values-prod.yaml
# Override specific values
helm template my-release ./mychart --set replicaCount=3
# Validate with kubeval
helm template my-release ./mychart | kubeval --strict
7. Chart Testing
# templates/tests/test-connection.yaml
apiVersion: v1
kind: Pod
metadata:
name: "{{ include "mychart.fullname" . }}-test"
annotations:
"helm.sh/hook": test
spec:
containers:
- name: curl
image: curlimages/curl:8.5.0
command: ['curl']
args: ['{{ include "mychart.fullname" . }}:{{ .Values.service.port }}/healthz']
restartPolicy: Never
# Run tests
helm test my-release -n production
8. Quiz
Q1: What is the difference between version and appVersion in Chart.yaml?
- version: The version of the Chart itself. It is incremented when the Chart structure, templates, or values change. Follows SemVer.
- appVersion: The version of the application that the Chart deploys. It is incremented when the application code changes.
Example: Chart version 1.2.0 may deploy appVersion 3.1.0.
Q2: What role does hook-weight play in Helm Hooks?
hook-weight determines the execution order when multiple hooks of the same type exist. Lower numbers execute first.
Example: A DB migration with weight -5 executes before a cache initialization with weight 0.
Q3: What is the purpose of the checksum/config annotation in deployment.yaml?
When a ConfigMap changes, the sha256sum changes, causing the Pod annotation to differ. This triggers a rolling update on the Deployment.
This pattern solves the problem where Pods do not restart by default when only the ConfigMap is changed.
Quiz
Q1: What is the main topic covered in "The Complete Guide to Helm Chart Authoring and Deployment
Automation"?
A hands-on guide covering Helm Chart structure, values.yaml design, template authoring, hooks, dependency management, and CI/CD pipeline integration.
Q2: What is Helm??
Helm is a package manager for Kubernetes. It packages complex Kubernetes resources into a single
Chart, making installation, upgrades, and rollbacks easy to manage. Why Helm?
Q3: Explain the core concept of Writing Templates.
_helpers.tpl - Common Helpers deployment.yaml
Q4: What are the key aspects of Helm Hooks?
Hooks allow you to execute tasks at specific points in the release lifecycle: Hook Types
Q5: How does CI/CD Pipeline Integration work?
GitHub Actions + Helm Local Validation with helm template