Kubernetes로 애플리케이션을 운영하다 보면 여러 YAML 파일을 반복적으로 관리해야 하는 상황이 생긴다. 개발 환경, 스테이징 환경, 운영 환경마다 이미지 태그, 리소스 설정, 환경변수가 달라서 파일을 일일이 수정하다 보면 실수가 발생하고 유지보수가 어려워진다. Kubernetes Helm은 이런 문제를 해결하기 위해 등장한 Kubernetes의 공식 패키지 매니저다. Helm을 활용하면 복잡한 Kubernetes 리소스를 Chart라는 단위로 패키징하고, 환경별 설정을 변수로 분리하며, 배포 이력을 추적하고 롤백할 수 있다.
Kubernetes Helm이란 무엇인가
Kubernetes Helm은 Kubernetes 애플리케이션의 패키지 매니저로, Linux의 apt나 yum, Node.js의 npm과 유사한 역할을 한다. 공식 사이트에서 확인할 수 있듯이, Helm은 다음 세 가지 핵심 개념으로 구성된다.
- Chart: Kubernetes 리소스를 정의하는 파일의 묶음. Deployment, Service, ConfigMap, Secret 등 관련된 모든 리소스를 하나의 단위로 관리한다.
- Repository: Chart를 저장하고 배포하는 저장소. Docker Hub처럼 공개 저장소(Artifact Hub)와 사설 저장소를 모두 지원한다.
- Release: Chart를 특정 Kubernetes 클러스터에 설치한 인스턴스. 동일한 Chart를 여러 번 설치하면 각각 독립적인 Release가 생성된다.
Kubernetes Helm의 가장 큰 장점은 템플릿 기반의 설정 관리다. Go 템플릿 문법을 사용하여 values.yaml 파일에서 환경별 설정을 주입할 수 있어, 동일한 Chart를 개발/스테이징/운영 환경에 재사용할 수 있다. 또한 Release 히스토리를 관리하여 문제가 발생했을 때 이전 버전으로 즉시 롤백이 가능하다.
Helm 3와 Helm 2의 차이점
Kubernetes Helm은 2019년 Helm 3가 출시되면서 아키텍처가 크게 변화했다. Helm 2에서는 클러스터 내부에 Tiller라는 서버 컴포넌트가 필요했지만, Helm 3에서는 Tiller가 제거되고 클라이언트만으로 동작하는 구조로 변경되었다. 이로 인해 보안이 크게 향상되었고, 운영 복잡성이 줄어들었다. Helm 2는 지원이 종료되었으므로 반드시 Helm 3를 사용해야 한다.
Helm 3에서 달라진 점을 정리하면 다음과 같다. 첫째, Tiller가 제거되어 클러스터 내부에 별도 컴포넌트를 설치할 필요가 없다. 둘째, Release 정보가 Tiller의 ConfigMap이 아닌 각 네임스페이스의 Secret으로 저장되어 RBAC 기반 접근 제어가 자연스럽게 적용된다. 셋째, chart 저장 방식이 OCI 레지스트리를 지원하도록 변경되어 Docker Hub나 AWS ECR 같은 OCI 호환 레지스트리를 Chart 저장소로 활용할 수 있다. 넷째, 동일한 Release 이름을 다른 네임스페이스에 독립적으로 사용할 수 있어 멀티테넌시 구성이 편리해졌다.
Kubernetes Helm 설치 및 기본 설정
설치 방법
Kubernetes Helm 설치는 운영체제별로 다양한 방법을 지원한다. 공식 GitHub 릴리즈 페이지에서 바이너리를 직접 다운로드하거나, 각 OS의 패키지 매니저를 이용할 수 있다.
# macOS에서 Homebrew로 설치
brew install helm
# Linux에서 공식 스크립트로 설치
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
# 설치 확인
helm version
# version.BuildInfo{Version:"v3.17.0", ...}ShellScript설치 후에는 kubeconfig가 올바르게 설정되어 있어야 한다. kubectl cluster-info 명령으로 클러스터에 접근 가능한지 먼저 확인한다. Kubernetes Helm은 내부적으로 kubeconfig를 사용하여 클러스터 API 서버와 통신하기 때문에, KUBECONFIG 환경변수나 ~/.kube/config 파일이 존재해야 한다.
공식 Repository 추가
Kubernetes Helm을 처음 설치하면 기본 Repository가 설정되어 있지 않다. Bitnami나 Artifact Hub에서 검증된 Chart를 사용하려면 Repository를 추가해야 한다.
# Bitnami Repository 추가 (검증된 오픈소스 Chart 모음)
helm repo add bitnami https://charts.bitnami.com/bitnami
# Jetstack Repository 추가 (cert-manager 등 인증서 관리 도구 Chart)
helm repo add jetstack https://charts.jetstack.io
# 추가된 Repository 목록 확인
helm repo list
# Repository 인덱스 갱신 (최신 Chart 버전 반영)
helm repo update
# Chart 검색
helm search repo bitnami/postgresqlShellScriptHelm Chart 구조 이해
Kubernetes Helm에서 Chart는 특정 디렉터리 구조를 따른다. helm create 명령으로 기본 Chart를 생성하면 그 구조를 바로 확인할 수 있다.
# my-app이라는 이름의 Chart 생성
helm create my-app
# 생성된 디렉터리 구조 확인
tree my-app/
# my-app/
# ├── Chart.yaml # Chart 메타데이터 (이름, 버전, 설명)
# ├── values.yaml # 기본 설정값 (환경별로 오버라이드 가능)
# ├── charts/ # 의존 Chart (sub-charts) 저장 위치
# ├── templates/ # Kubernetes 리소스 템플릿 파일
# │ ├── deployment.yaml
# │ ├── service.yaml
# │ ├── ingress.yaml
# │ ├── hpa.yaml
# │ ├── serviceaccount.yaml
# │ ├── NOTES.txt # 설치 후 출력할 안내 메시지
# │ └── _helpers.tpl # 재사용 가능한 템플릿 함수 정의
# └── .helmignore # Chart 패키징 시 제외할 파일 목록ShellScript각 파일의 역할을 이해하는 것이 Kubernetes Helm 활용의 핵심이다. 특히 _helpers.tpl은 공통 레이블이나 이름 생성 로직을 함수로 정의해두는 곳으로, 템플릿 파일 전반에서 재사용한다. NOTES.txt는 Chart 설치 후 사용자에게 표시할 안내 메시지를 작성하는 곳으로, 접속 URL이나 초기 설정 방법을 담는 것이 관례다.
Chart.yaml
Chart의 메타데이터를 정의하는 파일이다.
# Chart.yaml
apiVersion: v2 # Helm 3는 반드시 v2를 사용
name: my-app # Chart 이름 (소문자와 하이픈만 사용 권장)
description: "Spring Boot 기반 백엔드 API 서비스"
type: application # application 또는 library
version: 0.1.0 # Chart 자체의 버전 (SemVer 준수 필수)
appVersion: "1.0.0" # 배포되는 애플리케이션의 버전 (helm list에 표시)
dependencies:
- name: postgresql # sub-chart 의존성 선언
version: "15.2.5"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled # values.yaml 조건에 따라 포함 여부 결정YAMLversion은 Chart의 버전이고, appVersion은 실제 애플리케이션의 버전이다. 두 가지를 독립적으로 관리할 수 있어 Chart 구조를 수정할 때 애플리케이션 버전을 올리지 않아도 된다. dependencies 항목을 통해 다른 Chart를 sub-chart로 포함시킬 수 있으며, helm dependency update 명령으로 의존 Chart를 charts/ 디렉터리에 다운로드한다.
values.yaml
Chart의 기본 설정값을 정의하는 파일이다. 배포 환경별로 이 파일을 오버라이드하는 방식으로 환경 분리를 구현한다.
# values.yaml - Spring Boot 애플리케이션 기본 설정
replicaCount: 1 # 기본 파드 수 (개발 환경 기준)
image:
repository: my-registry.io/my-app # 컨테이너 이미지 저장소 주소
pullPolicy: IfNotPresent # 이미지 풀 정책 (로컬에 있으면 재사용)
tag: "latest" # 기본 이미지 태그 (CI/CD에서 커밋 해시로 오버라이드)
service:
type: ClusterIP # 서비스 타입 (클러스터 내부 통신용)
port: 8080 # Spring Boot 기본 포트
resources:
limits:
cpu: 500m # CPU 최대 500 밀리코어 (0.5 코어)
memory: 512Mi # 메모리 최대 512MiB
requests:
cpu: 100m # 스케줄링 기준 CPU 요청량
memory: 256Mi # 스케줄링 기준 메모리 요청량
# Spring Boot 환경변수 설정 (map 타입으로 유연하게 추가 가능)
env:
SPRING_PROFILES_ACTIVE: "default"
SERVER_PORT: "8080"
JAVA_OPTS: "-XX:MaxRAMPercentage=75.0" # 컨테이너 메모리 기반 JVM 힙 설정
# 데이터베이스 접속 정보 (Secret에서 주입하므로 기본값만 설정)
database:
enabled: true
host: "localhost"
port: "5432"
name: "mydb"
# Horizontal Pod Autoscaler 설정
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 70YAML_helpers.tpl
_helpers.tpl은 Chart 전체에서 재사용하는 Go 템플릿 함수를 정의하는 파일이다. helm create로 생성하면 기본 함수들이 자동으로 작성된다. Deployment 템플릿에서 include "my-app.fullname", include "my-app.labels" 같은 함수를 호출하는데, 이 함수들이 바로 _helpers.tpl에 정의된다.
{{/* _helpers.tpl */}}
{{/*
Chart 이름을 반환한다. Values에 nameOverride가 설정되어 있으면 그 값을 우선 사용한다.
DNS 규격상 63자를 초과할 수 없으므로 trunc 63으로 자른다.
*/}}
{{- define "my-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Release와 Chart 이름을 결합하여 리소스 이름을 생성한다.
Release 이름에 Chart 이름이 포함되어 있으면 중복을 피하기 위해 Release 이름만 사용한다.
예) Release: my-release, Chart: my-app → 결과: my-release-my-app
Release: my-app, Chart: my-app → 결과: my-app (중복 방지)
*/}}
{{- define "my-app.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 }}
{{/*
helm.sh/chart 레이블 값을 생성한다. Chart 이름과 버전을 결합하며 + 기호는 _로 치환한다.
SemVer 버전의 빌드 메타데이터(+build.1) 표기가 레이블에서 허용되지 않기 때문이다.
*/}}
{{- define "my-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Kubernetes 표준 레이블 세트를 생성한다.
모든 리소스의 metadata.labels에 공통으로 삽입하여 리소스 추적과 관리를 용이하게 한다.
helm.sh/chart: Chart 이름과 버전 (어떤 Chart로 생성됐는지 추적)
app.kubernetes.io/version: 앱 버전 (helm list의 APP VERSION 컬럼에 표시되는 값)
app.kubernetes.io/managed-by: 항상 Helm (관리 도구 식별)
*/}}
{{- 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 }}
{{/*
Selector 레이블을 생성한다. Deployment의 spec.selector.matchLabels와 Pod의 labels에 동일하게 사용한다.
name과 instance 두 레이블만 포함하여 불필요한 재시작을 방지한다.
(Selector는 한 번 설정하면 변경할 수 없으므로 최소한의 안정적인 값만 포함한다.)
*/}}
{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
ServiceAccount 이름을 반환한다.
values.yaml에서 serviceAccount.create가 true이면 my-app.fullname을 기본값으로 사용하고,
false이면 values.serviceAccount.name이 있을 경우 그 값을 사용하며, 없으면 "default"를 반환한다.
*/}}
{{- define "my-app.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "my-app.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}Go_helpers.tpl에서 정의한 함수는 include 또는 template 키워드로 다른 템플릿 파일에서 호출한다. include를 사용하면 | 파이프라인으로 nindent, trim 같은 후처리 함수를 연결할 수 있어 template보다 include가 권장된다. define 블록 앞뒤의 {{-와 -}}는 공백 제거 지시자로, 렌더링된 YAML에 불필요한 빈 줄이 생기지 않도록 방지한다.
실무 Chart 작성 방법: Spring Boot 앱 배포
실제 Spring Boot 애플리케이션을 Kubernetes Helm Chart로 배포하는 방법을 단계별로 살펴본다.
Deployment 템플릿 작성
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }} # _helpers.tpl에서 정의한 공통 이름 함수 사용
labels:
{{- include "my-app.labels" . | nindent 4 }} # 공통 레이블 블록 삽입 (4칸 들여쓰기)
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }} # HPA 미사용 시에만 replicas 설정
{{- end }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
# 이미지 태그가 values에 없으면 Chart.yaml의 appVersion을 기본값으로 사용
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.port }}
protocol: TCP
env:
# values.yaml의 env 맵을 순회하여 환경변수로 변환
{{- range $key, $value := .Values.env }}
- name: {{ $key }}
value: {{ $value | quote }} # 숫자가 문자열로 처리되도록 반드시 quote 사용
{{- end }}
# DB URL은 Secret에서 주입하여 평문 노출 방지
- name: SPRING_DATASOURCE_URL
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-db-secret
key: url
resources:
{{- toYaml .Values.resources | nindent 12 }} # YAML 블록 그대로 삽입
livenessProbe:
httpGet:
path: /actuator/health/liveness # Spring Boot Actuator 생존 여부 확인
port: http
initialDelaySeconds: 30 # 컨테이너 시작 후 30초 대기 (JVM 웜업 시간)
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness # 트래픽 수신 가능 여부 확인
port: http
initialDelaySeconds: 20
periodSeconds: 5
failureThreshold: 3YAMLSecret 템플릿 작성
민감한 정보는 Kubernetes Secret으로 관리하는 것이 원칙이다. 하지만 Chart에 평문 Secret을 포함시키면 Git 저장소에 노출될 위험이 있으므로, 실무에서는 External Secrets Operator나 Sealed Secrets를 함께 활용하는 것이 좋다.
# templates/secret.yaml
{{- if .Values.database.enabled }}
apiVersion: v1
kind: Secret
metadata:
name: {{ include "my-app.fullname" . }}-db-secret
labels:
{{- include "my-app.labels" . | nindent 4 }}
type: Opaque
stringData:
# helm install 시 --set database.password=... 로 실제 값을 주입
# CI/CD 시크릿 변수에서 주입하므로 Git에는 기본값이 노출되지 않음
url: "jdbc:postgresql://{{ .Values.database.host }}:{{ .Values.database.port }}/{{ .Values.database.name }}"
username: {{ .Values.database.username | default "app_user" | quote }}
password: {{ .Values.database.password | default "" | quote }}
{{- end }}YAML환경별 values 파일 분리
Kubernetes Helm에서 환경별 설정 분리는 values 파일을 여러 개 만들어 -f 옵션으로 지정하는 방식으로 구현한다. 기본 values.yaml은 개발 환경 기준으로 작성하고, 상위 환경일수록 오버라이드 값이 늘어나는 구조다.
# 권장 프로젝트 디렉터리 구조
helm/
├── my-app/ # Chart 디렉터리
│ ├── Chart.yaml
│ ├── values.yaml # 기본값 (개발 환경 기준)
│ └── templates/
└── values/ # 환경별 오버라이드 파일 (Git 관리)
├── values-staging.yaml
└── values-prod.yamlShellScript# helm/values/values-prod.yaml - 운영 환경 오버라이드
replicaCount: 3 # 운영 환경은 3개 파드로 HA 구성
image:
tag: "1.5.2" # 운영 환경은 고정 버전 태그 사용 (latest 금지)
resources:
limits:
cpu: 2000m # 운영 환경은 더 많은 리소스 허용
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
env:
SPRING_PROFILES_ACTIVE: "prod" # Spring 운영 프로필 활성화
JAVA_OPTS: "-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"
autoscaling:
enabled: true # 운영 환경은 HPA 활성화
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 60YAML# 운영 환경에 배포 (-f 옵션으로 환경별 값 오버라이드)
helm upgrade --install my-app ./helm/my-app \
-f ./helm/values/values-prod.yaml \
--set image.tag=$(git rev-parse --short HEAD) \
--set database.password="${DB_PASSWORD}" \
--namespace prod \
--create-namespaceShellScriptHelm Repository 구성 및 관리
팀에서 공통으로 사용하는 Chart를 관리하려면 사설 Helm Repository를 구성하는 것이 효과적이다. GitHub Pages, AWS S3, Harbor, ChartMuseum 등 다양한 방법을 선택할 수 있다. 소규모 팀이라면 GitHub Pages를 활용한 구성이 별도 인프라 없이 쉽게 시작할 수 있어 권장된다.
GitHub Pages를 활용한 Repository 구성
# Chart 패키징 (tgz 파일 생성)
helm package ./helm/my-app --destination ./charts/
# Repository 인덱스 파일 생성 (기존 index.yaml과 병합)
helm repo index ./charts/ --url https://my-org.github.io/helm-charts/
# 변경 사항을 Git에 커밋하면 GitHub Pages로 자동 공개
git add ./charts/
git commit -m "Add my-app chart v0.1.0"
git push origin main
# 팀원이 사설 Repository 추가
helm repo add my-org https://my-org.github.io/helm-charts/
helm search repo my-org/ShellScriptOCI 레지스트리를 활용하면 GitHub Container Registry나 AWS ECR을 Chart 저장소로 사용할 수도 있다. 이 방식은 기존 컨테이너 이미지 인프라를 재사용할 수 있어 대규모 팀에 적합하다.
# OCI 레지스트리에 Chart 푸시 (Helm 3.8 이상)
helm push my-app-0.1.0.tgz oci://ghcr.io/my-org/helm-charts
# OCI 레지스트리에서 설치
helm install my-app oci://ghcr.io/my-org/helm-charts/my-app --version 0.1.0ShellScriptKubernetes Helm으로 릴리즈 버전 관리하기
Kubernetes Helm의 Release 관리 기능은 Kubernetes 배포의 안정성을 높이는 핵심 기능이다. 단순 YAML 적용과 달리, Helm은 모든 배포 이력을 Kubernetes Secret으로 저장하여 언제든지 이전 상태로 복구할 수 있다.
주요 릴리즈 관리 명령어
# 신규 배포 또는 업그레이드 (--install 플래그로 신규 설치와 업그레이드 명령 통합)
# --atomic: 배포 실패 시 자동으로 이전 버전으로 롤백
# --timeout: 파드 Ready 대기 포함 5분
helm upgrade --install my-app ./helm/my-app \
-f ./helm/values/values-prod.yaml \
--namespace prod \
--atomic \
--timeout 5m
# 배포된 Release 목록 확인
helm list --namespace prod
# NAME NAMESPACE REVISION STATUS CHART APP VERSION
# my-app prod 3 deployed my-app-0.2.0 1.5.2
# 특정 Release의 현재 상태 및 설정값 확인
helm status my-app --namespace prod
# Release 히스토리 조회 (모든 배포 이력)
helm history my-app --namespace prod
# REVISION STATUS CHART DESCRIPTION
# 1 superseded my-app-0.1.0 Install complete
# 2 superseded my-app-0.1.1 Upgrade complete
# 3 deployed my-app-0.2.0 Upgrade complete
# 특정 revision으로 롤백 (즉시 실행)
helm rollback my-app 2 --namespace prod
# Release 완전 삭제 (히스토리도 함께 제거)
helm uninstall my-app --namespace prodShellScript--atomic 플래그는 실무에서 매우 중요하다. 새 버전 배포 시 파드가 Readiness 상태가 되지 않으면 자동으로 이전 버전으로 롤백하여 서비스 중단 시간을 최소화한다. CI/CD 파이프라인에서 반드시 활성화해야 하는 옵션이다.
배포 전 검증
Kubernetes Helm에서는 실제 배포 전에 렌더링된 YAML을 미리 확인할 수 있다. 운영 환경에 배포하기 전 반드시 이 단계를 거치는 것이 좋다.
# 렌더링 결과만 stdout으로 출력 (클러스터에 아무 변경 없음)
helm template my-app ./helm/my-app -f ./helm/values/values-prod.yaml
# 클라이언트 사이드 dry-run: 렌더링 결과를 클러스터 API 유효성 검증 없이 출력
# 클러스터와 통신하여 서버 사이드 검증이 필요하다면 --dry-run=server 사용
helm upgrade --install my-app ./helm/my-app \
-f ./helm/values/values-prod.yaml \
--namespace prod \
--dry-run
# Chart 문법 및 best practice 검증 (CI에서 필수 실행)
helm lint ./helm/my-app -f ./helm/values/values-prod.yaml
# 현재 배포된 버전과 새 버전의 diff 확인 (helm-diff 플러그인 필요)
helm plugin install https://github.com/databus23/helm-diff
helm diff upgrade my-app ./helm/my-app -f ./helm/values/values-prod.yaml --namespace prodShellScriptHelm Hooks 활용
Kubernetes Helm의 Hooks 기능을 사용하면 배포 전후에 특정 작업을 실행할 수 있다. 데이터베이스 마이그레이션이나 캐시 초기화 같은 작업을 배포 파이프라인에 자연스럽게 통합할 수 있다.
Hook 종류는 pre-install, post-install, pre-upgrade, post-upgrade, pre-rollback, post-rollback, pre-delete, post-delete 등이 있다. Spring Boot 애플리케이션에서 가장 자주 사용되는 패턴은 pre-upgrade와 pre-install을 결합하여 데이터베이스 마이그레이션 Job을 실행하는 것이다.
# templates/db-migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "my-app.fullname" . }}-db-migration
annotations:
# pre-upgrade, pre-install Hook 지정: 앱 배포 전에 이 Job을 먼저 실행
"helm.sh/hook": pre-upgrade,pre-install
# 기존에 같은 이름의 Hook 리소스가 있으면 먼저 삭제하고 생성
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
# Hook 실행 우선순위 (낮은 숫자가 먼저 실행)
"helm.sh/hook-weight": "-5"
spec:
backoffLimit: 3 # 실패 시 최대 3회 재시도
template:
spec:
restartPolicy: Never
containers:
- name: db-migration
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
# Flyway 마이그레이션만 실행하고 종료하는 Spring Boot 프로필 사용
command: ["java", "-jar", "app.jar", "--spring.profiles.active=migrate-only"]
env:
- name: SPRING_DATASOURCE_URL
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-db-secret
key: url
- name: SPRING_DATASOURCE_USERNAME
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-db-secret
key: username
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: {{ include "my-app.fullname" . }}-db-secret
key: passwordYAML운영 환경 베스트 프랙티스
Kubernetes Helm을 운영 환경에서 안정적으로 사용하기 위한 핵심 규칙들을 정리한다.
1. Chart와 이미지 버전 고정
운영 환경에서는 Chart 버전과 이미지 태그를 반드시 고정해야 한다. latest 태그나 floating 버전을 사용하면 재현 불가능한 배포 상태가 만들어진다.
# 특정 Chart 버전 명시 (cert-manager 예시)
helm upgrade --install cert-manager jetstack/cert-manager \
--version v1.16.0 \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=trueShellScript2. values 파일을 Git으로 관리
values.yaml과 환경별 오버라이드 파일을 Git 저장소에 저장하여 변경 이력을 추적한다. 단, 민감한 정보(비밀번호, API 키)는 절대 포함하지 않는다. 대신 CI/CD 시스템의 Secret 변수로 관리하고 --set 옵션으로 주입하는 방식이 권장된다.
3. Helm Secrets 플러그인 활용
민감한 설정값을 암호화하여 Git에 저장하려면 Helm Secrets 플러그인을 활용한다. SOPS나 Vault와 연동하여 values 파일 내 민감 정보를 암호화된 상태로 Git에 보관할 수 있다. 팀 전체가 동일한 복호화 키를 공유하는 방식이므로 키 관리 정책을 함께 수립해야 한다.
# Helm Secrets 플러그인 설치
helm plugin install https://github.com/jkroepke/helm-secrets
# SOPS로 암호화된 values 파일을 포함하여 배포
helm secrets upgrade --install my-app ./helm/my-app \
-f ./helm/values/values-prod.yaml \
-f ./helm/values/secrets-prod.yaml.enc \
--namespace prodShellScript4. CI/CD 파이프라인 통합
GitHub Actions에서 Kubernetes Helm 배포를 자동화하는 예시다. --atomic 플래그와 함께 배포 성공 여부를 Slack 알림으로 연동하면 배포 모니터링이 더욱 편리해진다.
# .github/workflows/deploy.yml
name: Deploy to Kubernetes
on:
push:
branches: [main]
tags: ["v*"] # 태그 푸시 시에도 배포 트리거
jobs:
deploy:
runs-on: ubuntu-latest
environment: production # GitHub Environment로 승인 게이트 설정 가능
steps:
- uses: actions/checkout@v4
- name: Set up Helm
uses: azure/setup-helm@v4
with:
version: "v3.17.0" # Helm 버전 고정으로 재현 가능한 배포 환경 보장
- name: Configure kubeconfig
run: |
mkdir -p $HOME/.kube
echo "${{ secrets.KUBECONFIG }}" > $HOME/.kube/config
chmod 600 $HOME/.kube/config
- name: Lint Chart
run: helm lint ./helm/my-app -f ./helm/values/values-prod.yaml
- name: Deploy with Helm
run: |
helm upgrade --install my-app ./helm/my-app \
-f ./helm/values/values-prod.yaml \
--set image.tag=${{ github.sha }} \
--set database.password="${{ secrets.DB_PASSWORD }}" \
--namespace prod \
--atomic \
--timeout 5m \
--history-max 10YAML지금까지 Kubernetes Helm에 대해서 정리해 보았다. Chart 작성과 환경별 values 분리, Release 관리, Hooks 활용, CI/CD 통합까지 실무에서 자주 사용되는 패턴을 중심으로 살펴보았다. Kubernetes Helm은 처음에는 학습 곡선이 존재하지만, 한 번 익혀두면 Kubernetes 운영의 복잡성을 크게 낮출 수 있다. 다음 단계로는 GitOps 도구인 Argo CD나 Flux와 Kubernetes Helm을 결합하는 방법을 학습하면 더욱 강력한 선언적 배포 자동화를 구현할 수 있다.
