3.1. Scheduling - Manual Scheduling, Labels, Taints and Node Affinity
Mục lục
- 1. Giới thiệu phần Scheduling
- 2. Lập lịch thủ công (Manual Scheduling)
- 3. Labels, Selectors và Annotations
- 4. Taints và Tolerations
- 5. Node Selectors
- 6. Node Affinity
- 7. Kết hợp Taints/Tolerations và Node Affinity
1. Giới thiệu phần Scheduling
Phần này đi sâu vào các khái niệm liên quan đến scheduling trong Kubernetes. Ở phần trước đã tìm hiểu cách cài đặt và cấu hình scheduler ở mức sơ lược. Phần này sẽ xem xét kỹ hơn các tùy chọn để tùy biến và cấu hình cách scheduler hoạt động, thông qua một loạt bài thực hành thú vị và có tính thử thách.
Bắt đầu với lập lịch thủ công (manual scheduling) và cách tự schedule một Pod theo cách thủ công. Sau đó là DaemonSets, labels và selectors, cách resource requests và limits ảnh hưởng đến scheduling. Cũng sẽ xem cách cấu hình nhiều scheduler cùng lúc và cách xem các scheduler event.
2. Lập lịch thủ công (Manual Scheduling)
Trong phần này, xem xét các cách khác nhau để lập lịch thủ công một Pod lên một node. Điều gì xảy ra khi cluster không có scheduler? Có thể không muốn phụ thuộc vào scheduler mặc định (built-in scheduler) mà muốn tự lập lịch cho các pod.
💡 Hình dung: Scheduler giống một tổng đài điều phối giao hàng — Pod (đơn hàng) không tự quyết định sẽ được giao ở đâu, tổng đài mới là bên gán địa chỉ (node) cho từng đơn. Nếu tổng đài nghỉ việc (không có scheduler), đơn hàng vẫn được tạo ra nhưng không ai gán địa chỉ — nó nằm chờ vô thời hạn (Pending). Lúc này, cách duy nhất để đơn được giao là tự ghi thẳng địa chỉ lên đó (set
nodeName) trước khi gửi đi, hoặc nếu đơn đã gửi rồi thì phải gửi kèm một phiếu chỉ định riêng (Binding object) — đúng như cách tổng đài vẫn làm ở phía sau.
Vậy chính xác thì scheduler hoạt động thế nào ở phía sau? Bắt đầu với một pod definition file đơn giản. Mỗi Pod đều có một trường gọi là nodeName, mặc định không được set. Người dùng thường không tự chỉ định trường này khi tạo pod manifest file — Kubernetes tự động thêm vào.
Scheduler duyệt qua tất cả các Pod và tìm những Pod chưa có thuộc tính này được set. Đó là các ứng viên (candidates) để lập lịch. Sau đó, scheduler xác định node phù hợp cho pod bằng cách chạy scheduling algorithm. Khi đã xác định được, scheduler lập lịch pod lên node bằng cách set thuộc tính nodeName thành tên node, thông qua việc tạo một Binding object.
Nếu không có scheduler để giám sát và lập lịch pod lên node, điều gì xảy ra? Các pod tiếp tục ở trạng thái Pending. Vậy có thể làm gì?
Có thể tự gán pod vào node theo cách thủ công. Không có scheduler, cách đơn giản nhất để lập lịch một pod là chỉ cần set trường nodeName thành tên của node ngay trong pod specification file khi tạo pod. Pod sau đó sẽ được gán vào node được chỉ định.
Chỉ có thể chỉ định nodeName tại thời điểm tạo (creation time). Vậy nếu Pod đã được tạo rồi và muốn gán Pod vào một node? Kubernetes sẽ không cho phép chỉnh sửa thuộc tính nodeName của một Pod đang tồn tại.
Cách khác để gán node cho một Pod đã tồn tại là tạo một Binding object và gửi một POST request tới Binding API của Pod, qua đó mô phỏng lại đúng những gì scheduler thực sự làm. Trong Binding object, chỉ định node đích (target node) bằng tên của node, sau đó gửi POST request tới Binding API của Pod với dữ liệu (data) được set là Binding object ở dạng JSON. Vì vậy phải chuyển đổi file YAML sang dạng JSON tương đương.
Lab: Thực hành lập lịch thủ công
Bài toán: cho sẵn một pod definition file nginx.yaml, yêu cầu tạo một Pod.
Trước tiên khảo sát môi trường bằng kubectl get nodes — đây là cluster hai node. File nginx.yaml là một pod definition file thông thường, với name set là nginx và image cũng set là nginx.
Tạo Pod này. Mục tiêu của bài tập chỉ là tạo Pod, chưa cần kiểm tra trạng thái ngay.
Pod ở trạng thái gì sau khi tạo? Kiểm tra thấy Pod ở trạng thái Pending.
Vì sao Pod ở trạng thái Pending? Kiểm tra các coordinator và control plane component. Đã biết scheduler là thành phần chịu trách nhiệm lập lịch Pod. Nếu trạng thái Pod là Pending, xem kỹ hơn Pod đó — không thấy chi tiết gì khác ngoài trạng thái Pending, nghĩa là scheduler chưa hoàn thành việc lập lịch Pod lên node (trường node đang set là none).
Kiểm tra scheduler: scheduler chạy dưới dạng Pod trong namespace kube-system. Thấy có API server, controller manager, proxy, etcd control plane, nhưng không có scheduler đang chạy — đó là lý do Pod này không chạy và đang ở trạng thái Pending. Trả lời: không có scheduler hiện diện (no scheduler present).
Lập lịch thủ công Pod lên node01. Pod đã được tạo nhưng chưa được gán vào node nào. Đã học ở phần trước là có thể lập lịch Pod thủ công. Chỉnh sửa file nginx.yaml, thêm thuộc tính nodeName và set giá trị là node01. Sau đó phải tạo lại Pod — cách để làm điều này trong một lệnh duy nhất là dùng kubectl replace --force -f nginx.yaml, lệnh này sẽ xóa Pod rồi tạo lại theo file được chỉ định.
Kiểm tra trạng thái Pod — đang ở trạng thái ContainerCreating. Nếu không muốn chạy lại lệnh kiểm tra trạng thái nhiều lần, có thể thêm option watch (kubectl get pods -w) để theo dõi và tự động cập nhật trạng thái khi có thay đổi. Sau đó Pod chuyển sang trạng thái Running.
Lập lịch cùng Pod đó lên control plane node. Thực hiện tương tự — chỉnh nodeName thành control plane. Lưu ý: không thể di chuyển một Pod đang chạy từ node này sang node khác. Về bản chất, một Pod thực chất là một container, tức là một process khác đang chạy trên hệ thống, nên không thể di chuyển một process đang chạy nguyên trạng từ máy này sang máy khác. Vì vậy không có cách nào để di chuyển Pod từ node này sang node khác — chỉ có thể xóa Pod ở một node rồi tạo lại ở node khác.
Lặp lại thao tác kubectl replace --force -f nginx.yaml. Xóa Pod thường mất một chút thời gian — đây không phải vấn đề của Kubernetes hay môi trường lab. Khi terminate hoặc delete một pod, ở backend hệ thống phải terminate process đang chạy (trong trường hợp này là process NGINX) — tức là gửi kill signal tới process. Tùy vào process đang chạy và cách nó xử lý kill signal, có thể mất một khoảng thời gian để xử lý request đó và thực sự kill hoặc graceful shutdown.
Kiểm tra lại Pod đang chạy — dùng option wide để xem Pod được lập lịch trên node nào. Kết quả: Pod nằm trên control plane node.
3. Labels, Selectors và Annotations
Labels và selectors là phương pháp tiêu chuẩn để nhóm các đối tượng lại với nhau. Giả sử có một tập hợp các loài khác nhau, muốn lọc chúng dựa trên các tiêu chí khác nhau, như dựa trên class hoặc kind, chúng là domestic hay wild, hoặc theo màu sắc. Không chỉ nhóm lại, mà còn muốn lọc dựa trên một tiêu chí, ví dụ tất cả động vật màu xanh (green), hoặc với nhiều tiêu chí cùng lúc, ví dụ mọi thứ màu xanh mà đồng thời cũng là loài chim.
Dù phân loại đó là gì, cần khả năng nhóm các đối tượng lại và lọc chúng theo nhu cầu. Và cách tốt nhất để làm điều đó là dùng labels. Labels là các thuộc tính gắn vào mỗi đối tượng — thêm thuộc tính vào mỗi item cho class, kind, và color. Selectors giúp lọc các item này. Ví dụ, khi nói class equals mammal, sẽ được danh sách các loài mammal. Khi nói color equals green, sẽ được các loài mammal màu xanh.
💡 Hình dung: Labels giống thẻ hành lý ở sân bay — mỗi vali được dán một thẻ ghi mã chuyến bay, hãng hàng không, hạng vé. Nhân viên phân loại hành lý không cần biết bên trong vali chứa gì, chỉ cần đọc thẻ (label) rồi lọc theo tiêu chí cần thiết (selector) — "chỉ lấy vali chuyến VN123" hay "chỉ lấy vali hạng thương gia". Kubernetes object cũng vậy — object không quan tâm label dùng để làm gì, nó chỉ mang thẻ; các object khác (ReplicaSet, Service) hay chính người vận hành mới là bên đọc thẻ và lọc theo selector.
Labels và selectors được sử dụng khắp mọi nơi, như các từ khóa (keywords) gắn vào video YouTube hay bài blog giúp người dùng lọc và tìm đúng nội dung. Cũng thấy labels được gắn vào các item trong một cửa hàng online, giúp thêm các bộ lọc khác nhau để xem sản phẩm.
Vậy labels và selectors được dùng trong Kubernetes như thế nào? Đã tạo rất nhiều loại object khác nhau trong Kubernetes: pods, services, ReplicaSets, Deployments, v.v. Với Kubernetes, tất cả đều là các object khác nhau. Theo thời gian, có thể có hàng trăm hoặc hàng nghìn object này trong cluster. Khi đó cần một cách để lọc và xem các object khác nhau theo các danh mục khác nhau — như nhóm object theo type, hoặc xem object theo application, hoặc theo chức năng (functionality). Dù là gì đi nữa, có thể nhóm và chọn object bằng labels và selectors. Với mỗi object, gắn label theo nhu cầu, như app, function, v.v. Sau đó khi chọn (selecting), chỉ định một điều kiện để lọc các object cụ thể, ví dụ app=App1.
Vậy chính xác thì chỉ định labels trong Kubernetes như thế nào? Trong một pod definition file, dưới metadata, tạo một section gọi là labels. Bên dưới đó, thêm labels theo định dạng key-value. Có thể thêm bao nhiêu label tùy ý. Khi Pod đã được tạo, để chọn Pod theo labels, dùng lệnh kubectl get pods cùng với option selector và chỉ định điều kiện, ví dụ app equals app1.
Đây chỉ là một use case của labels và selectors. Kubernetes object cũng dùng labels và selectors nội bộ để kết nối các object khác nhau lại với nhau. Ví dụ, để tạo một ReplicaSet gồm ba pod, trước tiên gắn label lên pod definition và dùng selector trong ReplicaSet để nhóm các pod. Trong một ReplicaSet definition file, sẽ thấy labels được định nghĩa ở hai chỗ. Đây là điểm mà người mới bắt đầu (beginners) dễ nhầm lẫn. Labels định nghĩa dưới section template là labels được cấu hình trên các pod. Labels ở phần trên cùng là labels của chính ReplicaSet.
Không thực sự cần quan tâm đến labels của ReplicaSet lúc này, vì đang cố để ReplicaSet phát hiện (discover) các pod. Labels trên ReplicaSet sẽ được dùng nếu có object khác cần cấu hình để phát hiện ReplicaSet đó. Để kết nối ReplicaSet với các pod, cấu hình trường selector dưới ReplicaSet specification để khớp với labels được định nghĩa trên các pod. Một label duy nhất cũng đủ nếu khớp đúng. Tuy nhiên, nếu nghĩ rằng có thể có các pod khác có cùng label nhưng chức năng khác, thì có thể chỉ định cả hai label để đảm bảo đúng pod được ReplicaSet phát hiện.
Khi tạo, nếu labels khớp, ReplicaSet được tạo thành công. Cơ chế tương tự áp dụng cho các object khác như Service. Khi một service được tạo, nó dùng selector định nghĩa trong service definition file để khớp với labels được set trên các pod trong ReplicaSet definition file.
Cuối cùng, nói về annotations. Trong khi labels và selectors dùng để nhóm và chọn object, annotations dùng để ghi lại các chi tiết khác cho mục đích thông tin (informational purpose). Ví dụ: chi tiết công cụ như tên, phiên bản, build information, v.v., hoặc thông tin liên hệ như số điện thoại, email ID, v.v. mà có thể dùng cho một số mục đích tích hợp (integration).
Lab: Thực hành với Labels và Selectors
Đã deploy một số pod, được gắn label với tier, ENV, và BU. Câu hỏi: có bao nhiêu pod tồn tại trong môi trường dev?
Chạy kubectl get pods — có một số pod. Lọc và xác định các pod thuộc môi trường DEV. Giả định ENV là label cho environment. Dùng selector: kubectl get pods --selector ENV=dev (hoặc env=dev theo giá trị được set) — kết quả có 7 pod trong môi trường dev.
Với số lượng lớn hơn, đếm thủ công sẽ khó khăn. Có thể dùng wc -l, nhưng vấn đề là nó cũng đếm cả dòng header. Có một cách để in kết quả mà không có header, dùng option --no-headers. Khi chạy với option đó, chỉ nhận output không có header, rồi thực hiện wc -l trên đó — kết quả là 7.
Có bao nhiêu pod thuộc BU finance? Label key là BU cho business unit, business unit đang xét là finance. Dùng cùng lệnh cho label này — kết quả là 6.
Có bao nhiêu object thuộc môi trường prod, bao gồm pods, ReplicaSets và các object khác? Dùng cùng lệnh, đổi label thành env=prod, nhưng chỉ đang list pods. Để list tất cả object, dùng all thay vì pods (kubectl get all --selector env=prod) — all sẽ list mọi object trong namespace đó. Kết quả gồm pods, service, và replica sets — tổng cộng 7 object.
Xác định pod thuộc cả môi trường prod, BU finance, và tier frontend — tức là pod thỏa cả ba điều kiện cùng lúc. ENV=prod, sau đó thêm các label khác, phân cách bằng dấu phẩy: BU=finance, tier=frontend. Đó chính là pod có tên bắt đầu bằng zxdf.
Vấn đề với một ReplicaSet definition file. Yêu cầu tạo ReplicaSet, nhưng có lỗi cần sửa. Khi thử tạo, báo lỗi: ReplicaSet is invalid, invalid value: selector does not match template labels. So sánh selector và template labels — hai phần này không khớp nhau. Đã biết để ReplicaSet quản lý được pod (đây là pod definition), ReplicaSet cần có hai phần này khớp nhau, nếu không nó sẽ không thể quản lý pod — đó là lỗi validation. Sửa bằng cách đổi giá trị cho khớp, lưu lại, tạo ReplicaSet — thành công.
4. Taints và Tolerations
Phần này bàn về mối quan hệ pod-to-node và cách hạn chế những pod nào được đặt trên những node nào. Khái niệm taints và tolerations có thể hơi khó hiểu với người mới bắt đầu, nên sẽ dùng một analogy về một con bọ tiếp cận một người để hiểu.
💡 Hình dung: Để ngăn con bọ đậu lên người, xịt thuốc chống côn trùng (repellent spray) lên người đó — gọi đó là taint. Con bọ không chịu được (intolerant) mùi đó, nên khi tiếp cận người, taint đặt trên người đẩy con bọ đi. Tuy nhiên, có thể có những con bọ khác chịu được (tolerant) mùi này, nên taint không thực sự ảnh hưởng đến chúng — chúng vẫn đậu lên người được. Vậy có hai yếu tố quyết định liệu một pod (con bọ) có thể "đậu" lên một node (người) hay không: thứ nhất là taint trên node, thứ hai là mức độ toleration của pod đối với taint đó.
Quay lại Kubernetes: người là node, và các pod là pods. Taints và tolerations không liên quan gì đến security hay xâm nhập vào cluster. Taints và tolerations được dùng để đặt giới hạn về những pod nào có thể được lập lịch lên một node.
Bắt đầu với một cluster đơn giản có ba worker node, đặt tên là 1, 2, và 3. Cũng có một tập hợp pod cần được deploy lên các node này — gọi chúng là A, B, C, và D. Khi các pod được tạo, Kubernetes scheduler cố gắng đặt các pod này lên các worker node có sẵn. Hiện tại chưa có giới hạn hay hạn chế nào, nên scheduler đặt các pod trải đều trên tất cả các node để cân bằng.
Giả sử có tài nguyên dành riêng (dedicated resources) trên node 1 cho một use case hoặc application cụ thể — chỉ muốn các pod thuộc application này được đặt lên node 1. Đầu tiên, ngăn tất cả pod khác được đặt lên node bằng cách đặt một taint lên node — gọi là blue. Mặc định, pod không có toleration nào. Nghĩa là, trừ khi được chỉ định khác, không pod nào có thể chịu được (tolerate) bất kỳ taint nào. Vì vậy trong trường hợp này, không pod nào có thể được đặt lên node 1 vì không pod nào chịu được taint blue.
Điều này giải quyết một nửa yêu cầu — không có pod không mong muốn nào được đặt lên node này. Nửa còn lại là cho phép một số pod nhất định được đặt lên node này. Để làm điều đó, phải chỉ định pod nào chịu được taint cụ thể này. Trong trường hợp này, chỉ muốn cho phép pod D được đặt lên node này, nên thêm một toleration vào pod D. Pod D bây giờ chịu được blue. Khi scheduler cố đặt pod này lên node 1, nó sẽ được chấp nhận. Node 1 giờ chỉ có thể chấp nhận các pod chịu được taint blue.
Với tất cả taints và tolerations được thiết lập, các pod sẽ được lập lịch như sau: scheduler cố đặt pod A lên node 1, nhưng do taint, nó bị đẩy ra và chuyển sang node 2. Scheduler tiếp tục cố đặt pod B lên node 1, nhưng cũng bị đẩy ra do taint, và được đặt lên node 3 — node trống tiếp theo. Scheduler cố đặt pod C lên node 1, cũng bị đẩy ra, kết thúc trên node 2. Cuối cùng, scheduler cố đặt pod D lên node 1 — vì pod này chịu được taint trên node 1, nó được chấp nhận.
Vậy hãy nhớ: taints được set trên node, tolerations được set trên pod.
Cách thực hiện: dùng lệnh kubectl taint nodes để taint một node. Chỉ định tên node cần taint, theo sau là chính taint đó, dưới dạng cặp key-value. Ví dụ, nếu muốn dành riêng node cho các pod thuộc application blue, cặp key-value sẽ là app=blue.
Taint effect định nghĩa điều gì sẽ xảy ra với pod nếu chúng không chịu được taint. Có ba taint effect:
- NoSchedule: pod sẽ không được lập lịch lên node — đây là điều đang thảo luận.
- PreferNoSchedule: hệ thống sẽ cố tránh đặt pod lên node, nhưng không đảm bảo.
- NoExecute: pod mới sẽ không được lập lịch lên node, và các pod hiện có trên node (nếu có) sẽ bị evicted nếu chúng không chịu được taint. Các pod này có thể đã được lập lịch lên node trước khi taint được áp dụng.
Câu lệnh ví dụ để taint node node1 với cặp key-value app=blue và effect NoSchedule:
kubectl taint nodes node1 app=blue:NoSchedule
Thêm toleration cho pod: trước tiên mở pod definition file. Trong section spec của pod definition file, thêm một section gọi là tolerations. Chuyển các giá trị tương tự được dùng khi tạo taint vào section này:
tolerations:
- key: "app"
operator: "Equal"
value: "blue"
effect: "NoSchedule"
Key là app, operator là Equal, value là blue, và effect là NoSchedule. Và hãy nhớ, tất cả các giá trị này cần được đặt trong dấu ngoặc kép (double quotes).
Khi pod được tạo mới hoặc cập nhật với toleration mới, chúng sẽ không được lập lịch lên node hoặc bị evicted khỏi node hiện tại tùy vào effect được set.
Hiểu sâu hơn về NoExecute
Trong ví dụ này, có ba node đang chạy một số workload, chưa có taint hay toleration nào, nên chúng được lập lịch trải đều. Sau đó quyết định dành riêng node 1 cho một application đặc biệt — taint node với tên application, và thêm toleration cho pod thuộc application đó (pod D trong trường hợp này). Khi taint node, set taint effect là NoExecute. Khi taint trên node có hiệu lực, nó evict pod C khỏi node — nghĩa là pod bị kill. Pod D tiếp tục chạy trên node, vì nó có toleration với taint blue.
Quay lại kịch bản ban đầu với taints và tolerations được cấu hình: nhớ rằng taints và tolerations chỉ nhằm hạn chế node chấp nhận những pod nào. Trong trường hợp này, node 1 chỉ có thể chấp nhận pod D. Nhưng điều đó không đảm bảo pod D sẽ luôn được đặt lên node 1. Vì không có taint hay hạn chế nào áp dụng lên hai node còn lại, pod D hoàn toàn có thể được đặt lên bất kỳ node nào trong hai node còn lại đó.
Vậy hãy nhớ, taints và tolerations không nói cho pod biết phải đi đến node nào cụ thể. Thay vào đó, nó nói cho node biết chỉ nên chấp nhận những pod có toleration nhất định. Nếu yêu cầu là giới hạn một pod vào các node cụ thể, điều đó đạt được thông qua một khái niệm khác gọi là node affinity.
Taint tự động trên control plane node
Từ trước đến giờ chỉ đề cập đến worker node, nhưng cũng có control plane node trong cluster — về mặt kỹ thuật, đây cũng chỉ là một node khác có đầy đủ khả năng host một pod, cộng với việc chạy toàn bộ management software. Có thể để ý rằng scheduler không lập lịch bất kỳ pod nào lên control plane node — vì sao vậy?
Khi Kubernetes cluster được thiết lập lần đầu, một taint được tự động set trên control plane node, ngăn không cho bất kỳ pod nào được lập lịch lên node này. Có thể xem cũng như chỉnh sửa hành vi này nếu cần, tuy nhiên best practice là không deploy application workload lên control plane node.
Để xem taint này, chạy lệnh kubectl describe node với tên control plane node, và tìm section taint — sẽ thấy taint node-role.kubernetes.io/control-plane:NoSchedule được set để không lập lịch bất kỳ pod nào lên node này.
Lab: Thực hành Taints và Tolerations
Có bao nhiêu node tồn tại trên hệ thống, bao gồm control plane? Kiểm tra số node — có hai node: control plane và node01. Trả lời: 2.
Có taint nào tồn tại trên node node01 không? Kiểm tra chi tiết node01 bằng kubectl describe node node01 — xem section taints, thấy none. Không có taint nào trên node này.
Tạo một taint trên node01 với key spray, value mortein, effect NoSchedule. Tiếp tục với ví dụ về xịt thuốc, mortein, muỗi, và ong (mosquito, bee) đã dùng ở phần lý thuyết trên. Dùng kubectl taint:
kubectl taint node node01 spray=mortein:NoSchedule
Xác nhận lại — thấy spray=mortein với effect NoSchedule. Vậy taint đã được đặt trên node01.
Tạo một Pod mới với image nginx và tên pod là mosquito.
kubectl run mosquito --image=nginx
Kiểm tra kubectl get pods — Pod ở trạng thái Pending. Kiểm tra lý do bằng kubectl describe pod mosquito — lý do là: the pod didn't tolerate one node that had taint spray=mortein — pod mosquito không chịu được taint mortein.
Tạo một Pod tên bee với image nginx, có toleration set với taint mortein. Vì không thể chỉ định toleration trực tiếp trên command line, dùng --dry-run=client để sinh ra file YAML cho pod này:
kubectl run bee --image=nginx --dry-run=client -o yaml > bee.yaml
Chỉnh sửa file bee.yaml, thêm section tolerations. Định dạng tham khảo từ tài liệu Kubernetes (trang Taints and Tolerations): - key, operator, value và effect.
tolerations:
- key: "spray"
operator: "Equal"
value: "mortein"
effect: "NoSchedule"
Lưu lại và tạo pod. Pod ở trạng thái ContainerCreating, sau đó chuyển sang Running. Lưu ý pod bee được lập lịch trên node01 — pod này đã chịu được taint set trên node01.
Có taint nào trên control plane node không? Kiểm tra — có taint set trên control plane node, với effect NoSchedule.
Xóa taint trên control plane, hiện đang có taint effect NoSchedule. Lấy lại taint hiện tại, dùng lệnh xóa giống lệnh tạo (kubectl taint), chỉ định node, chỉ định taint muốn xóa, và ở cuối thêm dấu gạch ngang (-) để xóa taint đó. Kết quả báo untainted.
Kiểm tra lại trạng thái các pod — pod mosquito, trước đây ở trạng thái Pending, giờ đang ở trạng thái Running. Kiểm tra pod được đặt trên node nào — pod hiện đang trên control plane node.
Tóm lại luồng của bài lab: ban đầu control plane node có taint set sẵn. Sau đó taint node01 và tạo pod mosquito — pod này không được đặt lên node nào vì cả hai node đều có taint. Sau đó tạo pod bee với toleration trên node01 — pod bee được đặt lên node01. Sau đó xóa taint trên control plane, và không cần làm gì thêm, pod mosquito tự động được đặt lên control plane vì node này không còn taint nào nữa. Đáp án: control plane.
5. Node Selectors
Phần này nói về node selectors trong Kubernetes. Bắt đầu với một ví dụ đơn giản: có một cluster ba node, trong đó hai node nhỏ hơn với tài nguyên phần cứng thấp hơn, và một node lớn hơn được cấu hình với tài nguyên cao hơn. Có nhiều loại workload khác nhau chạy trong cluster, muốn dành riêng các workload xử lý dữ liệu (data processing) đòi hỏi horsepower cao hơn cho node lớn, vì đó là node duy nhất sẽ không hết tài nguyên nếu job yêu cầu thêm tài nguyên.
Tuy nhiên, trong thiết lập mặc định hiện tại, bất kỳ pod nào cũng có thể đi đến bất kỳ node nào. Vậy pod C trong trường hợp này có thể sẽ kết thúc trên node 2 hoặc 3, điều này không mong muốn. Để giải quyết, có thể đặt một giới hạn lên các pod để chúng chỉ chạy trên những node cụ thể. Có hai cách để làm điều này. Cách đầu tiên là dùng node selectors — đơn giản và dễ hơn.
💡 Hình dung: Giống một xưởng có hai thợ phụ (node nhỏ) và một thợ chính khỏe hơn (node lớn) — nếu không dặn trước, quản đốc (scheduler) có thể giao việc nặng cho bất kỳ ai trong xưởng, kể cả thợ phụ, khiến họ quá tải.
nodeSelectorgiống một tấm biển dán sẵn trên bàn làm việc của thợ chính, ghi "size=large" — công việc nào cần biển này mới được giao đúng vào bàn đó.
Xem lại pod definition file đã tạo trước đó — file này có định nghĩa đơn giản để tạo một Pod với image xử lý dữ liệu. Để giới hạn pod này chỉ chạy trên node lớn, thêm một thuộc tính mới gọi là nodeSelector vào section spec và chỉ định size là large.
Nhưng khoan, size=large từ đâu ra? Và làm sao Kubernetes biết node nào là node lớn? Cặp key-value size và large thực chất là labels được gán cho các node. Scheduler dùng các label này để khớp và xác định đúng node cần đặt pod. Labels và selectors là chủ đề đã gặp nhiều lần ở các phần trước trong loạt tài liệu Kubernetes này, như với Services, ReplicaSets, và Deployments. Để dùng labels trong node selector như thế này, trước tiên phải label các node trước khi tạo Pod này.
Vậy quay lại xem cách label một node. Để label một node, dùng lệnh kubectl label nodes theo sau là tên node và label theo định dạng key-value. Trong trường hợp này, đó là:
kubectl label nodes node1 size=large
Sau khi đã label node, quay lại tạo pod, lần này với node selector set size là large. Khi pod được tạo, nó sẽ được đặt lên node01 như mong muốn.
Node selectors đáp ứng đúng mục đích, nhưng có giới hạn. Đã dùng một label và selector duy nhất để đạt mục tiêu. Nhưng nếu yêu cầu phức tạp hơn nhiều thì sao? Ví dụ, muốn nói một điều gì đó như "đặt pod lên node large hoặc medium". Hay "đặt pod lên bất kỳ node nào không phải small". Không thể đạt được điều này chỉ với node selectors. Vì lý do đó, tính năng node affinity đã được giới thiệu để giải quyết đúng vấn đề này — sẽ xem xét ngay sau đây.
(Node affinity thường được nhắc kèm với node anti-affinity — một khái niệm liên quan nhưng khác: node affinity gắn pod vào một node cụ thể, trong khi anti-affinity dùng để tách các pod ra xa nhau. Anti-affinity không nằm trong phạm vi tài liệu này.)
6. Node Affinity
Phần này nói về tính năng node affinity trong Kubernetes. Mục đích chính của node affinity là đảm bảo pod được host trên những node cụ thể. Trong trường hợp này, để đảm bảo pod xử lý dữ liệu lớn kết thúc trên node01. Ở phần trước đã làm điều này dễ dàng bằng node selectors. Đã thảo luận rằng không thể cung cấp các biểu thức nâng cao như OR hoặc NOT với node selectors. Tính năng Node Affinity cung cấp các khả năng nâng cao để giới hạn việc đặt pod lên các node cụ thể.
Đi kèm với sức mạnh lớn hơn là sự phức tạp lớn hơn. Node selector đơn giản trước đây giờ sẽ trông như sau với node affinity, dù cả hai đều làm chính xác cùng một việc: đặt pod lên node large.
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: size
operator: In
values:
- large
Xem xét kỹ hơn: dưới spec, có affinity, rồi nodeAffinity bên dưới đó. Sau đó có một thuộc tính trông như một câu văn dài, gọi là requiredDuringSchedulingIgnoredDuringExecution. Không cần giải thích thêm tên gọi này. Rồi có nodeSelectorTerms, đó là một array, và đó là nơi chỉ định các cặp key-value.
Các cặp key-value có dạng key, operator, và value, trong đó operator là In. Operator In đảm bảo pod sẽ được đặt lên một node có label size có bất kỳ giá trị nào trong danh sách values được chỉ định ở đây. Trong trường hợp này, chỉ có một giá trị là large.
Nếu nghĩ pod có thể được đặt lên node large hoặc medium, chỉ cần thêm giá trị vào danh sách values như sau. Có thể dùng operator NotIn để nói điều gì đó như size NotIn small, khi đó node affinity sẽ khớp với các node có size không set là small. Đã biết chỉ set label size cho node large và medium — các node nhỏ hơn thậm chí không có label này. Vậy không thực sự cần kiểm tra giá trị của label. Miễn là chắc chắn không set label size cho các node nhỏ, dùng operator Exists sẽ cho kết quả tương tự. Operator Exists chỉ đơn giản kiểm tra xem label size có tồn tại trên node hay không, và không cần section values cho việc này, vì nó không so sánh giá trị. Có một số operator khác nữa — kiểm tra tài liệu để biết chi tiết cụ thể.
Khi các pod được tạo, các rule này được xem xét và pod được đặt lên đúng node. Nhưng nếu node affinity không thể khớp với node nào theo biểu thức đã cho thì sao? Trường hợp không có node nào có label gọi là size? Giả sử đã set label và pod đã được lập lịch — nếu ai đó thay đổi label trên node tại một thời điểm trong tương lai thì sao? Pod có tiếp tục chạy trên node đó không?
Tất cả những câu hỏi này được trả lời bởi thuộc tính dài như một câu văn dưới nodeAffinity — đó chính là loại (type) của node affinity. Loại node affinity định nghĩa hành vi của scheduler đối với node affinity và các giai đoạn trong vòng đời của Pod.
Hiện tại có hai loại node affinity:
requiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution
Và có thêm một loại node affinity được lên kế hoạch cho tương lai: requiredDuringSchedulingRequiredDuringExecution — tính đến nay vẫn chưa được triển khai (xem ghi chú ở mục Nguồn tham khảo).
💡 Hình dung:
requiredDuringSchedulingIgnoredDuringExecutiongiống một yêu cầu tuyển dụng bắt buộc "phải có bằng đại học" — không có bằng thì hồ sơ bị loại thẳng, dù vị trí đó đang thiếu người.preferredDuringSchedulingIgnoredDuringExecutiongiống một tiêu chí ưu tiên "ưu tiên ứng viên có bằng đại học" — có thì tốt, nhưng nếu không tìm được ứng viên nào đủ điều kiện đó, vẫn tuyển người khác để lấp chỗ trống. Còn phần "IgnoredDuringExecution" giống việc một khi đã ký hợp đồng (Pod đã được lập lịch), công ty đổi tiêu chí tuyển dụng sau đó cũng không khiến nhân viên hiện tại bị đuổi.
Có hai trạng thái trong vòng đời của một Pod khi xét đến node affinity: during scheduling và during execution.
During scheduling là trạng thái khi pod chưa tồn tại và được tạo lần đầu. Đã ghi nhận rằng khi một pod được tạo lần đầu, các affinity rule được chỉ định sẽ được xem xét để đặt pod lên đúng node. Nhưng nếu không có node nào khớp label thì sao — ví dụ quên label node là large? Đó là lúc loại node affinity được dùng phát huy tác dụng. Nếu chọn loại required (loại đầu tiên), scheduler sẽ bắt buộc pod phải được đặt lên node có affinity rule đã cho. Nếu không tìm được node như vậy, Pod sẽ không được lập lịch. Loại này được dùng trong các trường hợp mà việc đặt pod là cực kỳ quan trọng — nếu không có node khớp, pod sẽ không được lập lịch.
Nhưng nếu việc đặt pod ít quan trọng hơn việc chạy được workload, có thể set thành preferred. Trong trường hợp không tìm được node khớp, scheduler sẽ đơn giản bỏ qua node affinity rule và đặt pod lên bất kỳ node nào có sẵn. Đây là cách nói với scheduler: "cố gắng hết sức để đặt pod lên node khớp, nhưng nếu thực sự không tìm được, cứ đặt ở bất kỳ đâu."
Phần thứ hai của thuộc tính, hay trạng thái còn lại, là DuringExecution. During execution là trạng thái khi một Pod đã đang chạy và có một thay đổi trong môi trường ảnh hưởng đến node affinity, ví dụ như thay đổi label của một node. Ví dụ, giả sử một administrator gỡ bỏ label đã set trước đó là size=Large khỏi node. Vậy điều gì sẽ xảy ra với các pod đang chạy trên node đó?
Như đã thấy, cả hai loại node affinity hiện có đều có giá trị này set là Ignored, nghĩa là pod sẽ tiếp tục chạy và bất kỳ thay đổi nào trong node affinity sẽ không ảnh hưởng đến chúng một khi đã được lập lịch.
Lab: Thực hành Node Affinity
Xác định các label trên node01. Đếm số label trên node01 bằng kubectl describe node01 (thực chất là kubectl describe node node01), xem section labels — có 6 label.
Giá trị của label kubernetes.io/arch trên node01 là gì? Trên node01, giá trị là amd64.
Áp dụng một label color=blue cho node01. Dùng lệnh kubectl label:
kubectl label node node01 color=blue
Xác nhận lại — thấy color=blue đã được set.
Tạo một deployment mới tên blue với image nginx và 3 replicas.
kubectl create deployment blue --image=nginx --replicas=3
Những node nào có thể đặt pod cho deployment blue? Kiểm tra taint trên cả hai node — không có taint nào trên node01 lẫn control plane node. Vậy hiện tại pod có thể được lập lịch lên cả hai node vì không có taint nào trên bất kỳ node nào.
Set node affinity cho deployment để chỉ đặt pod lên node01. Chỉnh sửa deployment: kubectl edit deployment blue. Trên pod specification, cần set node affinity dùng requiredDuringSchedulingIgnoredDuringExecution, với key color và values là blue. Tham khảo trang tài liệu Kubernetes về node affinity để lấy đúng cấu trúc affinity spec, sau đó copy vào đúng vị trí (dưới pod template của Deployment, cần thụt lề đúng — dùng Shift+. để dịch các dòng sang phải trong vi), set key là color, value là blue.
Sau khi lưu và áp dụng, kiểm tra kubectl get pods -o wide — tất cả các pod đều nằm trên node01.
Tạo một deployment mới tên red với image nginx và 2 replicas, đảm bảo được đặt lên control plane node. Khuyến nghị dùng label node-role.kubernetes.io/control-plane, vốn đã được set sẵn trên control plane node (label này không có giá trị được set). Chỉ cần set một node affinity rule nói rằng các pod thuộc deployment red nên được đặt lên một node có label này set — dù label không có giá trị, nếu label đó tồn tại thì đó là nơi cần đặt pod.
kubectl create deployment red --image=nginx --replicas=2 --dry-run=client -o yaml > red.yaml
Chỉnh sửa red.yaml, thêm cùng node affinity rule như trước (copy, dịch thụt lề bằng Shift+>). Đổi key thành label vừa nêu (node-role.kubernetes.io/control-plane). Vì label này không có giá trị, không cần kiểm tra value — bỏ section values, chỉ cần kiểm tra xem label có tồn tại hay không, dùng operator Exists (viết hoa chữ E).
Lưu lại và tạo Deployment — gặp lỗi ở dòng 26: did not find expected key. Vấn đề thực chất nằm ở vài dòng phía trên — do thụt lề chưa đúng, cần dịch dòng đó lùi lại hai ký tự. Sửa và thử lại — thành công.
Kiểm tra pod — các pod mới của deployment red nằm trên control plane node.
7. Kết hợp Taints/Tolerations và Node Affinity
Đã học về taints và tolerations, và node affinity, giờ kết hợp hai khái niệm này qua một bài tập thú vị. Có ba node và ba pod, mỗi cái theo ba màu: blue, red, và green. Mục tiêu cuối cùng là đặt pod blue lên node blue, pod red lên node red, và tương tự với green. Cluster Kubernetes này được chia sẻ với các team khác, vậy nên có các pod khác trong cluster cũng như các node khác. Không muốn bất kỳ pod nào khác được đặt lên node của mình, cũng không muốn pod của mình bị đặt lên các node đó.
Thử giải quyết vấn đề này chỉ với taints và tolerations. Áp taint lên các node, đánh dấu chúng theo màu: blue, red, và green. Sau đó set toleration trên các pod để chịu được màu tương ứng. Khi pod được tạo, các node đảm bảo chỉ chấp nhận pod có toleration đúng. Vậy pod green kết thúc trên node green, và pod blue kết thúc trên node blue. Tuy nhiên, taints và tolerations không đảm bảo rằng các pod chỉ được đặt lên những node này. Vậy pod red có thể kết thúc trên một trong các node khác không có taint hay toleration set — điều này không mong muốn.
Thử giải quyết cùng vấn đề với node affinity. Trước tiên label các node theo màu tương ứng: blue, red, và green. Sau đó set node selector trên các pod để gắn pod với node. Nhờ đó, pod kết thúc trên đúng node. Tuy nhiên, điều đó không đảm bảo rằng các pod khác không được đặt lên những node này. Trong trường hợp này, có khả năng một trong các pod khác kết thúc trên node của mình — điều này cũng không mong muốn.
Vì vậy, kết hợp taints và tolerations cùng với node affinity rule có thể được dùng để dành riêng hoàn toàn các node cho những pod cụ thể. Trước tiên dùng taints và tolerations để ngăn các pod khác được đặt lên node của mình. Sau đó dùng node affinity để ngăn pod của mình được đặt lên những node khác.
Nguồn tham khảo
Nguồn gốc: Khóa "Certified Kubernetes Administrator (CKA)" — module "3. Scheduling", nền tảng KodeKloud.
- Taint/label chuẩn để nhận diện control plane node hiện là
node-role.kubernetes.io/control-plane(thay chonode-role.kubernetes.io/master, deprecated từ Kubernetes v1.20, kubeadm áp dụng label mới từ v1.24) — 2026-09-09, Kubernetes: Well-Known Labels, Annotations and Taints - Label kiến trúc node hiện là
kubernetes.io/arch(thay chobeta.kubernetes.io/arch, deprecated từ v1.14, trở thành GA từ v1.19) — 2026-09-09, Kubernetes: Well-Known Labels, Annotations and Taints - Loại node affinity
requiredDuringSchedulingRequiredDuringExecutionvẫn chỉ là tính năng dự kiến trong tương lai, chưa được triển khai — 2026-09-09, Kubernetes: Assign Pods to Nodes, kubernetes/kubernetes issue #96149