§ blog · Server & Database19/06/2026
← Tất cả bài viết

Kubernetes cho ML và data workload: thiết kế cluster từ node pool đến GPU scheduling

Kubernetes là tiêu chuẩn cho container orchestration — nhưng cấu hình mặc định không đủ cho ML workload. GPU node cần taint và toleration riêng; training job có thể chiếm hết cluster nếu không có resource quota; model serving cần PodDisruptionBudget để không bị interrupt giữa inference. Bốn vấn đề thường gặp và cách thiết kế cluster đúng ngay từ đầu.

Server & DatabaseKubernetesMLOpsGPUInfrastructure9 phút đọc
FIG.S-02 · KUBERNETES ML CLUSTER● SERVERNODE POOLSSYSTEMcontrol planeCPU POOLETL · API · infer INT8GPU POOLtaint gpu=trueSPOT POOLtraining · −65% costK8s SCHEDULERResourceQuota · PriorityClassWORKLOADSINFERENCE SVCHPA · PDB · INT8DATA PIPELINEETL · dbt · AirflowTRAINING JOBGPU · KEDA · spotSTORAGEMODEL REGISTRYS3/R2 · MLflow · version ptrSERVER · LAYER 02 · GPU CLUSTER + MODEL REGISTRYpool → scheduler → workload → storage

Kubernetes giải quyết bài toán orchestration rất tốt cho stateless web service — nhưng ML workload có những đặc điểm khác: training job chạy vài tiếng rồi kết thúc, inference service cần latency thấp ổn định, data pipeline có memory footprint lớn, và GPU là tài nguyên khan hiếm cần chia sẻ cẩn thận giữa các team. Cấu hình Kubernetes mặc định không xử lý tốt những đặc điểm này — và những vấn đề này chỉ thực sự xuất hiện khi scale.

Bài này đi vào bốn lớp thiết kế: phân chia node pool theo loại workload, GPU provisioning đúng cách, storage layer cho model artifact, và resource quota để training job không chiếm hết cluster. Đây là những quyết định kiến trúc cần thực hiện trước khi deploy workload đầu tiên — thay đổi sau khi cluster đã có workload chạy phức tạp hơn nhiều.

Phân chia node pool — không để GPU node chạy web server

Lỗi phổ biến nhất khi bắt đầu với Kubernetes ML: đặt tất cả workload vào một node pool duy nhất. Kết quả là GPU node đắt tiền chạy Nginx vì scheduler điền pod vào node đầu tiên available, hoặc training job tạm dừng giữa chừng vì inference pod bị reschedule vào cùng node. Phân chia node pool theo loại workload là nền tảng kiến trúc đúng — và quyết định này cần thực hiện khi tạo cluster, không phải sau:

  • System pool: node nhỏ, dedicated cho Kubernetes system components (etcd, API server, CoreDNS, Ingress controller). Taint `CriticalAddonsOnly` để chỉ system pod mới schedule vào đây — tránh workload chiếm resource của control plane
  • CPU pool: node trung bình (8–32 vCPU) cho data processing, FastAPI inference server đã quantize, batch ETL, monitoring stack. Đây là pool scale ngang nhiều nhất — cluster autoscaler tự thêm/bớt node theo demand
  • GPU pool: node GPU (A100, H100, hay L4 tùy budget) với NVIDIA device plugin. Taint `gpu=true:NoSchedule` — chỉ pod có toleration tương ứng mới được schedule vào đây. Tránh GPU node chạy workload không cần GPU và lãng phí tài nguyên đắt tiền
  • Spot/Preemptible pool: node rẻ hơn 60–80% cho training job và experiment có thể chịu được interrupt — dùng khi cluster chạy trên AWS, GCP hay Azure. Cần checkpoint logic trong training code để resume khi node bị preempt giữa chừng

Mỗi pool có node selector và taint riêng. Training job khai báo toleration cho GPU pool và spot pool; inference server khai báo anti-affinity để không share node với training job dùng cùng GPU — tránh contention làm tăng inference latency.

GPU scheduling — taint, toleration và quota

GPU là tài nguyên quan trọng và tốn kém nhất trong cluster ML. Không có cơ chế kiểm soát, một training job không có resource limit có thể chiếm toàn bộ GPU của một node trong nhiều giờ, block inference service không scale lên được. Cần thiết lập đồng thời:

  • NVIDIA device plugin: DaemonSet chạy trên mỗi GPU node, expose `nvidia.com/gpu` như một schedulable resource. Cài qua Helm `nvidia/gpu-operator` — bao gồm driver, device plugin và DCGM exporter cho GPU utilization monitoring. Kubernetes không tự nhìn thấy GPU nếu không có plugin này
  • Resource request bắt buộc: mọi GPU pod phải khai báo `resources.limits.nvidia.com/gpu` — Kubernetes không partition GPU tự nhiên không như CPU/memory, nên pod không limit là chiếm toàn bộ GPU trên node. Dùng LimitRange để từ chối pod không khai báo resource limit trong namespace GPU
  • Namespace quota: mỗi team hoặc project nhận quota GPU riêng qua `ResourceQuota`. Team A tối đa 4 GPU, Team B tối đa 2 GPU — tránh một team monopolize cluster trong peak training
  • PriorityClass: inference service (`inference-high`) có priority cao hơn training job (`training-low`) — khi cluster đầy, training job bị preempt trước thay vì inference pod

Storage layer cho model artifact

Model artifact có kích thước từ vài trăm MB đến vài chục GB, cần truy cập từ nhiều pod song song khi inference có nhiều replica, và cần version control độc lập với code. Không nên đặt model trong container image — mỗi lần update model sẽ rebuild image và kéo dài deployment pipeline không cần thiết.

  • Object storage (S3, Cloudflare R2, GCS) là lựa chọn chính cho model artifact: cheap, durable, và mount vào pod qua sidecar hoặc init container. Pattern phổ biến: init container pull model từ S3 vào `emptyDir`, main container đọc từ local path — latency startup tăng nhưng image nhỏ và update model độc lập với code
  • PersistentVolume với ReadWriteMany (NFS hoặc Amazon EFS) cho trường hợp cần share model file giữa nhiều pod mà không muốn re-download: một pod write model mới vào PV, các inference pod đọc từ đó. Cần lưu ý cache invalidation khi update model — dùng symlink để atomic swap
  • Model registry (MLflow, Weights & Biases, hoặc tự xây trên object storage và metadata database): track version, metrics, và artifact location. Deployment pipeline reference model registry thay vì hardcode path — rollback về version cũ chỉ là thay đổi con trỏ model version, không rebuild image

Không đặt model artifact vào container image — image sẽ phình to, kéo dài CI/CD pipeline, và không tách được lifecycle của model với lifecycle của serving code. Hai thứ này thay đổi theo tốc độ khác nhau: code update theo sprint, model update theo training cycle.

Bốn cấu hình thường bị bỏ qua

Ngoài những quyết định lớn về node pool và storage, có bốn cấu hình nhỏ thường bị bỏ qua trong cluster ML mới nhưng gây vấn đề đáng kể sau khi scale lên vài chục pod:

  • PodDisruptionBudget cho inference service: khi cluster upgrade hoặc node bị drain, Kubernetes có thể evict tất cả replica của một Deployment cùng lúc nếu không có PDB. `minAvailable: 1` đảm bảo ít nhất một pod inference luôn chạy — quan trọng khi SLA yêu cầu uptime cao
  • TopologySpreadConstraint: phân phối inference pod đều qua các availability zone để một zone failure không hạ toàn bộ capacity. Đặc biệt quan trọng khi chạy trên managed cloud Kubernetes như EKS, GKE hay AKS với multi-AZ cluster
  • LimitRange mặc định cho namespace: nếu không có LimitRange, pod không khai báo resource request và limit vẫn schedule được — làm mất khả năng dự đoán resource usage và khiến quota vô hiệu. LimitRange đặt default và max limit cho mọi container trong namespace
  • Preemption policy cho training job: khai báo `preemptionPolicy: Never` cho training job không cần preempt pod khác để chạy — chỉ schedule khi đủ tài nguyên sẵn có. Tránh training job kick inference pod ra khỏi node và làm SLA vi phạm

Scaling cho inference và batch job

Inference service cần scale theo traffic; training job không cần. Hai cơ chế scaling phù hợp với đặc điểm khác nhau của hai loại workload này:

  • HPA (Horizontal Pod Autoscaler) dựa trên custom metric (request per second hoặc GPU utilization): inference pod scale out khi traffic tăng, scale in khi giảm. Với GPU model serving, cần lưu ý cold start time — một pod mới cần load model vào GPU memory (có thể mất 30–90 giây) trước khi serve request. ScaleDown stabilization window nên dài (5–10 phút) để tránh scale-in quá nhanh rồi phải scale-out lại
  • KEDA (Kubernetes Event-Driven Autoscaling) cho batch inference: scale theo queue depth (RabbitMQ, Kafka, SQS) — 0 pod khi queue rỗng, N pod khi có N batch waiting. KEDA hỗ trợ scale-to-zero — quan trọng với GPU node đắt tiền, không để GPU node idle khi không có job training hay batch inference

Ví dụ thực tế: cluster cho hệ thống phân tích retail

Trong dự án phân tích hành vi khách hàng và tối ưu tồn kho cho chuỗi 12 cửa hàng, Kubernetes cluster được thiết kế với ba pool: system (2 node), CPU (4–8 node auto-scale), và GPU (0–2 node với L4, KEDA scale-to-zero theo queue). Training job chạy trên GPU pool với spot instance — cost giảm 65% so với on-demand. Inference endpoint cho product recommendation chạy trên CPU pool sau khi model được quantize xuống INT8 — GPU không cần thiết khi serve. PDB `minAvailable: 2` đảm bảo recommendation service không xuống dưới 2 replica trong bất kỳ maintenance window nào.

Kết luận

Kubernetes không tự động giải quyết bài toán ML infrastructure — nó cung cấp primitive để xây giải pháp đúng. Node pool riêng theo loại workload, GPU taint và namespace quota, storage tách khỏi image, và bốn cấu hình nhỏ nhưng quan trọng là những quyết định cần thực hiện trước khi workload đầu tiên deploy. Trong lớp Server & Database của KonexForge, Kubernetes là nền tảng để các workload IoT, analytics và AI chạy ổn định với SLA — thiết kế đúng từ đầu giúp tránh refactor tốn kém khi hệ thống scale.

Có một bài toán tương tự đang cần giải?

Liên hệ team