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:

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

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:

mkdir helloworld/base -p
base/
├── configMap.yaml
├── deployment.yaml
├── ingress.yaml
├── kustomization.yaml
└── service.yaml

Content of each file:

configMap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: the-map
  namespace: default
data:
  altGreeting: "Hello World!"
  enableRisky: "false"

deployment.yaml

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

service.yaml

kind: Service
apiVersion: v1
metadata:
  name: the-service
  namespace: default
spec:
  selector:
    deployment: hello
  type: NodePort
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080

ingress.yaml

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: /

kustomization.yaml (base)

# Example configuration for the webserver
# at https://github.com/monopole/hello
commonLabels:
  app: hello
resources:
- deployment.yaml
- service.yaml
- configMap.yaml
- ingress.yaml

Build the base configuration: kustomize build ../base Apply it with kubectl:

# Directly using kubectl apply -k (cluster version > 1.14)
kubectl apply -k ../base/
# Or via kustomize pipe
kustomize build ../base | kubectl apply -f -

Create Overlays for Different Environments

Directory structure:

.
├── base
│   ├── configMap.yaml
│   ├── deployment.yaml
│   ├── ingress.yaml
│   ├── kustomization.yaml
│   └── service.yaml
└── overlays
    ├── dev
    ├── prod
    └── stag

Development Environment

Files under overlays/dev:

../dev/
├── ingress.yaml
├── kustomization.yaml
└── map.yaml

ingress.yaml

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: /

map.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: the-map
data:
  altGreeting: "Hello,This is Dev!"
  enableRisky: "true"

kustomization.yaml

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

Test the overlay:

kustomize build .

Staging Environment

../stag/
├── kustomization.yaml
└── map.yaml

map.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: the-map
data:
  altGreeting: "Hello,This is Stag!"
  enableRisky: "true"

kustomization.yaml

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

Production Environment

../prod/
├── deployment.yaml
├── kustomization.yaml
└── map.yaml

deployment.yaml (override)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: the-deployment
spec:
  replicas: 3

map.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: the-map
data:
  altGreeting: "Hello,This is prod!"
  enableRisky: "true"

kustomization.yaml

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

Publishing

Deploy the development environment:

cd helloworld/
kustomize build overlays/dev/ | kubectl apply -f -

Resulting resources (example):

# 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

Access the service via the configured host name.

Integrating with Continuous Deployment

To update the image in the dev overlay, run:

cd overlays/dev
kustomize edit set image monopole/hello=nginx:latest

The kustomization.yaml now contains:

images:
- name: monopole/hello
  newName: nginx
  newTag: latest

Apply the updated configuration:

# 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

Namespace can also be changed with:

kustomize edit set namespace test

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.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

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

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.