Overview
Kustomize is a template-free way to customize Kubernetes configurations. It allows you to maintain a base configuration and apply environment-specific overlays without duplicating YAML files.
Kustomize is built into kubectl (version 1.14+), making it a native Kubernetes configuration management tool with no external dependencies.
Why Kustomize?
Template-Free
No template language to learn
Pure Kubernetes YAML
Easy to validate
Better IDE support
DRY Principle
Define once, reuse everywhere
Environment-specific overlays
No duplication
Easy to maintain
Native Integration
Built into kubectl
No additional tools needed
GitOps friendly
ArgoCD/Flux support
Composability
Layer configurations
Patch specific fields
Merge resources
Transform objects
Directory Structure
k8s/
├── base/ # Base configurations
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── configmap.yaml
│ └── serviceaccount.yaml
│
├── overlays/ # Environment overlays
│ ├── development/
│ │ ├── kustomization.yaml
│ │ ├── deployment-patch.yaml
│ │ └── configmap.yaml
│ │
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ ├── deployment-patch.yaml
│ │ ├── hpa.yaml
│ │ └── configmap.yaml
│ │
│ └── production/
│ ├── kustomization.yaml
│ ├── deployment-patch.yaml
│ ├── hpa.yaml
│ ├── pdb.yaml
│ └── configmap.yaml
│
└── components/ # Optional components
├── redis/
│ ├── kustomization.yaml
│ └── redis.yaml
├── keycloak/
│ ├── kustomization.yaml
│ └── keycloak.yaml
└── openfga/
├── kustomization.yaml
└── openfga.yaml
Base Configuration
base/kustomization.yaml
apiVersion : kustomize.config.k8s.io/v1beta1
kind : Kustomization
namespace : mcp-server-langgraph
## Common labels applied to all resources
commonLabels :
app : mcp-server-langgraph
project : mcp-server
## Common annotations
commonAnnotations :
managed-by : kustomize
## Resources
resources :
- deployment.yaml
- service.yaml
- serviceaccount.yaml
- configmap.yaml
## ConfigMap generator
configMapGenerator :
- name : langgraph-config
literals :
- LOG_LEVEL=info
- AUTH_PROVIDER=inmemory
- SESSION_PROVIDER=memory
## Secret generator
secretGenerator :
- name : langgraph-secrets
literals :
- JWT_SECRET=changeme
type : Opaque
## Images to use
images :
- name : mcp-server-langgraph
newName : mcp-server-langgraph
newTag : latest
base/deployment.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : mcp-server-langgraph
spec :
replicas : 1
selector :
matchLabels :
app : mcp-server-langgraph
template :
metadata :
labels :
app : mcp-server-langgraph
spec :
serviceAccountName : mcp-server-langgraph
containers :
- name : agent
image : mcp-server-langgraph:latest
ports :
- containerPort : 8000
name : http
- containerPort : 9090
name : metrics
envFrom :
- configMapRef :
name : langgraph-config
- secretRef :
name : langgraph-secrets
livenessProbe :
httpGet :
path : /health/live
port : 8000
initialDelaySeconds : 30
periodSeconds : 10
readinessProbe :
httpGet :
path : /health/ready
port : 8000
initialDelaySeconds : 10
periodSeconds : 5
resources :
requests :
memory : "512Mi"
cpu : "250m"
limits :
memory : "1Gi"
cpu : "500m"
base/service.yaml
apiVersion : v1
kind : Service
metadata :
name : mcp-server-langgraph
spec :
type : ClusterIP
selector :
app : mcp-server-langgraph
ports :
- name : http
port : 8000
targetPort : 8000
- name : metrics
port : 9090
targetPort : 9090
base/serviceaccount.yaml
apiVersion : v1
kind : ServiceAccount
metadata :
name : mcp-server-langgraph
Development Overlay
overlays/development/kustomization.yaml
apiVersion : kustomize.config.k8s.io/v1beta1
kind : Kustomization
## Namespace
namespace : langgraph-dev
## Base
bases :
- ../../base
## Name prefix
namePrefix : dev-
## Labels
commonLabels :
environment : development
## ConfigMap override
configMapGenerator :
- name : langgraph-config
behavior : merge
literals :
- ENV=development
- DEBUG=true
- LOG_LEVEL=debug
- AUTH_PROVIDER=inmemory
- SESSION_PROVIDER=memory
- ENABLE_TRACING=false
- ENABLE_METRICS=false
## Patches
patchesStrategicMerge :
- deployment-patch.yaml
## Images
images :
- name : mcp-server-langgraph
newName : mcp-server-langgraph
newTag : dev
overlays/development/deployment-patch.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : mcp-server-langgraph
spec :
replicas : 1
template :
spec :
containers :
- name : agent
resources :
requests :
memory : "256Mi"
cpu : "100m"
limits :
memory : "512Mi"
cpu : "250m"
Staging Overlay
overlays/staging/kustomization.yaml
apiVersion : kustomize.config.k8s.io/v1beta1
kind : Kustomization
namespace : langgraph-staging
bases :
- ../../base
namePrefix : staging-
commonLabels :
environment : staging
configMapGenerator :
- name : langgraph-config
behavior : merge
literals :
- ENV=staging
- DEBUG=false
- LOG_LEVEL=info
- AUTH_PROVIDER=keycloak
- SESSION_PROVIDER=redis
- KEYCLOAK_URL=http://keycloak:8080
- REDIS_URL=redis://redis-master:6379
- OPENFGA_ENABLED=true
- OPENFGA_URL=http://openfga:8080
- ENABLE_TRACING=true
- ENABLE_METRICS=true
resources :
- hpa.yaml
patchesStrategicMerge :
- deployment-patch.yaml
images :
- name : mcp-server-langgraph
newName : gcr.io/project-id/mcp-server-langgraph
newTag : staging
overlays/staging/deployment-patch.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : mcp-server-langgraph
spec :
replicas : 2
template :
spec :
containers :
- name : agent
resources :
requests :
memory : "512Mi"
cpu : "250m"
limits :
memory : "1Gi"
cpu : "500m"
overlays/staging/hpa.yaml
apiVersion : autoscaling/v2
kind : HorizontalPodAutoscaler
metadata :
name : mcp-server-langgraph
spec :
scaleTargetRef :
apiVersion : apps/v1
kind : Deployment
name : mcp-server-langgraph
minReplicas : 2
maxReplicas : 5
metrics :
- type : Resource
resource :
name : cpu
target :
type : Utilization
averageUtilization : 70
Production Overlay
overlays/production/kustomization.yaml
apiVersion : kustomize.config.k8s.io/v1beta1
kind : Kustomization
namespace : mcp-server-langgraph
bases :
- ../../base
## No prefix for production
commonLabels :
environment : production
commonAnnotations :
prometheus.io/scrape : "true"
prometheus.io/port : "9090"
configMapGenerator :
- name : langgraph-config
behavior : merge
literals :
- ENV=production
- DEBUG=false
- LOG_LEVEL=warning
- AUTH_PROVIDER=keycloak
- SESSION_PROVIDER=redis
- KEYCLOAK_URL=http://keycloak:8080
- REDIS_URL=redis://redis-master:6379
- OPENFGA_ENABLED=true
- OPENFGA_URL=http://openfga:8080
- ENABLE_TRACING=true
- ENABLE_METRICS=true
- LANGSMITH_ENABLED=true
resources :
- hpa.yaml
- pdb.yaml
- network-policy.yaml
patchesStrategicMerge :
- deployment-patch.yaml
images :
- name : mcp-server-langgraph
newName : gcr.io/project-id/mcp-server-langgraph
newTag : v1.0.0
replicas :
- name : mcp-server-langgraph
count : 3
overlays/production/deployment-patch.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : mcp-server-langgraph
spec :
strategy :
type : RollingUpdate
rollingUpdate :
maxSurge : 1
maxUnavailable : 0
template :
metadata :
annotations :
prometheus.io/scrape : "true"
prometheus.io/port : "9090"
spec :
affinity :
podAntiAffinity :
preferredDuringSchedulingIgnoredDuringExecution :
- weight : 100
podAffinityTerm :
labelSelector :
matchExpressions :
- key : app
operator : In
values :
- mcp-server-langgraph
topologyKey : kubernetes.io/hostname
containers :
- name : agent
resources :
requests :
memory : "1Gi"
cpu : "500m"
limits :
memory : "2Gi"
cpu : "1000m"
overlays/production/pdb.yaml
apiVersion : policy/v1
kind : PodDisruptionBudget
metadata :
name : mcp-server-langgraph
spec :
minAvailable : 2
selector :
matchLabels :
app : mcp-server-langgraph
overlays/production/network-policy.yaml
apiVersion : networking.k8s.io/v1
kind : NetworkPolicy
metadata :
name : mcp-server-langgraph
spec :
podSelector :
matchLabels :
app : mcp-server-langgraph
policyTypes :
- Ingress
- Egress
ingress :
- from :
- namespaceSelector :
matchLabels :
name : ingress-nginx
ports :
- protocol : TCP
port : 8000
egress :
- to :
- podSelector :
matchLabels :
app : keycloak
ports :
- protocol : TCP
port : 8080
- to :
- podSelector :
matchLabels :
app : redis
ports :
- protocol : TCP
port : 6379
- to :
- podSelector :
matchLabels :
app : openfga
ports :
- protocol : TCP
port : 8080
- to :
- namespaceSelector : {}
ports :
- protocol : TCP
port : 53
- protocol : UDP
port : 53
Advanced Techniques
JSON Patches
## overlays/production/kustomization.yaml
patchesJson6902 :
- target :
group : apps
version : v1
kind : Deployment
name : mcp-server-langgraph
patch : | -
- op: add
path: /spec/template/spec/containers/0/env/-
value:
name: CUSTOM_ENV
value: "production-value"
Strategic Merge Patch
## Add sidecar container
apiVersion : apps/v1
kind : Deployment
metadata :
name : mcp-server-langgraph
spec :
template :
spec :
containers :
- name : cloud-sql-proxy
image : gcr.io/cloud-sql-connectors/cloud-sql-proxy:latest
args :
- "--structured-logs"
- "--port=5432"
- "project:region:instance"
## overlays/production/kustomization.yaml
transformers :
- | -
apiVersion: builtin
kind: PrefixSuffixTransformer
metadata:
name: prefix-suffix
prefix: prod-
suffix: -v1
fieldSpecs:
- path: metadata/name
Generators
## overlays/production/kustomization.yaml
generators :
- secret-generator.yaml
secret-generator.yaml :
apiVersion : builtin
kind : SecretGenerator
metadata :
name : db-credentials
literals :
- username=admin
- password=changeme
type : Opaque
Multi-Environment Workflow
Build and Preview
## Development
kubectl kustomize overlays/development
## Staging
kubectl kustomize overlays/staging
## Production
kubectl kustomize overlays/production
Deploy
## Development
kubectl apply -k overlays/development
## Staging
kubectl apply -k overlays/staging
## Production
kubectl apply -k overlays/production
Delete
## Delete environment
kubectl delete -k overlays/staging
CI/CD Integration
GitHub Actions
name : Deploy with Kustomize
on :
push :
branches : [ main ]
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v3
- name : Setup kubectl
uses : azure/setup-kubectl@v3
- name : Configure kubectl
run : |
echo "${{ secrets.KUBECONFIG }}" | base64 -d > kubeconfig
export KUBECONFIG=kubeconfig
- name : Deploy to production
run : |
kubectl apply -k k8s/overlays/production
- name : Verify deployment
run : |
kubectl rollout status deployment/mcp-server-langgraph -n mcp-server-langgraph
GitLab CI
deploy:production :
stage : deploy
image : bitnami/kubectl:latest
script :
- kubectl apply -k k8s/overlays/production
- kubectl rollout status deployment/mcp-server-langgraph -n mcp-server-langgraph
only :
- main
GitOps with ArgoCD
Create Application
apiVersion : argoproj.io/v1alpha1
kind : Application
metadata :
name : mcp-server-langgraph-production
namespace : argocd
spec :
project : default
source :
repoURL : https://github.com/vishnu2kmohan/mcp-server-langgraph
targetRevision : main
path : k8s/overlays/production
destination :
server : https://kubernetes.default.svc
namespace : mcp-server-langgraph
syncPolicy :
automated :
prune : true
selfHeal : true
allowEmpty : false
syncOptions :
- CreateNamespace=true
retry :
limit : 5
backoff :
duration : 5s
factor : 2
maxDuration : 3m
Deploy with ArgoCD
## Install ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
## Create application
kubectl apply -f argocd-application.yaml
## Sync application
argocd app sync mcp-server-langgraph-production
Best Practices
Use ConfigMap/Secret Generators
Always use generators instead of static files: # Good
configMapGenerator :
- name : app-config
literals :
- KEY=value
# Bad - don't commit static ConfigMaps
resources :
- configmap.yaml # Contains hardcoded values
Base should contain only common, environment-agnostic configuration:
Deployment structure
Service definition
Basic probes
No environment-specific values
Tag images with semantic versions: images :
- name : mcp-server-langgraph
newTag : v1.2.3 # Not 'latest'
Always validate Kustomize output: # Preview changes
kubectl kustomize overlays/production
# Validate
kubectl kustomize overlays/production | kubectl apply --dry-run=client -f -
# Then apply
kubectl apply -k overlays/production
Use Name Suffixes for ConfigMaps
Enable automatic rollouts on config changes: generatorOptions :
disableNameSuffixHash : false # Default
This adds hash suffix to ConfigMap names, triggering pod restarts.
Troubleshooting
Error : Error: accumulating resources: accumulation err='accumulating resources from '../../base': ...Solution :# Validate base first
kubectl kustomize k8s/base
# Check file references
ls -la k8s/base/
# Validate overlay
kubectl kustomize k8s/overlays/production
Problem : Strategic merge patch not workingSolution : Use JSON6902 patch instead:patchesJson6902 :
- target :
group : apps
version : v1
kind : Deployment
name : mcp-server-langgraph
patch : | -
- op: replace
path: /spec/replicas
value: 5
ConfigMap not updating pods
Problem : Pods not restarting on ConfigMap changeSolution : Ensure name suffix hash is enabled:generatorOptions :
disableNameSuffixHash : false
Or use Reloader: helm install reloader stakater/reloader
Testing Kustomize
## Validate YAML syntax
kubectl kustomize overlays/production | kubectl apply --dry-run=client -f -
## Check resource counts
kubectl kustomize overlays/production | grep "^kind:" | sort | uniq -c
## Diff between environments
diff <( kubectl kustomize overlays/staging) <( kubectl kustomize overlays/production)
## Test with kubeval
kubectl kustomize overlays/production | kubeval --strict
## Test with kube-score
kubectl kustomize overlays/production | kube-score score -
Next Steps
Kustomize Ready : Template-free Kubernetes configuration management!