backend / kubernetes / 06_helm.md

Helm

5 interview angles 4 min read source

Helm

The package manager for Kubernetes. Helm bundles related k8s manifests (Deployment + Service + ConfigMap + Ingress + …) into a versioned, parameterizable chart.

Why it exists

Without Helm, deploying a non-trivial app means kubectl apply -f over a dozen YAML files, with environment-specific substitutions done manually or via a templating tool. Helm gives you:

  • Templating — one set of YAML with {{ .Values.x }} placeholders, rendered per environment.
  • Packaging — a chart is a versioned tarball you can publish/share.
  • Release tracking — Helm remembers what version of what chart with what values is currently installed; rollback to previous revision in one command.
  • Dependencies — chart A can declare chart B as a dependency.

Chart layout

orders/
├── Chart.yaml          # name, version, dependencies
├── values.yaml         # default values
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   ├── configmap.yaml
│   ├── _helpers.tpl    # reusable template snippets
│   └── NOTES.txt       # printed after install
└── charts/             # sub-chart dependencies

Chart.yaml:

apiVersion: v2
name: orders
description: Orders service
type: application
version: 1.2.3              # the chart version
appVersion: "1.0.0"         # the app image version (informational)
dependencies:
- name: postgresql
  version: "13.x.x"
  repository: https://charts.bitnami.com/bitnami

values.yaml:

replicaCount: 3
image:
  repository: my-org/orders
  tag: "1.0.0"
resources:
  requests: { cpu: 200m, memory: 256Mi }
  limits:   { cpu: 1,    memory: 512Mi }
ingress:
  enabled: true
  host: api.example.com

A template using values:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}-orders
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
      - name: app
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
        resources: {{- toYaml .Values.resources | nindent 10 }}

Installing / upgrading

# Install
helm install orders ./orders -n prod -f values-prod.yaml

# Upgrade or install (idempotent)
helm upgrade --install orders ./orders -n prod -f values-prod.yaml

# See what's deployed
helm list -n prod
helm get values orders -n prod
helm get manifest orders -n prod          # rendered YAML actually applied

# Roll back
helm history orders -n prod
helm rollback orders 3 -n prod            # back to revision 3

helm template (no install) renders to stdout — useful for CI dry-runs and gitops.

Values precedence

From lowest to highest:

  1. values.yaml in the chart.
  2. Sub-chart values.
  3. -f values-prod.yaml on the CLI.
  4. --set key=value on the CLI.

Per-environment pattern: one chart, N values files (values-dev.yaml, values-staging.yaml, values-prod.yaml).

Common patterns

A _helpers.tpl for shared snippets

{{- define "orders.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end -}}
metadata:
  labels: {{- include "orders.labels" . | nindent 4 }}

Reusable; consistent labels everywhere.

Conditionally render

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ .Release.Name }}-orders
spec:
  rules:
  - host: {{ .Values.ingress.host }}
    ...
{{- end }}

Hooks for migrations

metadata:
  annotations:
    "helm.sh/hook": pre-upgrade,pre-install
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded

Runs a Job before the Deployment is applied — good place for alembic upgrade head. Caveat: hooks aren’t transactional with the rest of the release; a failing hook leaves a half-applied state.

Helm vs Kustomize

Helm Kustomize
Approach Go templating overlay/patch
Best for parameterizable charts you share environment-specific overlays of your own YAML
Tooling separate helm CLI built into kubectl apply -k
Complexity indent gymnastics in templates YAML-native, no templating

Many teams use both: Helm for third-party charts (postgres, kafka, cert-manager), Kustomize for in-house apps. Or skip Helm entirely for in-house and use Kustomize + GitOps.

GitOps with Helm

ArgoCD or Flux render charts and apply them, with the git repo as the source of truth.

# Argo Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata: { name: orders, namespace: argocd }
spec:
  source:
    repoURL: https://github.com/myorg/charts
    path: orders
    targetRevision: main
    helm:
      valueFiles: [values-prod.yaml]
  destination: { server: https://kubernetes.default.svc, namespace: prod }
  syncPolicy:
    automated: { prune: true, selfHeal: true }

Change values in git → Argo detects → applies. No one runs helm upgrade from a laptop.

Common bugs

  • Indentation hell. nindent + toYaml for nested objects, otherwise you get cryptic YAML errors. helm template to see the rendered output.
  • Forgetting --namespace. Helm doesn’t infer namespace from kubectl config; default is default.
  • .Release.Name collisions. Two releases of the same chart in one namespace → resources collide.
  • Secrets in values files. Don’t put secrets in values-prod.yaml in git. Use external secret store + reference, or SOPS-encrypted values, or sealed-secrets.
  • Hook failures leave state half-applied. Hooks don’t rollback the release on failure; the migration Job fails, the Deployment is half-rolled-out.
  • Chart bloat. if / range everywhere makes templates unreadable. If a chart has >10 toggles, you’re probably misusing it — split it.

Interview angle

  • “What is Helm?” — package manager for Kubernetes; charts bundle parameterizable YAML with versioning, dependencies, and release tracking. Replaces hand-managing 10+ YAML files per app.
  • “Helm vs Kustomize?” — Helm is templating + packaging (share charts across orgs). Kustomize is overlay-based (environment-specific patches). Use Helm for third-party (postgres, kafka); Kustomize fits in-house apps with environment variants.
  • “How do you do DB migrations with Helm?”pre-upgrade hook runs a Job that runs alembic upgrade head (or equivalent) before the new pods come up. Watch for: hook failures leaving release in inconsistent state; race with multi-pod migrations.
  • “How do you parameterize for multiple environments?” — one chart + one values.yaml per environment (values-dev.yaml, values-prod.yaml), applied with helm upgrade --install -f values-prod.yaml. Don’t fork the chart per env.
  • “Where do secrets live in a Helm chart?” — never plain in values.yaml in git. Either External Secrets Operator + reference in the chart, or SOPS-encrypted values, or sealed-secrets.