SiamCafe.net Blog
Technology

Kustomize Overlay Zero Downtime Deployment

kustomize overlay zero downtime deployment
Kustomize Overlay Zero Downtime Deployment | SiamCafe Blog
2025-06-27· อ. บอม — SiamCafe.net· 1,377 คำ

Kustomize Overlay คืออะไรและช่วย Zero Downtime Deployment อย่างไร

Kustomize เป็นเครื่องมือจัดการ Kubernetes manifest แบบ declarative ที่มาพร้อมกับ kubectl ไม่ต้องติดตั้งเพิ่ม จุดเด่นคือระบบ overlay ที่แยก base configuration ออกจาก environment-specific patches ทำให้จัดการ deployment ข้าม environment ได้สะดวก เมื่อรวมกับ rolling update strategy และ health checks จะทำ zero downtime deployment ได้โดยไม่ต้องพึ่ง Helm chart ที่ซับซ้อน

โครงสร้างโปรเจค Kustomize สำหรับ Multi-environment

# โครงสร้างไฟล์
# k8s/
# ├── base/
# │   ├── kustomization.yaml
# │   ├── deployment.yaml
# │   ├── service.yaml
# │   ├── hpa.yaml
# │   └── pdb.yaml
# ├── overlays/
# │   ├── dev/
# │   │   ├── kustomization.yaml
# │   │   ├── replicas-patch.yaml
# │   │   └── env-configmap.yaml
# │   ├── staging/
# │   │   ├── kustomization.yaml
# │   │   └── replicas-patch.yaml
# │   └── production/
# │       ├── kustomization.yaml
# │       ├── replicas-patch.yaml
# │       ├── resources-patch.yaml
# │       ├── rolling-update-patch.yaml
# │       └── env-configmap.yaml

Base Configuration

# k8s/base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

commonLabels:
  app.kubernetes.io/name: myapp
  app.kubernetes.io/managed-by: kustomize

resources:
  - deployment.yaml
  - service.yaml
  - hpa.yaml
  - pdb.yaml
# k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app.kubernetes.io/name: myapp
  template:
    metadata:
      labels:
        app.kubernetes.io/name: myapp
    spec:
      terminationGracePeriodSeconds: 60
      containers:
        - name: myapp
          image: myapp:latest
          ports:
            - containerPort: 8080
              protocol: TCP
          envFrom:
            - configMapRef:
                name: myapp-env
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20
            failureThreshold: 3
          startupProbe:
            httpGet:
              path: /healthz
              port: 8080
            failureThreshold: 30
            periodSeconds: 2
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh", "-c", "sleep 10"]
# k8s/base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
  selector:
    app.kubernetes.io/name: myapp
# k8s/base/hpa.yaml — Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Percent
          value: 50
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 25
          periodSeconds: 120
# k8s/base/pdb.yaml — PodDisruptionBudget ป้องกัน downtime
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: myapp
spec:
  minAvailable: "50%"
  selector:
    matchLabels:
      app.kubernetes.io/name: myapp

Production Overlay พร้อม Zero Downtime Strategy

# k8s/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: production

namePrefix: prod-

bases:
  - ../../base

patches:
  - path: replicas-patch.yaml
  - path: resources-patch.yaml
  - path: rolling-update-patch.yaml

configMapGenerator:
  - name: myapp-env
    literals:
      - APP_ENV=production
      - LOG_LEVEL=warn
      - DB_HOST=prod-db.internal
      - DB_PORT=5432
      - CACHE_TTL=300
      - RATE_LIMIT=1000

images:
  - name: myapp
    newName: registry.example.com/myapp
    newTag: v2.1.0
# k8s/overlays/production/replicas-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 6
# k8s/overlays/production/resources-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  template:
    spec:
      containers:
        - name: myapp
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: "2"
              memory: 2Gi
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app.kubernetes.io/name: myapp
# k8s/overlays/production/rolling-update-patch.yaml
# กุญแจสำคัญของ zero downtime
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 0        # ห้ามมี pod ที่ไม่พร้อมเลย
      maxSurge: 2              # สร้าง pod ใหม่ได้สูงสุด 2 ตัวพร้อมกัน
  minReadySeconds: 30          # รอ 30 วินาทีหลัง ready ก่อนนับว่าสำเร็จ

Dev Overlay แบบ Minimal

# k8s/overlays/dev/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: development

namePrefix: dev-

bases:
  - ../../base

patches:
  - path: replicas-patch.yaml

configMapGenerator:
  - name: myapp-env
    literals:
      - APP_ENV=development
      - LOG_LEVEL=debug
      - DB_HOST=dev-db.internal
      - DB_PORT=5432

images:
  - name: myapp
    newName: registry.example.com/myapp
    newTag: dev-latest
# k8s/overlays/dev/replicas-patch.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1

Deploy และ Monitor Zero Downtime

# ดู manifest ที่จะ apply (dry-run)
kubectl kustomize k8s/overlays/production/

# Apply production overlay
kubectl apply -k k8s/overlays/production/

# ดู rollout status แบบ real-time
kubectl rollout status deployment/prod-myapp -n production --timeout=300s

# ตัวอย่าง output ระหว่าง rolling update:
# Waiting for deployment "prod-myapp" rollout to finish:
#   4 out of 6 new replicas have been updated...
#   4 of 6 updated replicas are available...
#   5 of 6 updated replicas are available...
#   6 of 6 updated replicas are available...
# deployment "prod-myapp" successfully rolled out

# ตรวจสอบว่า pod ทุกตัว ready
kubectl get pods -n production -l app.kubernetes.io/name=myapp
# NAME                          READY   STATUS    AGE
# prod-myapp-7d9f8b6c4d-abc12  1/1     Running   2m
# prod-myapp-7d9f8b6c4d-def34  1/1     Running   2m
# prod-myapp-7d9f8b6c4d-ghi56  1/1     Running   1m
# prod-myapp-7d9f8b6c4d-jkl78  1/1     Running   1m
# prod-myapp-7d9f8b6c4d-mno90  1/1     Running   45s
# prod-myapp-7d9f8b6c4d-pqr12  1/1     Running   45s

# Rollback ถ้ามีปัญหา
kubectl rollout undo deployment/prod-myapp -n production

# ดู rollout history
kubectl rollout history deployment/prod-myapp -n production
# REVISION  CHANGE-CAUSE
# 1         Initial deployment
# 2         Update to v2.1.0
# 3         Rollback to revision 1

CI/CD Pipeline สำหรับ Kustomize Deployment

# .github/workflows/deploy.yml
name: Deploy with Kustomize
on:
  push:
    branches: [main]
    paths: ['src/**', 'Dockerfile', 'k8s/**']

env:
  REGISTRY: registry.example.com
  IMAGE_NAME: myapp

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image_tag: }
    steps:
      - uses: actions/checkout@v4

      - name: Build and push image
        id: meta
        run: |
          TAG="v$(date +%Y%m%d)-"
          echo "version=$TAG" >> $GITHUB_OUTPUT
          docker build -t $REGISTRY/$IMAGE_NAME:$TAG .
          docker push $REGISTRY/$IMAGE_NAME:$TAG

  deploy-staging:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Update image tag in staging overlay
        run: |
          cd k8s/overlays/staging
          kustomize edit set image myapp=$REGISTRY/$IMAGE_NAME:}

      - name: Deploy to staging
        run: kubectl apply -k k8s/overlays/staging/

      - name: Wait for rollout
        run: kubectl rollout status deployment/stg-myapp -n staging --timeout=300s

      - name: Run smoke tests
        run: |
          ENDPOINT=$(kubectl get svc stg-myapp -n staging -o jsonpath='{.spec.clusterIP}')
          curl -sf http://$ENDPOINT/healthz || exit 1

  deploy-production:
    needs: [build, deploy-staging]
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - name: Update image tag
        run: |
          cd k8s/overlays/production
          kustomize edit set image myapp=$REGISTRY/$IMAGE_NAME:}

      - name: Deploy to production
        run: kubectl apply -k k8s/overlays/production/

      - name: Monitor rollout
        run: |
          kubectl rollout status deployment/prod-myapp -n production --timeout=600s
          if [ $? -ne 0 ]; then
            echo "Rollout failed, initiating rollback"
            kubectl rollout undo deployment/prod-myapp -n production
            exit 1
          fi

FAQ - คำถามที่พบบ่อย

Q: Kustomize กับ Helm ต่างกันอย่างไร ควรเลือกตัวไหน?

A: Kustomize ใช้ patching แก้ไข YAML ตรงๆ ไม่ต้องเรียนรู้ template syntax ใหม่ เหมาะกับทีมที่ต้องการ simplicity Helm ใช้ Go templates สร้าง manifest มี package management และ release versioning เหมาะกับ application ที่ต้อง distribute ให้คนอื่นใช้ ถ้า deploy app ของตัวเองเลือก Kustomize ถ้าทำ reusable chart เลือก Helm

Q: maxUnavailable: 0 ทำให้ deploy ช้าไหม?

A: ช้ากว่า default เล็กน้อย เพราะต้องรอ pod ใหม่ ready ก่อนจึงจะ terminate pod เก่า แต่แลกมาด้วย zero downtime ที่ guarantee ได้ เพิ่ม maxSurge เป็น 2-3 เพื่อให้สร้าง pod ใหม่หลายตัวพร้อมกัน จะเร็วขึ้น

Q: PodDisruptionBudget จำเป็นไหม?

A: จำเป็นมากสำหรับ production PDB ป้องกันไม่ให้ Kubernetes evict pod มากเกินไปพร้อมกัน เช่น ตอน node drain หรือ cluster autoscaler ลด node ถ้าไม่มี PDB อาจเกิดสถานการณ์ที่ pod ทุกตัวถูก evict พร้อมกัน

Q: preStop hook sleep 10 ทำเพื่ออะไร?

A: เมื่อ Kubernetes ส่ง SIGTERM ไปที่ pod จะลบ pod ออกจาก Endpoints พร้อมกัน แต่ kube-proxy และ ingress controller อาจยังไม่ได้ update routing rules ทันที sleep 10 วินาทีก่อน shutdown ทำให้ pod ยังรับ request ที่ค้างอยู่ได้จนกว่า routing จะ update เสร็จ ป้องกัน connection reset ระหว่าง deploy

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

C# Minimal API Zero Downtime Deploymentอ่านบทความ → Healthchecks.io Zero Downtime Deploymentอ่านบทความ → GCP Vertex AI Zero Downtime Deploymentอ่านบทความ → Weights Biases Zero Downtime Deploymentอ่านบทความ → Kustomize Overlay Serverless Architectureอ่านบทความ →

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