Mastering Kustomize: Simplify Multi‑Environment Kubernetes Deployments
This guide explains how Kustomize streamlines Kubernetes configuration across multiple environments by eliminating YAML duplication, using base and overlay patterns, and integrating with kubectl and CI/CD pipelines, complete with installation steps, practical examples, and deployment commands.
Deploying applications to Kubernetes often relies on
kubectlor
Helm, both of which require YAML manifest files. Managing separate YAML files for development, testing, staging, and production can quickly lead to duplication and maintenance overhead.
Kustomize is an open‑source configuration management tool that addresses these challenges. Since Kubernetes v1.14,
kubectlfully supports Kustomize and kustomization files.
What is Kustomize?
kustomize lets you customize raw, template‑free YAML files for multiple purposes, leaving the original YAML untouched and usable as is.
Kustomize enables you to keep the original YAML unchanged while applying environment‑specific customizations.
Kustomize Benefits
When handling multiple environments in Kubernetes, you typically face:
Multiple YAML manifests for different environments and teams
Small differences that force copy‑and‑modify workflows
Helm’s additional learning curve
Kustomize solves these problems by:
Using a Base & Overlays model to maintain configurations for each environment
Applying patches to reuse the Base configuration while describing differences in Overlays
Managing native Kubernetes YAML without requiring a new DSL
Installation
On Kubernetes 1.14+ the tool is integrated into
kubectl. Run
kubectl --helpto view commands. For a separate installation, download the desired version from the official GitHub releases:
<code>wget https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv3.8.7/kustomize_v3.8.7_linux_amd64.tar.gz
tar xf kustomize_v3.8.7_linux_amd64.tar.gz
cp kustomize/kustomize /usr/local/bin</code>Now Kustomize is ready to use.
Practical Test
Background
Kubernetes version: 1.17.9
Cluster namespaces: dev, stag, prod
Test case: simple hello‑world application
Create Base Template
Create a
helloworld/basedirectory and add the following files:
<code>mkdir helloworld/base -p</code> <code>base/
├── configMap.yaml
├── deployment.yaml
├── ingress.yaml
├── kustomization.yaml
└── service.yaml</code>Content of each file:
configMap.yaml
<code>apiVersion: v1
kind: ConfigMap
metadata:
name: the-map
namespace: default
data:
altGreeting: "Hello World!"
enableRisky: "false"</code>deployment.yaml
<code>apiVersion: apps/v1
kind: Deployment
metadata:
name: the-deployment
namespace: default
spec:
replicas: 3
selector:
matchLabels:
deployment: hello
template:
metadata:
labels:
deployment: hello
spec:
containers:
- name: the-container
image: monopole/hello:1
command: ["/hello","--port=8080","--enableRiskyFeature=$(ENABLE_RISKY)"]
ports:
- containerPort: 8080
env:
- name: ALT_GREETING
valueFrom:
configMapKeyRef:
name: the-map
key: altGreeting
- name: ENABLE_RISKY
valueFrom:
configMapKeyRef:
name: the-map
key: enableRisky</code>service.yaml
<code>kind: Service
apiVersion: v1
metadata:
name: the-service
namespace: default
spec:
selector:
deployment: hello
type: NodePort
ports:
- protocol: TCP
port: 8080
targetPort: 8080</code>ingress.yaml
<code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: the-ingress
namespace: default
spec:
rules:
- host: test.coolops.cn
http:
paths:
- backend:
serviceName: the-service
servicePort: 8080
path: /</code>kustomization.yaml (base)
<code># Example configuration for the webserver
# at https://github.com/monopole/hello
commonLabels:
app: hello
resources:
- deployment.yaml
- service.yaml
- configMap.yaml
- ingress.yaml</code>Build the base configuration:
<code>kustomize build ../base</code>Apply it with kubectl:
<code># Directly using kubectl apply -k (cluster version > 1.14)
kubectl apply -k ../base/
# Or via kustomize pipe
kustomize build ../base | kubectl apply -f -</code>Create Overlays for Different Environments
Directory structure:
<code>.
├── base
│ ├── configMap.yaml
│ ├── deployment.yaml
│ ├── ingress.yaml
│ ├── kustomization.yaml
│ └── service.yaml
└── overlays
├── dev
├── prod
└── stag</code>Development Environment
Files under
overlays/dev:
<code>../dev/
├── ingress.yaml
├── kustomization.yaml
└── map.yaml</code>ingress.yaml
<code>apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: the-ingress
namespace: default
spec:
rules:
- host: hello-dev.coolops.cn
http:
paths:
- backend:
serviceName: the-service
servicePort: 8080
path: /</code>map.yaml
<code>apiVersion: v1
kind: ConfigMap
metadata:
name: the-map
data:
altGreeting: "Hello,This is Dev!"
enableRisky: "true"</code>kustomization.yaml
<code>namePrefix: dev-
commonLabels:
org: acmeCorporation
variant: dev
commonAnnotations:
note: Hello, This is dev!
patchesStrategicMerge:
- map.yaml
- ingress.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: dev</code>Test the overlay:
<code>kustomize build .</code>Staging Environment
<code>../stag/
├── kustomization.yaml
└── map.yaml</code>map.yaml
<code>apiVersion: v1
kind: ConfigMap
metadata:
name: the-map
data:
altGreeting: "Hello,This is Stag!"
enableRisky: "true"</code>kustomization.yaml
<code>namePrefix: stag-
commonLabels:
org: acmeCorporation
variant: stag
commonAnnotations:
note: Hello, This is stag!
patchesStrategicMerge:
- map.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: stag</code>Production Environment
<code>../prod/
├── deployment.yaml
├── kustomization.yaml
└── map.yaml</code>deployment.yaml (override)
<code>apiVersion: apps/v1
kind: Deployment
metadata:
name: the-deployment
spec:
replicas: 3</code>map.yaml
<code>apiVersion: v1
kind: ConfigMap
metadata:
name: the-map
data:
altGreeting: "Hello,This is prod!"
enableRisky: "true"</code>kustomization.yaml
<code>namePrefix: prod-
commonLabels:
org: acmeCorporation
variant: prod
commonAnnotations:
note: Hello, This is prod!
patchesStrategicMerge:
- deployment.yaml
- map.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namespace: prod</code>Publishing
Deploy the development environment:
<code>cd helloworld/
kustomize build overlays/dev/ | kubectl apply -f -</code>Resulting resources (example):
<code># kubectl get all -n dev
NAME READY STATUS RESTARTS AGE
pod/dev-the-deployment-6cdcbbc878-27n5g 1/1 Running 0 50s
pod/dev-the-deployment-6cdcbbc878-fgx89 1/1 Running 0 50s
pod/dev-the-deployment-6cdcbbc878-xz5q2 1/1 Running 0 50s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/dev-the-service NodePort 10.103.77.190 <none> 8080:32414/TCP 50s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/dev-the-deployment 3/3 3 3 50s
NAME DESIRED CURRENT READY AGE
replicaset.apps/dev-the-deployment-6cdcbbc878 3 3 3 50s</code>Access the service via the configured host name.
Integrating with Continuous Deployment
To update the image in the dev overlay, run:
<code>cd overlays/dev
kustomize edit set image monopole/hello=nginx:latest</code>The
kustomization.yamlnow contains:
<code>images:
- name: monopole/hello
newName: nginx
newTag: latest</code>Apply the updated configuration:
<code># kustomize build . | kubectl apply -f -
configmap/dev-the-map unchanged
service/dev-the-service unchanged
deployment.apps/dev-the-deployment configured
ingress.extensions/dev-the-ingress unchanged</code>Namespace can also be changed with:
<code>kustomize edit set namespace test</code>Conclusion
Using Kustomize consolidates repetitive YAML files into a layered model, reducing errors, simplifying manual configuration, and making the codebase easier to understand and maintain across multiple environments.
Ops Development Stories
Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.