SiamCafe · Blog
Kustomize Overlay Best Practices ที่ต้องรู้
บทความ

Kustomize Overlay Best Practices ที่ต้องรู้

เผยแพร่ 28 พฤษภาคม 2569

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