Cloud Native 13 min read

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.

Ops Development Stories
Ops Development Stories
Ops Development Stories
Mastering Kustomize: Simplify Multi‑Environment Kubernetes Deployments

Deploying applications to Kubernetes often relies on

kubectl

or

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,

kubectl

fully 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 --help

to 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/base

directory 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.yaml

now 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.

Cloud NativekubernetesConfiguration ManagementDevOpsHelmkubectlKustomize
Ops Development Stories
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.