- name
- loom-kustomize
- description
- Kubernetes-native configuration management with Kustomize.
- allowed-tools
- ["Read","Grep","Glob","Edit","Write","Bash"]
- triggers
- ["kustomize","kustomization","overlay","base","patch","strategic merge","json patch","json6902","configmap generator","secret generator","namespace","namePrefix","nameSuffix","commonLabels","commonAnnotations","component","transformer","replacement","multi-environment","dev/staging/prod configs","k8s manifest management"]
# Kustomize Skill
## Overview
Template-free, declarative Kubernetes config management via merging. `kubectl apply -k <dir>` and `kustomize build <dir>` render an overlay. Failure modes here are overwhelmingly **silent** (wrong output, exit 0) — always `diff` the `kustomize build` output before/after any structural change.
## Core Concepts
| Term | Meaning |
| --------------- | ------------------------------------------------------------------------------------------------ |
| **Base** | Dir with `kustomization.yaml` + shared resources; never modified in place. |
| **Overlay** | Dir referencing a base under `resources:`, applying env-specific patches/generators. |
| **Patch** | Partial resource that modifies existing ones — strategic merge (default) or JSON 6902. |
| **Component** | Reusable opt-in feature bundle (`kind: Component`); runs against the parent's accumulated set. |
| **Generator** | Builds ConfigMaps/Secrets from literals/files/envs, appending a content-hash name suffix. |
| **Transformer** | Cross-cutting mutation: `namespace`, `namePrefix`/`nameSuffix`, `labels`, `images`, `replicas`. |
| **Replacement** | Copies a field value from a source resource into target field paths (successor to `vars`). |
## Directory Layout
```text
k8s/
├── base/ # shared, env-agnostic (kustomization.yaml + resources)
├── overlays/
│ ├── dev/ # low resources, debug
│ ├── staging/ # + monitoring component
│ └── prod/ # HA, security, digest-pinned images
└── components/ # opt-in features: monitoring, ingress, debug-tools
```
Keep the hierarchy **shallow** (base → overlay, not base → overlay1 → overlay2). Paths in `resources:` are relative to the kustomization.yaml's own directory.
## Quick Reference
| Task | Command |
| ---------------- | -------------------------------------------------------------------------- |
| Build | `kustomize build k8s/overlays/prod` |
| Preview diff | `kubectl diff -k k8s/overlays/prod` |
| Apply | `kubectl apply -k k8s/overlays/prod` |
| Validate | `kustomize build k8s/overlays/prod \| kubectl apply --dry-run=client -f -` |
| Migrate deprecated fields | `kustomize edit fix` (idempotent; run per dir; see Idioms) |
| Set image tag | `kustomize edit set image myapp=registry/myapp:v1.2.3` |
| Add ConfigMap | `kustomize edit add configmap app-config --from-literal=KEY=value` |
**Generators** (`configMapGenerator` / `secretGenerator` items): `literals:` (`K=v`), `files:` (`app.properties`, or `alias=path`), `envs:` (`config.env`). Secrets add `type:` (e.g. `kubernetes.io/tls`). `behavior:` = `create` (default) | `merge` | `replace`.
**Transformers** (top-level keys): `namespace`, `namePrefix`, `nameSuffix`, `labels` (use over deprecated `commonLabels`), `commonAnnotations`, `images` (`newName`/`newTag`/`digest`), `replicas` (`- name: x count: n`), `replacements`.
## Patch Strategies
| Aspect | Strategic Merge (default) | JSON 6902 (RFC 6902) |
| ------------- | ----------------------------------------------------------- | ------------------------------------------------------ |
| Merge model | K8s-schema-aware; maps by key, lists merge on known key | `op` (`add`/`remove`/`replace`/`move`/`copy`/`test`) on JSON Pointer paths |
| Best for | Field/env/resource-limit tweaks, adding containers | Precise array-index ops, conditional `test`, CRDs |
| Whole-list replace | **Unreliable** — `$patch: replace` regressed (issue #2980, ignored/merged instead) | Reliable: `op: replace` on the list path |
Both go under the unified `patches:` field, which auto-detects type from content and whose `target` supports `group/version/kind/name/namespace` + `labelSelector`/`annotationSelector` (one patch → many resources). `patchesStrategicMerge`/`patchesJson6902` are deprecated (v5.0.0) and gone from the v1 API.
## Components
Reusable opt-in bundles for features only some environments need (monitoring, ingress, debug tools, security hardening). Use a **patch** instead for required, always-applied, or env-specific value tweaks.
```yaml
# k8s/components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1 # alpha — API may change without deprecation window
kind: Component
resources:
- servicemonitor.yaml
patches:
- path: patch-metrics.yaml
target: {kind: Deployment}
labels:
- pairs: {prometheus.io/scrape: "true"}
includeSelectors: false
```
> **Reference Components under `components:`, never `resources:`.** A Component placed under `resources:` is silently misapplied as a plain manifest; kustomize enforces the split (Components rejected from `resources:`, Kustomizations from `components:`). Components run against the parent's accumulated resource set and **can be nested**.
## Examples
### Base kustomization + Deployment
```yaml
# k8s/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: myapp
# Use the labels transformer, NOT deprecated commonLabels (which mutates the
# IMMUTABLE spec.selector.matchLabels → delete/recreate on any later change).
# includeSelectors defaults false (safe on live resources); includeTemplates
# reaches pod templates. Define real selector labels directly in deployment.yaml.
labels:
- pairs: {app: myapp, managed-by: kustomize}
includeSelectors: false
includeTemplates: true
commonAnnotations: {contact: team@example.com}
resources:
- deployment.yaml
- service.yaml
- serviceaccount.yaml
configMapGenerator:
- name: app-config
literals: [LOG_LEVEL=info, MAX_CONNECTIONS=100]
images:
- name: myapp
newName: registry.example.com/myapp
newTag: latest
```
```yaml
# k8s/base/deployment.yaml (selector label defined explicitly — not injected)
apiVersion: apps/v1
kind: Deployment
metadata: {name: myapp}
spec:
replicas: 1
selector:
matchLabels: {app: myapp}
template:
metadata:
labels: {app: myapp}
spec:
serviceAccountName: myapp
containers:
- name: app
image: myapp
ports: [{containerPort: 8080, name: http}]
envFrom: [{configMapRef: {name: app-config}}]
resources:
requests: {memory: 128Mi, cpu: 100m}
limits: {memory: 256Mi, cpu: 200m}
readinessProbe:
httpGet: {path: /ready, port: http}
initialDelaySeconds: 5
```
### Production overlay (annotated)
```yaml
# k8s/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: myapp-prod
labels:
- pairs: {environment: prod, criticality: high}
includeSelectors: false
includeTemplates: true
commonAnnotations:
oncall: sre-team@example.com
# SINGLE resources: block. A second resources: key later in the same document
# silently wins (duplicate YAML map keys), dropping ../../base entirely.
resources:
- ../../base
- poddisruptionbudget.yaml
- horizontalpodautoscaler.yaml
- networkpolicy.yaml
patches:
- path: patch-resources.yaml # bump requests/limits
- path: patch-affinity.yaml # required podAntiAffinity + nodeAffinity
- path: patch-security.yaml # runAsNonRoot, drop ALL, RO rootfs, seccomp
configMapGenerator:
- name: app-config
behavior: merge
envs: [config-values.env]
secretGenerator:
- name: app-secrets
envs: [secrets.env] # gitignored / external-managed
images:
- name: myapp
newTag: v1.2.3
digest: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 # pin by digest in prod
replicas:
- {name: myapp, count: 5}
components:
- ../../components/monitoring
- ../../components/ingress
```
Overlay patch files are minimal partial docs matched by `kind`/`name`, e.g. a security patch:
```yaml
# patch-security.yaml
apiVersion: apps/v1
kind: Deployment
metadata: {name: myapp}
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile: {type: RuntimeDefault}
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: {drop: [ALL]}
```
### JSON 6902 patch (precise array ops)
```yaml
# kustomization.yaml
patches:
- path: json-patch.yaml
target: {group: apps, version: v1, kind: Deployment, name: myapp}
```
```yaml
# json-patch.yaml — list of {op, path, value}; auto-detected as JSON 6902
- op: add # append sidecar
path: /spec/template/spec/containers/-
value: {name: log-shipper, image: fluent/fluent-bit:2.0}
- op: replace # container 0 image
path: /spec/template/spec/containers/0/image
value: registry.example.com/myapp:v1.2.3
- op: remove # env var by index
path: /spec/template/spec/containers/0/env/3
- op: test # conditional guard
path: /spec/replicas
value: 1
- op: replace
path: /spec/replicas
value: 5
```
### Generators
```yaml
configMapGenerator:
- name: app-config
behavior: merge # create (default) | replace | merge
literals: [LOG_LEVEL=debug] # overrides base key
files: [app.properties, tls.crt=certs/server.crt]
envs: [config.env]
options:
disableNameSuffixHash: true # ⚠ defeats config-driven rollouts (see Gotchas)
secretGenerator:
- name: tls-secrets
files: [tls.crt, tls.key]
type: kubernetes.io/tls
- name: app-secrets
envs: [secrets.env] # gitignored — never commit secrets
```
### Replacements (propagate a field value)
```yaml
replacements:
# Propagate hashed ConfigMap name into the Deployment volume ref
- source: {kind: ConfigMap, name: app-config, fieldPath: metadata.name}
targets:
- select: {kind: Deployment}
fieldPaths:
- spec.template.spec.volumes.[name=config].configMap.name
```
### Remote bases
```yaml
resources:
- https://github.com/org/repo//k8s/base?ref=v1.0.0 # pin to tag or full SHA (see Security)
```
## Common Tasks
```bash
# Validate / preview
kustomize build k8s/overlays/prod | kubectl apply --dry-run=server -f -
kubectl diff -k k8s/overlays/prod
# Inspect name/hash transformations
kustomize build k8s/overlays/prod | rg '^ name:'
kustomize build k8s/overlays/prod | rg -A2 'kind: ConfigMap'
# Migrate deprecated fields in place (idempotent; wrap in a build-diff)
kustomize edit fix
# Bootstrap kustomization.yaml from existing manifests
kustomize create --autodetect # or --resources deployment.yaml,service.yaml
```
## GitOps & CI/CD Integration
**ArgoCD**: `Application.spec.source.path: k8s/overlays/prod`, `syncPolicy.automated {prune, selfHeal}`.
**Flux**: `kustomize.toolkit.fluxcd.io/v1 Kustomization` with `spec.path`, `prune: true`, `sourceRef` (GitRepository), `healthChecks`.
Both bundle their **own** kustomize version — pin/match it (see Currency gotcha). Prefer the controller's authenticated Git source over remote bases in `resources:`.
```bash
# CI: render, then validate. kubeval is unmaintained — use kubeconform.
kustomize build "k8s/overlays/${OVERLAY}" > all.yaml
kubeconform -strict -summary -kubernetes-version 1.29.0 all.yaml
conftest test all.yaml # OPA policy gate
```
### Helm inflation gotcha
عرض على GitHub