GKE Autopilot Scheduling, Custom ComputeClasses & Scheduler Extenders
Vì sao lớp GKE thay đổi luật chơi scheduling
Tám file trước mô tả kube-scheduler — cơ chế chung của Kubernetes. File này nói về lớp GKE phủ lên trên, nơi Google thêm các ràng buộc và tự động hóa riêng. Trên GKE Autopilot, bạn không thấy node, không quản lý node pool, và Google chủ động can thiệp vào spec Pod của bạn trước khi scheduler thấy nó — điều chỉnh request, ép tỷ lệ CPU:memory, thậm chí từ chối workload. Hiểu lớp này là điều kiện để không bị bất ngờ khi "Deployment đúng chuẩn Kubernetes" lại bị Autopilot từ chối, hoặc khi request bạn khai 100m CPU lại biến thành 250m.
Custom ComputeClasses thì là chiều ngược lại — chúng cho bạn thêm quyền kiểm soát việc node nào được tạo, với cơ chế fallback ưu tiên khi capacity khan hiếm. Đây là công cụ quan trọng nhất để xử lý bài toán "không có capacity loại máy mong muốn" một cách có kiểm soát thay vì để Pod kẹt Pending.
Internal model: Autopilot scheduling
Autopilot là chế độ GKE quản lý toàn bộ node. Bạn chỉ khai Pod; GKE provision node phù hợp, scale, vá, và bảo mật chúng. Đổi lại sự tiện lợi này là một bộ ràng buộc về resource request mà scheduler (và admission) thực thi nghiêm ngặt (Autopilot resource requests).
Thực thi tỷ lệ CPU:memory theo compute class
Đây là khác biệt lớn nhất so với Standard. Mỗi compute class trên Autopilot ép một khoảng tỷ lệ CPU:memory (Autopilot resource requests):
| Compute class | Tỷ lệ CPU:memory cho phép |
|---|---|
| General-purpose (mặc định) | Giữa 1:1 và 1:6.5 |
| Balanced | Giữa 1:1 và 1:8 |
| Scale-Out | Đúng 1:4 |
| Accelerator & Performance | Không ép tỷ lệ |
Khi tỷ lệ bạn khai nằm ngoài khoảng, "Autopilot tự động tăng tài nguyên nhỏ hơn" để đưa về khoảng hợp lệ (Autopilot resource requests). Ví dụ: bạn khai cpu: 4, memory: 1Gi (tỷ lệ 1:0.25, ngoài khoảng general-purpose) — Autopilot tăng memory lên cho khớp tỷ lệ tối thiểu 1:1. Hệ quả thực tế: bạn trả tiền cho tài nguyên bị tăng tự động, dù không yêu cầu. Đây là nguồn gốc của "tại sao hóa đơn Autopilot cao hơn dự tính" — request bị làm tròn lên theo tỷ lệ.
Min/max và làm tròn
- Default khi không khai request: general-purpose/Balanced/Scale-Out mặc định 0.5 vCPU và 2 GiB; mọi container 1 GiB ephemeral storage; DaemonSet 50 mCPU, 100 MiB memory (Autopilot resource requests).
- Làm tròn CPU: trên cluster không hỗ trợ bursting, "Autopilot làm tròn CPU request lên bội số 0.25 vCPU gần nhất" (Autopilot resource requests). Cluster hỗ trợ bursting không ép bội số này.
- Min/max: general-purpose CPU 50m–30 vCPU (cluster bursting) hoặc 250m–30 vCPU (không bursting); memory tương ứng. Scale-Out CPU 0.25–54 vCPU (amd64).
- Vượt max → từ chối: "Nếu request lớn hơn maximum, Autopilot từ chối workload" (Autopilot resource requests). Request dưới min thì tự sửa lên min.
Hai hành vi này — từ chối khi vượt max, tự điều chỉnh khi dưới min hoặc lệch tỷ lệ — là điều khiến Autopilot "khó tính" hơn Standard. Một spec hoàn toàn hợp lệ trên Standard có thể bị từ chối hoặc bị viết lại trên Autopilot.
Hệ quả kiến trúc của Autopilot
- Bạn không đặt được
limitskhácrequeststheo cách tùy ý cho mọi class — Autopilot thường coi Pod là Guaranteed (requests==limits) trừ cấu hình bursting. Điều này đơn giản hóa QoS nhưng giảm khả năng burst. - Bạn không quản lý taint/affinity node theo ý muốn tự do như Standard; thay vào đó dùng compute classes và một số nodeSelector được hỗ trợ.
- Mọi tính toán capacity, allocatable, system overhead do Google ẩn — bạn chỉ thấy "Pod chạy hay không". Điều này tốt cho đơn giản nhưng tệ cho debug sâu, vì các đòn bẩy của tám file trước (taint thủ công, scheduler profile) bị giới hạn.
Lựa chọn Autopilot vs Standard vì thế là quyết định mức kiểm soát scheduling: Autopilot trao quyền cho Google đổi lấy đơn giản; Standard giữ toàn quyền đổi lấy trách nhiệm vận hành.
Internal model: Custom ComputeClasses
Custom ComputeClasses là custom resource của Kubernetes định nghĩa "profile gồm tập thuộc tính node mà GKE dùng để provision node" cho autoscaling (About custom compute classes). Chúng giải quyết bài toán cốt lõi của thực tế cloud: loại máy mong muốn không phải lúc nào cũng có capacity, và bạn cần khai báo "thử cái này, không được thì thử cái kia" thay vì để Pod kẹt.
priorities: danh sách fallback có thứ tự
Trường priorities là danh sách cấu hình fallback có thứ tự. GKE xử lý tuần tự khi scale-up: thử rule đầu, nếu capacity không có thì xuống rule kế (About custom compute classes).
apiVersion: cloud.google.com/v1
kind: ComputeClass
metadata:
name: cost-optimized
spec:
priorities:
- machineFamily: n4 # ưu tiên 1: n4 Spot (rẻ nhất)
spot: true
minCores: 4
- machineFamily: c3 # ưu tiên 2: c3 Spot
spot: true
- machineFamily: n4 # ưu tiên 3: n4 on-demand (đảm bảo có)
spot: false
whenUnsatisfiable: ScaleUpAnyway # hoặc DoNotScaleUp (mặc định 1.33+)
nodePoolAutoCreation:
enabled: trueMỗi rule chọn phần cứng qua machineFamily (n4, c3, e2...), machineType (n4-standard-32), gpu, hoặc tpu; kèm spot (bool), minCores/minMemoryGb, cấu hình disk, và location.zones (About custom compute classes). Đây chính là cách ComputeClass ánh xạ sang VM family thật — bạn diễn đạt ý định ("ưu tiên Spot rẻ, fallback on-demand"), GKE dịch thành loại máy Compute Engine.
whenUnsatisfiable: kẹt hay dùng mặc định
Khi không rule nào thỏa, whenUnsatisfiable quyết định (About custom compute classes):
ScaleUpAnyway: tạo node theo mặc định cluster (Pod được chạy nhưng không đúng profile).DoNotScaleUp(mặc định từ v1.33+): để Pod Pending cho tới khi có tài nguyên khớp.
DoNotScaleUp an toàn hơn về chi phí (không vô tình tạo node loại đắt), nhưng nghĩa là Pod kẹt nếu mọi rule fail — phải giám sát. Đây là một thay đổi default quan trọng cần biết khi nâng cấp lên 1.33+.
activeMigration & consolidation: tối ưu liên tục
ComputeClass không chỉ tác động lúc scale-up mà còn vận hành liên tục:
activeMigration.optimizeRulePriority: true: di chuyển workload từ rule ưu tiên thấp lên cao khi tài nguyên ưu tiên cao trở nên khả dụng (About custom compute classes). Ví dụ: Pod đang chạy trên on-demand (fallback) sẽ được di về Spot (ưu tiên 1) khi Spot có lại — tiết kiệm chi phí tự động. Đánh đổi: gây gián đoạn Pod khi di chuyển, nên chỉ bật cho workload chịu được.autoscalingPolicy: tinh chỉnh xóa node thiếu tải quaconsolidationDelayMinutes,consolidationThreshold(ngưỡng % CPU/memory),gpuConsolidationThreshold.
nodePoolAutoCreation và sử dụng trên Standard
Với nodePoolAutoCreation.enabled: true, GKE tự tạo node pool khớp rule — bạn không cần tạo pool thủ công. Trên Standard cluster với pool tạo tay, pool phải mang label cloud.google.com/compute-class=<TÊN> và taint cloud.google.com/compute-class=<TÊN>:NoSchedule để liên kết với ComputeClass (About custom compute classes).
Pod chọn ComputeClass qua nodeSelector:
spec:
nodeSelector:
cloud.google.com/compute-class: cost-optimizedScheduler Extenders vs Scheduler Plugins
Phần cuối chương khép lại bằng cơ chế mở rộng scheduler. Có hai cách mở rộng, và hiểu vì sao một cái thắng cái kia là bài học kiến trúc quan trọng.
Scheduler Extenders: mô hình webhook cũ
Scheduler extender là cơ chế mở rộng đời đầu: scheduler gọi một HTTP webhook ngoài tại các điểm Filter/Score/Bind. Bạn chạy một service riêng, scheduler gửi danh sách node ứng viên qua HTTP, service trả về node nào lọc/điểm bao nhiêu.
Nhược điểm cố hữu:
- Độ trễ mạng: mỗi lần scheduling phải gọi HTTP ra ngoài — chậm hơn nhiều so với code trong process, đẩy
scheduling_attempt_duration_seconds(file 1) lên. - Khả năng biểu đạt hạn chế: extender chỉ tham gia một số điểm (Filter/Score/Bind), không phải toàn bộ extension point. Không can thiệp được PreFilter, Reserve, Permit, QueueSort...
- Truyền dữ liệu tốn kém: phải serialize/deserialize trạng thái node qua JSON mỗi lần gọi.
- Khó chia sẻ state: extender không truy cập scheduler cache; phải tự dựng view trạng thái cluster.
Scheduler Plugins (Scheduling Framework): mô hình hiện đại
Như đã mô tả ở file 1, Scheduling Framework cho phép viết plugin biên dịch thẳng vào scheduler, tham gia mọi extension point, chia sẻ CycleState và scheduler cache, không tốn round-trip mạng (Scheduling Framework).
| Tiêu chí | Extender (webhook) | Plugin (framework) |
|---|---|---|
| Độ trễ | Cao (HTTP mỗi cycle) | Thấp (in-process) |
| Extension point | Hạn chế (Filter/Score/Bind) | Toàn bộ (13 điểm) |
| Chia sẻ state | Không (tự dựng) | Có (CycleState, cache) |
| Triển khai | Service riêng, dễ deploy độc lập | Phải build scheduler tùy chỉnh |
| Khuyến nghị hiện tại | Chỉ dùng khi không build được scheduler | Lựa chọn mặc định |
Kết luận: plugins thắng extenders ở gần như mọi tiêu chí kỹ thuật. Extender chỉ còn ý nghĩa khi bạn không thể build và deploy một scheduler tùy chỉnh (ví dụ ràng buộc tổ chức), và chấp nhận chi phí độ trễ.
Trên GKE: cả hai đều bị giới hạn
Quan trọng: trên GKE, bạn không sửa được scheduler mặc định (control plane do Google quản lý), nên không "cắm plugin" vào scheduler mặc định. Hai con đường thực tế:
- Chạy scheduler thứ hai trong cluster dưới dạng Deployment (vd Kueue, Volcano cho batch/gang scheduling), gán Pod qua
spec.schedulerName(file 1). - Dùng các đòn bẩy GKE-native (compute classes, autoscaling profile) thay vì can thiệp scheduler — thường đủ cho phần lớn nhu cầu và ít rủi ro vận hành hơn.
Khuyến nghị mạnh: trước khi nghĩ tới scheduler tùy chỉnh, hãy kiểm tra xem topology spread, affinity, priority, và ComputeClasses đã giải quyết được nhu cầu chưa — chúng giải quyết 95% trường hợp mà không cần một scheduler riêng để vận hành.
Production architecture patterns
Pattern: ComputeClass cho khả năng chịu khan hiếm capacity
Workload cần capacity lớn (ví dụ scale-up đột biến) thường gặp lỗi "không có capacity loại máy X trong zone Y". ComputeClass với danh sách priorities đa machine-family + đa zone biến lỗi cứng này thành fallback mềm: thử c3, không có thì n4, không có thì e2. Đây là cách production-grade để chịu được biến động capacity của cloud thay vì để Pod kẹt.
Pattern: tối ưu chi phí Spot có kiểm soát
ComputeClass ưu tiên Spot với fallback on-demand + activeMigration cho phép: chạy trên Spot khi có (rẻ), tự rơi xuống on-demand khi Spot bị thu hồi (đảm bảo chạy), rồi tự di lại Spot khi có lại (tiết kiệm). Kết hợp với PriorityClass (file 7) cho workload Spot để chúng nhường chỗ đúng thứ tự. Đây là pattern tiết kiệm chi phí mạnh nhất cho batch chịu gián đoạn.
Real-world scenarios
Tình huống: Autopilot từ chối Deployment hợp lệ
Một đội chuyển từ Standard sang Autopilot. Deployment với cpu: 100m, memory: 8Gi (tỷ lệ 1:80, ngoài khoảng 1:6.5 của general-purpose) bị Autopilot điều chỉnh tăng CPU lên mạnh, hóa đơn nhảy vọt. Đội tưởng bị tính phí sai. Thực ra Autopilot ép tỷ lệ CPU:memory. Sửa: hoặc đặt request đúng tỷ lệ, hoặc chọn compute class phù hợp (vd memory-optimized cho workload nặng RAM). Bài học: trên Autopilot, tỷ lệ CPU:memory không phải tự do — phải thiết kế theo compute class.
Tình huống: Pod Pending sau nâng cấp lên 1.33
Sau nâng GKE lên 1.33, một số Pod dùng ComputeClass đột nhiên Pending khi capacity loại máy ưu tiên cạn. Nguyên nhân: default whenUnsatisfiable đổi thành DoNotScaleUp. Trước 1.33 (ScaleUpAnyway) Pod vẫn chạy trên node mặc định; sau 1.33 nó chờ đúng loại. Sửa: thêm rule fallback rộng (e2 on-demand) hoặc đặt whenUnsatisfiable: ScaleUpAnyway tường minh nếu chấp nhận node mặc định.
Cơ chế sâu hơn: priorityScore và reservations
Từ phiên bản gần đây, ComputeClass hỗ trợ priorityScore (1–1000) để xếp hạng các rule bằng điểm thay vì thứ tự danh sách — rule điểm cao được ưu tiên, rule cùng điểm được xử lý cùng nhau (About custom compute classes). Điều này hữu ích khi nhiều rule "ngang nhau" về mức ưu tiên (vd ba machine-family đều chấp nhận được), cho phép GKE chọn cái nào có capacity thay vì cứng nhắc theo thứ tự liệt kê. Trường priorityDefaults cho phép đặt giá trị mặc định dùng chung cho các thuộc tính bị bỏ trống trong rule, giảm lặp lại cấu hình.
Trường reservations tích hợp Compute Engine capacity reservation — capacity bạn đặt trước (và trả tiền giữ chỗ) với Google. Với affinity: Specific, ComputeClass bắt buộc dùng reservation cụ thể, đảm bảo độ tin cậy capacity cho workload critical. Đây là công cụ cho các hệ thống không chấp nhận rủi ro "không có capacity": bạn trả phí giữ chỗ để chắc chắn có máy khi cần, thay vì cạnh tranh capacity on-demand lúc cao điểm. Pattern này phổ biến cho workload có đỉnh tải dự báo trước (sự kiện, mùa cao điểm thương mại).
Cơ chế sâu hơn: Autopilot bursting và mô hình QoS
GKE Autopilot ban đầu chỉ hỗ trợ Pod Guaranteed (requests==limits), đơn giản hóa nhưng mất khả năng burst. Các cluster Autopilot mới hơn hỗ trợ bursting: Pod có thể đặt limits > requests, dùng tài nguyên rảnh của node vượt requests khi có — đưa mô hình Burstable (file 6) vào Autopilot. Đây là thay đổi quan trọng: nó cho phép Autopilot chạy hiệu quả các workload có tải dao động (chỉ trả tiền cho requests, burst miễn phí lên phần rảnh) thay vì phải đặt requests theo đỉnh.
Trên cluster Autopilot hỗ trợ bursting, các quy tắc làm tròn cũng nới: không ép bội số 0.25 vCPU, min CPU xuống 50m thay vì 250m (Autopilot resource requests). Hệ quả thực tế: workload nhiều Pod nhỏ (microservice nhẹ) hiệu quả chi phí hơn hẳn trên cluster bursting — đáng kiểm tra phiên bản cluster trước khi kết luận "Autopilot đắt".
Cơ chế sâu hơn: vì sao scheduler thứ hai thắng việc sửa scheduler mặc định
Khi nhu cầu scheduling vượt khả năng của kube-scheduler mặc định (điển hình: gang scheduling cho batch/ML, fair-sharing đa tenant với quota), con đường đúng trên GKE không phải tìm cách sửa scheduler mặc định (bất khả thi — Google quản lý) mà là chạy một scheduler chuyên dụng song song. Các lựa chọn trưởng thành:
- Kueue: job-level queueing, gang scheduling, fair-sharing theo quota — chuẩn de-facto cho batch/ML trên Kubernetes hiện đại, do chính cộng đồng Kubernetes phát triển.
- Volcano: gang scheduling, fair-share, dành cho HPC/AI batch nặng.
Cả hai hoạt động bằng cách bổ sung lên trên kube-scheduler (quản lý hàng đợi job, quyết định khi nào nhả Pod cho scheduling) chứ không thay thế cơ chế bind node. Pod của batch được gán schedulerName tương ứng hoặc đi qua cơ chế admission của Kueue. Đây là kiến trúc đúng: kube-scheduler lo việc đặt Pod lên node (việc nó giỏi), scheduler bổ sung lo việc điều phối job (việc kube-scheduler không làm). Cố nhồi mọi thứ vào một scheduler là anti-pattern; phân tách trách nhiệm là pattern.
Real-world scenario bổ sung: nền tảng ML đa team với ngân sách GPU hữu hạn
Một tổ chức có 100 GPU dùng chung cho 10 team ML. Để kube-scheduler mặc định tự xử lý → team nào submit trước chiếm hết, team khác đói; không có fair-sharing, không gang scheduling nên job phân tán deadlock. Lời giải: triển khai Kueue với quota theo team (mỗi team được đảm bảo X GPU, mượn thêm khi rảnh), gang scheduling đảm bảo job đa-GPU chỉ chạy khi đủ, kết hợp ComputeClass cho fallback loại GPU và DWS cho job lớn. kube-scheduler vẫn làm việc bind; Kueue làm việc điều phối. Đây là kiến trúc production cho "GPU như tài nguyên dùng chung công bằng" — bài toán mà priority/preemption đơn thuần (file 7) không giải quyết được vì nó thiếu khái niệm quota và gang.
Khung quyết định: Autopilot vs Standard vs ComputeClass
| Nhu cầu | Lựa chọn | Lý do |
|---|---|---|
| Đơn giản tối đa, ít vận hành | Autopilot | Google lo node; chấp nhận ràng buộc tỷ lệ/từ chối |
| Toàn quyền kiểm soát node/scheduler | Standard | Tự quản node pool, taint, scheduler thứ hai |
| Chịu khan hiếm capacity nhiều loại máy | Standard/Autopilot + ComputeClass | Fallback priorities đa machine-family |
| Tối ưu chi phí Spot tự động | ComputeClass + activeMigration | Tự di Spot↔on-demand |
| Batch/ML đa team công bằng | Standard + Kueue + ComputeClass | Gang scheduling + quota + fallback |
Common mistakes / anti-patterns
- Quên Autopilot ép tỷ lệ CPU:memory → request bị tăng tự động, hóa đơn cao bất ngờ.
- Không biết default
whenUnsatisfiableđổi ở 1.33+ → Pod Pending sau nâng cấp. - ComputeClass chỉ có một rule → mất ý nghĩa fallback; luôn có rule cuối "rộng" để đảm bảo lập lịch.
- Bật
activeMigrationcho workload không chịu gián đoạn → bị di chuyển gây downtime ngoài ý muốn. - Lao vào scheduler extender/custom scheduler khi chưa thử đòn bẩy native → tăng độ phức tạp vận hành không cần thiết; thử topology spread/affinity/priority/ComputeClass trước.
- Dùng extender mới (thay vì plugin) → chịu độ trễ HTTP không cần thiết; plugin/scheduler thứ hai tốt hơn.
GCP-native implementation guidance
Tạo ComputeClass cost-optimized với fallback và auto-create node pool:
apiVersion: cloud.google.com/v1
kind: ComputeClass
metadata:
name: cost-optimized
spec:
priorities:
- machineFamily: n4
spot: true
- machineFamily: c3
spot: true
- machineFamily: n4
spot: false # fallback đảm bảo có capacity
whenUnsatisfiable: ScaleUpAnyway
activeMigration:
optimizeRulePriority: true # tự di về Spot khi có lại (chỉ cho workload chịu gián đoạn)
nodePoolAutoCreation:
enabled: trueKiểm tra workload Autopilot có bị điều chỉnh request không:
# So sánh request khai báo vs request thực tế sau admission
kubectl get pod <pod> -o jsonpath='{range .spec.containers[*]}{.name}{": "}{.resources.requests}{"\n"}{end}'
# Nếu khác giá trị bạn khai → Autopilot đã điều chỉnh theo min/tỷ lệ/làm trònTổng kết mental model
- Autopilot chủ động điều chỉnh request: ép tỷ lệ CPU:memory theo compute class, làm tròn, tự sửa dưới min, từ chối khi vượt max — nguồn gốc "hóa đơn cao bất ngờ" và "Deployment bị từ chối".
- Custom ComputeClasses = danh sách
prioritiesfallback ánh xạ sang VM family thật, biến lỗi khan hiếm capacity thành fallback mềm. whenUnsatisfiable:DoNotScaleUp(mặc định 1.33+) để Pod Pending;ScaleUpAnywaydùng node mặc định.activeMigrationtối ưu chi phí liên tục nhưng gây gián đoạn — chỉ cho workload chịu được.- Scheduler plugins (in-process, mọi extension point) thắng extenders (webhook, độ trễ cao). Trên GKE, thử đòn bẩy native trước; cần custom thì chạy scheduler thứ hai.