Bốn cơ chế cùng điều khiển việc pod chạy ở đâu, và chúng dễ lẫn vào nhau. Bài này đo từng cái trên cụm thật.
Ba loại vết
sc-worker = NoSchedule sc-worker2 = PreferNoSchedule
sc-worker3 = không vết
Tạo 6 pod không khai toleration nào:
6 pod đều lên sc-worker3
Rồi đổi sc-worker3 thành NoExecute:
mọi pod bị đuổi khỏi sc-worker3 sau 1,3 giây
| Tác dụng | |
|---|---|
NoSchedule |
Chặn pod mới |
PreferNoSchedule |
Cố gắng tránh, vẫn nhận nếu cần |
NoExecute |
Chặn pod mới và đuổi pod đang chạy |
1,3 giây cho NoExecute — nhanh hơn nhiều so với mọi cơ chế đuổi khác trong Kubernetes. So với phần 42: node chết thì mất gần sáu phút; node bị đánh vết thì 1,3 giây.
Khác biệt là ở chỗ Kubernetes biết chắc: bạn vừa nói với nó rằng node này không được chạy pod. Không cần suy đoán gì.
Đây cũng là cơ chế đằng sau việc đuổi pod khi node NotReady — Kubernetes tự thêm vết node.kubernetes.io/unreachable:NoExecute, và tolerationSeconds mặc định 300 giây là con số tạo ra sáu phút ở phần 42.
toleration cho phép, không ép
Thêm toleration:
tolerations:
- {key: vet, operator: Exists}
đủ 6 pod chạy lại sau 2,1 giây
2 sc-worker 3 sc-worker2 2 sc-worker3
Pod rải ra cả ba node — nhưng toleration không ép pod lên node có vết. Nó chỉ cho phép.
Đây là điểm hay nhầm nhất: khai toleration cho node GPU không khiến pod chạy trên node GPU. Nó chỉ khiến pod được phép chạy ở đó. Muốn ép thì cần nodeSelector hoặc nodeAffinity.
Cặp đúng cho node chuyên dụng là cả hai:
nodeSelector: {phan-cung: gpu} # ép pod lên node GPU
tolerations: # cho phép vượt qua vết
- {key: gpu, operator: Exists, effect: NoSchedule}
Vết giữ pod khác ra khỏi node GPU; nodeSelector đưa pod của bạn vào.
podAntiAffinity: cứng chặn, mềm chỉ ưu tiên
5 bản, topologyKey: kubernetes.io/hostname, cụm có 3 worker.
Cứng (requiredDuringSchedulingIgnoredDuringExecution):
3 Running (một mỗi node) 2 Pending
0/4 nodes are available: 3 node(s) didn't match pod anti-affinity rules
Mềm (preferredDuringSchedulingIgnoredDuringExecution):
5 Running
1 sc-worker 2 sc-worker2 2 sc-worker3
Cứng bảo đảm mỗi node đúng một bản, đổi lại số bản bị trần bởi số node. Mềm rải đều hết mức có thể, nhưng không bao giờ để pod Pending.
Với hầu hết ứng dụng, mềm là lựa chọn đúng: bạn muốn rải đều để một node chết không mất hết, nhưng không muốn một sự cố node biến thành pod không chạy được.
Cứng chỉ đúng khi hai bản trên cùng node là sai về nghĩa — ví dụ mỗi bản cần một cổng hostPort riêng.
topologySpreadConstraints: cách hiện đại hơn
podAntiAffinity chỉ nói "không cùng chỗ". topologySpreadConstraints nói mức lệch cho phép:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector: {matchLabels: {app: web}}
maxSkew: 1 nghĩa là chênh lệch giữa vùng nhiều nhất và ít nhất không quá 1. Đó là thứ bạn thật sự muốn — không phải "tối đa một bản mỗi nơi".
whenUnsatisfiable có hai giá trị, tương ứng với cứng và mềm ở trên: DoNotSchedule và ScheduleAnyway.
Với cụm nhiều vùng, đây là công cụ đúng. podAntiAffinity cứng trên zone sẽ giới hạn bạn ở đúng ba bản.
IgnoredDuringExecution nghĩa là gì
Cả hai dạng affinity đều kết thúc bằng IgnoredDuringExecution, và nó có nghĩa đen: luật chỉ áp lúc xếp chỗ, không áp sau đó.
Pod đã chạy sẽ ở nguyên chỗ dù luật bị vi phạm về sau — ví dụ ai đó gỡ nhãn khỏi node, hoặc thêm pod khiến anti-affinity không còn thoả.
Đây là khác biệt cơ bản với vết NoExecute, thứ có áp lúc chạy và đuổi pod ngay.
Bốn cơ chế, một bảng
| Ai điều khiển | Áp lúc nào | |
|---|---|---|
taint + toleration |
node đẩy pod ra | xếp chỗ, và cả lúc chạy với NoExecute |
nodeSelector |
pod chọn node | chỉ xếp chỗ |
nodeAffinity |
pod chọn node, có mức mềm | chỉ xếp chỗ |
podAffinity / podAntiAffinity |
pod chọn theo pod khác | chỉ xếp chỗ |
Hàng đầu là cơ chế từ chối; ba hàng sau là cơ chế thu hút. Node chuyên dụng cần cả hai loại.
Thử ba mươi giây
Xem vết nào đang tồn tại trên cụm của bạn:
kubectl get nodes -o custom-columns=\
TEN:.metadata.name,VET:.spec.taints[*].key,HIEU_LUC:.spec.taints[*].effect
Và pod nào có anti-affinity cứng — tức có thể kẹt Pending khi thiếu node:
kubectl get pods -A -o json | python3 -c '
import sys,json
for p in json.load(sys.stdin)["items"]:
a=(p["spec"].get("affinity") or {}).get("podAntiAffinity",{})
if a.get("requiredDuringSchedulingIgnoredDuringExecution"):
print(p["metadata"]["namespace"], p["metadata"]["name"])
'
Mỗi dòng là một ứng dụng mà số bản bị trần bởi số node — và điều đó chỉ lộ ra khi bạn thử mở rộng.
Phần sau đo PodDisruptionBudget: một dòng cấu hình sai khiến node không bao giờ rút ra bảo trì được.