--- id: devops-helm-deep title: Helm 깊이 — Chart / Templating / Hook category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [devops, helm, kubernetes, vibe-coding] tech_stack: { language: "YAML / Go template", applicable_to: ["DevOps"] } applied_in: [] aliases: [Helm chart, values.yaml, template, hook, dependency, helmfile] --- # Helm Deep > Kubernetes package manager. **Chart = template + values**. 환경별 다른 values + dependency + hook + rollback. 큰 프로덕션 K8s 의 표준. ## 📖 핵심 개념 - Chart: package (templates / values / metadata). - Values: 환경별 config. - Template: Go template + Helm functions. - Release: cluster 안 deployed instance. ## 💻 코드 패턴 ### Chart 구조 ``` my-app/ ├── Chart.yaml ├── values.yaml ├── values-prod.yaml ├── values-dev.yaml ├── templates/ │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── configmap.yaml │ ├── secret.yaml │ ├── serviceaccount.yaml │ ├── _helpers.tpl │ └── tests/ │ └── connection-test.yaml └── charts/ # dependency ``` ### Chart.yaml ```yaml apiVersion: v2 name: my-app description: Acme API service version: 1.0.0 # chart version appVersion: "1.5.0" # app version type: application dependencies: - name: postgresql version: 13.2.0 repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled - name: redis version: 18.0.0 repository: https://charts.bitnami.com/bitnami ``` ### values.yaml (default) ```yaml replicaCount: 2 image: repository: ghcr.io/myorg/my-app tag: "" # default = appVersion pullPolicy: IfNotPresent service: type: ClusterIP port: 80 ingress: enabled: true className: nginx hosts: - host: api.example.com resources: requests: cpu: 100m memory: 256Mi limits: cpu: 500m memory: 512Mi postgresql: enabled: true auth: username: app database: app env: NODE_ENV: production ``` ### values-prod.yaml (override) ```yaml replicaCount: 5 resources: requests: cpu: 500m memory: 1Gi limits: cpu: 2 memory: 4Gi postgresql: enabled: false # External RDS env: DATABASE_URL: postgresql://prod... ``` ### Deployment template ```yaml # templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-app.fullname" . }} labels: {{- include "my-app.labels" . | nindent 4 }} spec: replicas: {{ .Values.replicaCount }} 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 }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - containerPort: 8080 env: {{- range $k, $v := .Values.env }} - name: {{ $k }} value: {{ $v | quote }} {{- end }} resources: {{- toYaml .Values.resources | nindent 12 }} livenessProbe: httpGet: path: /healthz port: 8080 readinessProbe: httpGet: path: /readyz port: 8080 ``` ### Helpers (_helpers.tpl) ```yaml {{/* Common labels */}} {{- define "my-app.labels" -}} helm.sh/chart: {{ include "my-app.chart" . }} app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/version: {{ .Chart.AppVersion }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "my-app.selectorLabels" -}} app.kubernetes.io/name: {{ .Chart.Name }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Fullname */}} {{- define "my-app.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} ``` ### 명령 ```bash # Render local helm template my-app . -f values-prod.yaml # Lint helm lint . # Install helm install my-app . -f values-prod.yaml -n prod # Upgrade helm upgrade my-app . -f values-prod.yaml -n prod # Diff (변경 미리) helm plugin install https://github.com/databus23/helm-diff helm diff upgrade my-app . -f values-prod.yaml -n prod # Rollback helm rollback my-app 5 -n prod # revision 5 # Uninstall helm uninstall my-app -n prod ``` ### Conditional render ```yaml {{- if .Values.ingress.enabled }} apiVersion: networking.k8s.io/v1 kind: Ingress ... {{- end }} {{- if .Values.podAnnotations }} annotations: {{- toYaml .Values.podAnnotations | nindent 4 }} {{- end }} ``` ### Loop ```yaml spec: template: spec: containers: {{- range .Values.sidecars }} - name: {{ .name }} image: {{ .image }} {{- end }} ``` ### Secret (sealed / external) ```yaml # templates/secret.yaml apiVersion: v1 kind: Secret metadata: name: {{ include "my-app.fullname" . }} type: Opaque stringData: {{- range $k, $v := .Values.secrets }} {{ $k }}: {{ $v | quote }} {{- end }} ``` ⚠️ Secret in values.yaml prod = bad. Sealed Secrets / External Secrets Operator. ### Hooks (lifecycle) ```yaml # Pre-install / pre-upgrade — DB migration apiVersion: batch/v1 kind: Job metadata: name: {{ include "my-app.fullname" . }}-migrate annotations: "helm.sh/hook": pre-upgrade,pre-install "helm.sh/hook-weight": "1" "helm.sh/hook-delete-policy": before-hook-creation spec: template: spec: containers: - name: migrate image: ... command: ["yarn", "migrate:up"] restartPolicy: Never ``` → Helm install / upgrade 전 자동 migration. ### Test ```yaml # templates/tests/connection.yaml apiVersion: v1 kind: Pod metadata: name: "{{ .Release.Name }}-test-connection" annotations: "helm.sh/hook": test spec: containers: - name: wget image: busybox command: ['wget', '{{ include "my-app.fullname" . }}:{{ .Values.service.port }}/healthz'] restartPolicy: Never ``` ```bash helm test my-app ``` ### Dependency ```yaml # Chart.yaml dependencies: - name: postgresql version: ~13.0.0 repository: oci://registry-1.docker.io/bitnamicharts ``` ```bash helm dependency update helm install my-app . -f values.yaml ``` → Postgres 자동 같이 install. ### Helmfile (multiple chart) ```yaml # helmfile.yaml releases: - name: my-app namespace: prod chart: ./charts/my-app values: [values-prod.yaml] - name: monitoring namespace: monitoring chart: prometheus-community/kube-prometheus-stack values: [monitoring-values.yaml] ``` ```bash helmfile sync helmfile diff ``` → 여러 chart 한 번에. ### Multi-environment ``` values.yaml # baseline / dev values-staging.yaml # staging values-prod.yaml # prod helm upgrade my-app . -f values-staging.yaml -n staging helm upgrade my-app . -f values-prod.yaml -n prod ``` ### Chart museum / OCI registry ```bash # Push to OCI helm package . helm push my-app-1.0.0.tgz oci://registry.example.com/charts # Install from OCI helm install my-app oci://registry.example.com/charts/my-app --version 1.0.0 ``` ### Sub-chart override ```yaml # 자식 chart 의 values postgresql: primary: persistence: size: 100Gi auth: database: app ``` ### Common functions ```yaml {{ .Values.x | default "fallback" }} {{ .Values.x | quote }} {{ .Values.x | nindent 4 }} {{ tpl .Values.x . }} # template within value {{ include "common.template" . }} {{ required "x is required" .Values.x }} {{ printf "%s-%s" .a .b }} {{ toYaml .Values.complex | nindent 4 }} {{ b64enc "secret" }} {{ randAlphaNum 32 }} ``` ### Values schema validation ```json // values.schema.json { "$schema": "https://json-schema.org/draft-07/schema#", "properties": { "replicaCount": { "type": "integer", "minimum": 1 }, "image": { "type": "object", "required": ["repository"], "properties": { "repository": { "type": "string" }, "tag": { "type": "string" } } } }, "required": ["replicaCount"] } ``` → helm install 시 자동 검증. ### CI ```yaml - run: helm lint . - run: helm template . -f values-prod.yaml | kubectl --dry-run=server apply -f - - run: helm upgrade --install my-app . -f values-prod.yaml -n prod --atomic --timeout 5m ``` ### --atomic flag ```bash helm upgrade --install --atomic ``` → 실패 시 자동 rollback. 안전. ### Chart vs Kustomize ``` Helm: Template engine. Complex logic. Kustomize: Overlay (patch) — declarative, no logic. KCL / CUE: New schema lang. → Helm 가 가장 인기. Kustomize 가 단순. ``` ### ArgoCD + Helm (GitOps) ```yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: my-app spec: source: repoURL: https://github.com/myorg/charts chart: my-app targetRevision: 1.0.0 helm: valueFiles: [values-prod.yaml] destination: server: https://kubernetes.default.svc namespace: prod ``` → Git = truth. Argo 가 sync. ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | K8s app 배포 | Helm | | 단순 / overlay | Kustomize | | Multi-cluster / GitOps | ArgoCD + Helm | | 매우 dynamic | Helm + Helmfile | | 사내 chart 공유 | OCI registry | | Public chart | Artifact Hub | ## ❌ 안티패턴 - **Secret in values.yaml + git**: leak. - **--atomic 없이 prod**: 실패 시 broken state. - **Chart version + appVersion 같음**: chart 변경도 app version bump 필요. - **Single huge values.yaml**: 분리. - **Hook 가 idempotent X**: 재시도 시 깨짐. - **No values.schema**: 잘못된 values 통과. - **모든 env 같은 chart**: dev / prod 차이 안 됨. ## 🤖 LLM 활용 힌트 - Chart + values per env + atomic upgrade. - Sealed Secrets / External Secrets. - ArgoCD GitOps. - Helmfile 여러 chart. ## 🔗 관련 문서 - [[DevOps_Kubernetes_Basics]] - [[DevOps_ArgoCD_GitOps]] - [[DevOps_Service_Mesh_Deep]]