Kustomize overlays: Kubernetes-configuratie beheren zonder Helm

Kustomize laat je omgevingsspecifieke Kubernetes-manifests (dev, staging, productie) beheren door een gedeelde base te patchen in plaats van te templaten. De base-bestanden blijven geldige, deploybare YAML. Deze gids doorloopt de mappenstructuur, patchstrategieën, generators, components en integratie met ArgoCD en Flux.

Wat je aan het einde hebt

Een Kustomize-project met een gedeelde base en per-omgeving overlays (dev, staging, productie) die je met één kubectl apply -k commando kunt toepassen. Elke overlay past replicas, images, resource limits en configuratie aan zonder de base-manifests te wijzigen.

Vereisten

  • kubectl v1.21 of nieuwer (Kustomize-support werd bijgewerkt van v2.0.3 naar v4.0.5 in v1.21; oudere versies missen features die in deze gids worden gebruikt)
  • Een Kubernetes-cluster om tegen te deployen (elke distributie; een lokaal kind- of minikube-cluster werkt prima)
  • Bekendheid met Kubernetes Deployments, Services, ConfigMaps en namespaces
  • Optioneel: de standalone kustomize binary voor CI/CD-pipelines die nieuwere features nodig hebben dan wat je kubectl bundelt

Wanneer Kustomize gebruiken in plaats van Helm

Kustomize en Helm lossen verwante maar verschillende problemen op. Een kort besliskader:

Scenario Aanbevolen tool
Een upstream chart installeren (nginx-ingress, cert-manager) Helm
Je app distribueren naar externe teams of klanten Helm
Multi-environment configuratie voor een interne app Kustomize
Conditionele YAML-structuur (if/else-logica in manifests) Helm
Leesbare, auditeerbare YAML in Git zonder template-syntax Kustomize
Team dat niet bekend is met Go templates Kustomize

De twee sluiten elkaar niet uit. Volwassen platformteams gebruiken Helm voor het consumeren van upstream charts en Kustomize om die deployments per omgeving aan te passen. Voor een uitgebreide gids over de Helm-kant, zie mijn artikel over Helm chart best practices.

De base- en overlay-mappenstructuur opzetten

De gangbare structuur scheidt gedeelde manifests (de base) van omgevingsspecifieke wijzigingen (overlays):

k8s/
├── base/
│   ├── kustomization.yaml
│   ├── deployment.yaml
│   └── service.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   └── replica-patch.yaml
    ├── staging/
    │   └── kustomization.yaml
    └── prod/
        ├── kustomization.yaml
        └── resource-limits-patch.yaml

De base aanmaken

De base is een op zichzelf staande, geldige Kustomization die geen weet heeft van de overlays die ernaar verwijzen:

# k8s/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - deployment.yaml
  - service.yaml

labels:
  - includeSelectors: true
    includeTemplates: true
    pairs:
      app.kubernetes.io/name: "inventory-api"
# k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inventory-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app.kubernetes.io/name: inventory-api
  template:
    metadata:
      labels:
        app.kubernetes.io/name: inventory-api
    spec:
      containers:
        - name: app
          image: registry.example.com/inventory-api:latest
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 200m
              memory: 256Mi
# k8s/base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: inventory-api
spec:
  selector:
    app.kubernetes.io/name: inventory-api
  ports:
    - port: 80
      targetPort: 8080

Je kunt de base direct toepassen met kubectl apply -k k8s/base/. Dat is een bewust Kustomize-ontwerpprincipe: base-bestanden zijn altijd geldige Kubernetes-YAML, nooit templates.

Een overlay aanmaken

Elke overlay verwijst naar de base en legt omgevingsspecifieke wijzigingen eroverheen:

# k8s/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namespace: production
namePrefix: prod-

replicas:
  - name: inventory-api
    count: 5

images:
  - name: registry.example.com/inventory-api
    newTag: v1.4.2      # pin naar een release-tag in productie

patches:
  - path: resource-limits-patch.yaml
    target:
      kind: Deployment
      name: inventory-api
# k8s/overlays/prod/resource-limits-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inventory-api
spec:
  template:
    spec:
      containers:
        - name: app
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "1"
              memory: 1Gi

Een overlay toepassen

# Bekijk de gerenderde manifests
kubectl kustomize k8s/overlays/prod/

# Pas toe op het cluster
kubectl apply -k k8s/overlays/prod/

Verwachte output (verkort):

namespace/production configured
service/prod-inventory-api created
deployment.apps/prod-inventory-api created

Controleer of de Deployment is aangemaakt met het juiste aantal replicas:

kubectl get deployment prod-inventory-api -n production -o jsonpath='{.spec.replicas}'
# Verwacht: 5

Kies een patchstrategie

Kustomize ondersteunt twee patchmechanismen, beide toegankelijk via het uniforme patches-veld. De oudere velden patchesStrategicMerge en patchesJson6902 zijn deprecated in Kustomize v5.

Strategic merge patches

Een strategic merge patch ziet eruit als een gedeeltelijke kopie van de resource die je wijzigt. Kustomize merged het via Kubernetes merge-strategieën (lijsten van containers worden samengevoegd op naam, niet volledig vervangen).

Gebruik strategic merge als je velden toevoegt of wijzigt en het patchformaat intuïtief leesbaar is:

# Inline strategic merge patch in kustomization.yaml
patches:
  - patch: |-
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: inventory-api
      spec:
        template:
          spec:
            containers:
              - name: app
                env:
                  - name: LOG_LEVEL
                    value: "debug"
    target:
      kind: Deployment
      name: inventory-api

JSON 6902 patches

RFC 6902 patches gebruiken expliciete operaties (add, remove, replace, move, copy, test) met JSON Pointer-paden. Verboseer, maar de enige nette manier om een veld te verwijderen of een lijst-item op index aan te spreken:

patches:
  - patch: |-
      - op: replace
        path: /spec/template/spec/containers/0/resources/limits/memory
        value: "512Mi"
      - op: remove
        path: /spec/template/spec/containers/0/livenessProbe
    target:
      group: apps
      version: v1
      kind: Deployment
      name: inventory-api

Target selectors

Het target-blok ondersteunt regex en label-matching, waardoor één patch op meerdere resources kan worden toegepast:

target:
  kind: Deployment
  name: "inventory-.*"          # regex
  labelSelector: "tier=backend"

ConfigMaps en Secrets genereren met automatische rollouts

De configMapGenerator en secretGenerator maken resources aan vanuit bestanden, env-bestanden of literal waarden. De belangrijkste feature is het hash-achtervoegsel dat aan de gegenereerde naam wordt toegevoegd.

# k8s/overlays/prod/kustomization.yaml (toevoegen aan bestaand)
configMapGenerator:
  - name: app-config
    literals:
      - LOG_LEVEL=warn
      - MAX_CONNECTIONS=50
    envs:
      - .env.prod              # KEY=VALUE paren uit bestand

De gegenereerde ConfigMap krijgt een naam als app-config-9m5b4c7f. Wanneer de inhoud verandert, verandert de hash. Kustomize herschrijft elke referentie naar de ConfigMap in je Deployment-spec, waardoor de Deployment-spec zelf verandert en Kubernetes automatisch een rolling update start. Configuratiewijzigingen bereiken pods zonder handmatige kubectl rollout restart.

Verwijs in je base Deployment naar de generator-naam (zonder de hash):

# k8s/base/deployment.yaml (container spec)
envFrom:
  - configMapRef:
      name: app-config   # Kustomize herschrijft dit naar app-config-<hash>

Als je een stabiele naam nodig hebt (bijvoorbeeld voor een DaemonSet die zijn eigen config-reload afhandelt), schakel het achtervoegsel dan uit:

configMapGenerator:
  - name: stable-config
    literals:
      - KEY=value
    options:
      disableNameSuffixHash: true  # pods herstarten NIET automatisch bij config-wijziging

Secrets: commit geen plaintext

secretGenerator werkt identiek aan configMapGenerator, maar produceert base64-encoded Secrets. Base64 is encoding, geen encryptie. Commit nooit literals: [password=mysecret] naar Git.

Voor productie-GitOps gebruik je een van deze aanpakken:

  • SOPS + age-encryptie via de KSOPS-plugin: versleutelt alleen de waarden, keys blijven leesbaar voor diffs. Flux decodeert SOPS native.
  • External Secrets Operator: synchroniseert secrets vanuit AWS Secrets Manager, HashiCorp Vault of Azure Key Vault naar Kubernetes Secrets. Je kustomization verwijst naar ExternalSecret-CRD's, niet naar echte waarden.
  • Sealed Secrets: versleutel met de publieke sleutel van het cluster (Bitnami-controller). De SealedSecret-resource kan veilig gecommit worden.

Gebruik components voor cross-cutting features

Kustomize components (geïntroduceerd in v3.7.0) lossen het probleem op van N optionele features over M omgevingen. Zonder components kopieer je patchbestanden tussen overlays.

Een component gebruikt kind: Component en is niet zelfstandig deploybaar. Het werkt op de resources van welke parent Kustomization het ook includet:

# k8s/components/prometheus-metrics/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component

resources:
  - service-monitor.yaml          # voegt een ServiceMonitor toe

patches:
  - patch: |-
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: inventory-api
      spec:
        template:
          metadata:
            annotations:
              prometheus.io/scrape: "true"
              prometheus.io/port: "9090"
    target:
      kind: Deployment
      name: inventory-api

Neem het op in overlays die het nodig hebben:

# k8s/overlays/prod/kustomization.yaml
components:
  - ../../components/prometheus-metrics
  - ../../components/network-policy
# k8s/overlays/dev/kustomization.yaml
components:
  - ../../components/prometheus-metrics
  # geen network-policy in dev

Goede toepassingen voor components: Prometheus-scraping-annotaties, sidecar-injectie, HPA-configuratie, NetworkPolicy en feature flags via ConfigMap-patches.

Integratie met ArgoCD

ArgoCD heeft native Kustomize-ondersteuning. Verwijs een Application naar een map met kustomization.yaml en ArgoCD detecteert het automatisch en draait kustomize build voor het toepassen:

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: inventory-api-prod
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/my-org/inventory-api.git
    targetRevision: HEAD
    path: k8s/overlays/prod
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true

Voor multi-environment setups voorkomt een ApplicationSet herhaling per omgeving:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: inventory-api
spec:
  generators:
    - list:
        elements:
          - env: dev
            cluster: https://dev.k8s.example.com
          - env: staging
            cluster: https://staging.k8s.example.com
          - env: prod
            cluster: https://prod.k8s.example.com
  template:
    metadata:
      name: "inventory-api-{{env}}"
    spec:
      source:
        repoURL: https://github.com/my-org/inventory-api.git
        path: "k8s/overlays/{{env}}"
      destination:
        server: "{{cluster}}"
        namespace: "{{env}}"

Let op: de Kustomize-versie die ArgoCD bundelt kan achterlopen op de standalone binary. Als je v5-features nodig hebt (het moderne labels- of replacements-veld), configureer dan een custom versie in argocd-cm.

Integratie met Flux

Flux gebruikt de Kustomization CRD van kustomize.toolkit.fluxcd.io om Kustomize-overlays in een Kubernetes-native workflow te verpakken:

apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
  name: inventory-api
  namespace: flux-system
spec:
  interval: 1m
  url: https://github.com/my-org/inventory-api.git
  ref:
    branch: main
---
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
  name: inventory-api-prod
  namespace: flux-system
spec:
  interval: 10m
  sourceRef:
    kind: GitRepository
    name: inventory-api
  path: "./k8s/overlays/prod"
  prune: true
  targetNamespace: production
  wait: true

Flux voegt mogelijkheden toe bovenop gewone Kustomize. Twee die er het meest toe doen:

SOPS-decryptie zonder plugin: Flux' kustomize-controller decodeert SOPS-versleutelde secrets native:

spec:
  decryption:
    provider: sops
    secretRef:
      name: sops-age     # Secret met je age private key

Post-build variabele-substitutie: lost ${VARIABLE}-tokens op in manifests nadat kustomize build is gedraaid. Dit is een Flux-feature, geen native Kustomize:

spec:
  postBuild:
    substitute:
      ENVIRONMENT: production
      REGION: eu-west-1
    substituteFrom:
      - kind: ConfigMap
        name: cluster-vars

Afhankelijkheidsvolgorde zorgt dat infrastructuur vóór applicaties landt:

spec:
  dependsOn:
    - name: infra-prod

Het complete project verifiëren

Draai na het opzetten van de volledige structuur een dry-run diff voordat je toepast, om misconfiguraties te vangen:

# Render en vergelijk met de clusterstatus
kubectl diff -k k8s/overlays/prod/

Als de diff er goed uitziet, pas dan toe:

kubectl apply -k k8s/overlays/prod/

Bevestig dat alle resources zijn aangemaakt:

kubectl get all -n production -l app.kubernetes.io/name=inventory-api

Veelvoorkomende problemen

Immutable selector-fout bij opnieuw toepassen. Als je het verouderde commonLabels-veld hebt gebruikt, wijzigt dat label selectors, die Kubernetes na de eerste apply immutable maakt. Migreer naar het moderne labels-veld met includeSelectors: false, of verwijder en hermaak de Deployment.

Configuratiewijzigingen worden niet opgepikt door pods. Als je disableNameSuffixHash: true hebt gezet op een generator, verandert de Deployment-spec niet wanneer de ConfigMap-inhoud verandert. Verwijder de vlag om automatische rollouts te herstellen, of voeg kubectl rollout restart deployment/inventory-api toe aan je CI-pipeline.

Build faalt met "path not found." Kustomize lost alle paden op relatief aan het kustomization.yaml-bestand. Controleer dat ../../base daadwerkelijk naar de base-map verwijst vanuit de locatie van de overlay.

Deprecation-waarschuwingen in Kustomize v5. De velden patchesStrategicMerge, patchesJson6902, commonLabels en vars zijn deprecated. Gebruik in plaats daarvan patches, labels en replacements. Zie de migratiegids van Nick Janetakis.

kubectl-gebundelde Kustomize-versie komt niet overeen. De Kustomize-versie binnen kubectl kan ouder zijn dan de standalone binary. Draai kubectl version --client en kustomize version om te vergelijken. Installeer voor CI de standalone binary en gebruik kustomize build | kubectl apply -f -.

Compleet overlay-voorbeeld

Ter referentie: hier is de volledige prod overlay kustomization.yaml met alle features uit deze gids gecombineerd:

# k8s/overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

resources:
  - ../../base

namespace: production
namePrefix: prod-

replicas:
  - name: inventory-api
    count: 5

images:
  - name: registry.example.com/inventory-api
    newTag: v1.4.2

configMapGenerator:
  - name: app-config
    literals:
      - LOG_LEVEL=warn
      - MAX_CONNECTIONS=50

components:
  - ../../components/prometheus-metrics
  - ../../components/network-policy

patches:
  - path: resource-limits-patch.yaml
    target:
      kind: Deployment
      name: inventory-api

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.