Back to Notes

Notes

K8s 04. Helm

Helm을 Kubernetes package manager로 이해하고, Chart, Values, Template, Release, Repository를 중심으로 애플리케이션 배포와 운영 lifecycle을 정리한다.

Published
Updated
Area
Cloud Infrastructure
Type
concept
Series
Kubernetes Essentials
Category
Notes
KubernetesHelmChartValuesReleaseCI/CDDeploymentYAML

K8s 04. Helm

Kubernetes는 containerized application을 선언형으로 배포하고 운영하기 위한 강력한 platform이다. 하지만 실제 애플리케이션을 Kubernetes에 올리다 보면 Deployment 하나만으로 끝나는 경우는 많지 않다. 보통 하나의 서비스에도 여러 Kubernetes resource가 함께 필요하다.

Deployment
Service
Ingress
ConfigMap
Secret
ServiceAccount
Role
RoleBinding
PersistentVolumeClaim
HorizontalPodAutoscaler
NetworkPolicy

작은 API 서버 하나를 배포하더라도 Deployment, Service, ConfigMap, Secret 정도는 자주 함께 등장한다. 여기에 외부 노출이 필요하면 Ingress, 권한 제어가 필요하면 ServiceAccountRoleBinding, autoscaling이 필요하면 HorizontalPodAutoscaler가 추가된다.

환경이 dev, staging, production으로 나뉘면 문제는 더 커진다. 구조는 거의 같은데 image tag, replica 수, domain, resource limit, secret name, ingress annotation만 달라지는 YAML이 반복된다.

Helm은 이 반복을 줄이기 위한 Kubernetes package manager다. 여러 Kubernetes manifest를 하나의 reusable package인 Chart로 묶고, 환경별 차이는 Values로 분리하며, 실제 cluster에 설치된 인스턴스는 Release 단위로 관리한다.

반복되는 Kubernetes YAML 묶음

template + values로 일반화

Chart라는 package로 관리

Release라는 설치 인스턴스로 cluster에 배포

Helm을 한 문장으로 정의하기

Helm은 Kubernetes application을 구성하는 여러 manifest를 Chart라는 package로 묶고, Values를 통해 환경별 설정을 주입하며, Release 단위로 install, upgrade, rollback, uninstall을 관리하는 Kubernetes package manager다.

중요한 점은 Helm이 container image를 build하는 도구가 아니라는 것이다. 애플리케이션 코드는 보통 Dockerfile이나 다른 build process를 통해 container image로 만들어지고, Helm은 그 image를 Kubernetes에 어떤 resource 형태로 배포할지 정의한다.

Dockerfile / Container Image
  → 애플리케이션 실행 단위

Helm Chart
  → 그 image를 Kubernetes에 어떻게 배포할지 정의하는 package

즉 Helm은 Kubernetes를 대체하지 않는다. Helm은 결국 Kubernetes YAML을 rendering하고 Kubernetes API Server에 적용한다. Helm을 사용하더라도 Deployment, Service, Pod, Ingress, ConfigMap, Secret 같은 Kubernetes object를 이해해야 한다.


Helm이 필요한 이유

Kubernetes YAML은 강력하지만 반복이 많다

Kubernetes는 선언형 API를 사용한다. 사용자는 YAML manifest로 원하는 상태를 선언하고, Kubernetes는 실제 cluster 상태를 그 선언에 맞추려 한다.

예를 들어 API 서버 하나를 배포하려면 최소한 Deployment가 필요하다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: registry.example.com/api:1.0.0
          ports:
            - containerPort: 3000

내부 접근을 위해 Service를 추가할 수 있다.

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 3000

애플리케이션 설정은 ConfigMap으로 분리할 수 있다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: api-config
data:
  LOG_LEVEL: "info"

민감한 정보는 Secret으로 관리할 수 있다.

apiVersion: v1
kind: Secret
metadata:
  name: api-secret
type: Opaque
stringData:
  DB_PASSWORD: "[REDACTED]"

외부 접근이 필요하면 Ingress도 필요하다.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: api
spec:
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api
                port:
                  number: 80

여기까지는 단순해 보인다. 하지만 실제 운영에서는 파일이 빠르게 늘어난다.

deployment.yaml
service.yaml
ingress.yaml
configmap.yaml
secret.yaml
serviceaccount.yaml
role.yaml
rolebinding.yaml
hpa.yaml
pdb.yaml
networkpolicy.yaml
pvc.yaml

그리고 환경별로 같은 파일을 복사하기 시작한다.

dev/
  deployment.yaml
  service.yaml
  ingress.yaml

staging/
  deployment.yaml
  service.yaml
  ingress.yaml

prod/
  deployment.yaml
  service.yaml
  ingress.yaml

이 구조의 문제는 YAML 자체가 아니라 반복과 drift다.

복사-붙여넣기 YAML의 문제

환경별 manifest를 복사해 관리하면 초반에는 빠르다. 하지만 시간이 지나면 다음과 같은 문제가 생긴다.

dev에는 image tag가 1.1.0인데 staging은 1.0.8이다.
prod에는 readinessProbe가 있는데 dev에는 없다.
staging에만 resource limit이 빠져 있다.
ingress annotation이 환경마다 조금씩 다르다.
service name을 바꾸면서 selector label을 같이 못 바꿨다.

대표적인 문제는 다음과 같다.

문제설명
label mismatchService.selectorDeployment.template.metadata.labels가 달라 traffic이 가지 않음
image tag drift환경별 manifest에 서로 다른 tag가 남아 배포 상태 추적이 어려움
config driftdev/staging/prod 간 설정 차이가 의도인지 실수인지 구분하기 어려움
duplicate YAML거의 같은 파일이 여러 환경에 중복되어 변경 누락 발생
rollback 어려움여러 YAML 변경이 하나의 application 단위로 묶이지 않아 이전 상태 복원이 복잡
dependency 누락app은 설치했지만 필요한 DB, exporter, config, RBAC 등이 빠짐

Helm은 이런 문제를 template, values, chart, release라는 구조로 완화한다.


Helm을 package manager로 이해하기

Linux에서 apt, yum, dnf, brew 같은 package manager를 사용하면 소스 다운로드, 의존성 확인, 설치 경로 구성, 업데이트, 삭제를 매번 직접 하지 않아도 된다.

apt install nginx

Helm도 Kubernetes에서 비슷한 위치에 있다.

helm install my-nginx bitnami/nginx

다만 Helm은 OS package manager처럼 binary를 운영체제에 설치하는 도구가 아니다. Helm은 Kubernetes resource manifest를 rendering하고, 그 결과를 Kubernetes API Server에 적용한다.

Chart + Values

Kubernetes YAML render

Kubernetes API Server에 적용

Release record 저장

따라서 Helm의 본질은 다음에 가깝다.

  • Kubernetes manifest를 package화한다.
  • 반복되는 YAML을 template으로 일반화한다.
  • 환경별 차이를 values로 분리한다.
  • 설치된 application instance를 release로 추적한다.
  • upgrade와 rollback의 단위를 application release로 묶는다.

Helm의 핵심 구성요소

Helm을 이해하려면 다음 다섯 개념을 먼저 잡아야 한다.

Chart
Template
Values
Release
Repository
개념의미
ChartKubernetes application을 설치하기 위한 Helm package
TemplateKubernetes YAML을 생성하기 위한 parameterized manifest
Valuestemplate에 주입되는 설정값
Releasechart를 특정 values로 cluster에 설치한 인스턴스
Repositorychart를 저장하고 공유하는 저장소

Chart

Chart는 Helm package다. Kubernetes application을 설치하는 데 필요한 template, 기본 values, metadata를 담는다.

간단한 chart 구조는 다음과 같다.

myapp/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   └── configmap.yaml
└── charts/

각 파일과 디렉터리의 역할은 다음과 같다.

파일/디렉터리역할
Chart.yamlchart 이름, version, appVersion, description 같은 metadata
values.yamlchart의 기본 설정값
templates/Kubernetes manifest template
charts/dependency chart 저장 위치
.helmignorechart package에 포함하지 않을 파일 지정

실제 chart는 더 많은 template을 포함할 수 있다.

myapp/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── _helpers.tpl
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── serviceaccount.yaml
│   ├── hpa.yaml
│   └── tests/
│       └── test-connection.yaml
└── charts/

Chart.yaml

Chart.yaml은 chart의 metadata를 담는다.

apiVersion: v2
name: myapp
description: A Helm chart for my API application
type: application
version: 0.1.0
appVersion: "1.0.0"

여기서 versionappVersion은 구분해야 한다.

필드의미
versionchart 자체의 version
appVersionchart가 배포하는 application version
apiVersionchart specification version
typeapplication 또는 library

예를 들어 chart template만 변경되어도 version은 올라갈 수 있다. 반면 application image가 그대로라면 appVersion은 유지될 수 있다.

반대로 application image tag가 바뀌면 appVersion도 함께 올리는 것이 일반적이다.


Template

Template은 고정된 Kubernetes YAML이 아니라, values를 주입받아 최종 manifest로 변환되는 파일이다.

일반 Kubernetes manifest에서는 image가 고정된다.

image: registry.example.com/api:1.0.0

Helm template에서는 값을 변수화할 수 있다.

image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"

예를 들어 Deployment template은 다음처럼 작성할 수 있다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-api
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}-api
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-api
    spec:
      containers:
        - name: api
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.service.targetPort }}

이 template은 아직 Kubernetes가 직접 이해할 수 있는 YAML이 아니다. Helm이 values를 주입해 rendering한 뒤 최종 YAML이 된다.

values.yaml이 다음과 같다고 하자.

replicaCount: 3

image:
  repository: registry.example.com/api
  tag: "1.0.0"

service:
  targetPort: 3000

렌더링 결과는 다음처럼 된다.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-release-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: my-release-api
  template:
    metadata:
      labels:
        app: my-release-api
    spec:
      containers:
        - name: api
          image: "registry.example.com/api:1.0.0"
          ports:
            - containerPort: 3000

즉 Helm template은 Kubernetes YAML을 생성하기 위한 parameterized manifest다.

_helpers.tpl

templates/_helpers.tpl는 반복되는 이름 생성 로직, label, selector 등을 함수처럼 정의할 때 자주 사용한다.

{{- define "myapp.name" -}}
{{ .Chart.Name }}
{{- end }}

{{- define "myapp.fullname" -}}
{{ .Release.Name }}-{{ .Chart.Name }}
{{- end }}

다른 template에서 사용할 수 있다.

metadata:
  name: {{ include "myapp.fullname" . }}

이런 helper를 쓰면 release name, chart name, label naming을 일관되게 유지할 수 있다.


Values

Values는 template에 주입되는 설정값이다. 기본값은 chart 내부의 values.yaml에 들어간다.

replicaCount: 2

image:
  repository: registry.example.com/api
  tag: "1.0.0"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 3000

ingress:
  enabled: false
  host: ""

resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "500m"
    memory: "512Mi"

환경별 설정은 별도 values 파일로 분리할 수 있다.

chart/
  Chart.yaml
  values.yaml
  values-dev.yaml
  values-staging.yaml
  values-prod.yaml
  templates/

설치할 때 values 파일을 지정한다.

helm install api ./myapp -f values-prod.yaml

특정 값만 command line에서 덮어쓸 수도 있다.

helm install api ./myapp --set image.tag=1.1.0 --set replicaCount=5

다만 --set을 과도하게 사용하면 배포 재현성이 떨어진다. 대부분의 환경 설정은 values 파일에 두고, CI/CD에서 매번 바뀌는 image tag 정도만 --set으로 넘기는 방식이 관리하기 쉽다.

helm upgrade --install api ./chart \
  -f values-prod.yaml \
  --set image.tag=${GIT_SHA}

Values 설계는 chart의 API가 된다

Chart를 다른 팀이나 다른 사람이 사용하게 되면 values.yaml 구조는 사실상 API가 된다.

처음에 이렇게 만들었다고 하자.

imageTag: "1.0.0"

나중에 구조를 이렇게 바꾸면 기존 사용자에게 breaking change가 될 수 있다.

image:
  tag: "1.0.0"

기존 사용자는 다음 명령을 사용하고 있었을 수 있다.

helm upgrade api ./chart --set imageTag=1.1.0

따라서 chart를 배포할 때는 values 구조를 신중하게 설계하고, chart versioning을 제대로 해야 한다.


Release

Release는 chart를 cluster에 설치한 실제 인스턴스다. Chart는 package이고, Release는 그 package를 특정 values로 설치한 결과다.

Chart: nginx
Release #1: frontend-nginx
Release #2: admin-nginx
Release #3: internal-nginx

같은 chart를 여러 번 설치할 수 있다.

helm install frontend ./nginx-chart -f values-frontend.yaml
helm install admin ./nginx-chart -f values-admin.yaml

release 목록은 다음 명령으로 확인한다.

helm list

예시:

NAME       NAMESPACE   REVISION   STATUS     CHART        APP VERSION
frontend   default     1          deployed   nginx-1.2.0  1.25.0
admin      default     1          deployed   nginx-1.2.0  1.25.0

Helm이 단순 YAML rendering 도구와 다른 점은 release history를 관리한다는 것이다.

helm history frontend

release는 upgrade와 rollback의 기준이 된다.


Repository

Repository는 chart를 저장하고 공유하는 장소다. Docker image가 container registry에 저장되는 것처럼, Helm chart는 chart repository나 OCI registry에 저장될 수 있다.

Container image → Container registry
Helm chart      → Chart repository / OCI registry

repository를 추가할 수 있다.

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

chart를 검색한다.

helm search repo nginx

chart를 설치한다.

helm install my-nginx bitnami/nginx

이 구조를 사용하면 chart를 직접 작성하지 않아도, 이미 공개되어 있거나 조직 내부에서 관리하는 chart를 재사용할 수 있다.


Helm의 동작 흐름

Helm의 기본 동작은 다음 순서로 이해할 수 있다.

1. Chart 선택
2. Values 결정
3. Template rendering
4. Kubernetes manifest 생성
5. Kubernetes API Server에 적용
6. Release record 저장

helm install

helm install api ./myapp -f values-prod.yaml

이 명령을 실행하면 Helm은 대략 다음을 수행한다.

Chart 디렉터리 읽기
values.yaml 읽기
values-prod.yaml로 값 override
templates/ 내부 파일 rendering
최종 Kubernetes YAML 생성
Kubernetes API Server에 resource 생성 요청
release 정보를 cluster에 저장

설치 전 렌더링 결과를 확인하려면 dry run을 사용한다.

helm install api ./myapp -f values-prod.yaml --dry-run --debug

이 명령은 실제 cluster에 반영하기 전에 어떤 manifest가 생성되는지 확인할 때 유용하다.

helm template

helm template은 chart를 rendering하지만 cluster에는 적용하지 않는다.

helm template api ./myapp -f values-prod.yaml

이 명령은 CI에서 특히 유용하다.

Chart template 문법 확인
최종 Kubernetes YAML 확인
Git diff에서 manifest 변화 확인
정책 검사 도구와 연결

예를 들어 다음 흐름을 만들 수 있다.

helm template

kubeconform / kubeval

conftest / OPA

kubectl apply 또는 GitOps sync

helm upgrade

이미 설치된 release를 변경하려면 helm upgrade를 사용한다.

helm upgrade api ./myapp -f values-prod.yaml --set image.tag=1.1.0

설치와 업그레이드를 하나의 idempotent한 명령으로 처리할 수도 있다.

helm upgrade --install api ./myapp -f values-prod.yaml

이 방식은 CI/CD에서 자주 사용된다.

release가 없으면 install
release가 있으면 upgrade

helm rollback

새 버전 배포 후 문제가 생기면 이전 revision으로 rollback할 수 있다.

helm history api

예시:

REVISION   UPDATED                  STATUS      CHART        APP VERSION
1          2026-06-01 10:00:00      superseded  myapp-0.1.0  1.0.0
2          2026-06-01 11:00:00      deployed    myapp-0.1.1  1.1.0

이전 revision으로 되돌린다.

helm rollback api 1

주의할 점은 Helm rollback이 application data schema까지 안전하게 되돌리는 것은 아니라는 점이다.

app v1.1.0 배포

DB schema 변경

app만 v1.0.0으로 rollback

구버전 app이 변경된 schema와 호환되지 않을 수 있음

따라서 Helm rollback은 Kubernetes resource 상태를 되돌리는 데 유용하지만, database migration까지 자동으로 안전하게 되돌린다고 보면 안 된다.

helm uninstall

release를 제거할 때는 helm uninstall을 사용한다.

helm uninstall api

주의할 점은 resource 종류에 따라 삭제 결과가 다를 수 있다는 것이다.

Deployment, Service, ConfigMap 등은 삭제됨
PVC는 reclaim policy나 chart 설계에 따라 남을 수 있음
CRD는 Helm이 일반 resource처럼 upgrade/delete하지 않는 경우가 있음
외부 cloud resource는 별도 정리 필요할 수 있음

Helm이 Kubernetes YAML을 줄이는 방식

환경별 replica 수가 다르다고 하자.

dev: replicas = 1
staging: replicas = 2
prod: replicas = 5

Helm 없이 관리하면 환경별 deployment YAML이 생긴다.

deployment-dev.yaml
deployment-staging.yaml
deployment-prod.yaml

Helm에서는 template 하나와 values 파일 세 개로 나눌 수 있다.

templates/deployment.yaml
values-dev.yaml
values-staging.yaml
values-prod.yaml

templates/deployment.yaml:

spec:
  replicas: {{ .Values.replicaCount }}

values-dev.yaml:

replicaCount: 1

values-staging.yaml:

replicaCount: 2

values-prod.yaml:

replicaCount: 5

설치 또는 업그레이드는 다음처럼 수행한다.

helm upgrade --install api ./myapp -f values-prod.yaml

이렇게 하면 “구조는 같고 값만 다른 manifest”를 깔끔하게 관리할 수 있다.


Helm과 Kubernetes object의 관계

Helm을 쓰면 Kubernetes object가 사라지는 것이 아니다. Helm은 결국 Kubernetes manifest를 생성한다.

예를 들어 chart에 Deployment, Service, Ingress template이 있으면 Helm은 최종적으로 다음 resource를 cluster에 만든다.

Deployment/api
Service/api
Ingress/api
ConfigMap/api-config
Secret/api-secret

Helm release는 이 resource들을 하나의 application 단위로 묶어 관리한다.

Release: api
├─ Deployment/api
├─ Service/api
├─ Ingress/api
├─ ConfigMap/api-config
└─ Secret/api-secret

따라서 문제를 디버깅할 때는 Helm과 Kubernetes 양쪽을 모두 봐야 한다.

Helm 관점에서 확인할 명령:

helm list
helm status api
helm history api
helm get values api
helm get manifest api

Kubernetes 관점에서 확인할 명령:

kubectl get pods
kubectl get deploy
kubectl get svc
kubectl describe pod <pod-name>
kubectl logs <pod-name>

역할을 구분하면 다음과 같다.

도구주로 보는 것
helmchart, values, release, revision, rendered manifest
kubectl실제 Kubernetes resource 상태, Pod 상태, logs, events

Helm의 upgrade와 Kubernetes rollout의 차이

자칫 실수하기 쉬운 부분이 있다.

helm upgrade와 Kubernetes rolling update는 같은 것인가?

정확히는 다르다.

helm upgrade는 release의 chart/values를 바꾸고, rendering된 manifest를 Kubernetes API에 적용하는 작업이다.

Kubernetes rolling update는 Deployment controller가 새 Pod와 old Pod를 교체하는 작업이다.

helm upgrade

새 Deployment manifest 적용

Deployment spec.template 변경

Kubernetes Deployment controller가 rollout 수행

예를 들어 image tag를 바꾼다.

helm upgrade api ./myapp --set image.tag=1.1.0

그 후 rollout 확인은 Kubernetes 명령으로 봐야 한다.

kubectl rollout status deployment/api

Helm 상태도 함께 확인한다.

helm status api

운영에서는 다음 네 가지를 함께 확인해야 한다.

Helm release status = deployed
Kubernetes rollout = 성공
Pod readiness = 통과
Application metric = 정상

helm upgrade가 성공했다고 해서 애플리케이션이 정상 동작한다는 뜻은 아니다. manifest 적용은 성공했지만 Pod가 CrashLoopBackOff일 수 있다.


Helm과 CI/CD

Helm은 CI/CD pipeline에서 자주 사용된다. 일반적인 흐름은 다음과 같다.

1. Source code commit
2. CI test
3. Container image build
4. Image push to registry
5. Helm values 또는 chart version 업데이트
6. helm upgrade --install
7. rollout 확인
8. 문제 시 rollback

예시 pipeline 명령:

docker build -t registry.example.com/api:${GIT_SHA} .
docker push registry.example.com/api:${GIT_SHA}

helm upgrade --install api ./chart \
  -f values-prod.yaml \
  --set image.tag=${GIT_SHA} \
  --namespace production

여기서 중요한 것은 image tag와 release revision이 연결된다는 점이다.

Git commit → image tag → Helm release revision → Kubernetes deployment

이 연결이 명확하면 장애 발생 시 추적이 쉬워진다.

helm history api
helm get values api --revision 3
helm get manifest api --revision 3

운영자는 언제 어떤 chart와 values로 배포되었는지 확인할 수 있다.


Secret 관리 주의점

values 파일에 secret을 평문으로 두면 안 된다.

피해야 할 예시는 다음과 같다.

database:
  password: my-real-password

Git에 올라가는 values 파일에는 secret을 직접 넣지 않는 것이 좋다. 대안은 다음과 같다.

Kubernetes Secret을 별도로 관리
External Secrets Operator 사용
Sealed Secrets 사용
SOPS로 암호화
CI/CD secret store에서 주입

Helm chart에서는 secret 값 자체가 아니라 secret name만 받게 만들 수 있다.

database:
  existingSecret: api-db-secret

template에서는 해당 secret을 참조한다.

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: {{ .Values.database.existingSecret }}
        key: password

이 방식은 chart와 values에서 민감한 값을 분리하는 데 도움이 된다.


Helm release lifecycle

Helm은 application lifecycle을 release 단위로 관리한다.

install

upgrade

rollback

uninstall
작업명령
설치helm install <release> <chart>
설치 또는 업그레이드helm upgrade --install <release> <chart>
업그레이드helm upgrade <release> <chart>
상태 확인helm status <release>
목록 확인helm list
history 확인helm history <release>
rollbackhelm rollback <release> <revision>
제거helm uninstall <release>
렌더링 확인helm template <release> <chart>
dry runhelm install <release> <chart> --dry-run --debug

Helm 2와 Helm 3의 구분

현재 Helm을 사용할 때는 Helm 3 기준으로 이해하는 것이 일반적이다. Helm 2와 Helm 3의 중요한 차이는 Tiller 제거다.

Helm 2에는 cluster 안에 Tiller라는 server-side component가 있었다.

Helm 2
helm client

Tiller in cluster

Kubernetes API Server

Helm 3에서는 Tiller가 제거되었다.

Helm 3+
helm client

Kubernetes API Server

이 차이는 보안 모델에서 중요하다. Helm 3에서는 사용자의 kubeconfig와 Kubernetes RBAC 권한을 기준으로 Helm 작업이 수행된다. 따라서 Helm을 실행하는 사용자가 어떤 namespace와 resource에 대해 어떤 권한을 갖는지 명확하게 설계해야 한다.


Helm과 Kustomize의 차이

Kubernetes manifest를 재사용하는 도구로 Helm만 있는 것은 아니다. 대표적으로 Kustomize도 있다.

간단히 구분하면 다음과 같다.

구분HelmKustomize
핵심 방식template + valuesbase + overlay patch
package 개념Chart명시적 package manager보다는 customization
release 관리있음없음
rollback/historyHelm release revision 기반별도 Git/CI/CD로 관리
외부 chart 설치강함주 목적은 아님
template 언어Go templateYAML patch 중심
복잡한 조건문가능제한적
단순 환경 차이 관리가능매우 적합
app lifecycle 관리강함kubectl apply 흐름에 가까움

Helm은 조건문을 사용할 수 있다.

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "myapp.fullname" . }}
{{- end }}

Kustomize는 기존 YAML에 patch를 덧씌우는 방식에 가깝다.

base/
  deployment.yaml
  service.yaml

overlays/
  dev/
    kustomization.yaml
  prod/
    kustomization.yaml

선택 기준은 다음처럼 잡을 수 있다.

상황적합한 도구
외부 애플리케이션 설치, release 관리, packagingHelm
내부 manifest의 환경별 patch, GitOps 단순화Kustomize
Helm chart 결과를 검토한 뒤 후처리Helm template + Kustomize
Argo CD/Flux로 chart 직접 배포Helm + GitOps

Helm이 편하지만 위험해지는 지점

Template이 너무 복잡해지는 문제

처음에는 간단한 변수 치환으로 시작한다.

replicas: {{ .Values.replicaCount }}

하지만 시간이 지나면 조건문과 반복문이 많아질 수 있다.

{{- if .Values.ingress.enabled }}
{{- range .Values.ingress.hosts }}
...
{{- end }}
{{- end }}

chart가 application 배포 정의가 아니라 작은 programming language처럼 변하면 유지보수가 어려워진다.

주의할 점은 다음과 같다.

조건문이 지나치게 많으면 chart 이해가 어려워진다.
values schema가 불명확하면 사용자가 잘못된 값을 넣기 쉽다.
template rendering 결과를 보지 않으면 실제 manifest를 예측하기 어렵다.

검증 명령:

helm lint ./chart
helm template api ./chart -f values-prod.yaml

Helm release와 수동 kubectl edit 충돌

Helm으로 관리되는 resource를 사람이 직접 수정하면 drift가 생길 수 있다.

kubectl edit deployment api

이후 helm upgrade를 실행하면 수동 변경이 덮어써질 수 있다.

운영 원칙은 명확해야 한다.

Helm이 관리하는 resource는 Helm values/chart로 변경한다.
긴급 수동 변경이 있었다면 chart/values에 반드시 반영한다.
helm get manifest와 kubectl get yaml 차이를 비교한다.

확인 명령:

helm get manifest api
kubectl get deployment api -o yaml

CRD 관리

일부 Helm chart는 CRD(CustomResourceDefinition)를 포함한다. 예를 들어 operator 계열 chart에서는 CRD가 중요한 역할을 한다.

CRD는 일반 resource와 lifecycle이 다르게 다뤄질 수 있다. CRD를 잘못 삭제하면 그 CRD를 기반으로 생성된 custom resource에도 영향이 생길 수 있다.

운영에서는 다음을 확인해야 한다.

chart가 CRD를 포함하는가?
CRD upgrade는 chart upgrade로 자동 처리되는가?
CRD 삭제 시 custom resource가 어떻게 되는가?
GitOps 도구가 CRD lifecycle을 어떻게 다루는가?

chart를 설치하기 전에 chart 문서와 CRD 포함 여부를 확인하는 습관이 필요하다.


설치 전 검증 포인트

Helm chart를 적용하기 전에 다음 명령으로 기본 검증을 수행한다.

helm lint ./chart
helm template api ./chart -f values-prod.yaml
helm install api ./chart -f values-prod.yaml --dry-run --debug

확인할 항목은 다음과 같다.

template 문법 오류가 없는가?
렌더링된 YAML이 예상과 같은가?
Secret 값이 평문으로 노출되지 않는가?
resource requests/limits가 설정되어 있는가?
Service selector와 Pod label이 일치하는가?
Ingress host가 올바른가?

특히 Service.selector와 Pod label이 맞지 않으면 Service endpoint가 비어 traffic이 전달되지 않는다.

kubectl get endpoints api

endpoint가 비어 있다면 label mismatch를 우선 의심해야 한다.


설치 후 검증 포인트

설치 또는 upgrade 후에는 Helm 상태와 Kubernetes 상태를 함께 확인해야 한다.

helm status api
helm list
kubectl get all
kubectl get pods
kubectl get svc
kubectl describe pod <pod-name>
kubectl logs <pod-name>

확인할 항목은 다음과 같다.

Helm release status가 deployed인가?
Deployment rollout이 완료되었는가?
Pod readiness가 통과했는가?
Service endpoint가 존재하는가?
Ingress가 expected host로 연결되는가?
ConfigMap/Secret이 올바르게 주입되었는가?

helm status가 정상이어도 실제 Pod가 CrashLoopBackOff일 수 있다. 따라서 release status, rollout, Pod 상태, logs, metrics를 함께 확인해야 한다.


간단한 Helm chart 작성 예시

간단한 API chart를 만든다고 하자.

helm create api

Helm은 기본 chart 구조를 생성한다.

api/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── serviceaccount.yaml
│   ├── hpa.yaml
│   └── tests/
└── charts/

직접 최소 구조를 만든다면 다음 정도로 시작할 수 있다.

api/
├── Chart.yaml
├── values.yaml
└── templates/
    ├── deployment.yaml
    └── service.yaml

Chart.yaml:

apiVersion: v2
name: api
description: API service chart
type: application
version: 0.1.0
appVersion: "1.0.0"

values.yaml:

replicaCount: 2

image:
  repository: registry.example.com/api
  tag: "1.0.0"

service:
  port: 80
  targetPort: 3000

templates/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-api
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      app: {{ .Release.Name }}-api
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}-api
    spec:
      containers:
        - name: api
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.service.targetPort }}

templates/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}-api
spec:
  selector:
    app: {{ .Release.Name }}-api
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}

렌더링:

helm template myrelease ./api

설치:

helm install myrelease ./api

업그레이드:

helm upgrade myrelease ./api --set image.tag=1.1.0

삭제:

helm uninstall myrelease

운영에서 권장하는 Helm 사용 흐름

운영에서는 대략 다음 흐름이 안전하다.

1. chart와 values를 Git에 저장한다.
2. secret은 평문으로 저장하지 않는다.
3. CI에서 helm lint를 수행한다.
4. CI에서 helm template 결과를 생성한다.
5. 생성된 manifest에 대해 policy/schema 검증을 수행한다.
6. image tag는 immutable하게 관리한다.
7. 배포는 helm upgrade --install로 수행한다.
8. rollout과 readiness를 확인한다.
9. 장애 발생 시 helm history와 rollback 경로를 확인한다.
10. 수동 kubectl 변경은 최소화하고 chart/values로 되돌려 반영한다.

예시:

helm lint ./chart

helm template api ./chart \
  -f values-prod.yaml \
  --set image.tag=${GIT_SHA}

helm upgrade --install api ./chart \
  -f values-prod.yaml \
  --set image.tag=${GIT_SHA} \
  --namespace production \
  --create-namespace

kubectl rollout status deployment/api -n production

Helm이 해결하는 문제와 해결하지 않는 문제

Helm이 잘 해결하는 문제

문제Helm의 역할
반복 YAMLtemplate과 values로 중복 감소
애플리케이션 묶음 설치chart 단위로 여러 Kubernetes resource 배포
환경별 설정values 파일로 dev/staging/prod 차이 관리
version 관리chart version과 release revision 관리
rollbackrelease revision 기준 rollback
공유chart repository로 package 배포
CI/CD 통합helm upgrade --install 기반 배포 자동화

Helm이 직접 해결하지 않는 문제

문제설명
잘못된 Kubernetes 설계chart가 잘못된 manifest를 만들면 그대로 잘못 배포된다.
애플리케이션 bugHelm은 app code를 고치지 않는다.
DB migration 안전성rollback 시 schema 호환성은 별도 설계가 필요하다.
Secret 보안values에 평문 secret을 넣으면 Helm이 보호해주지 않는다.
ObservabilityHelm으로 설치할 수는 있지만, 운영 관찰성 설계는 별도 문제다.
RBAC 정책Helm 실행자의 Kubernetes 권한 설계가 필요하다.
Drift 방지수동 변경을 완전히 막지는 못한다. GitOps 정책과 함께 관리해야 한다.

자칫 실수하기 쉬운 부분

Helm chart를 container image로 착각하기

Chart는 application binary가 아니다.

Container image
  → app code와 runtime을 담는 실행 패키지

Helm chart
  → 그 image를 Kubernetes에 어떻게 배포할지 정의하는 패키지

Helm을 쓰면 Kubernetes를 몰라도 된다고 생각하기

Helm은 Kubernetes manifest를 생성한다. 결국 문제가 생기면 Deployment, Service, Pod, Ingress, ConfigMap, Secret을 이해해야 한다.

Helm은 Kubernetes를 숨기는 도구가 아니라,
Kubernetes manifest 관리를 구조화하는 도구다.

helm install 성공을 애플리케이션 정상으로 착각하기

helm install이 성공했다는 것은 Kubernetes API에 resource 생성 요청이 성공했다는 의미에 가깝다. 애플리케이션 정상 여부는 따로 확인해야 한다.

kubectl get pods
kubectl rollout status deployment/api
kubectl logs <pod-name>
kubectl get endpoints api

values 파일을 정리하지 않고 계속 덧붙이기

오래된 values가 남아 있으면 chart upgrade 시 문제가 생길 수 있다.

deprecated value 사용
더 이상 쓰지 않는 field 잔존
chart version과 values schema 불일치
환경별 values drift

upgrade 전에는 현재 chart의 values 구조와 실제 배포 values를 비교해야 한다.

helm show values <chart>
helm get values api

요약

Helm은 Kubernetes application을 구성하는 여러 manifest를 Chart로 묶고, 환경별 차이는 Values로 분리하며, 실제 cluster에 설치된 인스턴스를 Release로 관리한다.

핵심 구조는 다음과 같다.

Kubernetes YAML 반복

Helm template으로 일반화

values로 환경별 차이 분리

chart로 packaging

release로 lifecycle 관리

Helm을 잘 사용하려면 단순히 helm install 명령만 알면 부족하다. 다음 구분을 명확히 이해해야 한다.

Chart는 package다.
Values는 configuration API다.
Template은 manifest generator다.
Release는 cluster에 설치된 chart instance다.
Upgrade는 manifest 변경을 Kubernetes에 적용하는 작업이다.
Rollback은 Helm revision 기준으로 resource 상태를 되돌리는 작업이다.
실제 Pod 교체와 self-healing은 Kubernetes controller가 수행한다.

결국 Helm은 Kubernetes의 복잡도를 없애는 도구라기보다, 반복되는 manifest와 application lifecycle을 package와 release라는 단위로 구조화하는 도구다. 따라서 Helm을 사용할수록 chart 구조, values 설계, secret 관리, rollout 검증, RBAC, drift 관리까지 함께 신경 써야 한다.