Kubernetes autoscaling ทำงานสามชั้น และแต่ละชั้นตอบคำถามคนละข้อ Horizontal Pod Autoscaler (HPA) ตัดสินว่าควรรันกี่ pod, Vertical Pod Autoscaler (VPA) ตัดสินว่าแต่ละ pod ควร request CPU และ memory เท่าไร ส่วน Cluster Autoscaler ตัดสินว่าคลัสเตอร์ต้องมีกี่ node ถึงจะวาง pod เหล่านั้นได้ครบ ทั้งสามเป็น controller แยกกันที่ไม่ได้ประสานงานกันโดยตรง และปัญหา autoscaling ส่วนใหญ่เกิดจากการไม่รู้ว่าชั้นหนึ่งจบตรงไหน อีกชั้นเริ่มตรงไหน
บทความนี้ไล่ตั้งแต่สูตรของ HPA พร้อมตัวอย่างคำนวณ, metric ที่ HPA ใช้ได้, VPA ทำอะไรและเมื่อไรควรใช้แค่โหมดแนะนำค่า, Cluster Autoscaler ตอบสนองต่อ pod ที่ค้าง Pending ยังไง และกับดักที่เจอบ่อยใน production
สามชั้นของ Kubernetes autoscaling
| ชั้น | ปรับอะไร | ถูกกระตุ้นด้วย | มากับ Kubernetes ไหม |
|---|---|---|---|
| HPA | จำนวน replica ของ Deployment หรือ StatefulSet | metric เทียบกับเป้า (CPU, memory, custom, external) | มี (autoscaling/v2) ต้องมี metrics-server |
| VPA | requests (และ limits) ของ CPU และ memory | การใช้งานจริงที่สังเกตตามเวลา | ไม่มี ต้องติดตั้งแยก |
| Cluster Autoscaler | จำนวน node | pod ค้าง Pending หรือ node ที่ใช้งานน้อย | ไม่มี ติดตั้งตาม cloud หรือ platform |
ทั้งสามชั้นพึ่ง requests ของ resource ทั้งหมด HPA วัด CPU utilization เป็นเปอร์เซ็นต์ของ request, VPA มีหน้าที่ตั้ง request โดยตรง และ Cluster Autoscaler เพิ่ม node จาก request ไม่ใช่การใช้งานจริง ถ้า request ผิด ทุกชั้นจะตัดสินใจผิดตามไปด้วย วิธีตั้งค่าให้เหมาะอ่านได้ใน Kubernetes requests, limits และ QoS
HPA ทำงานยังไง: control loop และสูตรคำนวณ
HPA เป็น control loop เหมือน controller ตัวอื่นใน Kubernetes ค่า default คือวนทุก 15 วินาที แต่ละรอบอ่าน metric ปัจจุบัน คำนวณจำนวน replica ที่ควรมี แล้วอัปเดต field replicas ของเป้าหมาย
สูตรหลักคือ:
desiredReplicas = ceil( currentReplicas × currentMetricValue / targetMetricValue )ตัวอย่างการคำนวณ
มี 3 pod แต่ละตัว request CPU 500m และตอนนี้ใช้อยู่ 450m คือ utilization 90% โดยตั้งเป้าไว้ที่ 50%
ให้คิดเป็น "งานรวม" สาม pod ที่ทำงาน 90% เท่ากับงาน 3 × 90 = 270 หน่วย ถ้าอยากให้ทุก pod ลดลงเหลือ 50% ต้องใช้ 270 ÷ 50 = 5.4 pod แต่รัน 0.4 pod ไม่ได้ HPA จึงปัดขึ้น:
desiredReplicas = ceil(3 × 90 / 50) = ceil(5.4) = 6เมื่อมี 6 pod ช่วยกันรับโหลดเท่าเดิม utilization เฉลี่ยจะลดเหลือ 270 ÷ 6 = 45% ต่ำกว่าเป้าเล็กน้อย
มีสองรายละเอียดที่ทำให้พฤติกรรมจริงนุ่มนวลกว่าสูตรดิบ:
- Tolerance ถ้าอัตราส่วน
current / targetห่างจาก 1.0 ไม่เกิน 10% (อยู่ระหว่าง 0.9 ถึง 1.1) HPA จะไม่ทำอะไร เช่น 53% เทียบเป้า 50% ได้อัตราส่วน 1.06 จึงไม่เปลี่ยน ช่วยกันไม่ให้ปรับขึ้นลงถี่ ๆ - หลาย metric ถ้าตั้งทั้ง CPU และ requests-per-second HPA จะคำนวณจำนวนที่ต้องการจากแต่ละตัว แล้วเลือกค่าที่ สูงที่สุด
เพราะ utilization คิดเทียบกับ request ค่าจึงเกิน 100% ได้ pod ที่ request 500m แต่ limit 1 CPU อาจรายงาน 200% ซึ่งเป็นเรื่องปกติ และเป็นอีกเหตุผลที่ request ควรสะท้อนการใช้งานจริง
HPA manifest สำหรับ production
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: api
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 30
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 20
periodSeconds: 60block behavior คุมความเร็วในการขยับ แนวที่นิยมคือ scale up เร็ว scale down ช้า ในตัวอย่างนี้ HPA เพิ่ม pod ได้เท่าตัวทุก 30 วินาที แต่ลดได้ไม่เกิน 20% ต่อนาที และจะลดก็ต่อเมื่อค่าที่แนะนำต่ำต่อเนื่องครบ 5 นาที ค่า default ของ scale-down stabilization window ก็คือ 300 วินาทีอยู่แล้ว แต่เขียนไว้ชัด ๆ จะได้เห็นเจตนา
ดูว่า HPA เห็นอะไรอยู่:
kubectl get hpa api
kubectl describe hpa api # shows current metrics, conditions and recent scaling eventsถ้าช่อง TARGETS ขึ้น <unknown> แปลว่า metrics-server ไม่ทำงาน หรือ pod ไม่ได้ตั้ง CPU request
เลือก metric ให้ HPA: resource, custom และ external
CPU เป็นค่า default เพราะมีให้ใช้เสมอ แต่ไม่ได้เป็นสัญญาณที่ถูกต้องทุกครั้ง
| ชนิด metric | ตัวอย่าง | แหล่งข้อมูล |
|---|---|---|
Resource | CPU หรือ memory utilization ทั้ง pod | metrics-server |
ContainerResource | CPU เฉพาะ container ของแอป ไม่นับ sidecar | metrics-server |
Pods | requests per second ต่อ pod | Custom metrics API (เช่น Prometheus Adapter) |
Object | requests per second ที่ Ingress | Custom metrics API |
External | ความยาวคิวใน RabbitMQ, SQS หรือ consumer lag ของ Kafka | External metrics API (เช่น KEDA) |
หลักคิดคร่าว ๆ:
- web service ที่กิน CPU: ใช้ CPU utilization ได้ดี
- service ที่รอ I/O เป็นหลัก: CPU ต่ำตลอดแต่ latency พุ่ง ควร scale ตาม requests per second หรือจำนวน request ที่ค้างอยู่
- worker ที่ดึงงานจากคิว: scale ตาม backlog ส่วนใหญ่ใช้ KEDA ซึ่ง scale Deployment ลงเหลือศูนย์ได้ตอนคิวว่าง
- memory: เป็นสัญญาณที่อ่อนสำหรับ runtime ส่วนใหญ่ JVM, Go และ Node.js มักไม่คืน memory หลังโหลดลด HPA จึง scale ขึ้นแล้วไม่ลงอีก
ต้องเก็บ metric เหล่านี้ได้ก่อนถึงจะ scale ตามได้ ส่วนนี้อ่านต่อได้ที่ observability สำหรับ microservices
Vertical Pod Autoscaler: ตั้ง request ให้พอดี
คนส่วนใหญ่ตั้ง request ด้วยการเดา ตั้งสูงไป node ก็ดูเต็มทั้งที่ว่าง เปลืองเงิน ตั้งต่ำไป pod ก็โดน CPU throttle หรือโดน OOMKill VPA คอยดูการใช้งานจริงตามเวลา แล้วแนะนำหรือปรับค่าที่เหมาะกว่าให้
VPA มีสามส่วน:
- Recommender: วิเคราะห์ประวัติการใช้งานแล้วคำนวณค่าเป้าหมาย
- Updater: evict pod ที่ request ห่างจากค่าแนะนำมาก ให้เกิดใหม่พร้อมค่าใหม่
- Admission controller: เขียนทับ request ของ pod ใหม่ตอนถูกสร้าง
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: api
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: api
updatePolicy:
updateMode: "Off"
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: "2"
memory: 2Giค่า updateMode:
Offแค่ให้คำแนะนำ อ่านได้จากkubectl describe vpa apiในส่วนRecommendationแล้วเราเอาไปแก้ manifest เองInitialใช้ค่าแนะนำเฉพาะตอนสร้าง podRecreate(และAuto) จะ evict pod ที่รันอยู่เพื่อใส่ค่าใหม่
VPA เวอร์ชันใหม่ ๆ มีโหมด InPlaceOrRecreate เพิ่มมาด้วย ซึ่งใช้ in-place pod resize ปรับ resource โดยไม่ต้อง restart pod ถ้าคลัสเตอร์รองรับ
เริ่มจากโหมด Off ปลอดภัยที่สุด ได้ตัวเลขที่อิงข้อมูลจริงโดยไม่มี pod โดน evict แบบไม่ทันตั้งตัว
อย่าให้ HPA และ VPA ปรับตาม CPU หรือ memory ของ workload เดียวกันพร้อมกัน เมื่อโหลดขึ้น HPA เพิ่ม pod การใช้งานต่อ pod ลดลง VPA ก็ลด request ลง utilization ที่คิดเป็นเปอร์เซ็นต์ของ request ที่เล็กลงก็กลับขึ้นไปอีก สอง controller ก็ตีกันไปมา ถ้าต้องใช้ทั้งคู่ ให้ HPA ใช้ custom metric อย่าง requests per second แล้วปล่อย CPU และ memory ให้ VPA หรือใช้ VPA แค่โหมด Off
Cluster Autoscaler: เพิ่มและลด node
HPA สร้าง pod ได้ แต่ถ้าไม่มี node ไหนมีที่พอกับ request ของ pod เหล่านั้น pod ก็ค้างอยู่ที่ Pending Cluster Autoscaler คอยดูสถานการณ์นี้โดยเฉพาะ:
- Scale up: เมื่อมี pod ที่ schedule ไม่ได้เพราะทรัพยากรไม่พอ Cluster Autoscaler จะจำลองดูว่าถ้าเพิ่ม node จาก node group ที่มีอยู่ pod จะลงได้ไหม ถ้าได้ก็ขอ node ใหม่จาก cloud provider
- Scale down: เมื่อ resource ที่ถูก request บน node ต่ำกว่าเกณฑ์ (default 50%) ต่อเนื่องนานพอ (default 10 นาที) และ pod ทุกตัวบน node ย้ายไปที่อื่นได้ node นั้นจะถูก drain แล้วลบทิ้ง
สิ่งที่ขวางการ scale down คือ pod ที่ย้ายอย่างปลอดภัยไม่ได้ เช่น PodDisruptionBudget ที่ไม่ยอมให้ disrupt เลย, pod เปล่าที่ไม่มี controller ดูแล, pod ที่มี annotation cluster-autoscaler.kubernetes.io/safe-to-evict: "false" และ pod บางตัวใน kube-system
บน AWS และ Azure มี Karpenter เป็นทางเลือกยอดนิยม แทนที่จะ scale node group ที่กำหนดไว้ล่วงหน้า มันเลือก instance type ที่พอดีกับ pod ที่ค้างอยู่โดยตรง แต่ตัวกระตุ้นยังเหมือนเดิม คือ pod ที่ Pending และ request ของมัน
สามตัวนี้ทำงานร่วมกันยังไง
เมื่อ traffic พุ่ง ลำดับเหตุการณ์จะเป็นแบบนี้:
- โหลดเพิ่ม CPU utilization ขึ้นเกินเป้า
- HPA เพิ่ม
replicasภายใน 15 ถึง 60 วินาที - pod ใหม่ที่ลง node เดิมได้จะเริ่มทันที
- pod ที่ลงไม่ได้จะค้าง
Pendingแล้ว Cluster Autoscaler ขอ node เพิ่ม - node ใหม่ boot เข้าร่วมคลัสเตอร์ และ pull image ซึ่งใช้เวลาเป็นนาที
- pod ผ่าน readiness probe แล้วเริ่มรับ traffic
การ scale pod ใช้เวลาเป็นวินาที ส่วนการ scale node ใช้เวลาเป็นนาที ถ้า traffic พุ่งเร็วกว่าเวลาที่ใช้สร้าง node ต้องเผื่อที่ว่างไว้ ด้วยการตั้ง minReplicas สูงขึ้น หรือทำ overprovisioning คือรัน placeholder pod ที่ priority ต่ำไว้จองที่ เมื่อ pod จริงมา มันจะ preempt placeholder ทันที แล้ว placeholder ที่ถูกไล่ออกก็ไปค้าง Pending ทำให้ Cluster Autoscaler เพิ่ม node ล่วงหน้าก่อนที่จะขาดจริง
กับดักที่พบบ่อยใน Kubernetes autoscaling
- ไม่ตั้ง CPU request HPA คำนวณ utilization ไม่ได้ และ Cluster Autoscaler วางแผน capacity ไม่ได้
- ตั้ง
replicasไว้ใน Deployment manifest ทุกครั้งที่kubectl applyหรือ GitOps sync จำนวนจะถูกรีเซ็ตและตีกับ HPA เมื่อ HPA ดูแล workload แล้วให้ลบspec.replicasออก - HPA และ VPA ใช้ metric เดียวกัน ตามที่อธิบายไปข้างบน
- แอป start ช้า ถ้า pod ใช้ 90 วินาทีกว่าจะพร้อม การรอ scale ตอน CPU 90% ถือว่าช้าไป ให้ลดเป้าลงหรือเพิ่ม
minReplicas - เพดานที่มองไม่เห็น
maxReplicas, จำนวน node สูงสุดของ Cluster Autoscaler และ quota ของ cloud ล้วนจำกัดการ scale แบบเงียบ ๆ ควรมี alert เมื่อชนเพดาน - scale ผิดชั้น เพิ่ม pod ของ API ไม่ช่วยอะไรถ้าคอขวดอยู่ที่ database และอาจแย่ลงเพราะเปิด connection เพิ่ม
- PDB เข้มเกินไป ตั้ง
minAvailableเท่ากับจำนวน replica ทำให้ drain node ไม่ได้เลย และไม่มีวัน scale down
คำถามที่พบบ่อย
HPA กับ VPA ต่างกันยังไง
HPA เปลี่ยนจำนวน pod ส่วน VPA เปลี่ยน CPU และ memory ที่แต่ละ pod request HPA ใช้รับโหลดที่กระจายไปหลาย replica ได้ ส่วน VPA ใช้แก้ pod ที่ตั้งขนาดไว้ผิด
ทำไม HPA ขึ้น <unknown> ที่ช่อง targets
ส่วนใหญ่เป็นเพราะไม่ได้ติดตั้ง metrics-server หรือมันไม่ทำงาน หรือ pod เป้าหมายไม่ได้ตั้ง CPU request ลองรัน kubectl top pods และดู block resources ของ container
HPA scale ลงเหลือศูนย์ได้ไหม
การตั้งค่ามาตรฐานทำไม่ได้ minReplicas ต้องเป็นอย่างน้อย 1 เว้นแต่จะเปิด feature gate ที่ยังเป็น alpha ถ้าต้องการ scale-to-zero สำหรับงานแบบ event-driven ให้ใช้ KEDA
Cluster Autoscaler ใช้เวลาเพิ่ม node นานแค่ไหน
การตัดสินใจเร็ว ปกติไม่ถึงนาที เวลาส่วนใหญ่หมดไปกับการที่ cloud provider boot VM, node เข้าร่วมคลัสเตอร์ และการ pull image รวมแล้วควรเผื่อไว้หลายนาที
ควรใช้ CPU หรือ memory กับ HPA
service ส่วนใหญ่ใช้ CPU หรือ request rate memory แทบไม่ลดลงเมื่อโหลดลด HPA ที่อิง memory จึงมักขึ้นแล้วค้างอยู่อย่างนั้น
เช็กลิสต์ autoscaling
- ทุก container มี CPU และ memory request ที่สมจริง ใช้ VPA โหมด
Offช่วยหาค่า - HPA ของงานที่ผู้ใช้เข้าถึงตั้ง
minReplicasอย่างน้อย 2 หรือ 3 และ scale down ช้ากว่า scale up - metric ตรงกับคอขวดจริง ไม่ว่าจะเป็น CPU, request rate หรือความยาวคิว
- ลบ
spec.replicasออกจาก manifest ที่ HPA ดูแล - ตั้ง Cluster Autoscaler หรือ Karpenter พร้อม alert เมื่อชนจำนวน node สูงสุดหรือ quota
- PDB ยอมให้ disrupt ได้อย่างน้อยหนึ่งตัว
autoscaling ทำงานได้ดีเมื่อทั้งสามชั้นได้ request ที่ถูกต้องและใช้สัญญาณที่ใช่ ถ้าอยากฝึกปรับ HPA และ Cluster Autoscaler บนคลัสเตอร์จริง ลองดู คอร์ส DevOps ของ Vectorkub
