Requests, limits, OOMKilled, HPA, VPA, PDB và volume

4 câu hỏi phỏng vấn~30 phútTừ khóa: requests, limits, millicpu, QoS, Guaranteed, Burstable, BestEffort, CPU throttling, OOMKilled, MaxRAMPercentage, HPA, metrics-server, VPA, PodDisruptionBudget, PV, PVC, StorageClass

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 đề

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ệnBị evict
GuaranteedMọi container có requests = limits cho cả CPU và memorySau cùng
BurstableCó ít nhất một request/limit nhưng không đủ điều kiện GuaranteedỞ giữa
BestEffortKhô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

HPAVPACluster Autoscaler
Chỉnh gìSố Podrequests/limits của PodSố node
Dựa vàoCPU, memory, custom/external metric (queue length, req/s qua KEDA)Lịch sử usagePod Pending
Hợp vớiService statelessTìm requests hợp lý, workload khó scale ngangMọi cluster cloud
Lưu ýCần requestsChế độ Auto phải tạo lại Pod; không dùng cùng HPA trên cùng metric CPU/memoryThêm node mất vài phút

7. Khi nào nên dùng / không nên dùng

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ả:

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 qua kubectl top và 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ùng MaxRAMPercentage khoả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 with kubectl top and 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 use MaxRAMPercentage around 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: 2 thì 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: 2 can 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


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).