6. Scheduling

Bin-Packing Scheduler

seniordistributed-systemsalgorithmic-optimization

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 cpu and mem for 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 NoSchedule taint 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

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.

hints
bin_packing_scheduler.pysign in to run
Loading...
test result

This is a preview. Sign in to run this exercise against the real tests.