Overview

  • Chapter 14 (the “Ecosystem”) of the Kubernetes Cookbook: Helm (Recipes 14.1–14.3) and kompose (14.4). Plus what’s become standard since: Kustomize (built into kubectl), server-side apply and GitOps.
  • The problem all of these solve: raw YAML copied per environment (dev/staging/prod) drifts and duplicates. You want one source with small, controlled differences.
  • Back to the hub: Kubernetes Cookbook · Related: Best Practices (CI/CD, versioning)

Helm vs. Kustomize at a Glance

HelmKustomize
ApproachGo templates + values.yamlPatches/overlays on plain YAML
Packaging & sharingYes: charts, repos, OCI registriesNo (just directories, or Git URLs)
Release tracking / rollbackYes (helm history, helm rollback)No (use Git, or GitOps)
Learning curveTemplates can get gnarlyPlain YAML, easy to read
Built into kubectlNoYes (kubectl apply -k)
Typical useInstalling third-party software, distributing your appPer-environment tweaks of your own manifests
  • They combine fine: Argo CD and Flux can render a Helm chart and then apply Kustomize patches on top.

Recipes

Recipe 14.1 — Install Helm

  • Problem: You don’t want to hand-write manifests for common software.
  • Solution:
brew install helm
helm version
  • Discussion: A chart = templated manifests + default values + metadata. Installing one creates a release. Helm 3 stores release state as Secrets in the release’s namespace and uses your kubeconfig permissions.

Changed since the book:

Helm 3 has no Tiller. There’s no helm init, and no server component running with cluster-admin in kube-system (that was a big security hole). The Best Practices book’s “one Tiller per namespace” workaround is obsolete too.

Source: K8s Cookbook, Recipe 14.1

Recipe 14.2 — Install Applications with Helm

  • Problem: Find a chart and deploy it.
  • Solution:
helm search hub redis                                 # searches Artifact Hub
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm show values prometheus-community/kube-prometheus-stack > values.yaml   # see the knobs
helm install mon prometheus-community/kube-prometheus-stack \
  -n monitoring --create-namespace -f my-values.yaml
helm install cache oci://registry.example.com/charts/redis --version 1.2.3  # charts from OCI registries
 
helm list -A
helm upgrade --install mon prometheus-community/kube-prometheus-stack -n monitoring -f my-values.yaml
helm history mon -n monitoring
helm rollback mon 1 -n monitoring
helm uninstall mon -n monitoring
  • Gotchas:
    • Pin --version in anything automated.
    • helm upgrade with a missing -f silently resets values to defaults. Keep your values files in Git.
    • Uninstall keeps PVCs created by StatefulSets.

Changed since the book:

The release name is now required (helm install <name> <chart>, or --generate-name). helm search is now helm search repo / helm search hub. The old stable repo at storage.googleapis.com/kubernetes-charts is dead, so find charts on Artifact Hub. Charts can live in OCI registries.

Source: K8s Cookbook, Recipe 14.2

Recipe 14.3 — Create Your Own Chart

  • Problem: Package your multi-manifest app so it’s installable with parameters.
  • Solution:
helm create mychart          # Chart.yaml, values.yaml, templates/, charts/
helm lint mychart
helm template demo mychart -f values-prod.yaml | less    # render locally: no cluster needed
helm install demo ./mychart --dry-run=server             # validate against the cluster
helm package mychart                                     # → mychart-0.1.0.tgz
helm push mychart-0.1.0.tgz oci://registry.example.com/charts
# templates/deployment.yaml (excerpt)
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}  # restart on config change
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
  • Discussion: Bump version (the chart’s version) and appVersion (your app’s version) in Chart.yaml separately. Bundle components that must upgrade and roll back together into one chart (Best Practices, Ch. 6). Hooks (helm.sh/hook: pre-upgrade) run Jobs such as DB backups or migrations at the right moment.
  • Gotchas: Template files aren’t valid YAML until rendered, so lint with helm lint / helm template, not a YAML linter.

Changed since the book:

No more ~/.helm/repository/local or helm serve. Share charts via OCI registries (GHCR, ECR, ACR, Harbor) or a static chart repo.

Source: K8s Cookbook, Recipe 14.3 · Best Practices book, Ch. 4 & 6

Modern Extra — Kustomize (built into kubectl)

  • Problem: Same app, three environments, small differences. You don’t want templates.
  • Solution: a base/ plus one overlays/<env>/ per environment.
app/
├── base/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/
    └── prod/
        ├── kustomization.yaml
        └── replicas-patch.yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
  - deployment.yaml
  - service.yaml
labels:
  - pairs:
      app.kubernetes.io/name: web
    includeSelectors: true
configMapGenerator:
  - name: web-config
    literals:
      - LOG_LEVEL=info
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: prod
resources:
  - ../../base
images:
  - name: nginx
    newTag: "1.27.2"
patches:
  - path: replicas-patch.yaml
configMapGenerator:
  - name: web-config
    behavior: merge
    literals:
      - LOG_LEVEL=warn
kubectl kustomize overlays/prod          # render and inspect
kubectl diff -k overlays/prod            # compare with the live cluster
kubectl apply -k overlays/prod
  • Discussion: configMapGenerator adds a content hash to the ConfigMap name (web-config-7f9k2…) and rewrites references. So changing config automatically rolls the Deployment, the trick the Best Practices book recommends for ConfigMap changes.
  • Gotchas: kubectl’s bundled kustomize can lag behind the standalone kustomize CLI. Check kubectl version (“Kustomize Version”).

Modern Extra — kubectl apply --server-side

  • What: The API server (not kubectl) merges your manifest and tracks which manager owns which field (managedFields).
  • Why:
    • No more giant last-applied-configuration annotation (which breaks on objects over 256 KB, like big CRDs).
    • Conflicts are explicit: if the HPA owns replicas and you try to set it, you get an error instead of a silent fight.
    • Several tools can safely manage different fields of the same object.
kubectl apply --server-side --field-manager=ci-pipeline -f manifests/
kubectl apply --server-side --force-conflicts -f crds/     # take ownership deliberately

Recipe 14.4 — Convert Docker Compose to Kubernetes (kompose)

  • Problem: You have a compose.yaml and want Kubernetes manifests.
  • Solution:
brew install kompose
kompose convert -f compose.yaml -o k8s/     # writes Deployments + Services
  • Discussion: kompose is a starting point, not a finished product. Review the result and add probes, resources, Secrets, PVCs and an Ingress by hand. Volumes and networks rarely translate cleanly.
  • Gotchas: Compose’s depends_on has no direct equivalent. Use readiness probes, init containers or retry logic (see Creating & Modifying Workloads).

Changed since the book:

Prefer kompose convert → review → commit → kubectl apply over kompose up, so the result lives in Git. Modern Compose files no longer need the version: key.

Source: K8s Cookbook, Recipe 14.4

Modern Extra — GitOps (the destination)

  • Idea: Git is the source of truth. A controller in the cluster (Argo CD or Flux) continuously pulls and applies it, and shows or undoes drift. CI builds and pushes images and bumps a tag in Git. CD = the GitOps controller syncing.
  • Why: auditable (every change is a commit), easy rollback (git revert), no cluster credentials in CI, and consistent multi-cluster rollouts (Best Practices, Ch. 5 & 12).

Summary

  • Helm 3 (no Tiller) to install and package apps: repo add → install/upgrade --install → history/rollback. Pin versions and keep values in Git.
  • Kustomize (kubectl apply -k) for per-environment overlays of plain YAML. Hashed configMapGenerator gives automatic rollouts on config change.
  • Server-side apply for explicit field ownership and conflict detection.
  • kompose gives you a first draft from Compose. GitOps (Argo CD/Flux) is where it all ends up.