The problem
The config repo (26.1) must say "payments runs with 1 replica in dev and 3 in prod, prod has more memory, both run image 1.5.0". Copying the whole Deployment YAML into a dev folder and a prod folder works for a week, then the copies drift apart and nobody knows which differences are on purpose. You want one shared description plus a short list of what each environment changes.
What you need to know already: 26.1 (config repo, environments as directories), 15.16 (Deployments), 15.29 (ConfigMaps, envFrom), 15.40 (kubectl apply, declarative YAML), 25.19 and 25.24 (Helm charts and templates, for the comparison at the end), 10.5 (image tags and digests).
Patching instead of templating
Helm (25.19) solves this with templates: YAML with {{ }} holes, filled from values. kustomize does it differently. It takes plain, valid Kubernetes YAML and applies transformations to it - edits such as "set the namespace", "change the replicas", "merge in this partial object". There is no template language.
- The base is ordinary manifests you could
kubectl applyas they are. - An overlay is a directory that points at the base and lists the edits for one environment.
- Each directory has a
kustomization.yaml: the file that says what goes in and which edits to make.
kustomize is built into kubectl: kubectl kustomize DIR prints the result, kubectl apply -k DIR (-k = "this is a kustomize directory") applies it. Argo CD recognises a directory with a kustomization.yaml by itself.
A base
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources: # the files that make up the base
- deployment.yaml
- service.yaml
configMapGenerator: # build a ConfigMap from these key=value pairs
- name: payments-config
literals:
- SPRING_PROFILES_ACTIVE=default
- PAYMENTS_GATEWAY_TIMEOUT=2s
An overlay
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: pay-prod # sets metadata.namespace on everything
resources:
- ../../base # a directory with its own kustomization.yaml
replicas:
- name: payments-api # the Deployment's name
count: 3
images:
- name: registry.lab/pay/payments-api # matches this image NAME in any container
newTag: "1.5.0" # or digest: sha256:... (or newName: to change the name)
configMapGenerator:
- name: payments-config
behavior: merge # add to / override keys of the base's generator of the same name
literals:
- SPRING_PROFILES_ACTIVE=prod
patches:
- path: memory.yaml # a strategic-merge patch, below
A patch
A patch is a partial object: only the fields you want to change, plus enough to identify the object (kind + name). kustomize merges it into the base.
# overlays/prod/memory.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
template:
spec:
containers:
- name: payments-api # lists of containers merge by NAME
resources:
limits:
memory: 768Mi
This kind is called a strategic merge patch: Kubernetes-aware merging, where a list like containers is matched item by item by its name field instead of being replaced whole.
kubectl kustomize overlays/prod prints the finished YAML; kubectl apply -k overlays/prod applies it. Always read the output before applying: it is the only place the whole, final object exists.
The transformers you will use
A transformer is one kind of edit kustomize can make:
namespace: x put every object into namespace x (and fix references like RoleBinding subjects)
namePrefix: / nameSuffix: rename every object - and every reference to it
labels: add labels (includeSelectors: true also changes selectors - careful)
images: change an image's name/tag/digest without a patch
replicas: change replicas of a Deployment/StatefulSet by name
patches: strategic merge (a partial object, as above) or JSON 6902:
- target: {kind: Deployment, name: payments-api}
patch: |-
- op: replace
path: /spec/template/spec/containers/0/imagePullPolicy
value: IfNotPresent
A JSON 6902 patch (named after RFC 6902, the standard that defines it) is a list of operations - add, replace, remove - each on a path into the object. /containers/0/ means "the first container": items are addressed by index (position). That breaks silently when someone adds a container to the base and the positions move. Prefer strategic merge unless you must delete or replace a list item.
Generators and the hash
A generator creates an object for you. configMapGenerator and secretGenerator create a ConfigMap or Secret whose name gets a hash of its content appended: payments-config-7k2fdm9t4c (a hash is a short fingerprint computed from the data; different data, different hash).
kustomize rewrites every reference to it (envFrom, volumes, valueFrom) to the hashed name. So change one literal and:
- the ConfigMap's name changes,
- so the Deployment's pod template changes (it names the ConfigMap),
- so the Deployment rolls its pods (15.16) - they pick up the new config.
Plain ConfigMaps do not do that: pods keep the old env values until restarted (15.29). This is the same effect as Helm's checksum annotation (25.24), for free. The old ConfigMap stays in the cluster until something prunes it (deletes what is no longer in git) - Argo CD with prune: true does, 26.9. generatorOptions: {disableNameSuffixHash: true} turns the hash off - and with it the automatic roll.
secretGenerator exists, but a Secret generated from a literal in git is a password in git. Lesson 26.22 is about what to do instead.
Helm or kustomize?
helm packaging and distribution (versioned charts, repos, OCI), parameters with
defaults, logic (if/range), hooks, a release history. Best for things you
SHIP to others: platform base charts, third-party software.
kustomize plain YAML, environment differences as patches, no release state.
Best for YOUR OWN environments of a service.
They combine. Argo CD can render a Helm chart with a values file per environment, and kustomize can render a chart itself (helmCharts: in kustomization.yaml, run with --enable-helm) and then patch the output - the usual answer when a third-party chart lacks one setting you need. In bank-style platforms you see both: a Helm base chart owned by the platform team, and per-service overlays or values files in a config repo.
What you can now do
- Write a base and a per-environment overlay with namespace, replicas, image and a strategic-merge patch.
- Explain why the generator's hash makes pods restart when config changes.
- Render with
kubectl kustomizeand apply withkubectl apply -k.