Kustomize Overlay Best Practices ที่ต้องรู้
Kustomize เป็น Kubernetes native configuration management tool ที่ใช้ overlay pattern แก้ไข YAML manifests โดยไม่ต้องใช้ templates เหมือน Helm ถูก built-in ใน kubectl ตั้งแต่ v1.14 ทำให้ไม่ต้องติดตั้งเพิ่ม Overlay คือการซ้อน configuration patches บน base manifests เพื่อสร้าง environment-specific configs (dev, staging, production) โดยไม่ duplicate YAML บทความนี้อธิบาย best practices สำหรับ Kustomize overlays พร้อมตัวอย่าง Python automation tools
Kustomize Fundamentals
# kustomize_basics.py — Kustomize fundamentals
import json
class KustomizeBasics:
CONCEPTS = {
"base": {
"name": "Base",
"description": "Shared resources ที่ใช้ร่วมกันทุก environment — deployment, service, configmap",
"path": "base/",
},
"overlay": {
"name": "Overlay",
"description": "Environment-specific patches — replicas, resources, env vars, images",
"path": "overlays/dev/, overlays/staging/, overlays/production/",
},
"kustomization": {
"name": "kustomization.yaml",
"description": "Config file ที่ระบุ resources, patches, transformers ที่จะใช้",
},
"patch": {
"name": "Patches",
"description": "JSON/YAML patches สำหรับแก้ไข base resources — Strategic Merge Patch หรือ JSON Patch",
},
}
DIRECTORY = """
# Recommended directory structure
my-app/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ ├── service.yaml
│ └── configmap.yaml
├── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ ├── patch-replicas.yaml
│ │ └── patch-resources.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ └── production/
│ ├── kustomization.yaml
│ ├── patch-replicas.yaml
│ ├── patch-resources.yaml
│ └── patch-hpa.yaml
└── components/
├── monitoring/
│ ├── kustomization.yaml
│ └── service-monitor.yaml
└── ingress/
├── kustomization.yaml
└── ingress.yaml
"""
def show_concepts(self):
print("=== Kustomize Concepts ===\n")
for key, concept in self.CONCEPTS.items():
print(f"[{concept['name']}]")
print(f" {concept['description']}")
print()
def show_directory(self):
print("=== Directory Structure ===")
print(self.DIRECTORY[:500])
basics = KustomizeBasics()
basics.show_concepts()
basics.show_directory()
Base Configuration
# base_config.py — Base Kustomize configuration
import json
class BaseConfig:
BASE_KUSTOMIZATION = """
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- configmap.yaml
commonLabels:
app.kubernetes.io/name: my-app
app.kubernetes.io/managed-by: kustomize
configMapGenerator:
- name: app-config
literals:
- LOG_LEVEL=info
- DB_POOL_SIZE=10
"""
BASE_DEPLOYMENT = """
# base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 1
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:latest
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
envFrom:
- configMapRef:
name: app-config
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
"""
def show_kustomization(self):
print("=== Base kustomization.yaml ===")
print(self.BASE_KUSTOMIZATION)
def show_deployment(self):
print("=== Base deployment.yaml ===")
print(self.BASE_DEPLOYMENT[:500])
base = BaseConfig()
base.show_kustomization()
base.show_deployment()
Overlay Patterns
# overlay_patterns.py — Overlay best practices
import json
class OverlayPatterns:
DEV_OVERLAY = """
# overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: dev-
namespace: dev
patches:
- path: patch-replicas.yaml
- path: patch-resources.yaml
configMapGenerator:
- name: app-config
behavior: merge
literals:
- LOG_LEVEL=debug
- DB_HOST=dev-db.internal
images:
- name: my-app
newTag: dev-latest
"""
PROD_OVERLAY = """
# overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
namespace: production
patches:
- path: patch-replicas.yaml
- path: patch-resources.yaml
- path: patch-hpa.yaml
configMapGenerator:
- name: app-config
behavior: merge
literals:
- LOG_LEVEL=warn
- DB_HOST=prod-db.internal
- DB_POOL_SIZE=50
images:
- name: my-app
newTag: v1.2.3
"""
PATCH_REPLICAS = """
# overlays/production/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 5
"""
PATCH_RESOURCES = """
# overlays/production/patch-resources.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
spec:
containers:
- name: my-app
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "2"
memory: 1Gi
"""
def show_dev(self):
print("=== Dev Overlay ===")
print(self.DEV_OVERLAY[:400])
def show_prod(self):
print("\n=== Production Overlay ===")
print(self.PROD_OVERLAY[:400])
def show_patches(self):
print("\n=== Patch Examples ===")
print(self.PATCH_REPLICAS)
print(self.PATCH_RESOURCES[:300])
overlay = OverlayPatterns()
overlay.show_dev()
overlay.show_prod()
overlay.show_patches()
Best Practices
# best_practices.py — Kustomize best practices
import json
class BestPractices:
PRACTICES = {
"small_bases": {
"name": "Keep bases small and focused",
"description": "แยก base ตาม service/component — อย่ารวมทุกอย่างใน base เดียว",
"example": "base/api/, base/worker/, base/redis/ — แต่ละ base มี resources ของตัวเอง",
},
"avoid_duplication": {
"name": "ห้าม duplicate YAML ใน overlays",
"description": "ใช้ patches แก้เฉพาะส่วนที่ต่าง — อย่า copy deployment.yaml ทั้งไฟล์",
"example": "patch เฉพาะ replicas, resources, env — ไม่ต้อง copy ทุก field",
},
"use_components": {
"name": "ใช้ Components สำหรับ optional features",
"description": "Components = reusable Kustomize configs ที่ overlay ไหนัก็ include ได้",
"example": "components/monitoring/, components/ingress/, components/autoscaling/",
},
"image_tags": {
"name": "ใช้ images field แทน patch สำหรับ image tags",
"description": "images: newTag ง่ายกว่า patch — CI/CD set tag ได้ง่าย",
"example": "kustomize edit set image my-app=my-app:v1.2.3",
},
"configmap_generator": {
"name": "ใช้ configMapGenerator + behavior: merge",
"description": "Base กำหนด defaults, overlay merge เฉพาะ env-specific values",
"example": "Base: LOG_LEVEL=info → Prod overlay: behavior: merge, LOG_LEVEL=warn",
},
"naming": {
"name": "ใช้ namePrefix/nameSuffix สำหรับ multi-env",
"description": "หลีกเลี่ยง resource name collision ข้าม environments",
"example": "namePrefix: prod- → prod-my-app, prod-app-config",
},
"validation": {
"name": "Validate ก่อน apply ทุกครั้ง",
"description": "kustomize build → kubectl diff → kubectl apply",
"example": "kustomize build overlays/prod | kubectl diff -f - → ดูว่าจะเปลี่ยนอะไร",
},
"git_ops": {
"name": "ใช้ GitOps (ArgoCD/Flux) กับ Kustomize",
"description": "ArgoCD/Flux อ่าน kustomization.yaml จาก Git → auto-sync",
"example": "ArgoCD Application: spec.source.kustomize.namePrefix: prod-",
},
}
def show_practices(self):
print("=== Best Practices ===\n")
for key, bp in self.PRACTICES.items():
print(f"[{bp['name']}]")
print(f" {bp['description']}")
print(f" Example: {bp['example']}")
print()
bp = BestPractices()
bp.show_practices()
Python Automation
# automation.py — Python tools for Kustomize
import json
class KustomizeAutomation:
CODE = """
# kustomize_tools.py — Automate Kustomize operations
import subprocess
import json
import yaml
from pathlib import Path
class KustomizeManager:
def __init__(self, base_path):
self.base_path = Path(base_path)
def build(self, overlay_path):
'''Build Kustomize overlay'''
result = subprocess.run(
["kustomize", "build", str(overlay_path)],
capture_output=True, text=True,
)
if result.returncode != 0:
return {"error": result.stderr}
resources = list(yaml.safe_load_all(result.stdout))
return {"resources": resources, "count": len(resources)}
def diff(self, overlay_path):
'''Show diff between current cluster state and overlay'''
build = subprocess.run(
["kustomize", "build", str(overlay_path)],
capture_output=True, text=True,
)
diff = subprocess.run(
["kubectl", "diff", "-f", "-"],
input=build.stdout,
capture_output=True, text=True,
)
return diff.stdout
def validate(self, overlay_path):
'''Validate overlay with kubeval/kubeconform'''
build = subprocess.run(
["kustomize", "build", str(overlay_path)],
capture_output=True, text=True,
)
validate = subprocess.run(
["kubeconform", "-strict", "-summary", "-"],
input=build.stdout,
capture_output=True, text=True,
)
return {
"valid": validate.returncode == 0,
"output": validate.stdout,
}
def set_image(self, overlay_path, image, tag):
'''Set image tag in overlay'''
result = subprocess.run(
["kustomize", "edit", "set", "image", f"{image}={image}:{tag}"],
cwd=str(overlay_path),
capture_output=True, text=True,
)
return result.returncode == 0
def compare_overlays(self, overlay1, overlay2):
'''Compare two overlays'''
build1 = self.build(overlay1)
build2 = self.build(overlay2)
diffs = []
resources1 = {f"{r['kind']}/{r['metadata']['name']}": r for r in build1.get('resources', [])}
resources2 = {f"{r['kind']}/{r['metadata']['name']}": r for r in build2.get('resources', [])}
all_keys = set(resources1.keys()) | set(resources2.keys())
for key in sorted(all_keys):
if key not in resources1:
diffs.append(f"+ {key} (only in overlay2)")
elif key not in resources2:
diffs.append(f"- {key} (only in overlay1)")
elif resources1[key] != resources2[key]:
diffs.append(f"~ {key} (different)")
return diffs
# mgr = KustomizeManager("./my-app")
# result = mgr.build("./my-app/overlays/production")
# mgr.set_image("./my-app/overlays/production", "my-app", "v1.2.4")
"""
def show_code(self):
print("=== Kustomize Automation ===")
print(self.CODE[:600])
auto = KustomizeAutomation()
auto.show_code()
FAQ - คำถามที่พบบ่อย
Q: Kustomize กับ Helm อันไหนดีกว่า?
A: Kustomize: ง่ายกว่า, built-in kubectl, ไม่ต้อง template language — ดีสำหรับ simple overlays Helm: powerful กว่า, charts ecosystem ใหญ่, template logic — ดีสำหรับ complex apps + reusable packages ใช้ Kustomize: internal apps ที่ต้องการ env-specific configs ใช้ Helm: third-party apps (nginx, prometheus), complex templating รวมกัน: Helm render → Kustomize post-process (ดีที่สุด)
Q: Strategic Merge Patch กับ JSON Patch ต่างกันอย่างไร?
A: Strategic Merge Patch: YAML format เหมือน resource เดิม — ง่าย อ่านง่าย เหมาะ patch ส่วนใหญ่ JSON Patch: array ของ operations (add, remove, replace) — ละเอียดกว่า เหมาะ arrays ใช้ Strategic Merge Patch: 90% ของกรณี — เขียนง่าย อ่านง่าย ใช้ JSON Patch: เมื่อต้อง manipulate arrays (เช่น เพิ่ม container, volume)
Q: Components ใน Kustomize คืออะไร?
A: Components = reusable Kustomize configs ที่ overlay ไหนัก็ include ได้ ต่างจาก base: base เป็น required dependency, component เป็น optional feature ตัวอย่าง: component/monitoring (ServiceMonitor), component/ingress (Ingress), component/autoscaling (HPA) ใช้: components: [../../components/monitoring] ใน kustomization.yaml
Q: CI/CD กับ Kustomize ทำยังไง?
A: GitOps (แนะนำ): ArgoCD/Flux watch Git repo → auto-sync เมื่อ kustomization.yaml เปลี่ยน Pipeline: CI build image → kustomize edit set image → commit → ArgoCD sync Validation: kustomize build | kubeconform → validate ก่อน merge PR Diff: kustomize build | kubectl diff → review changes ก่อน apply
