매니페스트 50장을 복붙하던 날들 — Helm이 템플릿으로 만든 재사용

한 팀이 세 환경(dev/stage/prod)에 같은 앱을 띄운다. 매니페스트가 이미지 태그·replica 수·도메인만 다르고 나머진 동일한데, 환경마다 디렉터리를 복사해 50장씩 유지했다. 이미지를 바꾸면 세 디렉터리를 일일이 고쳤다. 어느 날 prod만 고치는 걸 깜빡해 stage와 prod가 달라졌다. Helm은 이 "매니페스트의 복붙 지옥"을 템플릿 하나로 끝낸다.

이 글이 푸는 것은: Helm이 매니페스트를 어떻게 재사용 가능한 패키지(chart)로 만드는가, 그리고 kubectl apply와 무엇이 근본적으로 다른가다.

Helm은 Kubernetes의 패키지 매니저

Helm은 Kubernetes 매니페스트의 패키지 매니저다. apt/yarn/npm이 소프트웨어 패키지를 다루듯, Helm은 차트(chart)라는 매니페스트 묶음을 다룬다.

  • chart: 템플릿 + 설정 값(values) 묶음. "재사용 가능한 매니페스트 패키지".
  • release: chart를 특정 이름으로 설치한 인스턴스. 같은 chart를 여러 release로(예: dev/stage) 따로 둘 수 있다.
  • values: chart를 구체화하는 변수. 환경마다 다른 부분(이미지 태그, replica, 도메인)만 여기로 뺀다.
flowchart LR
    C["chart<br/>(템플릿 + 기본 values)"] -->|"helm install"| R["release: myapp-prod"]
    V["values-prod.yaml<br/>(오버라이드)"] --> R
    C -->|"helm install"| R2["release: myapp-dev"]
    V2["values-dev.yaml"] --> R2

복붙의 끝: 같은 chart에 다른 values만 줘서 dev/prod를 만든다. 차트는 하나, 환경별 차이는 values 파일로.

템플릿 — 매니페스트에 변수를 넣는다

chart의 핵심은 Go template이다. 매니페스트 안에 {{ }} 변수를 넣고, values로 치환한다.

# templates/deployment.yaml (chart 안)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: {{ .Values.image.repo }}:{{ .Values.image.tag }}
# values.yaml (기본값)
replicaCount: 1
image: {repo: myapp, tag: "1.0"}
# my-prod.yaml (오버라이드)
replicaCount: 5
image: {repo: myapp, tag: "2.0"}
helm install myapp-prod ./mychart -f my-prod.yaml

이 과정에서 템플릿이 렌더링돼 최종 매니페스트가 나오고, 그것이 apiserver에 적용된다. 결국 Helm도 kubectl apply를 자동화한 것이지만 — 변수화 + 패키징 + 릴리스 관리가 더해진 버전이다.

chart 구조

mychart/
├── Chart.yaml          # 차트 메타데이터(이름, 버전)
├── values.yaml         # 기본 설정값
├── templates/          # 템플릿 파일들
│   ├── deployment.yaml
│   ├── service.yaml
│   └── _helpers.tpl    # 재사용 템플릿 조각
└── charts/             # 하위 의존 차트

Chart.yaml이 차트 정체성(이름, 버전)을 갖고, templates/가 렌더링 대상, values.yaml이 기본값이다.

helm install / upgrade / rollback — 릴리스 생명주기

helm install myapp-prod ./mychart -f prod.yaml      # 설치
helm upgrade myapp-prod ./mychart -f prod.yaml       # 업그레이드(values/차트 변경 시)
helm rollback myapp-prod 2                           # 이전 리비전으로 롤백
helm list                                            # 설치된 release 목록
helm uninstall myapp-prod                            # 제거

Helm은 release마다 리비전(revision) 히스토리를 유지한다(기본적으로 Secret/ConfigMap에 저장). 그래서 rollback이 가능하다 — kubectl apply로는 못 하는 일. 이것이 Helm이 단순 템플릿 렌더러가 아니라 릴리스 관리자인 이유다.

Helm 3의 중요한 변화: Helm 2엔 Tiller라는 클러스터 내 서버 컴포넌트가 있어 보안 문제가 있었다(클러스터 권한을 크게 가짐). Helm 3는 Tiller를 없애고, 설치하는 사용자의 권한(kubeconfig)으로만 동작. RBAC 모델이 단순해졌다.

subchart와 의존성 — 복잡한 앱 조립

복잡한 앱은 여러 chart(예: 앱 chart + DB chart + 모니터링 chart)를 조립할 수 있다. Chart.yamldependencies로 하위 chart(subchart)를 선언하고, 각각의 values를 부모 chart에서 오버라이드.

Helm vs Kustomize — 두 접근

Helm 외에 Kustomize(kubectl에 내장)라는 또 다른 접근이 있다. 차이:

  • Helm: 템플릿 + 변수. 동적 렌더링.
  • Kustomize: 오버레이. 기본 매니페스트(base)에 패치(overlay)를 겹쳐 환경별 차이를 만듦. 템플릿 변수 없이 순수 YAML 조합.
Helm Kustomize
접근 템플릿 + 변수 오버레이(패치)
학습 곤란도 Go template 문법 YAML 겹치기(비교적 단순)
릴리스 관리 있음(revision/rollback) 없음(순수 렌더링)
배포 kubectl 필요 없음 kubectl에 내장

선택은 취향/생태계. 공식 차트가 많은(e.g. 데이터플랫폼) 생태계엔 Helm이 유리하고, 단순 환경 분리엔 Kustomize가 가볍다.

helm template의 함정 — 렌더링 후엔 kubectl과 같다

helm template(렌더링만 하고 설치 안 함)으로 나온 매니페스트를 kubectl apply하는 패턴이 있다(GitOps). 이때 중요한 점: Helm은 매니페스트를 렌더링해 apiserver에 보낼 뿐, 그 이후 상태는 Kubernetes가 관리한다. Helm hook(pre-install, post-upgrade 등)으로 일부 작업을 끼울 순 있지만, 본질은 "렌더링 + apply"다.

그래서 Helm으로 설치한 리소스를 직접 kubectl edit로 고치면, 다음 helm upgrade가 그것을 덮어쓴다. Helm이 release 상태를 추적하므로 — "Helm이 관리하는 리소스는 Helm으로만 고쳐라"가 원칙.

직접 확인하기

# Kubernetes 1.36 — Helm 설치 후 (공식 chart repo 추가)
helm repo add bitnami https://charts.bitnami.com/bitnami
helm install my-nginx bitnami/nginx --set replicaCount=3
helm list

확인할 것: my-nginx release가 있고, Deployment가 3 replica로 생김.

kubectl get deploy -l app.kubernetes.io/instance=my-nginx
helm upgrade my-nginx bitnami/nginx --set replicaCount=5
helm history my-nginx
helm rollback my-nginx 1

확인할 것: upgrade로 리비전이 올라가고, rollback으로 이전 리비전으로 돌아간다.

Helm CLI 별도 설치 필요. kind 클러스터에서 동작. 미검증 환경은 노트.

chart 의존성과 subchart — 복잡한 앱 조립

복잡한 앱은 여러 chart를 조립할 수 있다. Chart.yamldependencies로 하위 chart(subchart)를 선언:

# Chart.yaml
dependencies:
- name: postgresql
  version: 12.x.x
  repository: https://charts.bitnami.com/bitnami
- name: redis
  version: 17.x.x
  repository: https://charts.bitnami.com/bitnami

helm dependency update가 subchart를 다운로드해 charts/에 배치. 이제 부모 chart를 설치하면 subchart(postgres, redis)도 같이 설치. 부모 chart의 values.yaml에서 subchart의 값을 오버라이드:

# 부모 values.yaml
postgresql:
  auth: {password: "strongpw"}
redis:
  replica: {replicaCount: 3}

이것이 "한 chart에 전체 스택(앱 + DB + 캐시)"을 패키징하는 패턴. 복잡한 앱을 한 번에 배포.

Helm hooks — 설치/업그레이드에 끼어들기

Helm hooks가 특정 시점에 임의 작업을 끼워 넣게 한다:

  • pre-install: 설치 (예: DB 마이그레이션).
  • post-install: 설치 (예: 초기 데이터 시딩).
  • pre-upgrade, post-upgrade, pre-rollback 등.
# pre-install hook (Job)
metadata:
  annotations:
    "helm.sh/hook": pre-install
    "helm.sh/hook-weight": "-5"      # 여러 hook 중 순서
    "helm.sh/hook-delete-policy": hook-succeeded

hook은 일반 리소스와 달리 설치 과정에 끼어들어 실행. 예: "DB 스키마 마이그레이션 Job을 업그레이드 전에 돌려라". 단 hook 실패 시 설치/업그레이드가 어떻게 되는지(--wait/hook 정책)를 이해해야 — hook이 실패하면 전체 배포가 멈출 수 있다.

OCI registry — chart를 컨테이너 레지스트리에

최근 Helm은 chart를 OCI 레지스트리(container registry)에 저장 지원. 기존 Helm repo(HTTP) 대신 OCI:

helm install myapp oci://registry.example.com/charts/myapp --version 1.2.3

이점: chart와 컨테이너 이미지를 같은 레지스트리에서 관리. 서명(cosign, 03-k8s-security 08장)으로 chart 무결성도 검증. OCI가 Helm chart 배포의 차세대 표준 방향.

흔히 묻는 것, 흔히 틀리는 것

오해 정정
"Helm은 kubectl을 대체한다" 아니다. 렌더링해 apiserver에 보내는 건 같다. 변수화+패키징+릴리스 관리가 차이
"chart를 설치하면 영원히 Helm 관리다" uninstall하면 Helm이 만든 리소스가 같이 지워짐. 단 직접 만든 건 남음
"Helm으로 설치 후 kubectl edit 해도 된다" 다음 upgrade가 덮어쓴다. Helm 관리 리소스는 Helm으로만
"Helm 2와 3은 같다" Tiller 제거 등 큰 변화. Helm 3는 사용자 권한으로만 동작
"values.yaml은 고정이다" -f로 여러 오버라이드 파일, --set으로 단일 값 주입 가능
"Kustomize는 Helm의 하위 호환이다" 다른 철학(오버레이 vs 템플릿). 대체가 아니라 선택

요약 — 이 글의 결론

  • Helm은 매니페스트의 패키지 매니저. chart(템플릿+values 묶음) + release(설치 인스턴스)로 복붙 지옥을 끝낸다.
  • Go template + values가 변수화의 핵심. 같은 chart에 다른 values로 dev/prod를 만든다 — 매니페스트는 하나.
  • 릴리스 관리(revision/rollback)가 Helm을 단순 렌더러와 구분. kubectl apply로는 못 하는 롤백을 제공.
  • Helm 3는 Tiller 제거로 보안 단순화 — 사용자의 kubeconfig 권한으로만 동작.
  • Helm 관리 리소스는 Helm으로만 고쳐야. 직접 kubectl edit하면 다음 upgrade가 덮어쓴다.
  • Kustomize는 다른 철학(오버레이). 둘 중 선택이지 하위/상위가 아니다. 공식 chart 생태계엔 Helm, 단순 환경 분리엔 Kustomize가 자주 선택.

생각해 볼 문제

  1. helm upgrade가 내부적으로 하는 일을 "렌더링 → apply"로 설명하라. rollback은 어떻게 구현되나?
  2. 같은 chart를 dev/stage/prod로 세 release로 띄운다. 각각 어떻게 격리되는가? (namespace, release name)
  3. Helm으로 설치한 Deployment를 kubectl scale로 replica를 바꿨다. 다음 helm upgrade에 어떻게 되나?
  4. Helm hook(pre-install)으로 DB 마이그레이션을 넣었다. 실패하면 설치는 어떻게 되나?
  5. GitOps(ArgoCD/Flux)에서 Helm을 쓸 때 "helm install" 대신 쓰는 패턴은? (helm template + apply)
  6. Kustomize가 Helm보다 나은 구체적 상황을 하나 설계하라.

참고

'Tech Artifacts > Kubernetes' 카테고리의 다른 글

Kubernetes - 16. crd controllers  (0) 2026.07.13
Kubernetes - 14. kubeadm  (0) 2026.07.13
Kubernetes - 13. node administration  (0) 2026.07.13
Kubernetes - 12. autoscaling  (0) 2026.07.13
Kubernetes - 11. resource qos  (0) 2026.07.13