Kubernetes' scheduler has more than one strategy for picking a node. The
default tends to SPREAD pods out for resilience, but a "bin-packing" strategy
(Kubernetes calls its version MostAllocated) does the opposite on purpose:
when you want to consolidate load onto fewer nodes -- to let a cluster
autoscaler safely scale down the now-empty ones -- prefer the node that would
end up FULLEST after placing the pod, not the emptiest.
Implement select_node(nodes, pod_request): nodes is a list of
{"name":..., "cpu": free_cpu, "mem": free_mem, "taints": [...]} dicts (free
capacity, not total capacity; "taints" is optional). pod_request is
{"cpu":..., "mem":..., "tolerations": [...]} ("tolerations" is optional).
A node only qualifies if BOTH:
- It has ENOUGH free capacity in BOTH
cpuandmemfor the pod. - Every one of its taints with
"effect": "NoSchedule"is tolerated: for each such taint{"key": k, "value": v, "effect": "NoSchedule"},pod_request's tolerations must contain a matching{"key": k, "value": v}. (Taints with any other effect are out of scope here and never block scheduling.)
Among qualifying nodes, pick the one with the SMALLEST combined remaining
capacity after hypothetically placing the pod ((node.cpu - pod.cpu) + (node.mem - pod.mem)) -- the tightest fit. Return that node's name, or
None if no node qualifies.
The missing link: "fits" isn't just a resource-capacity question
A resource-only version of this scheduler is dangerously incomplete: it would happily pack pods onto nodes that are physically reserved for something else -- a GPU node meant only for ML workloads, a node under maintenance, a node dedicated to a specific tenant. Kubernetes models "this node rejects pods unless they explicitly opt in" with taints (on the node) and tolerations (on the pod): a taint repels every pod that doesn't carry a matching toleration, regardless of how much free capacity the node has. Resource fit and taint tolerance are two INDEPENDENT filters -- a pod must pass both, and plenty of capacity on one axis says nothing about the other.
Example
nodes = [{"name": "roomy", "cpu": 100, "mem": 100}, {"name": "tight", "cpu": 12, "mem": 12}]
select_node(nodes, {"cpu": 10, "mem": 10}) # -> "tight" (remaining: 4, vs roomy's 180)
gpu_node = {"name": "gpu", "cpu": 100, "mem": 100,
"taints": [{"key": "gpu", "value": "true", "effect": "NoSchedule"}]}
select_node([gpu_node], {"cpu": 10, "mem": 10}) # -> None: taint not tolerated
Constraints
- A node that's short on EITHER resource must be excluded entirely, even if it has plenty of the other.
- A node with an untolerated
NoScheduletaint must be excluded entirely, even if it fits the pod's resource request perfectly. - On a tie (equal remaining capacity among qualifying nodes), pick whichever qualifying node came first in the input list.
Real-world grounding
- Kubernetes'
NodeResourcesFit/MostAllocatedscoring plugin:pkg/scheduler/framework/plugins/noderesources/ - The
TaintTolerationfilter plugin (the real version of this exercise's taint/toleration check):pkg/scheduler/framework/plugins/tainttoleration/
The real scheduler scores across many more dimensions (multiple resource types, pod affinity/anti-affinity) and combines several scoring plugins with weights -- but "does it fit, is it tolerated, and how tightly" is the core computation this exercise implements.