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
| Helm | Kustomize | |
|---|---|---|
| Approach | Go templates + values.yaml | Patches/overlays on plain YAML |
| Packaging & sharing | Yes: charts, repos, OCI registries | No (just directories, or Git URLs) |
| Release tracking / rollback | Yes (helm history, helm rollback) | No (use Git, or GitOps) |
| Learning curve | Templates can get gnarly | Plain YAML, easy to read |
| Built into kubectl | No | Yes (kubectl apply -k) |
| Typical use | Installing third-party software, distributing your app | Per-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 inkube-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
--versionin anything automated. helm upgradewith a missing-fsilently resets values to defaults. Keep your values files in Git.- Uninstall keeps PVCs created by StatefulSets.
- Pin
Changed since the book:
The release name is now required (
helm install <name> <chart>, or--generate-name).helm searchis nowhelm search repo/helm search hub. The oldstablerepo atstorage.googleapis.com/kubernetes-chartsis 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) andappVersion(your app’s version) inChart.yamlseparately. 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/localorhelm 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 oneoverlays/<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=warnkubectl kustomize overlays/prod # render and inspect
kubectl diff -k overlays/prod # compare with the live cluster
kubectl apply -k overlays/prod- Discussion:
configMapGeneratoradds 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
kustomizeCLI. Checkkubectl 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-configurationannotation (which breaks on objects over 256 KB, like big CRDs). - Conflicts are explicit: if the HPA owns
replicasand 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.
- No more giant
kubectl apply --server-side --field-manager=ci-pipeline -f manifests/
kubectl apply --server-side --force-conflicts -f crds/ # take ownership deliberatelyRecipe 14.4 — Convert Docker Compose to Kubernetes (kompose)
- Problem: You have a
compose.yamland 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_onhas 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 applyoverkompose up, so the result lives in Git. Modern Compose files no longer need theversion: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. HashedconfigMapGeneratorgives 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.