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
