G
GuideDevOps
Lesson 15 of 17

Kustomize

Part of the Kubernetes tutorial series.

What is Kustomize?

Kustomize is a template-free way to customize Kubernetes manifests. Built into kubectl, it uses composition and patches instead of templating.

Kustomize vs Helm

FeatureKustomizeHelm
Learning curveEasier (YAML-based)Steeper (Go templates)
TemplatingNo (composition instead)Yes (Go templates)
Releases/VersionsNot built-inBuilt-in
DependenciesLimitedFull support
GitOps-friendlyVeryYes

Basic Directory Structure

my-app/
├── base/                  # Base, environment-agnostic manifests
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   ├── service.yaml
│   └── configmap.yaml
└── overlays/              # Environment-specific overlays
    ├── dev/
    │   └── kustomization.yaml
    ├── staging/
    │   └── kustomization.yaml
    └── prod/
        └── kustomization.yaml

Base Configuration

Base kustomization.yaml

The base contains environment-agnostic resources:

# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
namespace: default
 
commonLabels:
  app: myapp
  managed-by: kustomize
 
commonAnnotations:
  description: "My application"
 
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
 
replicas:
- name: myapp
  count: 3

Base deployment.yaml

# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3  # Will be overridden by kustomization.yaml
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: app
        image: myapp:latest
        ports:
        - containerPort: 8080
        env:
        - name: LOG_LEVEL
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: log-level
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi

Base service.yaml

# base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  type: ClusterIP
  ports:
  - port: 80
    targetPort: 8080
  selector:
    app: myapp

Base configmap.yaml

# base/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  log-level: "info"
  database-host: "db.default.svc.cluster.local"
  api-timeout: "30"

Overlays

Overlays customize the base for specific environments.

Development Overlay

# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
namespace: development
 
bases:
- ../../base
 
namePrefix: dev-
 
commonLabels:
  environment: dev
 
replicas:
- name: myapp
  count: 1  # Only 1 replica in dev
 
patchesStrategicMerge:
- deployment-patch.yaml
 
configMapGenerator:
- name: app-config
  files:
  - configmap-dev.env
  behavior: create
# overlays/dev/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:dev
        resources:
          requests:
            cpu: 10m
            memory: 32Mi
          limits:
            cpu: 100m
            memory: 128Mi
# overlays/dev/configmap-dev.env
LOG_LEVEL=debug
DATABASE_HOST=localhost:5432
API_TIMEOUT=10

Production Overlay

# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
namespace: production
 
bases:
- ../../base
 
namePrefix: prod-
 
commonLabels:
  environment: prod
 
replicas:
- name: myapp
  count: 10  # 10 replicas in prod
 
patchesStrategicMerge:
- deployment-patch.yaml
- service-patch.yaml
 
resources:
- ingress.yaml
- pdb.yaml
 
secretsGenerator:
- name: db-secret
  envs:
  - secret.env
  behavior: create
# overlays/prod/deployment-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
      - name: app
        image: myapp:v1.0.0
        resources:
          requests:
            cpu: 500m
            memory: 512Mi
          limits:
            cpu: 1000m
            memory: 1Gi
# overlays/prod/service-patch.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  type: LoadBalancer
# overlays/prod/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: prod-myapp
            port:
              number: 80
# overlays/prod/pdb.yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: myapp
spec:
  minAvailable: 5
  selector:
    matchLabels:
      app: myapp

Using Kustomize

Preview Manifests

# Kustomize output (without applying)
kustomize build overlays/dev
kustomize build overlays/prod
 
# Or with kubectl
kubectl kustomize overlays/dev
kubectl kustomize overlays/prod

Deploy

# Dev environment
kubectl apply -k overlays/dev
 
# Prod environment
kubectl apply -k overlays/prod

Update Image

# In overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
 
bases:
- ../../base
 
images:
- name: myapp
  newTag: "1.2.0"  # Update this

Then apply:

kubectl apply -k overlays/dev

Advanced Patching

JSON Patch

# overlays/prod/kustomization.yaml
patches:
- target:
    kind: Deployment
    name: myapp
  patch: |-
    - op: replace
      path: /spec/replicas
      value: 10
    - op: add
      path: /spec/template/spec/affinity
      value:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchLabels:
                app: myapp
            topologyKey: kubernetes.io/hostname

Merge Patch

patchesJson6902:
- target:
    group: apps
    version: v1
    kind: Deployment
    name: myapp
  patch: |-
    [{"op": "replace", "path": "/spec/replicas", "value":10}]

Variable Replacement

# overlays/prod/kustomization.yaml
vars:
- name: DATABASE_HOST
  objref:
    kind: ConfigMap
    name: app-config
    apiVersion: v1
  fieldref:
    fieldpath: data.database-host
 
replicas:
- name: myapp
  count: 10

Use in ConfigMap:

data:
  database-connection-string: "postgresql://$(DATABASE_HOST)/mydb"

CommonLabels and Annotations

# base/kustomization.yaml
commonLabels:
  app: myapp
  managed-by: kustomize
  version: v1
 
commonAnnotations:
  description: "My awesome app"
  last-updated: "2024-01-15"
  contact: "platform@example.com"

Automatically applies to all resources.


Generators

ConfigMap from file

configMapGenerator:
- name: app-config
  files:
  - configs/app.yaml
  - configs/db.yaml

ConfigMap from envs

configMapGenerator:
- name: app-config
  envs:
  - config.env

Secrets

secretGenerator:
- name: db-secret
  envs:
  - secret.env
  type: Opaque

Best Practices

One base, multiple overlays

  • Keep base minimal and environment-agnostic
  • All customization in overlays

Use namePrefix to avoid conflicts

namePrefix: prod-  # All resources get prod- prefix

Track in Git

git add base/ overlays/
git commit -m "Update myapp configuration"
git push

Use with GitOps tools

# ArgoCD Application
spec:
  source:
    repoURL: https://github.com/myorg/myapp
    path: overlays/prod
  kind: kustomize

Don't nest overlays

# Bad
overlays/
  prod/
    overlays/
      east/