Sổ tay kỹ thuật

Kubernetes – resource requests/limits, QoS và probe

concept#kubernetes· cập nhật 2026-11-06

Hiểu requests/limits, QoS class và liveness/readiness/startup probe để Pod được schedule đúng, không bị OOMKill/throttle vô lý và không restart oan.

Sửa trang này
Mục lục

Hai nhóm field trong container spec quyết định Pod sống ổn định hay không: resources (scheduler đặt Pod ở đâu, kernel giới hạn bao nhiêu) và probes (kubelet khi nào gửi traffic, khi nào restart container).

Requests và limits

requests limits
Ai dùng Scheduler: chỉ đặt Pod lên node còn đủ allocatable trừ tổng requests Kernel (cgroup) trên node lúc chạy
CPU Trọng số chia CPU khi tranh chấp Vượt thì bị throttle (chậm đi, không chết)
Memory Cơ sở để xếp hạng khi node thiếu RAM Vượt thì container bị OOMKilled (exit code 137)
Đơn vị CPU: 500m = 0.5 core; Memory: 128Mi, 1Gi (hệ 2), 128M là hệ 10
Thiếu field Chỉ có limits thì requests mặc định bằng limits Không có limit thì dùng tới hết node (hoặc theo LimitRange của namespace)

Thực tế hay dùng: đặt requests CPU và memory sát mức dùng thường ngày (xem kubectl top), đặt limits.memory, cân nhắc bỏ limits.cpu để tránh throttle khi app có burst ngắn (nhiều team chọn cách này; cluster nhiều tenant thì vẫn giữ limit CPU).

QoS class

Kubernetes tự gán QoS class cho Pod dựa trên resources của mọi container trong Pod:

QoS class Điều kiện Khi node thiếu memory
Guaranteed Mọi container có cả CPU và memory, requests == limits Bị đuổi (evict) sau cùng
Burstable Không đạt Guaranteed nhưng ít nhất một container có request hoặc limit Pod dùng vượt requests bị đuổi trước
BestEffort Không container nào có requests/limits Bị đuổi đầu tiên
kubectl get pod <pod> -o jsonpath='{.status.qosClass}'
kubectl get pods -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass
kubectl describe node <node> | grep -A10 "Allocated resources"   # tổng requests/limits trên node

Có hai cơ chế khác nhau: OOMKilled do container vượt limits.memory của chính nó (restart theo restartPolicy), còn Evicted do kubelet giải phóng tài nguyên khi node bị memory/disk pressure (Pod bị xoá, controller tạo Pod mới).

Probe

flowchart LR S["Container start"] --> SP{"startupProbe đạt?"} SP -->|"Chưa, quá failureThreshold"| R["Restart container"] SP -->|Đạt| L["liveness + readiness chạy định kỳ"] L --> RD{"readiness fail?"} RD -->|Có| EP["Bỏ khỏi EndpointSlice, không nhận traffic"] L --> LV{"liveness fail liên tiếp?"} LV -->|Có| R
Probe Fail thì sao Dùng để
startupProbe Quá failureThreshold thì restart; trong lúc chạy, liveness/readiness bị tạm hoãn App khởi động chậm (JVM, migrate, nạp model)
readinessProbe Pod 0/1 Ready, bị rút khỏi Service; không restart App tạm chưa phục vụ được (warm-up, mất kết nối DB)
livenessProbe Restart container App treo (deadlock), không tự hồi phục

Kiểu probe: httpGet (2xx, 3xx là đạt), tcpSocket, exec (exit 0 là đạt), grpc (gRPC health protocol).

Field Mặc định Ghi chú
initialDelaySeconds 0 Nên dùng startupProbe thay vì delay lớn
periodSeconds 10
timeoutSeconds 1 Endpoint chậm hơn 1 giây là fail
failureThreshold 3 Thời gian chịu đựng ≈ periodSeconds × failureThreshold
successThreshold 1 Liveness và startup bắt buộc là 1

Ví dụ

deploy-api.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  selector:
    matchLabels: { app: api }
  template:
    metadata:
      labels: { app: api }
    spec:
      containers:
        - name: api
          image: registry.example.com/api:1.4.2
          ports: [{ name: http, containerPort: 8000 }]
          resources:
            requests: { cpu: 200m, memory: 256Mi }
            limits: { memory: 512Mi }
          startupProbe:
            httpGet: { path: /healthz, port: http }
            periodSeconds: 5
            failureThreshold: 30        # cho tối đa 150 giây để khởi động
          readinessProbe:
            httpGet: { path: /ready, port: http }
            periodSeconds: 5
            timeoutSeconds: 2
          livenessProbe:
            httpGet: { path: /healthz, port: http }
            periodSeconds: 10
            timeoutSeconds: 2
            failureThreshold: 3

Áp mặc định cho namespace để Pod không thành BestEffort khi dev quên khai báo:

limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: defaults
  namespace: app
spec:
  limits:
    - type: Container
      defaultRequest: { cpu: 100m, memory: 128Mi }
      default: { memory: 256Mi }

Sai lầm hay gặp

Sai lầm Hậu quả
Liveness gọi tới DB/dependency bên ngoài DB chậm là toàn bộ Pod restart dây chuyền. Liveness chỉ nên kiểm tra chính process
Liveness và readiness dùng chung endpoint, cùng ngưỡng Pod bị restart thay vì chỉ rút khỏi Service
timeoutSeconds: 1 mặc định với endpoint nặng Fail ngẫu nhiên khi CPU bị throttle
limits.memory sát mức dùng của JVM/Node OOMKilled khi GC/burst. Đặt heap theo container (-XX:MaxRAMPercentage)
Không requests Scheduler xếp quá nhiều Pod lên một node, rồi bị evict hàng loạt

Khi nào dùng

  • Mọi workload production: luôn có requests CPU/memory, limits.memory, readinessProbe.
  • livenessProbe: chỉ thêm khi app thật sự có thể treo mà không tự thoát; app crash thì kubelet đã tự restart.
  • startupProbe: khi thời gian khởi động dao động lớn.
  • Guaranteed: workload nhạy cảm (database, ingress controller) cần ít bị evict nhất.
  • Pod không lên hoặc restart liên tục: tra Kubernetes – lệnh troubleshooting.