Kustomize for Kubernetes Configuration Management

Kustomize provides template-free customization of Kubernetes manifests, enabling environment-specific variations without duplicating YAML. Built into kubectl, it manages configuration variations through strategic patches and overlays.

What is Kustomize

Kustomize transforms Kubernetes manifests without templates, using declarative overlays to customize base configurations.

Template-Free Approach

Traditional template approach with Helm:

# values.yaml replicaCount: {{ .Values.replicaCount }} image: repository: {{ .Values.image.repository }} tag: {{ .Values.image.tag }}

Kustomize approach:

# base/deployment.yaml - Plain Kubernetes YAML apiVersion: apps/v1 kind: Deployment metadata: name: api-service spec: replicas: 3 template: spec: containers: - name: api image: registry.example.com/api-service:v1.0.0

No templating syntax - pure Kubernetes manifests that work directly with kubectl.

Built Into kubectl

Kustomize integrates natively with kubectl:

# Apply kustomization directly kubectl apply -k overlays/production/ # Preview generated manifests kubectl kustomize overlays/production/ # Diff against cluster kubectl diff -k overlays/production/

No separate tool installation required for basic usage.

graph TD A[Base Manifests] --> B[Kustomization] C[Overlay Patches] --> B D[Environment Vars] --> B B --> E[kubectl apply -k] E --> F[Kubernetes Cluster] style B fill:#e1f5ff style F fill:#d4f1d4

Kustomize Structure

Kustomize organizes manifests using base and overlay directories.

Base Configuration

Base contains common configuration:

k8s/ └── base/ ├── kustomization.yaml ├── deployment.yaml ├── service.yaml └── configmap.yaml

Base deployment:

# base/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: api-service labels: app: api-service spec: replicas: 3 selector: matchLabels: app: api-service template: metadata: labels: app: api-service spec: containers: - name: api image: registry.example.com/api-service:v1.0.0 ports: - containerPort: 8080 env: - name: LOG_LEVEL value: info resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi

Base service:

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

Base kustomization:

# base/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - deployment.yaml - service.yaml - configmap.yaml commonLabels: app: api-service managed-by: kustomize

Overlays

Overlays customize base for specific environments:

k8s/ ├── base/ │ ├── kustomization.yaml │ ├── deployment.yaml │ └── service.yaml └── overlays/ ├── development/ │ ├── kustomization.yaml │ └── replica-patch.yaml ├── staging/ │ ├── kustomization.yaml │ └── replica-patch.yaml └── production/ ├── kustomization.yaml ├── replica-patch.yaml └── hpa.yaml

Development overlay:

# overlays/development/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization bases: - ../../base namePrefix: dev- commonLabels: environment: development replicas: - name: api-service count: 1 images: - name: registry.example.com/api-service newTag: dev-latest configMapGenerator: - name: api-config behavior: merge literals: - LOG_LEVEL=debug

Production overlay:

# overlays/production/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization bases: - ../../base namePrefix: prod- commonLabels: environment: production replicas: - name: api-service count: 5 images: - name: registry.example.com/api-service newTag: v2.1.0 resources: - hpa.yaml patches: - path: replica-patch.yaml target: kind: Deployment name: api-service configMapGenerator: - name: api-config behavior: merge literals: - LOG_LEVEL=warn

Production HPA:

# overlays/production/hpa.yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: api-service spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: api-service minReplicas: 5 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70

Patching Strategies

Kustomize supports multiple patching approaches for customization.

Strategic Merge Patch

Merge patches into existing manifests:

# overlays/production/replica-patch.yaml apiVersion: apps/v1 kind: Deployment metadata: name: api-service spec: replicas: 5 template: spec: containers: - name: api resources: requests: cpu: 200m memory: 256Mi limits: cpu: 1000m memory: 1Gi

Strategic merge intelligently combines with base deployment.

JSON Patch

Precise modifications using JSON patch syntax:

# overlays/staging/kustomization.yaml patchesJson6902: - target: version: v1 kind: Deployment name: api-service patch: |- - op: replace path: /spec/replicas value: 3 - op: add path: /spec/template/spec/containers/0/env/- value: name: FEATURE_FLAG_NEW_API value: "true" - op: remove path: /spec/template/spec/containers/0/env/0

JSON patches provide surgical precision for complex modifications.

Patch Transformers

Apply patches based on selectors:

# kustomization.yaml patches: - target: kind: Deployment labelSelector: "tier=backend" patch: |- apiVersion: apps/v1 kind: Deployment metadata: name: not-important spec: template: spec: securityContext: runAsNonRoot: true runAsUser: 1000

Transformers apply changes to multiple resources matching criteria.

Kustomization Features

Kustomize provides powerful features for manifest management.

Image Management

Update container images without editing manifests:

# kustomization.yaml images: - name: registry.example.com/api-service newName: registry.example.com/api-service-v2 newTag: 2.1.0 # Or using digest for immutability - name: registry.example.com/api-service digest: sha256:abc123...

Image transformers enable CI/CD integration:

# Update image tag from CI pipeline kustomize edit set image registry.example.com/api-service:$GIT_SHA # Generated kustomization.yaml includes: # images: # - name: registry.example.com/api-service # newTag: abc123def

ConfigMap and Secret Generators

Generate ConfigMaps and Secrets from files or literals:

# kustomization.yaml configMapGenerator: - name: app-config files: - configs/application.properties - configs/logging.conf literals: - DATABASE_HOST=postgresql.production.svc - DATABASE_PORT=5432 secretGenerator: - name: app-secrets files: - secrets/api-key.txt - secrets/database-password.txt literals: - ADMIN_PASSWORD=ChangeMe123 options: disableNameSuffixHash: false

Generators automatically append hashes to names, triggering pod restarts when configurations change:

# Generated ConfigMap name apiVersion: v1 kind: ConfigMap metadata: name: app-config-8m2d4b7gkf data: DATABASE_HOST: postgresql.production.svc DATABASE_PORT: "5432"

Name Prefix and Suffix

Add prefixes or suffixes to resource names:

# kustomization.yaml namePrefix: prod- nameSuffix: -v2 resources: - deployment.yaml # Becomes prod-api-service-v2 - service.yaml # Becomes prod-api-service-v2

Naming conventions support multi-tenancy and versioning.

Label and Annotation Transformers

Add labels or annotations to all resources:

# kustomization.yaml commonLabels: environment: production team: platform cost-center: engineering commonAnnotations: managed-by: kustomize deployment-date: "2024-08-31" contact: platform-team@example.com

Labels and annotations propagate to all resources including selectors.

Composition Patterns

Combine multiple kustomizations for complex scenarios.

Multi-Base Composition

Reference multiple bases:

# overlays/production/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization bases: - ../../components/api-service - ../../components/database - ../../components/monitoring namePrefix: prod- commonLabels: environment: production

Multi-base composition assembles complete applications from components.

Components

Reusable configuration fragments:

k8s/ ├── base/ │ └── kustomization.yaml ├── components/ │ ├── monitoring/ │ │ ├── kustomization.yaml │ │ ├── servicemonitor.yaml │ │ └── prometheusrule.yaml │ ├── tls/ │ │ ├── kustomization.yaml │ │ └── certificate.yaml │ └── vault-integration/ │ ├── kustomization.yaml │ └── external-secret.yaml └── overlays/ └── production/ └── kustomization.yaml

Component kustomization:

# components/monitoring/kustomization.yaml apiVersion: kustomize.config.k8s.io/v1alpha1 kind: Component resources: - servicemonitor.yaml - prometheusrule.yaml

Include components selectively:

# overlays/production/kustomization.yaml bases: - ../../base components: - ../../components/monitoring - ../../components/tls - ../../components/vault-integration
graph TD A[Base Configuration] --> E[Production Overlay] B[Monitoring Component] --> E C[TLS Component] --> E D[Vault Component] --> E E --> F[Final Manifests] F --> G[kubectl apply] style E fill:#fff4e6 style F fill:#e1f5ff

Integration with CI/CD

Automate Kustomize in deployment pipelines.

GitOps with ArgoCD

ArgoCD supports Kustomize natively:

# argocd-application.yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: api-service namespace: argocd spec: project: default source: repoURL: https://github.com/organization/k8s-manifests targetRevision: main path: overlays/production destination: server: https://kubernetes.default.svc namespace: production syncPolicy: automated: prune: true selfHeal: true

ArgoCD automatically detects Kustomize and applies overlays.

CI Pipeline Integration

Update images from CI pipeline:

# .github/workflows/deploy.yaml name: Build and Deploy on: push: branches: [main] jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout application code uses: actions/checkout@v3 path: app - name: Build and push image working-directory: app run: | docker build -t registry.example.com/api-service:${{ github.sha }} . docker push registry.example.com/api-service:${{ github.sha }} - name: Checkout manifests repo uses: actions/checkout@v3 with: repository: organization/k8s-manifests path: manifests token: ${{ secrets.GITHUB_TOKEN }} - name: Update image tag working-directory: manifests/overlays/production run: | kustomize edit set image registry.example.com/api-service:${{ github.sha }} - name: Commit and push working-directory: manifests run: | git config user.name "GitHub Actions" git config user.email "actions@github.com" git add overlays/production/kustomization.yaml git commit -m "Update api-service to ${{ github.sha }}" git push

Pipeline updates kustomization, triggering ArgoCD sync.

Advanced Techniques

Sophisticated Kustomize usage for complex requirements.

Remote Bases

Reference bases from external repositories:

# kustomization.yaml bases: - github.com/kubernetes-sigs/kustomize/examples/multibases/dev/?ref=v3.7.0 - https://github.com/organization/k8s-common//base?ref=v1.2.0 resources: - deployment.yaml

Remote bases enable sharing common configurations across teams.

Kustomization Transformers

Custom transformers with plugins:

# kustomization.yaml transformers: - |- apiVersion: builtin kind: PrefixSuffixTransformer metadata: name: customTransformer prefix: prod- fieldSpecs: - path: metadata/name kind: ConfigMap - |- apiVersion: builtin kind: LabelTransformer metadata: name: labelTransformer labels: version: v2.1.0 fieldSpecs: - path: metadata/labels create: true

Variable Substitution

Replace variables in manifests:

# kustomization.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - deployment.yaml vars: - name: SERVICE_NAME objref: kind: Service name: api-service apiVersion: v1 fieldref: fieldpath: metadata.name configurations: - varreference.yaml

Variable reference configuration:

# varreference.yaml varReference: - path: spec/template/spec/containers/env/value kind: Deployment

Deployment using variable:

# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: api-service spec: template: spec: containers: - name: api env: - name: SERVICE_NAME value: $(SERVICE_NAME)

Kustomize vs Helm

Understanding when to use each tool:

Aspect Kustomize Helm
Templates No templates, pure YAML Go templates
Learning Curve Shallow Steeper
Complexity Simple overlays Complex logic possible
kubectl Integration Native Separate tool
Package Management No Yes (charts as packages)
Version Management Git-based Chart versions
Dependencies File-based Chart dependencies

Use Kustomize when:

Use Helm when:

Common Pitfalls

Name Conflicts: Multiple overlays using same names cause conflicts. Use unique prefixes per environment.

Patch Ordering: Patches apply in specific order. Test generated manifests to verify expected results.

Base Modifications: Changing bases affects all overlays. Test overlays after base changes.

ConfigMap Hash Suffix: Generated names with hashes break hard-coded references. Use variable substitution or disable hashing carefully.

Key Takeaways