SiamCafe.net Blog
Technology

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

kustomize overlay best practices ทตองร
Kustomize Overlay Best Practices ที่ต้องรู้ | SiamCafe Blog
2025-11-30· อ. บอม — SiamCafe.net· 1,527 คำ

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

📖 บทความที่เกี่ยวข้อง

Python httpx Best Practices ที่ต้องรู้อ่านบทความ → Weights Biases Best Practices ที่ต้องรู้อ่านบทความ → gRPC Protobuf Best Practices ที่ต้องรู้อ่านบทความ → AWS Glue ETL Best Practices ที่ต้องรู้อ่านบทความ → Falco Runtime Security Best Practices ที่ต้องรู้อ่านบทความ →

📚 ดูบทความทั้งหมด →