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.
Demo: tự lập lịch một Pod khi không có scheduler
Mô phỏng đúng tình huống "cluster mất scheduler" — tạo một Pod bình thường rồi tự tay gán node cho nó, dùng file nginx.yaml xuyên suốt demo:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
Tạo Pod và quan sát trạng thái.
kubectl create -f nginx.yaml
kubectl get pods
# Output:
NAME READY STATUS RESTARTS AGE
nginx 0/1 Pending 0 5s
Pod kẹt ở Pending. Kiểm tra xem scheduler có đang chạy không — đây chính là nguyên nhân phổ biến nhất của Pending kéo dài không rõ lý do:
kubectl get pods -n kube-system
# Output:
NAME READY STATUS RESTARTS AGE
etcd-master 1/1 Running 0 10d
kube-apiserver-master 1/1 Running 0 10d
kube-controller-manager-master 1/1 Running 0 10d
kube-proxy-lzt6f 1/1 Running 0 10d
Không có dòng kube-scheduler-master nào — đúng như dự đoán, cluster này không có scheduler, nên không ai gán nodeName cho Pod cả.
Tự gán node bằng cách sửa nodeName rồi tạo lại. Vì nodeName chỉ nhận giá trị tại thời điểm tạo Pod, không sửa được trên Pod đang tồn tại, nên phải xóa và tạo lại trong 1 lệnh bằng --force:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
nodeName: node01
kubectl replace --force -f nginx.yaml
kubectl get pods -w
# Output:
NAME READY STATUS RESTARTS AGE
nginx 0/1 ContainerCreating 0 3s
nginx 1/1 Running 0 8s
kubectl get pods -w (watch) tự cập nhật trạng thái theo thời gian thực, khỏi phải chạy lại lệnh liên tục.
Muốn chuyển Pod này sang control plane node thì sao? Không có cách nào "di chuyển" một Pod đang chạy — về bản chất Pod là 1 process đang sống trên node đó, không di chuyển nguyên trạng một process từ máy này sang máy khác được. Cách duy nhất vẫn là sửa nodeName rồi replace --force y hệt trên, chấp nhận việc Pod cũ bị xóa và Pod mới được tạo lại từ đầu trên node đích:
kubectl replace --force -f nginx.yaml
kubectl get pods -o wide
# Output:
NAME READY STATUS RESTARTS AGE IP NODE
nginx 1/1 Running 0 6s 10.244.0.9 master
Cột NODE xác nhận Pod đã "chuyển nhà" sang control plane — thực chất là bị xóa và tạo lại, không phải di chuyển.
Lập lịch thủ công chỉ là biện pháp chữa cháy khi không có scheduler. Trong vận hành bình thường, câu hỏi thật sự không phải "gán node bằng tay thế nào" mà là "làm sao nhóm và lọc đúng tập Pod cần thao tác giữa hàng trăm object khác nhau" — đó là việc của labels và selectors.
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 = mammal, sẽ được danh sách các loài mammal. Khi nói color = 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 = 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.
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).
Demo: lọc object bằng label selector
Giả sử cluster đang chạy nhiều Pod được gắn sẵn label tier, env, và bu (business unit) — tình huống hay gặp khi nhiều team share chung 1 cluster.
Lọc theo đúng 1 label.
kubectl get pods --selector env=dev
# Output:
NAME READY STATUS RESTARTS AGE
webapp-dev-1 1/1 Running 0 2h
webapp-dev-2 1/1 Running 0 2h
...
Đếm nhanh số kết quả mà không dính dòng header — thêm --no-headers trước khi đưa vào wc -l:
kubectl get pods --selector env=dev --no-headers | wc -l
# Output:
7
Lọc theo tài nguyên khác Pod. Selector không chỉ áp dụng cho Pod — dùng all để lọc trên mọi loại object (Pod, Service, ReplicaSet,...) trong cùng namespace:
kubectl get all --selector env=prod --no-headers | wc -l
# Output:
7
Kết hợp nhiều điều kiện cùng lúc — phân cách bằng dấu phẩy, Kubernetes tự hiểu là AND (phải khớp tất cả):
kubectl get pods --selector env=prod,bu=finance,tier=frontend
# Output:
NAME READY STATUS RESTARTS AGE
prod-fe-zxdf1234 1/1 Running 0 3h
Selector cũng chính là cơ chế ReplicaSet dùng để nhận diện Pod — nếu selector không khớp template.metadata.labels, tạo ReplicaSet sẽ báo lỗi:
kubectl create -f replicaset-definition.yaml
# Output:
The ReplicaSet "myapp-replicaset" is invalid: spec.selector: Invalid value:
metav1.LabelSelector{...}: selector does not match template labels
Đây là lỗi validation, không phải bug — sửa 1 trong 2 chỗ (selector hoặc label trong template) cho khớp nhau rồi tạo lại là xong.
Đủ để lọc đúng tập Pod cần thao tác — nhưng lọc để xem là một chuyện, còn ngăn scheduler đặt sai Pod lên sai node lại là chuyện khác. Đó là việc của taints và tolerations.
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 hình dung qua một tình huống quen thuộc ở văn phòng để dễ nắm bản chất.
💡 Hình dung: Một khu bàn trong canteen công ty được dán biển "Chỉ dành cho đội Vận hành" — đó chính là taint đặt trên khu bàn (node). Nhân viên phòng ban khác đi ngang qua, thấy biển, sẽ tự động chọn bàn khác — mặc định không ai được phép ngồi vào khu có biển cấm. Nhưng ai đang cầm thẻ xác nhận thuộc đội Vận hành (toleration khớp đúng với taint đó) thì vẫn ngồi vào khu bàn này bình thường, biển cấm không có tác dụng với riêng người đó. Vậy có hai yếu tố quyết định một pod (nhân viên) có "ngồi" được vào một node (khu bàn) hay không: taint đặt trên node, và toleration của pod đối với đúng taint đó.
Quay lại Kubernetes: khu bàn tương ứng với node, còn nhân viên tương ứng với pod. 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 effect, và điểm mấu chốt để phân biệt chúng là đặt đúng hai câu hỏi: effect này có chặn pod mới không? và effect này có đuổi pod đang chạy đi không?
NoSchedule — chặn pod mới, không đụng pod cũ. Scheduler sẽ không đặt pod mới lên node trừ khi pod có toleration khớp. Nhưng mọi pod đã chạy sẵn trên node trước khi taint được thêm vào thì vẫn tiếp tục chạy bình thường, kể cả khi chúng không hề có toleration. Đây là effect được dùng nhiều nhất, và cũng là effect an toàn nhất khi muốn "khoanh vùng" một node mà không gây gián đoạn gì.
PreferNoSchedule — một lời gợi ý, không phải luật. Scheduler sẽ cố gắng tránh node này, nhưng nếu không còn node nào khác phù hợp thì nó vẫn đặt pod lên đây. Đây là phiên bản "mềm" của NoSchedule: thay vì loại node ra khỏi danh sách ứng viên, scheduler chỉ trừ điểm node đó khi chấm điểm. Không có gì đảm bảo cả. Dùng khi muốn bày tỏ một ưu tiên mà không chấp nhận rủi ro pod bị Pending vô thời hạn vì không tìm được chỗ.
NoExecute — chặn pod mới và đuổi pod đang chạy. Đây là effect duy nhất tác động ngược lại vào những pod đã nằm sẵn trên node: ngay khi taint có hiệu lực, mọi pod không có toleration khớp sẽ bị evict — tức là bị kill và (nếu thuộc một ReplicaSet/Deployment) được tạo lại ở node khác. Đây cũng là effect dễ gây sự cố nhất nếu gõ nhầm, vì hậu quả xảy ra tức thì trên workload đang phục vụ.
| Effect | Pod mới không có toleration | Pod đang chạy không có toleration | Dùng khi nào |
|---|---|---|---|
NoSchedule | Bị từ chối, không lên được node | Vẫn chạy yên ổn | Khoanh vùng node cho một nhóm workload, không muốn làm phiền thứ đang chạy |
PreferNoSchedule | Cố tránh, nhưng vẫn có thể lên | Vẫn chạy yên ổn | Chỉ muốn gợi ý ưu tiên, chấp nhận scheduler phá lệ khi bí chỗ |
NoExecute | Bị từ chối, không lên được node | Bị evict khỏi node | Cần dọn sạch node ngay, hoặc node đang có vấn đề và phải đẩy workload đi |
💡 Hình dung: Vẫn là khu bàn canteen có biển cấm.
NoSchedulelà tấm biển dựng ở lối vào — ai chưa ngồi thì không được vào nữa, còn ai đã ngồi sẵn bên trong thì cứ ăn nốt bữa của mình.PreferNoSchedulelà tấm biển ghi "ưu tiên nhường đội Vận hành" — người khác thấy thì tránh, nhưng hết bàn thì vẫn ngồi đại.NoExecutelà bảo vệ đi vào tận nơi, mời tất cả những người không có thẻ đứng dậy ra ngoài ngay lập tức.
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.
Equal và Exists — hai kiểu so khớp toleration
Field operator quyết định toleration so khớp với taint chặt đến mức nào, và nó chỉ nhận đúng hai giá trị.
Equal là giá trị mặc định khi không khai báo gì: toleration chỉ khớp khi cả key, value và effect đều trùng với taint. Đây là kiểu dùng trong ví dụ ở trên.
Exists lỏng hơn: chỉ cần key và effect trùng là khớp, không quan tâm value là gì. Khi dùng Exists thì không được khai báo value — đây là lỗi cú pháp hay gặp.
tolerations:
- key: "app"
operator: "Exists"
effect: "NoSchedule"
Toleration trên khớp với mọi taint có key app và effect NoSchedule, bất kể value là blue, red hay gì đi nữa.
Hai trường hợp đặc biệt đáng nhớ, vì chúng hay xuất hiện trong manifest của các system component:
- Bỏ trống
keykèmoperator: "Exists"— khớp với mọi taint, bất kể key và value. Đây là cách các DaemonSet hạ tầng (CNI plugin, log agent, monitoring agent) tự cho phép mình chạy trên mọi node kể cả node bị taint. - Bỏ trống
effect— khớp với mọi effect của đúng key đó, thay vì phải liệt kê từng effect một.
tolerations:
- operator: "Exists"
Đúng hai dòng đó nghĩa là "pod này chịu được tất cả mọi taint trong cluster". Rất tiện cho DaemonSet hạ tầng, và rất nguy hiểm nếu vô tình copy vào một application pod bình thường — pod đó sẽ len lỏi lên được cả những node đang bị cô lập vì sự cố.
Nhiều taint trên một node, nhiều toleration trên một pod
Một node hoàn toàn có thể mang nhiều taint cùng lúc, và một pod cũng có thể khai báo nhiều toleration cùng lúc. Trong thực tế đây mới là tình huống phổ biến, chứ không phải trường hợp một-taint-một-toleration gọn gàng ở trên.
Vậy khi node có ba taint mà pod chỉ chịu được hai thì sao? Kubernetes xử lý việc này như một bộ lọc: lấy toàn bộ taint đang có trên node, gạch đi những taint mà pod có toleration khớp, rồi xem những taint còn sót lại nói gì. Chỉ những taint còn sót lại mới có tác dụng.
Sau khi lọc xong, ba quy tắc được áp dụng theo đúng thứ tự sau:
- Nếu còn ít nhất một taint
NoSchedulechưa được tolerate, pod không được lập lịch lên node. - Nếu không còn taint
NoSchedulenào nhưng còn ít nhất một taintPreferNoSchedule, Kubernetes sẽ cố tránh đặt pod lên node đó. - Nếu còn ít nhất một taint
NoExecutechưa được tolerate, pod sẽ bị evict khỏi node nếu đang chạy, và không được lập lịch lên node nếu chưa chạy.
Điểm tinh tế nhất nằm ở chỗ ba quy tắc này hoạt động độc lập với nhau. Một pod hoàn toàn có thể rơi vào tình trạng "không được lên node mới, nhưng nếu đã chạy rồi thì vẫn không bị đuổi" — nghe mâu thuẫn, nhưng hoàn toàn hợp lý khi nhìn vào bộ lọc.
Ví dụ cụ thể. Taint node node1 ba lần liên tiếp:
kubectl taint nodes node1 key1=value1:NoSchedule
kubectl taint nodes node1 key1=value1:NoExecute
kubectl taint nodes node1 key2=value2:NoSchedule
Kiểm tra lại xem node đang mang những taint nào:
kubectl describe node node1 | grep -A4 Taints
# Output minh họa
# Taints: key1=value1:NoSchedule
# key1=value1:NoExecute
# key2=value2:NoSchedule
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Chú ý rằng key1=value1 xuất hiện hai lần với hai effect khác nhau. Đây là điều hợp lệ: cùng một cặp key-value nhưng khác effect thì được tính là hai taint riêng biệt, và pod muốn được miễn trừ cả hai thì phải khai báo đủ hai toleration.
Bây giờ tạo một pod chỉ chịu được hai taint đầu:
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
Chạy bộ lọc bằng tay: hai taint key1=value1 bị gạch đi vì pod có toleration khớp. Còn lại đúng một taint chưa được tolerate — key2=value2:NoSchedule.
Kết quả: pod không thể được lập lịch lên node1, vì quy tắc 1 vẫn bị kích hoạt bởi key2=value2:NoSchedule. Nhưng nếu pod này đã chạy sẵn trên node1 từ trước khi các taint được thêm vào, nó sẽ không bị evict — vì taint NoExecute duy nhất trên node đã được pod tolerate, nên không còn taint NoExecute nào sót lại để kích hoạt quy tắc 3.
Đúc kết đáng nhớ nhất của mục này: pod phải tolerate toàn bộ taint trên node thì mới lên được node đó, chứ không phải chỉ cần khớp một cái là đủ. Chỉ cần sót một taint chưa được tolerate là pod bị chặn — và đây là nguyên nhân rất hay gặp khi một pod cứ mãi ở trạng thái Pending dù nhìn qua thì "đã thêm toleration rồi mà".
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.
tolerationSeconds — chịu được, nhưng chỉ trong một khoảng thời gian. Mặc định, quan hệ với một taint NoExecute là chuyện được ăn cả ngã về không: pod không tolerate thì bị evict ngay lập tức, còn pod có tolerate thì ở lại mãi mãi. Riêng với effect NoExecute, toleration có thêm một field tùy chọn là tolerationSeconds để diễn đạt ý ở giữa — "tôi chịu được taint này, nhưng chỉ trong chừng này giây thôi".
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoExecute"
tolerationSeconds: 3600
Pod này đang chạy trên node, node bị gắn taint khớp — pod không bị đuổi ngay, mà được ở lại thêm 3600 giây. Hết khoảng đó, node lifecycle controller mới evict nó. Và nếu taint được gỡ khỏi node trước khi đồng hồ chạy hết, pod không bị evict nữa. Rất hợp cho những sự cố thoáng qua, khi đuổi workload đi ngay còn tệ hơn là chờ thêm vài phút xem node có tự hồi phục không.
Một ví dụ về nhiều taint mà bạn đang dùng hằng ngày mà không biết. Khi một node mất liên lạc, node controller tự gắn lên nó các taint node.kubernetes.io/not-ready:NoExecute và node.kubernetes.io/unreachable:NoExecute. Theo đúng lý thuyết ở mục trên, mọi pod trên node đó lẽ ra phải bị evict ngay tức khắc. Nhưng thực tế thì pod thường trụ lại khoảng 5 phút — vì sao?
Vì có một admission controller tên DefaultTolerationSeconds tự động chèn vào mọi pod hai toleration cho đúng hai taint đó, với tolerationSeconds: 300, nếu pod chưa tự khai báo. Nói cách khác, gần như mọi pod trong cluster đều đang mang sẵn nhiều toleration hơn bạn viết trong YAML. Kiểm chứng rất nhanh trên một pod bất kỳ:
kubectl get pod nginx -o jsonpath='{.spec.tolerations}' | python3 -m json.tool
# Output minh họa
# [
# {
# "effect": "NoExecute",
# "key": "node.kubernetes.io/not-ready",
# "operator": "Exists",
# "tolerationSeconds": 300
# },
# {
# "effect": "NoExecute",
# "key": "node.kubernetes.io/unreachable",
# "operator": "Exists",
# "tolerationSeconds": 300
# }
# ]
(Output minh họa — cấu trúc và tên field đúng theo lệnh thật, giá trị cụ thể chỉ mang tính ví dụ.)
Để ý rằng hai toleration này dùng operator: "Exists" chứ không phải Equal — vì hai taint đó không mang value, chỉ có key. Con số 300 giây cũng chính là lời giải thích cho một câu hỏi rất hay gặp lúc vận hành: vì sao node đã NotReady rồi mà pod trên đó vẫn nằm im chừng năm phút mới thấy bị tạo lại ở chỗ khác.
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.
Demo: taint node và toleration của Pod
Kịch bản: cluster 2 node (control-plane và node01), cả hai ban đầu chưa có taint tự đặt thêm nào (ngoài taint mặc định trên control plane đã nói ở trên).
Taint node01, rồi thử tạo Pod bình thường (không có toleration).
kubectl taint node node01 spray=mortein:NoSchedule
# Output:
node/node01 tainted
kubectl run mosquito --image=nginx
kubectl get pods
# Output:
NAME READY STATUS RESTARTS AGE
mosquito 0/1 Pending 0 10s
mosquito kẹt ở Pending vì cả 2 node hiện đều "chặn" nó — node01 có taint mới, control plane có taint mặc định. Xác nhận bằng describe:
kubectl describe pod mosquito
# Output (rút gọn):
Warning FailedScheduling ... 0/2 nodes are available: 2 node(s) had untolerated taint.
Tạo Pod có toleration khớp đúng taint đó. Vì --dry-run=client không nhận trực tiếp field toleration qua flag, generate khung YAML rồi tự thêm:
kubectl run bee --image=nginx --dry-run=client -o yaml > bee.yaml
apiVersion: v1
kind: Pod
metadata:
name: bee
spec:
containers:
- name: bee
image: nginx
tolerations:
- key: "spray"
operator: "Equal"
value: "mortein"
effect: "NoSchedule"
kubectl apply -f bee.yaml
kubectl get pods -o wide
# Output:
NAME READY STATUS RESTARTS AGE NODE
mosquito 0/1 Pending 0 2m <none>
bee 1/1 Running 0 8s node01
bee chịu được taint nên vào được node01; mosquito vẫn Pending vì chưa có toleration nào.
Gỡ taint mặc định trên control plane — mosquito khi đó chỉ còn đúng 1 node "mở cửa":
kubectl taint node controlplane node-role.kubernetes.io/control-plane:NoSchedule-
# Output:
node/controlplane untainted
Dấu - ở cuối là cú pháp xóa taint (giống hệt cú pháp tạo, chỉ thêm -). Kiểm tra lại:
kubectl get pods -o wide
# Output:
NAME READY STATUS RESTARTS AGE NODE
mosquito 1/1 Running 0 5m controlplane
bee 1/1 Running 0 3m node01
mosquito tự động chuyển sang Running trên control plane — không cần làm gì thêm, vì Kubernetes liên tục cố lập lịch lại các Pod đang Pending mỗi khi điều kiện thay đổi. Đúng như diagram ở trên minh họa: taint chỉ chặn cửa, không hề chỉ đường — Pod nào không bị chặn ở đâu thì có thể rơi vào đó.
Taint/toleration giải quyết được "ai không được vào node này". Nhưng còn bài toán ngược lại — "Pod này phải vào đúng node kia" — thì cần một cơ chế khác: node selector.
5. Node Selectors
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 khóa học 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
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.
Demo: dùng Node Affinity thay cho Node Selector
Cluster 2 node (node01, controlplane), muốn ghim Deployment vào đúng node theo label.
Gắn label và tạo Deployment.
kubectl label node node01 color=blue
kubectl create deployment blue --image=nginx --replicas=3
# Output:
node/node01 labeled
deployment.apps/blue created
Chưa có affinity rule nào, nên cả 3 Pod có thể rơi vào bất kỳ node nào (không node nào bị taint).
Thêm node affinity để ghim cứng vào node01 — sửa trực tiếp Deployment đang chạy:
kubectl edit deployment blue
spec:
template:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: color
operator: In
values:
- blue
kubectl get pods -o wide
# Output:
NAME READY STATUS RESTARTS AGE NODE
blue-7d8f9c6b5d-2n5kp 1/1 Running 0 10s node01
blue-7d8f9c6b5d-8qz4t 1/1 Running 0 10s node01
blue-7d8f9c6b5d-x6vwr 1/1 Running 0 10s node01
Cả 3 Pod đều rơi đúng vào node01.
Ghim vào control plane bằng đúng label nó vốn đã có sẵn — không cần tự gắn label mới, dùng thẳng node-role.kubernetes.io/control-plane (label không có giá trị, nên dùng operator Exists thay vì In):
kubectl create deployment red --image=nginx --replicas=2 --dry-run=client -o yaml > red.yaml
# thêm vào dưới spec.template.spec của red.yaml
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: Exists
kubectl create -f red.yaml
kubectl get pods -o wide
# Output:
NAME READY STATUS RESTARTS AGE NODE
red-6b7d4f9c8d-abc12 1/1 Running 0 8s controlplane
red-6b7d4f9c8d-xyz34 1/1 Running 0 8s controlplane
⚠️ Lỗi hay gặp: YAML dưới
matchExpressionsrất dễ sai thụt lề khi copy-paste từ tài liệu — lỗi thường gặp nhất làdid not find expected key, đa phần do 1 dòng lùi/thụt sai 1-2 ký tự so với dòng anh em của nó. Soi kỹ đúng cột thẳng hàng trước khi tạo lại.
Taint/toleration ngăn Pod lạ vào node của mình; node affinity ghim đúng Pod của mình vào đúng node. Nhưng dùng riêng lẻ, mỗi cơ chế đều có lỗ hổng — phần cuối kết hợp cả hai để bịt kín hoàn toàn.
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)" — phần "Scheduling" (Manual Scheduling: lập lịch thủ công qua nodeName và Binding object; Labels, Selectors và Annotations; Taints và Tolerations; Node Selectors; Node Affinity; kết hợp Taints/Tolerations và Node Affinity), nền tảng KodeKloud. Giảng viên: Mumshad Mannambeth.
Repo ghi chú, link tài liệu, và đáp án các practice question của toàn bộ khóa học: kodekloudhub/certified-kubernetes-administrator-course.
Fact-check:
- 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 - Effect
NoSchedulekhông evict pod đang chạy,PreferNoSchedulelà phiên bản "mềm" không đảm bảo,NoExecuteevict pod đang chạy không có toleration khớp — 2026-09-23, Kubernetes: Taints and Tolerations - Kubernetes xử lý nhiều taint và toleration như một bộ lọc: bỏ qua các taint đã được tolerate, các taint còn lại mới có hiệu lực; còn
NoSchedulechưa tolerate thì pod không được lập lịch, cònNoExecutechưa tolerate thì pod bị evict — 2026-09-23, Kubernetes: Taints and Tolerations - Operator
Equalyêu cầu khớp cả key, value và effect; operatorExistschỉ khớp key và effect và không được khai báovalue;keyrỗng kèmExistskhớp mọi taint,effectrỗng khớp mọi effect của key đó — 2026-09-23, Kubernetes: Taints and Tolerations - Field
tolerationSecondschỉ áp dụng cho effectNoExecute, giữ pod ở lại node trong khoảng thời gian khai báo rồi mới bị evict; nếu taint được gỡ trước khi hết hạn thì pod không bị evict — 2026-09-23, Kubernetes: Taints and Tolerations - Admission controller
DefaultTolerationSecondstự thêm toleration chonode.kubernetes.io/not-ready:NoExecutevànode.kubernetes.io/unreachable:NoExecutevớitolerationSecondsmặc định 300 giây, cấu hình qua--default-not-ready-toleration-secondsvà--default-unreachable-toleration-seconds— 2026-09-23, Kubernetes: Admission Controllers Reference