Zero-Downtime Deployment คือการ Deploy application เวอร์ชันใหม่ขึ้น Production โดยที่ผู้ใช้ไม่รู้สึกถึงการเปลี่ยนแปลง ไม่มี Downtime ไม่มี Error page ไม่มี "ระบบกำลังปรับปรุง กรุณารอสักครู่" ผู้ใช้ใช้งานได้ต่อเนื่องตลอดเวลาระหว่างที่ระบบกำลัง Deploy เวอร์ชันใหม่
ในยุคที่ทุก Application ต้อง Available 24/7 ไม่ว่าจะเป็น E-commerce, Banking, Social media หรือ SaaS Downtime แม้แค่ไม่กี่นาทีก็สร้างความเสียหายมหาศาล ทั้งรายได้ที่สูญเสีย ความเชื่อมั่นของลูกค้าที่ลดลง และ SLA ที่ถูกละเมิด
ทำไม Zero-Downtime ถึงสำคัญ?
Revenue Loss: Amazon เคยประเมินว่า Downtime 1 นาที = เสียรายได้ ~$220,000 สำหรับ E-commerce ในไทยที่มียอดขาย 10 ล้านบาท/วัน Downtime 1 ชั่วโมง = เสีย ~416,000 บาท
User Trust: ผู้ใช้ที่เจอ Error page หรือ Downtime จะเสีย Trust และอาจไปใช้ Competitor จากสถิติ 88% ของผู้ใช้จะไม่กลับมาหลังเจอ Bad experience
SLA Obligations: หลาย Contract กำหนด SLA 99.9% uptime (Downtime ได้ไม่เกิน 8.7 ชั่วโมง/ปี) หรือ 99.99% (52.6 นาที/ปี) ถ้า Deploy ทุกสัปดาห์ แต่ละครั้งมี Downtime 30 นาที = 26 ชั่วโมง/ปี ซึ่งเกิน SLA 99.9%
Deploy Frequency: Modern software teams Deploy 10-100+ ครั้ง/วัน (CI/CD) ถ้าทุกครั้งมี Downtime แม้แค่ 1 นาที ก็เท่ากับ 100 นาที/วัน = Unacceptable
Deployment Strategies เปรียบเทียบ
| Strategy | Downtime | ความซับซ้อน | Rollback Speed | Resource Cost | Risk |
|---|---|---|---|---|---|
| Recreate (ลบเก่า สร้างใหม่) | มี Downtime | ง่ายมาก | ช้า (Deploy ใหม่) | ต่ำ (1x) | สูง |
| Rolling Update | ไม่มี | ง่าย | ปานกลาง | ต่ำ (1x + buffer) | ปานกลาง |
| Blue-Green | ไม่มี | ปานกลาง | ทันที (สลับ Route) | สูง (2x) | ต่ำ |
| Canary | ไม่มี | สูง | เร็ว (ลด Traffic) | ต่ำ-กลาง | ต่ำมาก |
| Shadow/Dark | ไม่มี | สูงมาก | ไม่ต้อง (ยังไม่ได้ Serve จริง) | สูง (2x) | ต่ำมาก |
Rolling Update — Default ของ Kubernetes
Rolling Update คือการทยอยเปลี่ยน Pods ทีละกลุ่ม จาก Version เก่าไป Version ใหม่ โดยตลอดเวลาจะมี Pods ที่พร้อมให้บริการอยู่เสมอ เป็น Default strategy ของ Kubernetes Deployment:
# Kubernetes Rolling Update
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 6
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # ลดได้ทีละ 1 pod (ต้อง Available อย่างน้อย 5)
maxSurge: 2 # เพิ่มได้ 2 pods เกินจาก replicas (สูงสุด 8 pods)
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: myapp:v2.0 # เปลี่ยน Image tag → Trigger rolling update
readinessProbe: # สำคัญมาก! ต้องมี Readiness probe
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 10
# Deploy: kubectl apply -f deployment.yaml
# ดูสถานะ: kubectl rollout status deployment/web-app
# Rollback: kubectl rollout undo deployment/web-app
ข้อดี: ง่าย ใช้ Resources น้อย (ไม่ต้อง 2x) K8s ทำให้อัตโนมัติ Rollback ง่ายด้วย kubectl rollout undo
ข้อเสีย: ระหว่าง Rolling มี 2 Versions ทำงานพร้อมกัน (Old + New) ถ้า Version ใหม่มี Breaking API change อาจมีปัญหา Rollback ใช้เวลา (ต้อง Rolling กลับ)
Blue-Green Deployment — Instant Switch
Blue-Green คือการมี 2 Environment เหมือนกันทุกประการ: Blue (Production ปัจจุบัน) และ Green (Version ใหม่ที่เตรียมไว้) เมื่อ Green พร้อมแล้ว สลับ Traffic จาก Blue → Green ทันที:
# Blue-Green ด้วย Kubernetes Service
# Step 1: Blue running (current production)
apiVersion: v1
kind: Service
metadata:
name: web-app
spec:
selector:
app: web-app
version: blue # ชี้ไปที่ Blue pods
ports:
- port: 80
targetPort: 8080
---
# Blue Deployment (v1.0)
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-blue
spec:
replicas: 6
selector:
matchLabels:
app: web-app
version: blue
template:
metadata:
labels:
app: web-app
version: blue
spec:
containers:
- name: web-app
image: myapp:v1.0
---
# Step 2: Deploy Green (v2.0) — ยังไม่มี Traffic
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app-green
spec:
replicas: 6
selector:
matchLabels:
app: web-app
version: green
template:
metadata:
labels:
app: web-app
version: green
spec:
containers:
- name: web-app
image: myapp:v2.0
# Step 3: Test Green internally
# curl http://web-app-green:8080/health
# Step 4: Switch traffic (แก้ Service selector)
# kubectl patch service web-app -p '{"spec":{"selector":{"version":"green"}}}'
# Step 5: ถ้ามีปัญหา Rollback ทันที!
# kubectl patch service web-app -p '{"spec":{"selector":{"version":"blue"}}}'
# Step 6: ถ้า Green OK ลบ Blue
# kubectl delete deployment web-app-blue
ข้อดี: Switch ทันที (เปลี่ยน Selector) Rollback ทันที (เปลี่ยนกลับ) ทดสอบ Green ก่อน Switch ได้ ไม่มีช่วงที่ 2 Versions mixed
ข้อเสีย: ใช้ Resources 2 เท่า (ต้องมีทั้ง Blue + Green) Database schema change ต้องระวัง (ทั้ง Blue และ Green ใช้ DB เดียวกัน)
Canary Deployment — Traffic Splitting
Canary คือการ Deploy Version ใหม่แล้วส่ง Traffic เพียงส่วนน้อย (เช่น 5%) ไปทดสอบก่อน ถ้า Metrics ดี (Error rate ต่ำ, Latency ปกติ) จึงค่อยๆ เพิ่ม Traffic จนถึง 100%:
# Canary ด้วย Istio VirtualService
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: web-app
spec:
hosts:
- web-app
http:
- route:
- destination:
host: web-app
subset: stable # v1.0
weight: 95 # 95% traffic
- destination:
host: web-app
subset: canary # v2.0
weight: 5 # 5% traffic
---
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
name: web-app
spec:
host: web-app
subsets:
- name: stable
labels:
version: v1
- name: canary
labels:
version: v2
# Canary progression:
# Day 1: 5% canary → Monitor errors, latency
# Day 2: 25% canary → Still OK? Continue
# Day 3: 50% canary → Check metrics
# Day 4: 100% canary → Full rollout!
# If errors spike at any stage → Set canary weight to 0 (instant rollback)
# Canary ด้วย Nginx Ingress (ไม่ต้อง Service mesh)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-app-canary
annotations:
nginx.ingress.kubernetes.io/canary: "true"
nginx.ingress.kubernetes.io/canary-weight: "10" # 10% traffic
spec:
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app-canary # Service ชี้ไปที่ v2.0
port:
number: 80
ข้อดี: ลดความเสี่ยงมาก (ถ้า Bug เจอแค่ 5% ของ Users) ตัดสินใจจาก Metrics จริง (Data-driven) Rollback เร็ว (ลด Weight เป็น 0)
ข้อเสีย: ซับซ้อนกว่า (ต้องมี Traffic splitting: Istio, Nginx canary, etc.) ต้องมี Monitoring ดี (ต้องรู้ว่า Canary มีปัญหาหรือไม่) ใช้เวลานานกว่า (ต้อง Gradual rollout)
Shadow/Dark Deployment — Traffic Mirroring
Shadow deployment คือการส่ง Copy ของ Production traffic ไปที่ Version ใหม่ แต่ Response จาก Version ใหม่ถูก Discard (ผู้ใช้ได้รับ Response จาก Version เก่าเสมอ):
# Shadow deployment ด้วย Istio Traffic Mirroring
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: web-app
spec:
hosts:
- web-app
http:
- route:
- destination:
host: web-app
subset: stable # User ได้รับ Response จาก v1.0
mirror:
host: web-app
subset: shadow # Copy traffic ไปที่ v2.0
mirrorPercentage:
value: 100.0 # Mirror 100% ของ traffic
# v2.0 ได้รับ Traffic จริง (Production traffic)
# แต่ Response ของ v2.0 ถูก Discard
# ดูจาก v2.0 logs/metrics ว่ามี Error หรือ Performance issues ไหม
# ถ้า OK → ค่อย Switch traffic จริง (Blue-Green หรือ Canary)
ข้อดี: ปลอดภัยที่สุด (ผู้ใช้ไม่ได้รับผลกระทบเลย) ทดสอบด้วย Production traffic จริง (ดีกว่า Staging) เหมาะสำหรับ Major version changes
ข้อเสีย: ใช้ Resources 2 เท่า ซับซ้อนในการ Setup Write operations ต้องระวัง (Shadow อาจเขียนข้อมูลซ้ำ)
Database Migration สำหรับ Zero-Downtime
Database migration เป็นส่วนที่ยากที่สุดของ Zero-downtime deployment เพราะ Schema change อาจ Break application ที่ใช้ Schema เก่า:
Expand-Contract Pattern (แนะนำ)
# ปัญหา: ต้องเปลี่ยนชื่อ Column "name" เป็น "full_name"
# ถ้าทำทีเดียว: ALTER TABLE users RENAME COLUMN name TO full_name
# → App v1 (ใช้ "name") จะ Crash ทันที!
# วิธีที่ถูก: Expand-Contract Pattern (3 ขั้นตอน)
# Step 1: EXPAND — เพิ่ม Column ใหม่ (ไม่ลบ Column เก่า)
ALTER TABLE users ADD COLUMN full_name VARCHAR(255);
# Trigger เพื่อ Sync ข้อมูล
CREATE TRIGGER sync_name
AFTER INSERT OR UPDATE ON users
FOR EACH ROW SET NEW.full_name = COALESCE(NEW.full_name, NEW.name);
# Backfill ข้อมูลเก่า
UPDATE users SET full_name = name WHERE full_name IS NULL;
# ตอนนี้ทั้ง App v1 (ใช้ name) และ App v2 (ใช้ full_name) ทำงานได้!
# Step 2: MIGRATE — Deploy App v2 ที่ใช้ full_name
# ทำ Rolling update / Blue-Green / Canary
# Step 3: CONTRACT — ลบ Column เก่า (หลังจาก App v1 ถูกลบหมดแล้ว)
DROP TRIGGER sync_name;
ALTER TABLE users DROP COLUMN name;
Online DDL Tools
# สำหรับ MySQL — pt-online-schema-change (Percona)
pt-online-schema-change --alter "ADD COLUMN full_name VARCHAR(255)" --execute D=mydb,t=users
# สำหรับ MySQL — gh-ost (GitHub)
gh-ost --alter "ADD COLUMN full_name VARCHAR(255)" --database=mydb --table=users --execute
# วิธีทำงาน:
# 1. สร้าง Shadow table ใหม่ (schema ใหม่)
# 2. Copy ข้อมูลจาก Table เก่าไปใหม่ทีละ batch
# 3. ใช้ Triggers/Binlog sync ข้อมูลที่เปลี่ยนระหว่าง copy
# 4. สลับ Table names (atomic rename)
# ไม่มี Downtime!
Connection Draining — Graceful Shutdown
Connection draining คือการรอให้ Requests ที่กำลัง Process อยู่เสร็จก่อน ก่อนที่จะ Shutdown pod เก่า:
# Kubernetes — Graceful shutdown
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
terminationGracePeriodSeconds: 60 # รอ 60 วินาทีก่อน Force kill
containers:
- name: web-app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # รอ 5 วินาทีให้ LB ถอด Pod ออก
# ลำดับเหตุการณ์เมื่อ Pod ถูก Terminate:
# 1. Pod ถูก Mark เป็น "Terminating" (ไม่รับ Request ใหม่จาก Service)
# 2. preStop hook ทำงาน (sleep 5 → รอให้ LB ถอด Pod ออก)
# 3. SIGTERM ถูกส่งไป App
# 4. App จัดการ Graceful shutdown:
# - หยุดรับ Request ใหม่
# - รอ Request ที่กำลัง Process อยู่ให้เสร็จ
# - ปิด Database connections
# - ปิด File handles
# 5. ถ้า App ไม่ Shutdown ภายใน terminationGracePeriodSeconds → SIGKILL
# Node.js — Graceful shutdown
# process.on('SIGTERM', async () => {
# server.close(() => {
# db.close();
# process.exit(0);
# });
# setTimeout(() => process.exit(1), 30000); // Force exit after 30s
# });
Health Check Alignment
Health checks ที่ถูกต้องเป็นกุญแจสำคัญของ Zero-downtime deployment:
| Probe | จุดประสงค์ | ตั้งค่าอย่างไร |
|---|---|---|
| Readiness Probe | บอกว่า Pod พร้อมรับ Traffic หรือยัง | ตรวจสอบว่า App start เสร็จ, DB connected, Cache warm |
| Liveness Probe | บอกว่า Pod ยัง Healthy อยู่หรือไม่ | ตรวจสอบว่า App ยังตอบสนอง ไม่ Hang |
| Startup Probe | รอจน App start เสร็จก่อน ค่อยเริ่ม Readiness/Liveness | สำหรับ App ที่ Start ช้า (30-60 วินาที) |
# Health check ที่ถูกต้องสำหรับ Zero-downtime
containers:
- name: web-app
startupProbe: # รอ App start (สูงสุด 60 วินาที)
httpGet:
path: /health
port: 8080
failureThreshold: 12 # 12 x 5s = 60 วินาที max startup time
periodSeconds: 5
readinessProbe: # พร้อมรับ Traffic?
httpGet:
path: /ready # ตรวจสอบ DB, Cache, Dependencies
port: 8080
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 3 # Fail 3 ครั้ง → ถอดออกจาก Service
successThreshold: 1 # Pass 1 ครั้ง → เพิ่มกลับเข้า Service
livenessProbe: # App ยัง Alive?
httpGet:
path: /health # Basic health check
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 3 # Fail 3 ครั้ง → Restart pod
Feature Flags — แยก Deploy ออกจาก Release
Feature flags ช่วยให้ Deploy code ใหม่ได้โดยไม่ต้อง "เปิดใช้" ทันที:
# Feature flag concept
# Deploy v2.0 with new feature (ซ่อนไว้หลัง Flag)
if feature_flags.is_enabled("new_checkout_flow", user_id):
return new_checkout_flow(request)
else:
return old_checkout_flow(request)
# ข้อดี:
# 1. Deploy ได้ทุกเมื่อ (Code อยู่แล้ว แต่ Flag ปิด)
# 2. เปิด Feature ให้ User บางกลุ่มก่อน (Beta, Internal)
# 3. ถ้ามีปัญหา ปิด Flag ทันที (ไม่ต้อง Rollback deploy)
# 4. A/B Testing ได้ง่าย
# Tools: LaunchDarkly, Unleash (open-source), Flagsmith, ConfigCat
# Simple implementation: ใช้ Environment variable หรือ Database flag
Rollback Strategies
| Strategy | Rollback Method | Speed |
|---|---|---|
| Rolling Update | kubectl rollout undo (Roll กลับทีละ Pod) | นาที |
| Blue-Green | สลับ Route กลับไป Blue | วินาที |
| Canary | ลด Canary weight เป็น 0 | วินาที |
| Feature Flag | ปิด Flag | วินาที (ไม่ต้อง Deploy) |
Monitoring During Deployment
ระหว่าง Deploy ต้อง Monitor metrics เหล่านี้อย่างใกล้ชิด:
Error Rate: ถ้า Error rate เพิ่มขึ้นมากกว่า 1% → Rollback ทันที
Latency (P50, P95, P99): ถ้า P99 latency เพิ่มขึ้นมากกว่า 2x → Investigate, อาจต้อง Rollback
Throughput (RPS): ถ้า RPS ลดลงผิดปกติ → มีปัญหา
CPU/Memory: ถ้า Resource usage พุ่งสูงผิดปกติ → Memory leak? CPU-intensive bug?
Business Metrics: Conversion rate, Cart abandonment, Payment success rate ถ้าตัวเลข Business ลดลงระหว่าง Deploy → อาจมี Bug ที่ Monitoring ทั่วไปจับไม่ได้
# Automated rollback ด้วย Argo Rollouts
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: web-app
spec:
strategy:
canary:
steps:
- setWeight: 5 # 5% traffic
- pause: {duration: 5m} # รอ 5 นาที
- analysis: # ตรวจ Metrics อัตโนมัติ
templates:
- templateName: error-rate
- setWeight: 25
- pause: {duration: 5m}
- analysis:
templates:
- templateName: error-rate
- setWeight: 50
- pause: {duration: 10m}
- setWeight: 100
---
apiVersion: argoproj.io/v1alpha1
kind: AnalysisTemplate
metadata:
name: error-rate
spec:
metrics:
- name: error-rate
interval: 1m
successCondition: result[0] < 0.01 # Error rate < 1%
provider:
prometheus:
address: http://prometheus:9090
query: |
sum(rate(http_requests_total{status=~"5..",app="web-app"}[5m]))
/
sum(rate(http_requests_total{app="web-app"}[5m]))
# ถ้า Error rate > 1% → Argo Rollouts จะ Rollback อัตโนมัติ!
Real-World Zero-Downtime Checklist
ใช้ Checklist นี้ก่อนทำ Zero-downtime deployment:
Application: App รองรับ Graceful shutdown (handle SIGTERM), Health check endpoints พร้อม (/health, /ready), Backward-compatible API (Version ใหม่ทำงานกับ Version เก่าได้), Feature flags สำหรับ Risky features
Database: Schema changes เป็น Backward-compatible (Expand-Contract), ใช้ Online DDL tools (gh-ost, pt-osc) สำหรับ Large tables, Migration scripts ถูก Test แล้ว, Rollback migration script พร้อม
Infrastructure: Readiness probe ตั้งค่าถูกต้อง, terminationGracePeriodSeconds เพียงพอ, PDB (Pod Disruption Budget) ตั้งค่าแล้ว, Connection draining ทำงาน
Monitoring: Error rate dashboard พร้อม, Latency dashboard พร้อม, Automated rollback rules ตั้งค่าแล้ว (ถ้าใช้ Argo Rollouts), Alert ตั้งค่าสำหรับ Anomaly ระหว่าง Deploy
CI/CD: Pipeline ทำงานอัตโนมัติ, Tests ผ่านก่อน Deploy, Canary/Blue-Green configured, Rollback command/button พร้อมใช้
สรุป
Zero-Downtime Deployment ไม่ใช่แค่เทคนิค แต่เป็น Culture ที่ต้องปลูกฝังในทีม ตั้งแต่ Developer ที่เขียน Code ต้องคำนึงถึง Backward compatibility, DBA ที่ต้องทำ Expand-Contract migration, DevOps ที่ต้องตั้ง Health checks และ Monitoring ให้ถูกต้อง, และ Product team ที่ต้อง Plan releases ด้วย Feature flags
เริ่มต้นจาก Rolling Update (ง่ายที่สุด) → Blue-Green (เมื่อต้องการ Instant rollback) → Canary (เมื่อต้องการ Data-driven deployment) → Shadow (สำหรับ Critical changes) แต่ไม่ว่าจะใช้ Strategy ไหน สิ่งที่ขาดไม่ได้คือ Health checks ที่ถูกต้อง, Graceful shutdown, Backward-compatible database migrations, และ Monitoring ที่ครอบคลุม
