Requests, limits, OOMKilled, HPA, VPA, PDB và volume
Tiếp đi: CPU/RAM requests, limits và autoscaling.
Bài này gom các phần liên quan trực tiếp: requests/limits → scheduler → QoS → OOMKilled → HPA, thêm PDB và volume.
1. Bài toán
Node A: 4 CPU, 8 GB Node B: 4 CPU, 8 GB
Deploy payment-service replicas: 3, order-service replicas: 3, postgres: 1
Traffic thay đổi theo giờ:
02:00 100 req/s
12:00 5,000 req/s (flash sale)
02:00 100 req/s
2. Vấn đề
- K8s không biết mỗi Pod cần bao nhiêu CPU/RAM, scheduler xếp đại, có node bị nhồi quá tải.
- Một Pod rò rỉ memory ăn hết RAM node, kéo các Pod khác chết theo.
- Luôn chạy 10 Pod thì đêm lãng phí; luôn 3 Pod thì trưa quá tải.
- Admin drain node để nâng cấp, cả 3 Pod payment cùng nằm trên node đó bị xóa một lúc.
3. Giải pháp
resources
├── requests → "Pod cần ít nhất bao nhiêu" → scheduler dùng để chọn node, HPA dùng để tính %
└── limits → "Pod được dùng tối đa bao nhiêu" → runtime chặn (cgroups)
Đơn vị: CPU tính bằng millicpu (1000m = 1 core, 250m = 0.25 core). Memory: 512Mi, 1Gi (Mi = 1024², M = 1000²).
CPU và memory vượt limit xử lý khác nhau:
CPU vượt limit → throttling (bị bóp, chạy chậm), KHÔNG bị kill
Memory vượt limit → kernel OOM killer giết container → OOMKilled (exit 137) → restart
QoS class (K8s tự gán, quyết định ai bị đuổi trước khi node thiếu RAM):
| QoS | Điều kiện | Bị evict |
|---|---|---|
| Guaranteed | Mọi container có requests = limits cho cả CPU và memory | Sau cùng |
| Burstable | Có ít nhất một request/limit nhưng không đủ điều kiện Guaranteed | Ở giữa |
| BestEffort | Không khai báo gì | Đầu tiên |
Autoscaling: HPA tăng/giảm số Pod (scale ngang); VPA đề xuất/chỉnh requests (scale dọc); Cluster Autoscaler / Karpenter thêm node khi Pod Pending vì thiếu chỗ.
PodDisruptionBudget: giới hạn số Pod bị xóa cùng lúc khi có disruption tự nguyện (drain node, nâng cấp cluster).
4. Code từng bước
Bước 1: requests/limits cho payment-service + JVM theo container
containers:
- name: payment
image: myregistry/payment-service:2.0
resources:
requests:
cpu: "500m"
memory: "768Mi"
limits:
memory: "768Mi" # memory: request = limit, tránh bị OOM vì node quá tải
# cpu limit: nhiều team bỏ để tránh throttling, tùy chính sách cluster
env:
- name: JAVA_TOOL_OPTIONS
value: "-XX:MaxRAMPercentage=75.0 -XX:+ExitOnOutOfMemoryError"
Các con số chỉ là ví dụ. Production phải đo bằng load test và metrics rồi chỉnh.
Bước 2: HPA (cần metrics-server)
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: payment-service
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: payment-service
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # 70% của CPU REQUEST
behavior:
scaleDown:
stabilizationWindowSeconds: 300 # chờ 5 phút ổn định mới giảm, tránh dao động
Khi có HPA, bỏ replicas khỏi Deployment YAML (hoặc để Helm không set) để mỗi lần apply không ghi đè số HPA đã chọn.
Bước 3: PodDisruptionBudget
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: payment-service
spec:
minAvailable: 2 # khi drain node, luôn còn ít nhất 2 Pod payment
selector:
matchLabels:
app: payment-service
Thêm topologySpreadConstraints hoặc pod anti-affinity để 3 Pod không nằm chung một node/zone.
Bước 4: Volume cho postgres (tóm tắt) và test
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-data
spec:
storageClassName: gp3 # StorageClass do cluster cung cấp
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 10Gi
Container ── volumeMount /var/lib/postgresql/data
└─► Pod volume ──► PVC "tôi cần 10Gi" ──bound──► PV "đây là 10Gi" ──► EBS / Azure Disk
│
StorageClass tự tạo PV (dynamic provisioning)
StatefulSet dùng volumeClaimTemplates để mỗi Pod có PVC riêng (bài 17.3).
kubectl top pods # CPU/RAM đang dùng (cần metrics-server)
kubectl top nodes
kubectl get hpa -w
# NAME TARGETS MINPODS MAXPODS REPLICAS
# payment-service 82%/70% 3 10 5
kubectl describe node <node> | grep -A5 "Allocated resources"
kubectl get pod <pod> -o jsonpath='{.status.qosClass}'
kubectl get pvc,pv,sc
kubectl get pdb
5. Cách hoạt động
Scheduler dùng requests, không dùng usage thực tế
Node A: allocatable 4 CPU, đã request 3.7 CPU → còn 0.3
Node B: allocatable 4 CPU, đã request 1.0 CPU → còn 3.0
Pod mới request 500m:
Node A ❌ (0.5 > 0.3) Node B ✅
Không node nào đủ → Pod Pending
Events: 0/2 nodes are available: 2 Insufficient cpu.
JVM trong container: vì sao OOMKilled
Container memory limit = 768Mi
┌──────────────────────────────┐
│ Heap (MaxRAMPercentage 75%) │ ~576Mi
│ Metaspace │
│ Thread stacks (200 thread) │ ← ngoài heap
│ Code cache, GC, direct buffer│
└──────────────────────────────┘
Sai: limit 768Mi mà -Xmx768m → heap + non-heap > 768Mi → kernel kill → OOMKilled
OutOfMemoryError trong Java ≠ OOMKilled:
OOME = JVM hết heap, app tự ném lỗi, có stack trace
OOMKilled = kernel giết process vì vượt cgroup limit, không có log Java
HPA tính số Pod
desiredReplicas = ceil( currentReplicas × currentUtilization / targetUtilization )
3 Pod, CPU trung bình 140% request, target 70%:
ceil(3 × 140 / 70) = 6 Pod
metrics-server ──(15s)──► HPA controller ──► sửa replicas của Deployment
└─► ReplicaSet tạo thêm Pod
└─► Pending? → Cluster Autoscaler thêm node
HPA không tự tạo Pod. Nó chỉ chỉnh số replicas của Deployment. Pod mới còn phải chờ JVM start và readiness, nên HPA chậm hơn spike vài chục giây đến vài phút. Các bẫy khi scale microservice (connection pool DB nhân theo số Pod, downstream bị dồn tải) đã nói ở bài 4.6.9 và bài 6.8.
6. So sánh
| HPA | VPA | Cluster Autoscaler | |
|---|---|---|---|
| Chỉnh gì | Số Pod | requests/limits của Pod | Số node |
| Dựa vào | CPU, memory, custom/external metric (queue length, req/s qua KEDA) | Lịch sử usage | Pod Pending |
| Hợp với | Service stateless | Tìm requests hợp lý, workload khó scale ngang | Mọi cluster cloud |
| Lưu ý | Cần requests | Chế độ Auto phải tạo lại Pod; không dùng cùng HPA trên cùng metric CPU/memory | Thêm node mất vài phút |
7. Khi nào nên dùng / không nên dùng
- Luôn đặt requests cho mọi container; không có requests thì HPA theo % CPU không chạy và Pod là BestEffort.
- Memory limit nên có; với Java đặt heap bằng
MaxRAMPercentage60–75% thay vì-Xmxcứng. - CPU limit làm Java chậm khi khởi động và GC (throttling); nhiều team chỉ đặt request CPU. Theo chính sách cluster.
- HPA theo CPU hợp với API tính toán; consumer queue nên scale theo độ dài hàng đợi (KEDA).
- Có từ 2 replica trở lên thì thêm PDB. Không đặt
minAvailablebằng số replicas, drain node sẽ bị chặn vĩnh viễn.
Container limit 1Gi thì mình set -Xmx1g cho JVM được không?
Không nên. -Xmx chỉ giới hạn heap. JVM còn dùng memory ngoài heap: metaspace, stack của mỗi thread (mặc định ~1MB, Tomcat 200 thread là ~200MB), code cache, GC, direct buffer của Netty/NIO.
limit 1Gi, -Xmx1g:
heap đầy 1Gi + metaspace 100Mi + threads 200Mi + ... > 1Gi
→ kernel OOM kill → OOMKilled, exit code 137, không có log Java
Cách đúng: để JVM tự tính theo container (Java 10+ nhận cgroup limit):
-XX:MaxRAMPercentage=75.0 # heap = 75% limit → ~768Mi
-XX:+ExitOnOutOfMemoryError # hết heap thì thoát luôn để K8s restart sạch
Phần trăm cụ thể phụ thuộc số thread và thư viện; xem kubectl top pod và metric jvm.memory.used rồi chỉnh. Nếu bị OOMKilled mà heap vẫn thấp, nguyên nhân thường nằm ngoài heap (thread, direct buffer).
HPA target 70% là 70% của cái gì nhỉ? Của limit hay của node?
Không phải cả hai. Với type: Utilization, % được tính trên request:
requests.cpu = 500m, target 70% → mục tiêu ~350m mỗi Pod
Pod dùng trung bình 600m → 120% → HPA scale up
Hệ quả:
- Không đặt request thì HPA báo
<unknown>/70%và không scale. - Request đặt quá thấp (100m) thì utilization lúc nào cũng vài trăm %, HPA luôn đẩy lên max.
- Utilization có thể vượt 100% vì Pod được dùng CPU trên request (tới limit hoặc tới hết node).
Muốn đặt theo giá trị tuyệt đối thì dùng type: AverageValue, ví dụ averageValue: 400m.
Phỏng vấn hỏi về resource và autoscaling thì trả lời sao?
Q1. Requests khác limits thế nào?
🇻🇳 Tiếng Việt:
Requests là lượng tài nguyên scheduler dùng để chọn node và đảm bảo cho container; HPA cũng tính % CPU dựa trên request. Limits là mức tối đa runtime cho phép. Vượt CPU limit thì container bị throttle và chậm lại; vượt memory limit thì bị kernel kill, trạng thái OOMKilled. Requests và limits cũng quyết định QoS class: Guaranteed, Burstable hay BestEffort, ảnh hưởng thứ tự bị evict khi node thiếu RAM.
🇬🇧 English:
Requests are what the scheduler uses to place Pods and what the container is guaranteed; HPA also computes CPU percentage against requests. Limits are the maximum the runtime allows. Going over the CPU limit causes throttling and slowness; going over the memory limit gets the container killed by the kernel as OOMKilled. Requests and limits also set the QoS class, Guaranteed, Burstable or BestEffort, which decides eviction order when a node runs low on memory.
Q2. Pod Java hay bị OOMKilled, bạn điều tra và xử lý thế nào?
🇻🇳 Tiếng Việt:
Em xác nhận bằng
kubectl describe pod, thấy Last State là OOMKilled, exit code 137, rồi so memory usage với limit quakubectl topvà metric JVM. OOMKilled khác OutOfMemoryError: đây là kernel giết vì cả process vượt limit, thường vì heap đặt bằng limit mà quên phần non-heap như metaspace, thread stack, direct buffer. Em dùngMaxRAMPercentagekhoảng 70–75%, kiểm tra số thread, và nếu memory thật sự tăng mãi thì lấy heap dump để tìm leak, rồi mới tăng limit.
🇬🇧 English:
I confirm it with
kubectl describe pod: Last State OOMKilled, exit code 137, then compare usage against the limit withkubectl topand JVM metrics. OOMKilled is not an OutOfMemoryError; the kernel killed the whole process for exceeding its cgroup limit, usually because the heap was sized to the limit and non-heap memory such as metaspace, thread stacks and direct buffers was forgotten. I useMaxRAMPercentagearound 70–75%, check the thread count, and if memory keeps growing I take a heap dump to look for a leak before raising the limit.
Q3. HPA hoạt động thế nào và có giới hạn gì?
🇻🇳 Tiếng Việt:
HPA đọc metric từ metrics-server hoặc custom metrics API theo chu kỳ, tính số replica cần bằng công thức replicas hiện tại nhân tỉ lệ metric hiện tại trên target, rồi chỉnh replicas của Deployment trong khoảng min và max. Giới hạn: cần đặt requests, phản ứng chậm vì Pod Java cần thời gian start, scale down có cửa sổ ổn định, và Pod mới có thể Pending nếu không có Cluster Autoscaler. Nó cũng không giúp nếu nút thắt là database.
🇬🇧 English:
HPA periodically reads metrics from metrics-server or a custom metrics API, computes the desired replicas as current replicas times the ratio of current metric to target, and sets the Deployment's replicas between min and max. Its limits: it needs resource requests, it reacts with a delay because Java Pods take time to start, scale-down has a stabilization window, and new Pods may stay Pending without a Cluster Autoscaler. It also doesn't help when the bottleneck is the database.
Q4. PodDisruptionBudget dùng để làm gì?
🇻🇳 Tiếng Việt:
PDB giới hạn số Pod của một ứng dụng bị xóa cùng lúc trong các disruption tự nguyện như drain node khi nâng cấp cluster. Ví dụ payment có 3 replica với
minAvailable: 2thì drain chỉ được đuổi từng Pod một, chờ Pod mới Ready rồi mới đuổi tiếp. PDB không bảo vệ trước sự cố như node chết đột ngột; với cái đó cần nhiều replica trải qua nhiều node và zone.
🇬🇧 English:
A PDB limits how many Pods of an application can be taken down at once during voluntary disruptions, such as draining nodes for a cluster upgrade. For example, payment with 3 replicas and
minAvailable: 2can only be evicted one Pod at a time, waiting for the replacement to be Ready. A PDB doesn't protect against involuntary failures like a node crashing; for that you need multiple replicas spread across nodes and zones.
Cách nhớ ngắn gọn
- requests → scheduler + HPA; limits → trần runtime.
- CPU vượt → throttle; memory vượt → OOMKilled (137).
- Java:
MaxRAMPercentage, không-Xmx= limit. - HPA chỉnh số Pod theo % request; VPA chỉnh request; Cluster Autoscaler thêm node.
- PDB chống drain đồng loạt; PVC xin ổ đĩa, PV là ổ đĩa, StorageClass tự cấp.
Tự kiểm tra
Pod Pending, Events báo Insufficient memory. Có phải node đang dùng hết RAM thật không?
Không nhất thiết. Scheduler tính theo tổng requests, không theo usage thực tế.
Container có requests = limits cho cả CPU và memory thuộc QoS nào?
Guaranteed.
HPA: 4 Pod, CPU trung bình 105% request, target 70%. Số Pod mới?
ceil(4 × 105 / 70) = 6.
Exit code 137 nghĩa là gì?
Process bị SIGKILL (128 + 9), thường do OOMKilled hoặc hết grace period.
PVC ở trạng thái Pending lâu. Kiểm tra gì?
StorageClass có tồn tại/đúng tên không, provisioner có chạy không, access mode có được hỗ trợ không (kubectl describe pvc).