Kustomize Advanced Overlays: Production Kubernetes Configuration Management Guide

On this page
Learn advanced Kustomize techniques including sophisticated overlay strategies, component composition, strategic merge patches, generator plugins, and production-ready patterns for managing complex Kubernetes configurations at scale.
Executive Summary
Kustomize provides a template-free way to customize Kubernetes configurations through declarative overlays and patches. This comprehensive guide covers advanced Kustomize patterns including multi-environment overlays, component architecture, strategic merge patches, and production-ready configuration management strategies for enterprise Kubernetes deployments.
Table of Contents
- Kustomize Fundamentals
- Advanced Overlay Patterns
- Component Architecture
- Strategic Merge Patches
- JSON Patches and Transformers
- Generator Plugins
- Multi-Environment Management
- Security and Secrets
- GitOps Integration
- Testing and Validation
Kustomize Fundamentals
Core Concepts
# Directory structure overview
kustomize-project/
├── base/ # Base configurations
│ ├── kustomization.yaml # Base kustomization
│ ├── deployment.yaml # Base deployment
│ ├── service.yaml # Base service
│ └── configmap.yaml # Base config
│
├── components/ # Reusable components
│ ├── monitoring/ # Monitoring component
│ │ ├── kustomization.yaml
│ │ └── servicemonitor.yaml
│ └── security/ # Security component
│ ├── kustomization.yaml
│ └── networkpolicy.yaml
│
└── overlays/ # Environment overlays
├── development/
│ ├── kustomization.yaml
│ └── patches/
├── staging/
│ ├── kustomization.yaml
│ └── patches/
└── production/
├── kustomization.yaml
└── patches/Basic Kustomization File
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
metadata:
name: myapp-base
# Resources
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
# Common labels
commonLabels:
app: myapp
managed-by: kustomize
# Common annotations
commonAnnotations:
description: "Base configuration for myapp"
documentation: "https://wiki.example.com/myapp"
# Name prefix/suffix
namePrefix: myapp-
nameSuffix: -v1
# Namespace
namespace: default
# Images
images:
- name: myapp
newName: gcr.io/my-project/myapp
newTag: latest
# ConfigMap generator
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=info
- FEATURE_FLAG_A=false
# Secret generator
secretGenerator:
- name: app-secrets
literals:
- DB_PASSWORD=changeme
type: Opaque
# Replicas
replicas:
- name: myapp-deployment
count: 3Advanced Overlay Patterns
Hierarchical Overlay Structure
# Production overlay with multiple layers
overlays/
└── production/
├── kustomization.yaml # Main overlay
├── patches/
│ ├── deployment-patch.yaml # Deployment modifications
│ ├── hpa-patch.yaml # HPA settings
│ └── resources-patch.yaml # Resource adjustments
├── configs/
│ ├── app-config.properties # Application config
│ └── logging-config.yaml # Logging config
├── us-east-1/ # Region-specific
│ ├── kustomization.yaml
│ └── patches/
│ └── region-patch.yaml
├── us-west-2/
│ ├── kustomization.yaml
│ └── patches/
│ └── region-patch.yaml
└── eu-west-1/
├── kustomization.yaml
└── patches/
└── region-patch.yamlMulti-Layer Overlay
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Reference base
bases:
- ../../base
# Include components
components:
- ../../components/monitoring
- ../../components/security
- ../../components/istio-ingress
# Production namespace
namespace: production
# Common labels for production
commonLabels:
environment: production
tier: frontend
cost-center: engineering
# Production-specific annotations
commonAnnotations:
deployed-by: "GitOps Pipeline"
contacts: "sre-team@example.com"
# Image overrides
images:
- name: myapp
newName: gcr.io/my-project/myapp
newTag: v2.3.4
# Replica count
replicas:
- name: myapp-deployment
count: 10
# Resource patches
patches:
- path: patches/deployment-patch.yaml
target:
kind: Deployment
name: myapp-deployment
- path: patches/hpa-patch.yaml
target:
kind: HorizontalPodAutoscaler
# ConfigMap from file
configMapGenerator:
- name: app-config
files:
- configs/app-config.properties
behavior: merge
- name: logging-config
files:
- configs/logging-config.yaml
behavior: replace
# Secrets from external source
secretGenerator:
- name: database-credentials
literals:
- username=produser
envs:
- .env.production
# Strategic merge patches
patchesStrategicMerge:
- patches/resources-patch.yaml
# JSON patches
patchesJson6902:
- target:
version: v1
kind: Service
name: myapp-service
path: patches/service-patch.jsonRegional Overlay Extension
# overlays/production/us-east-1/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
# Reference production base
bases:
- ../
# Region-specific labels
commonLabels:
region: us-east-1
availability-zone: multi
# Region-specific annotations
commonAnnotations:
aws-region: "us-east-1"
backup-region: "us-west-2"
# Region-specific patches
patches:
- path: patches/region-patch.yaml
target:
kind: Deployment
- path: patches/storage-class-patch.yaml
target:
kind: PersistentVolumeClaim
# Region-specific config
configMapGenerator:
- name: region-config
literals:
- REGION=us-east-1
- AVAILABILITY_ZONES=us-east-1a,us-east-1b,us-east-1c
- S3_BUCKET=myapp-prod-us-east-1
- DYNAMODB_TABLE=myapp-prod-us-east-1Component Architecture
Monitoring Component
# components/monitoring/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
# Component resources
resources:
- servicemonitor.yaml
- prometheusrule.yaml
- grafana-dashboard.yaml
# Labels for monitoring
commonLabels:
monitoring: enabled
prometheus: scraped
# Annotations
commonAnnotations:
monitoring-tier: "application"# components/monitoring/servicemonitor.yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: app-metrics
spec:
selector:
matchLabels:
app: myapp
endpoints:
- port: metrics
interval: 30s
path: /metrics
scheme: http
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: app-alerts
spec:
groups:
- name: application
interval: 30s
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m]))
/
sum(rate(http_requests_total[5m]))
> 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "High error rate detected"
description: "Error rate is {{ $value | humanizePercentage }}"
- alert: HighLatency
expr: |
histogram_quantile(0.95,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le)
) > 1
for: 10m
labels:
severity: warning
annotations:
summary: "High latency detected"
description: "P95 latency is {{ $value }}s"Security Component
# components/security/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
resources:
- networkpolicy.yaml
- podsecuritypolicy.yaml
- serviceaccount.yaml
- rbac.yaml
commonLabels:
security: enabled
patches:
- path: security-context-patch.yaml
target:
kind: Deployment# components/security/networkpolicy.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-netpol
spec:
podSelector:
matchLabels:
app: myapp
policyTypes:
- Ingress
- Egress
ingress:
# Allow from ingress controller
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
# Allow from monitoring
- from:
- namespaceSelector:
matchLabels:
name: monitoring
ports:
- protocol: TCP
port: 9090
egress:
# Allow DNS
- to:
- namespaceSelector:
matchLabels:
name: kube-system
ports:
- protocol: UDP
port: 53
# Allow external HTTPS
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443
# Allow database
- to:
- namespaceSelector:
matchLabels:
name: database
ports:
- protocol: TCP
port: 5432
---
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: app-psp
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'downwardAPI'
- 'persistentVolumeClaim'
hostNetwork: false
hostIPC: false
hostPID: false
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
readOnlyRootFilesystem: true# components/security/security-context-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: not-important
spec:
template:
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: myapp
securityContext:
allowPrivilegeEscalation: false
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
readOnlyRootFilesystem: trueStrategic Merge Patches
Resource Modification Patch
# overlays/production/patches/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
template:
spec:
containers:
- name: myapp
resources:
requests:
cpu: 1000m
memory: 2Gi
ephemeral-storage: 1Gi
limits:
cpu: 2000m
memory: 4Gi
ephemeral-storage: 2Gi
env:
# Add production environment variables
- name: ENVIRONMENT
value: production
- name: LOG_LEVEL
value: warn
- name: ENABLE_PROFILING
value: "true"
# Liveness probe adjustments
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# Readiness probe adjustments
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 30
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
# Add init container
initContainers:
- name: migration
image: gcr.io/my-project/myapp:v2.3.4
command: ["/app/migrate"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: database-credentials
key: url
# Topology spread
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: myapp
# Affinity rules
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: myapp
topologyKey: kubernetes.io/hostnameHPA Configuration Patch
# overlays/production/patches/hpa-patch.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: myapp-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myapp-deployment
minReplicas: 10
maxReplicas: 100
metrics:
# CPU-based scaling
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
# Memory-based scaling
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
# Custom metrics
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000"
- type: Pods
pods:
metric:
name: queue_depth
target:
type: AverageValue
averageValue: "100"
# External metrics
- type: External
external:
metric:
name: pubsub_queue_length
selector:
matchLabels:
queue: myapp-tasks
target:
type: AverageValue
averageValue: "50"
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
- type: Pods
value: 5
periodSeconds: 60
selectPolicy: Min
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
- type: Pods
value: 10
periodSeconds: 30
selectPolicy: MaxJSON Patches and Transformers
JSON6902 Patch
# overlays/production/kustomization.yaml
patchesJson6902:
- target:
version: v1
kind: Service
name: myapp-service
path: patches/service-patch.json
- target:
version: v1
kind: ConfigMap
name: app-config
patch: |-
- op: add
path: /data/NEW_FEATURE
value: "enabled"
- op: replace
path: /data/MAX_CONNECTIONS
value: "1000"
- op: remove
path: /data/DEPRECATED_SETTING// patches/service-patch.json
[
{
"op": "add",
"path": "/spec/ports/-",
"value": {
"name": "metrics",
"port": 9090,
"targetPort": 9090,
"protocol": "TCP"
}
},
{
"op": "add",
"path": "/metadata/annotations",
"value": {
"service.beta.kubernetes.io/aws-load-balancer-type": "nlb",
"service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled": "true",
"service.beta.kubernetes.io/aws-load-balancer-backend-protocol": "http"
}
},
{
"op": "replace",
"path": "/spec/sessionAffinity",
"value": "ClientIP"
},
{
"op": "add",
"path": "/spec/sessionAffinityConfig",
"value": {
"clientIP": {
"timeoutSeconds": 10800
}
}
}
]Custom Transformers
# transformers/add-prometheus-annotations.yaml
apiVersion: builtin
kind: AnnotationsTransformer
metadata:
name: add-prometheus-annotations
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
fieldSpecs:
- path: spec/template/metadata/annotations
kind: Deployment
- path: spec/template/metadata/annotations
kind: StatefulSet
- path: spec/template/metadata/annotations
kind: DaemonSet# transformers/add-common-env.yaml
apiVersion: builtin
kind: PatchTransformer
metadata:
name: add-common-env
patch: |-
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: CLUSTER_NAME
value: production-us-east-1
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
target:
kind: Deployment# Use transformers in kustomization
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
transformers:
- transformers/add-prometheus-annotations.yaml
- transformers/add-common-env.yamlGenerator Plugins
ConfigMap from Multiple Sources
# overlays/production/kustomization.yaml
configMapGenerator:
# From literals
- name: app-config
literals:
- ENVIRONMENT=production
- LOG_LEVEL=warn
- METRICS_ENABLED=true
behavior: merge
# From files
- name: app-properties
files:
- configs/application.properties
- configs/database.properties
behavior: create
# From env file
- name: app-env
envs:
- configs/.env.production
behavior: replace
# From command output (requires plugin)
- name: build-info
literals:
- BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
- GIT_COMMIT=$(git rev-parse HEAD)
- GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
# Disable name suffix hash
options:
disableNameSuffixHash: false
labels:
config-type: application
annotations:
generated-by: kustomizeSecret Generator with External Sources
# Using sealed-secrets generator
# Install: kubectl apply -f https://github.com/bitnami-labs/sealed-secrets/releases/download/v0.18.0/controller.yaml
secretGenerator:
# Basic secret from literals
- name: api-keys
literals:
- API_KEY=secret-value
type: Opaque
# Secret from files
- name: tls-cert
files:
- tls.crt=certs/server.crt
- tls.key=certs/server.key
type: kubernetes.io/tls
# Using external secrets operator
# Note: This requires External Secrets Operator installed
---
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: database-credentials
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: database-credentials
creationPolicy: Owner
data:
- secretKey: username
remoteRef:
key: prod/database
property: username
- secretKey: password
remoteRef:
key: prod/database
property: password
- secretKey: connection-string
remoteRef:
key: prod/database
property: connection_stringCustom Generator Plugin
#!/bin/bash
# generators/git-info-generator.sh - Custom generator plugin
cat <<EOF
apiVersion: v1
kind: ConfigMap
metadata:
name: git-info
data:
GIT_COMMIT: "$(git rev-parse HEAD)"
GIT_SHORT_COMMIT: "$(git rev-parse --short HEAD)"
GIT_BRANCH: "$(git rev-parse --abbrev-ref HEAD)"
GIT_TAG: "$(git describe --tags --always)"
BUILD_DATE: "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
BUILD_USER: "$(whoami)@$(hostname)"
EOF# Use custom generator
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
generators:
- generators/git-info-generator.shMulti-Environment Management
Environment Matrix
#!/bin/bash
# deploy-all-environments.sh - Deploy to multiple environments
set -euo pipefail
ENVIRONMENTS=(
"development"
"qa"
"staging"
"production/us-east-1"
"production/us-west-2"
"production/eu-west-1"
)
for env in "${ENVIRONMENTS[@]}"; do
echo "Deploying to $env..."
# Build with kustomize
kubectl kustomize "overlays/$env" > "/tmp/manifests-${env//\//-}.yaml"
# Validate
kubeval --strict "/tmp/manifests-${env//\//-}.yaml"
# Dry-run
kubectl apply --dry-run=server -f "/tmp/manifests-${env//\//-}.yaml"
# Apply with confirmation
read -p "Deploy to $env? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
kubectl apply -f "/tmp/manifests-${env//\//-}.yaml"
echo "✓ Deployed to $env"
else
echo "✗ Skipped $env"
fi
doneEnvironment Configuration Matrix
# environments.yaml - Centralized environment configuration
environments:
development:
namespace: dev
replicas: 1
resources:
cpu: "100m"
memory: "128Mi"
image_tag: "latest"
autoscaling: false
monitoring: false
qa:
namespace: qa
replicas: 2
resources:
cpu: "250m"
memory: "256Mi"
image_tag: "qa-latest"
autoscaling: false
monitoring: true
staging:
namespace: staging
replicas: 3
resources:
cpu: "500m"
memory: "512Mi"
image_tag: "staging"
autoscaling: true
monitoring: true
min_replicas: 3
max_replicas: 10
production:
namespace: production
replicas: 10
resources:
cpu: "1000m"
memory: "2Gi"
image_tag: "v2.3.4"
autoscaling: true
monitoring: true
min_replicas: 10
max_replicas: 100
regions:
us-east-1:
node_selector:
topology.kubernetes.io/region: us-east-1
storage_class: gp3
availability_zones:
- us-east-1a
- us-east-1b
- us-east-1c
us-west-2:
node_selector:
topology.kubernetes.io/region: us-west-2
storage_class: gp3
availability_zones:
- us-west-2a
- us-west-2b
- us-west-2c
eu-west-1:
node_selector:
topology.kubernetes.io/region: eu-west-1
storage_class: gp3
availability_zones:
- eu-west-1a
- eu-west-1b
- eu-west-1cSecurity and Secrets
Sealed Secrets Integration
# Generate sealed secret
kubectl create secret generic my-secret \
--from-literal=password=supersecret \
--dry-run=client -o yaml | \
kubeseal --format yaml > sealed-secret.yaml
# overlays/production/sealed-secret.yaml
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: database-credentials
namespace: production
spec:
encryptedData:
username: AgCQC5fqZ... (encrypted)
password: AgBpQx3fL... (encrypted)
connection-string: AgDLmN8hW... (encrypted)
template:
metadata:
name: database-credentials
labels:
app: myapp
type: Opaque# Reference in kustomization
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- sealed-secret.yamlExternal Secrets Operator
# components/external-secrets/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1alpha1
kind: Component
resources:
- secretstore.yaml
- externalsecret.yaml# components/external-secrets/secretstore.yaml
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: aws-secrets-manager
spec:
provider:
aws:
service: SecretsManager
region: us-east-1
auth:
jwt:
serviceAccountRef:
name: external-secrets-sa
---
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
name: vault
spec:
provider:
vault:
server: "https://vault.example.com"
path: "secret"
version: "v2"
auth:
kubernetes:
mountPath: "kubernetes"
role: "myapp"
serviceAccountRef:
name: external-secrets-sa# components/external-secrets/externalsecret.yaml
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
name: app-secrets
spec:
refreshInterval: 1h
secretStoreRef:
name: aws-secrets-manager
kind: SecretStore
target:
name: app-secrets
creationPolicy: Owner
template:
engineVersion: v2
data:
config.yaml: |
database:
host: {{ .db_host }}
port: {{ .db_port }}
username: {{ .db_username }}
password: {{ .db_password }}
api:
key: {{ .api_key }}
secret: {{ .api_secret }}
dataFrom:
- extract:
key: prod/myapp/database
- extract:
key: prod/myapp/api
data:
- secretKey: special-key
remoteRef:
key: prod/myapp/special
property: valueGitOps Integration
ArgoCD Integration
# argocd/application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-production
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/myorg/myapp-config.git
targetRevision: main
path: overlays/production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- PrunePropagationPolicy=foreground
- PruneLast=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
# Health assessment
ignoreDifferences:
- group: apps
kind: Deployment
jsonPointers:
- /spec/replicas
# Kustomize-specific options
source:
kustomize:
namePrefix: prod-
nameSuffix: -v2
images:
- gcr.io/my-project/myapp:v2.3.4
commonLabels:
deployed-by: argocd
commonAnnotations:
argocd.argoproj.io/sync-wave: "1"Flux CD Integration
# flux/kustomization.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: myapp-production
namespace: flux-system
spec:
interval: 10m0s
retryInterval: 1m0s
timeout: 5m0s
sourceRef:
kind: GitRepository
name: myapp-config
path: ./overlays/production
prune: true
wait: true
force: false
# Health checks
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: myapp-deployment
namespace: production
# Post-build substitutions
postBuild:
substitute:
cluster_name: "production-us-east-1"
region: "us-east-1"
substituteFrom:
- kind: ConfigMap
name: cluster-vars
# Decryption
decryption:
provider: sops
secretRef:
name: sops-gpg
# Patches
patches:
- patch: |
- op: replace
path: /spec/replicas
value: 10
target:
kind: Deployment
name: myapp-deploymentTesting and Validation
Automated Testing Script
#!/bin/bash
# test-kustomize.sh - Comprehensive testing script
set -euo pipefail
export TEST_DIR="$(mktemp -d)"
trap "rm -rf ${TEST_DIR}" EXIT
echo "=== Kustomize Configuration Testing ==="
# Test 1: Build validation
echo -e "\n--- Test 1: Build Validation ---"
for overlay in overlays/*/; do
env=$(basename "$overlay")
echo "Testing $env..."
if kubectl kustomize "$overlay" > "${TEST_DIR}/${env}.yaml"; then
echo "✓ $env build successful"
else
echo "✗ $env build failed"
exit 1
fi
done
# Test 2: YAML validation
echo -e "\n--- Test 2: YAML Validation ---"
for file in "${TEST_DIR}"/*.yaml; do
env=$(basename "$file" .yaml)
echo "Validating $env..."
if yamllint -c .yamllint "$file"; then
echo "✓ $env YAML valid"
else
echo "✗ $env YAML invalid"
exit 1
fi
done
# Test 3: Kubernetes API validation
echo -e "\n--- Test 3: Kubernetes API Validation ---"
for file in "${TEST_DIR}"/*.yaml; do
env=$(basename "$file" .yaml)
echo "Validating $env against Kubernetes API..."
if kubectl apply --dry-run=server -f "$file" &>/dev/null; then
echo "✓ $env API validation passed"
else
echo "✗ $env API validation failed"
exit 1
fi
done
# Test 4: Schema validation with kubeval
echo -e "\n--- Test 4: Schema Validation ---"
for file in "${TEST_DIR}"/*.yaml; do
env=$(basename "$file" .yaml)
echo "Schema validation for $env..."
if kubeval --strict --ignore-missing-schemas "$file"; then
echo "✓ $env schema valid"
else
echo "✗ $env schema invalid"
exit 1
fi
done
# Test 5: Policy validation with OPA
echo -e "\n--- Test 5: Policy Validation ---"
for file in "${TEST_DIR}"/*.yaml; do
env=$(basename "$file" .yaml)
echo "Policy validation for $env..."
if conftest test "$file"; then
echo "✓ $env policies passed"
else
echo "✗ $env policies failed"
exit 1
fi
done
# Test 6: Security scanning with kubesec
echo -e "\n--- Test 6: Security Scanning ---"
for file in "${TEST_DIR}"/*.yaml; do
env=$(basename "$file" .yaml)
echo "Security scan for $env..."
score=$(kubesec scan "$file" | jq '.[0].score')
if [ "$score" -ge 5 ]; then
echo "✓ $env security score: $score"
else
echo "✗ $env security score too low: $score"
exit 1
fi
done
# Test 7: Diff between environments
echo -e "\n--- Test 7: Environment Diff Analysis ---"
echo "Comparing staging vs production..."
diff -u "${TEST_DIR}/staging.yaml" "${TEST_DIR}/production.yaml" || true
echo -e "\n=== All Tests Passed ==="OPA Policy Testing
# policies/required-labels.rego
package main
deny[msg] {
input.kind == "Deployment"
not input.metadata.labels.app
msg = "Deployment must have 'app' label"
}
deny[msg] {
input.kind == "Deployment"
not input.metadata.labels.environment
msg = "Deployment must have 'environment' label"
}
deny[msg] {
input.kind == "Deployment"
not input.spec.template.spec.securityContext.runAsNonRoot
msg = "Deployment must run as non-root user"
}
deny[msg] {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.resources.limits.cpu
msg = sprintf("Container %s must have CPU limits", [container.name])
}
deny[msg] {
input.kind == "Deployment"
container := input.spec.template.spec.containers[_]
not container.resources.limits.memory
msg = sprintf("Container %s must have memory limits", [container.name])
}# conftest configuration
# .conftest.yaml
policy:
- policies/
namespace: main
# Ignore certain kinds
ignore:
- kustomize.config.k8s.io/v1beta1/KustomizationConclusion
Kustomize provides a powerful, template-free approach to Kubernetes configuration management through sophisticated overlay patterns, component composition, and declarative transformations. This guide has covered advanced techniques including strategic merge patches, JSON patches, custom generators, multi-environment management, and GitOps integration.
Key takeaways:
- Overlay Architecture: Hierarchical overlays enable environment-specific customizations while maintaining DRY principles
- Components: Reusable components promote consistency across applications
- Patch Strategies: Multiple patch types provide flexibility for different modification scenarios
- Security: Integration with sealed secrets and external secrets operators ensures secure credential management
- GitOps: Native integration with ArgoCD and Flux CD enables automated deployment workflows
- Testing: Comprehensive validation ensures configuration correctness before deployment
For more information on Kubernetes configuration and GitOps, see our guides on Advanced GitOps implementation and Helm chart testing strategies.