ResourceQuota & LimitRange
Tại sao kiểm soát tài nguyên là vấn đề sống còn của multi-tenancy
Trong cluster dùng chung, tài nguyên (CPU, memory, ephemeral storage, số lượng object) là hữu hạn và được chia sẻ. Không có kiểm soát, một tenant có thể — vô tình hoặc cố ý — chiếm dụng toàn bộ. Một Deployment cấu hình sai scale lên 10.000 Pod, một batch job không giới hạn memory gây OOM cho cả node, một bug tạo vô số ConfigMap làm phình etcd. Đây là vấn đề "noisy neighbor": hành vi của một tenant làm suy giảm dịch vụ của mọi tenant khác.
Theo tài liệu GKE, ResourceQuota "đảm bảo không tenant nào dùng quá phần được phân bổ". Nhưng ResourceQuota một mình không đủ — nó cần LimitRange đi kèm để hoạt động đúng. Hiểu sự phối hợp giữa hai cơ chế này, và cách chúng tương tác với scheduler và QoS class, là khác biệt giữa fair allocation thực sự và một cluster thường xuyên bị một tenant làm nghẹt.
Internal model: ResourceQuota vs LimitRange
Hai cơ chế giải quyết hai vấn đề khác nhau nhưng phụ thuộc lẫn nhau:
ResourceQuota — giới hạn tổng tài nguyên toàn namespace. "Namespace tenant-a được dùng tối đa 100 CPU, 200Gi memory, 50 PVC, 200 Pod." Đây là trần tổng cho cả tenant.
LimitRange — đặt giá trị mặc định và biên cho từng Pod/Container trong namespace. "Mỗi container mặc định request 100m CPU / 128Mi nếu không khai báo; không container nào được request quá 2 CPU / 4Gi." Đây là ràng buộc per-object.
Mối quan hệ then chốt khiến cả hai phải đi cùng nhau:
Khi một namespace có ResourceQuota cho CPU/memory (request hoặc limit), thì mọi Pod trong namespace đó bắt buộc phải khai báo request/limit tương ứng — nếu không, Pod bị từ chối. LimitRange cung cấp giá trị mặc định để các Pod không khai báo vẫn được chấp nhận.
Nếu bạn đặt ResourceQuota mà không có LimitRange, mọi Pod không khai báo resources sẽ bị reject — phá vỡ deployment của tenant. Đây là lý do hai cơ chế gần như luôn được triển khai cùng nhau.
ResourceQuota: các chiều giới hạn
ResourceQuota kiểm soát nhiều loại tài nguyên:
apiVersion: v1
kind: ResourceQuota
metadata:
name: tenant-a-quota
namespace: tenant-a
spec:
hard:
# Compute — tổng requests và limits
requests.cpu: "100"
requests.memory: 200Gi
limits.cpu: "200"
limits.memory: 400Gi
requests.ephemeral-storage: 50Gi
# Storage
persistentvolumeclaims: "50"
requests.storage: 1Ti
# Object counts — chống phình etcd
pods: "200"
services: "30"
services.loadbalancers: "2" # chống tạo LB tốn tiền
configmaps: "100"
secrets: "100"
count/jobs.batch: "50"Các chiều quan trọng cho multi-tenancy mà nhiều người bỏ sót:
services.loadbalancers: Mỗi LoadBalancer Service tạo một LB GCP tốn tiền và chiếm IP. Không giới hạn, một tenant có thể tạo hàng chục LB. Giới hạn chặt (thường 0-2).Object counts (
configmaps,secrets,count/...): etcd có giới hạn kích thước (~mặc định vài GB). Một tenant tạo hàng triệu object làm phình etcd, ảnh hưởng toàn cluster (control plane chung). Giới hạn object count bảo vệ control plane.requestsvslimits: Quota trênrequests.*kiểm soát tài nguyên được đảm bảo (ảnh hưởng scheduling/capacity planning). Quota trênlimits.*kiểm soát trần burst. Thường đặt cả hai.
Quota theo PriorityClass (scope selectors)
ResourceQuota có thể scope theo PriorityClass — quan trọng để ngăn tenant lạm dụng priority cao:
spec:
hard:
pods: "10"
scopeSelector:
matchExpressions:
- operator: In
scopeName: PriorityClass
values: ["high-priority"]Điều này giới hạn tenant chỉ được 10 Pod high-priority, ngăn họ tạo hàng loạt Pod priority cao để preempt tenant khác.
LimitRange: default và biên per-object
apiVersion: v1
kind: LimitRange
metadata:
name: tenant-a-limits
namespace: tenant-a
spec:
limits:
- type: Container
default: # limit mặc định nếu container không khai báo
cpu: 500m
memory: 512Mi
defaultRequest: # request mặc định nếu không khai báo
cpu: 100m
memory: 128Mi
max: # không container nào vượt
cpu: "4"
memory: 8Gi
min: # không container nào dưới
cpu: 50m
memory: 64Mi
maxLimitRequestRatio: # chống overcommit cực đoan
cpu: "4" # limit không quá 4× request
- type: PersistentVolumeClaim
max:
storage: 100Gi
min:
storage: 1GiVai trò của từng trường trong multi-tenancy:
defaultRequest/default: Cho phép Pod không khai báo resources vẫn được chấp nhận khi có ResourceQuota. Đây là "chất bôi trơn" giữa quota và developer experience.max: Ngăn một tenant tạo một Pod khổng lồ chiếm cả node (ví dụ request 60 CPU). Trần per-Pod bảo vệ bin-packing.min: Ngăn Pod request quá ít (ví dụ 1m CPU) rồi chiếm tài nguyên thực tế vượt xa — gây noisy neighbor ở mức node.maxLimitRequestRatio: Giới hạn tỷ lệ limit/request. Nếu request 100m nhưng limit 8 CPU, Pod được schedule như "nhẹ" nhưng burst rất mạnh — gây contention. Giới hạn ratio buộc khai báo trung thực hơn.
Tương tác với QoS class và scheduler
Đây là phần mà nhiều đội bỏ qua, dẫn đến hành vi production bất ngờ. Kubernetes gán QoS class cho Pod dựa trên request/limit:
- Guaranteed: request = limit cho mọi container. Ưu tiên cao nhất, bị evict cuối cùng.
- Burstable: có request nhưng request < limit. Evict ở giữa.
- BestEffort: không request/limit. Evict đầu tiên khi node thiếu tài nguyên.
Hệ quả cho multi-tenancy: LimitRange defaultRequest khiến Pod trở thành Burstable thay vì BestEffort. Điều này tốt — BestEffort Pod là nạn nhân đầu tiên khi node áp lực, nhưng cũng dễ trở thành noisy neighbor vì không có request để scheduler tính toán. Bằng cách buộc mọi Pod có request (qua LimitRange), scheduler bin-pack chính xác hơn và một tenant không thể "ẩn" tải bằng cách bỏ request.
Lưu ý quan trọng với GKE Autopilot: Autopilot tự enforce requests và có quy tắc riêng (xem Chương 16). LimitRange vẫn áp dụng nhưng tương tác với cơ chế resource enforcement của Autopilot — kiểm tra hành vi thực tế.
Production architecture patterns
Pattern 1: Quota tier theo hạng tenant
Định nghĩa các tier quota (small/medium/large) và gán theo hợp đồng/tầm quan trọng của tenant:
tier-small: 10 CPU, 20Gi, 50 Pod, 0 LB
tier-medium: 50 CPU, 100Gi, 200 Pod, 2 LB
tier-large: 200 CPU, 400Gi, 500 Pod, 5 LBTenant trả tiền nhiều hơn nhận tier cao hơn. Quota trở thành công cụ enforcement cho mô hình kinh doanh, không chỉ kỹ thuật.
Pattern 2: HierarchicalResourceQuota (HNC)
Khi một tenant có nhiều namespace (qua HNC, file 02), ResourceQuota thường chỉ áp một namespace — không kiểm soát được tổng của cả tenant. HierarchicalResourceQuota (HRQ) đặt quota ở namespace cha bao trùm tổng tiêu thụ của mọi namespace con. Tenant tự do phân bổ giữa các namespace con miễn tổng không vượt trần.
Pattern 3: Overcommit có kiểm soát ở platform level
Platform team có thể cố ý đặt tổng quota của các tenant vượt capacity vật lý (overcommit), đặt cược rằng không phải mọi tenant dùng hết cùng lúc. Đây là tối ưu chi phí — nhưng rủi ro: nếu nhiều tenant burst đồng thời, node thiếu tài nguyên và xảy ra eviction. Chỉ overcommit trên limits, giữ requests trong capacity thực để đảm bảo schedulability.
Real-world scenario
Internal platform 40 đội, chống sự cố lan rộng: Sau một sự cố nơi một batch job của một đội tạo 5.000 Pod và làm cạn capacity cluster (khiến Pod của các đội khác không schedule được trong 2 giờ), platform team áp ResourceQuota cho mọi namespace: trần Pod count, trần CPU/memory, và quan trọng nhất count/jobs.batch + scope quota cho high-priority. Kết quả: một đội misconfigure giờ chỉ ảnh hưởng namespace của chính họ (job bị từ chối khi vượt quota), không lan ra cluster. LimitRange đảm bảo mọi Pod có request để scheduler tính capacity chính xác.
Common mistakes / anti-patterns
Anti-pattern 1: ResourceQuota không có LimitRange
Vì sao xảy ra: Đội áp quota để "giới hạn tenant" nhưng quên LimitRange.
Hệ quả ở scale: Khi quota cho CPU/memory tồn tại, mọi Pod phải khai báo request/limit, nếu không bị reject. Các Deployment hiện có không khai báo resources đột ngột bị từ chối — outage cho tenant. Đội đổ lỗi cho quota và gỡ bỏ.
Cách phòng tránh: Luôn triển khai LimitRange cùng ResourceQuota. LimitRange cung cấp default để Pod không khai báo vẫn được chấp nhận.
Anti-pattern 2: Quên giới hạn object count và LoadBalancer
Vì sao xảy ra: Chỉ tập trung CPU/memory, bỏ qua các chiều khác.
Hệ quả ở scale: Một tenant tạo hàng nghìn Secret/ConfigMap phình etcd (ảnh hưởng control plane toàn cluster), hoặc tạo nhiều LoadBalancer Service đốt tiền và cạn IP quota. CPU/memory không phải tài nguyên duy nhất bị lạm dụng.
Cách phòng tránh: Đặt quota cho pods, services.loadbalancers, configmaps, secrets, persistentvolumeclaims. Bảo vệ etcd và chi phí, không chỉ compute.
Anti-pattern 3: Quota quá chặt gây throttling âm thầm
Vì sao xảy ra: Đặt quota CPU thấp; tenant đặt limit thấp để vừa quota.
Hệ quả ở scale: CPU limit thấp gây CPU throttling (CFS throttling) — ứng dụng chậm bất thường mà không OOM, khó debug. Tenant thấy latency cao nhưng không thấy lỗi rõ ràng.
Cách phòng tránh: Cân bằng quota với nhu cầu thực. Giám sát container_cpu_cfs_throttled_periods. Cân nhắc bỏ CPU limit (chỉ giữ request) cho workload latency-sensitive, dựa vào quota request cho fairness.
Anti-pattern 4: Bỏ qua quota cho ephemeral-storage
Vì sao xảy ra: Ít người để ý ephemeral storage (disk node cho logs, emptyDir, container layers).
Hệ quả ở scale: Một tenant ghi log/temp file không giới hạn làm đầy disk node, gây eviction mọi Pod trên node (kể cả tenant khác) do disk pressure. Một con đường noisy neighbor ít người biết.
Cách phòng tránh: Đặt requests.ephemeral-storage/limits.ephemeral-storage trong quota và LimitRange.