nodeSelector says "exactly these labels". Real fleets need "one of these zones", "any node with a GPU label at all", or "prefer the fast disks but run anywhere". That is node affinity.
Required
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: zone
operator: In
values: [east, west]required… is a filter, like nodeSelector, with operators: In, NotIn, Exists, DoesNotExist, Gt, Lt. The expressions inside one term are ANDed; several terms in the list are ORed. A node that fails every term is out, and the Pending message is the same one nodeSelector produces.
Preferred
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: disk
operator: In
values: [nvme]preferred… does not filter. Each matching preference adds its weight to the node's score, so the Pod goes to an nvme node when one has room, and anywhere else when none does.
The long names
RequiredDuringScheduling means evaluated when the Pod is placed. IgnoredDuringExecution means a running Pod is left alone if the node's labels change afterwards. Relabel a node and nothing moves until a Pod is recreated.
Reading it
kubectl get pod market-db -o jsonpath='{.spec.affinity.nodeAffinity}'
kubectl describe pod market-dbDescribe shows Node-Selectors: for nodeSelector but not affinity; the jsonpath, or -o yaml, does.