<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Scheduling · XB Field Notes</title><link>https://www.xbcatm.com/tags/scheduling/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://www.xbcatm.com/tags/scheduling/index.xml" rel="self" type="application/rss+xml"/><item><title>Node affinity places workloads on matching nodes</title><link>https://www.xbcatm.com/posts/k8s-node-affinity-pins-workloads/</link><pubDate>Thu, 09 Dec 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/k8s-node-affinity-pins-workloads/</guid><description>Affinity constraints steer pods to nodes carrying certain labels, so a workload lands where disk, GPUs, or isolation properties exist.</description><content:encoded>&lt;p&gt;Affinity is placement matching: prefer a label or force it, and use anti-affinity to keep replicas spread apart for resilience. Hardware is never uniform for long — a GPU pool here, fast local NVMe there, an isolated zone for regulated data — and workloads quietly depend on landing in the right place. Without explicit rules, the scheduler spreads pods by free capacity alone, and the pod that needs a GPU lands on the node that has none, failing at runtime instead of at schedule time.&lt;/p&gt;
&lt;p&gt;Affinity moves that failure earlier, where it belongs. Stated as data on nodes (labels) matched against rules on pods, placement becomes reviewable configuration rather than tribal knowledge about &amp;ldquo;which nodes are the special ones.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="match-nodes-by-label"&gt;Match nodes by label&lt;/h2&gt;
&lt;p&gt;Affinity rules read node labels, so you can state that a pod should, or must, run only on nodes carrying a particular label. Put a stable label on your nodes once, then let scheduling match against it. Labels like &lt;code&gt;accelerator=nvidia-a100&lt;/code&gt;, &lt;code&gt;storage=nvme&lt;/code&gt;, or &lt;code&gt;zone=regulated&lt;/code&gt; describe durable properties of the machine, and because they live on the node object, every pod rule referencing them stays valid across node replacements — new hardware gets the same label, old rules keep working.&lt;/p&gt;
&lt;p&gt;Choose label names with the same care as API fields, because renaming a widely referenced label is a migration across every manifest that matches it. Validate labels in CI the way you validate image tags — a pod requiring a label no node carries is a typo that should fail the pipeline, not the 3 a.m. deploy.&lt;/p&gt;
&lt;h2 id="prefer-or-require"&gt;Prefer or require&lt;/h2&gt;
&lt;p&gt;Preferred rules are best-effort and balance load when possible, while required rules hard-block a pod that cannot satisfy the label. Choose required only when placement genuinely matters; otherwise preference keeps the scheduler flexible. The cost model is straightforward: required rules trade availability for correctness — if no matching node has room, the pod stays Pending rather than running in the wrong place — while preferred rules trade correctness for availability, landing somewhere suboptimal but alive.&lt;/p&gt;
&lt;p&gt;Most estates want required for the few hard constraints (GPU present, compliance zone, architecture) and preferred for everything soft (spread across racks, favor the newer instance family). Review required rules quarterly, because hardware that was scarce last year may be plentiful now, and a required rule nobody remembers is how a cluster with free capacity stops scheduling.&lt;/p&gt;
&lt;h2 id="anti-affinity-splits-replicas"&gt;Anti-affinity splits replicas&lt;/h2&gt;
&lt;p&gt;Anti-affinity looks at other pods, spreading replicas of one workload across different nodes so a single node failure stings less. Pair it with replicas and topology keys so the split is meaningful rather than cosmetic. &lt;code&gt;topologyKey: kubernetes.io/hostname&lt;/code&gt; spreads across machines; &lt;code&gt;topologyKey: topology.zone&lt;/code&gt; spreads across failure domains.&lt;/p&gt;
&lt;p&gt;Required anti-affinity deserves the same caution as required affinity: with three replicas and a required hostname spread, the fourth node going down is fine but the workload can never exceed the node count, and a two-node cluster cannot host it at all. For most services, preferred anti-affinity with a high weight gives nearly all the resilience with none of the scheduling fragility. Reserve the required form for the handful of workloads — quorum members, single-leader pairs — where co-location is genuinely worse than Pending.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;The signature failure is the permanently Pending pod: events show &lt;code&gt;didn't match pod affinity rules&lt;/code&gt; while nodes sit half empty. Causes are almost always label-side — the node label was never applied, was renamed, or carries a different value than the rule expects (&lt;code&gt;nvme&lt;/code&gt; vs &lt;code&gt;nvme-ssd&lt;/code&gt;). &lt;code&gt;kubectl get nodes -L hardware,workload-class&lt;/code&gt; shows the actual label surface in one table; diff it against the pod&amp;rsquo;s requirements and the mismatch usually confesses immediately.&lt;/p&gt;
&lt;p&gt;The subtler failure is the satisfied-but-wrong placement: preferred rules silently lose to capacity pressure, and over weeks the GPU workloads drift onto shared nodes while the GPU pool idles. Preferred means the scheduler may ignore you, and under pressure it will. Audit placement periodically against intent — a query grouping pods by workload and node label — and promote the rules that keep drifting from preferred to required, or fix the capacity shortage that forces the compromise.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;See labels, requirements, and actual placement together:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get nodes -L accelerator,workload-class --no-headers
kubectl describe pod api-0 | grep -A 8 &amp;#34;Node-Selectors\|Affinity&amp;#34;
kubectl get pods -o wide -l app=api | awk &amp;#39;{print $1, $7}&amp;#39; | sort | uniq -c | sort -rn | head
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first shows the label surface, the second shows what one pod demands, and the third proves whether replicas actually spread or quietly piled onto one node.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/"&gt;Kubernetes node affinity docs&lt;/a&gt;, node affinity semantics, checked 2026-06-10.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/"&gt;Kubernetes taints and tolerations&lt;/a&gt;, documents repelling pods from specialized nodes, checked 2026-09-05.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Requests and limits set a Pod's floor and cap</title><link>https://www.xbcatm.com/posts/kubernetes-requests-limits-floor-cap/</link><pubDate>Thu, 27 May 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/kubernetes-requests-limits-floor-cap/</guid><description>Requests are the reserved floor used for scheduling, limits are the hard cap; using both well avoids noisy neighbors and surprise kills.</description><content:encoded>&lt;p&gt;The cluster schedules on requests but enforces limits, so a container that sets only one side leaves a half-open contract. Requests-only pods schedule honestly but burst without bound, free to starve neighbors when they spike. Limits-only pods are capped at runtime yet scheduled as if they cost nothing, so the scheduler packs them densely until the node saturates. Either half-contract fails under pressure; the two numbers only work as a pair.&lt;/p&gt;
&lt;p&gt;Think of requests as the booking and limits as the house rules: the booking decides which node has room, the rules decide what happens when the guest gets loud. Set both from measurement and the cluster stays predictable.&lt;/p&gt;
&lt;h2 id="two-numbers-two-jobs"&gt;Two numbers, two jobs&lt;/h2&gt;
&lt;p&gt;A CPU request pledges what the scheduler reserves on a node, while the limit caps the share a container can hit before the cgroup throttles it. Both matter: requests decide placement, limits decide ceiling. CPU is compressible, so breaching the CPU limit only slows the container — throttling, not death. Memory is not: breaching the memory limit kills the process via OOM, with no slowdown phase in between. That asymmetry drives every sane policy: CPU limits can sit well above requests to absorb bursts, while memory limits should hug the measured working set plus headroom.&lt;/p&gt;
&lt;p&gt;Omissions have defaults with teeth, and admission policy can change the exact result. Without a CPU request the scheduler accounts for zero requested CPU unless a LimitRange or an explicit limit supplies a default; the Pod may then be packed too densely and receive little share under contention. Without a memory limit the process can grow until node pressure makes the kubelet reclaim or evict work. Inspect the admitted Pod, not only the source YAML, because namespace defaults may have filled in values you did not write.&lt;/p&gt;
&lt;h2 id="the-oom-corner"&gt;The OOM corner&lt;/h2&gt;
&lt;p&gt;An untuned memory limit is the classic cause of the kernel reclaiming the workload when a spike crosses the advertised cap. If you set a memory limit, give it headroom above the measured working set. The working set is not the average — it is the p95-to-max range across a full cycle including deploys, batch windows, and traffic peaks. Set the limit at average and every routine spike becomes a kill; set it at 2x max and the reservation waste shows up as Pending pods elsewhere. The honest window is max plus twenty to fifty percent, tuned per workload, rechecked after every scaling change.&lt;/p&gt;
&lt;p&gt;Java and other runtime-managed workloads deserve special care: heap plus metaspace plus native plus off-heap must all fit under the limit, not just the -Xmx you remember setting. Size the container limit from the observed process RSS, then work backwards to heap flags — never the reverse.&lt;/p&gt;
&lt;h2 id="start-near-reality"&gt;Start near reality&lt;/h2&gt;
&lt;p&gt;Measure the steady state, set requests near it, keep limits a defensive step above, and revisit whenever the workload shape shifts. Iterating from real usage beats guessing a number that protects nothing. The practical loop: run a week on honest estimates, read p95 usage per container from the metrics server or Prometheus, set requests at that line and limits above max with headroom, then watch throttling and OOM counters for a week. Zero throttling with huge headroom means requests can drop; any throttling on latency-sensitive paths means limits must rise.&lt;/p&gt;
&lt;p&gt;Encode the ratio as team policy rather than folklore: 1:1 memory (Guaranteed QoS) for critical stateful services, 1:2 for web tiers, looser for batch — written down with the workload evidence linked, so the next engineer copies the reasoning along with the numbers.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;The signature failure is the OOMKilled loop: restarts climbing, exit code 137, last week&amp;rsquo;s deploy the only change — and the change was a ten-percent traffic lift against a limit set exactly at old max. Diagnose with the usage-versus-limit graph, not the logs: the process did nothing wrong, it simply outgrew its cap. The fix is headroom, and the prevention is alerting on usage-to-limit ratio crossing eighty percent, which pages days before the kill instead of after.&lt;/p&gt;
&lt;p&gt;The mirror failure is the throttled-to-death service: p99 latency doubled, CPU flat at the limit line, dashboard blaming the application. CPU throttling is invisible to in-container metrics — the app sees idle time it cannot use. When latency degrades with CPU pinned exactly at the limit, raise the limit before profiling the code.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Compare booked versus real per container:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl top pods -n prod --sort-by=memory | head
kubectl get pods -n prod -o custom-columns=NAME:.metadata.name,REQ:.spec.containers[*].resources.requests.memory,LIM:.spec.containers[*].resources.limits.memory --no-headers | head -n 15
kubectl get events -n prod --field-selector reason=OOMKilling --sort-by=.lastTimestamp | tail -n 5
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Usage shows reality, the manifest dump shows the contract, and the event tail shows where the contract already broke.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/"&gt;Kubernetes resource management&lt;/a&gt;, Official doc describing how requests and limits are interpreted by the scheduler, checked 2026-06-01.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/quality-service-pod/"&gt;Kubernetes QoS classes&lt;/a&gt;, explains Guaranteed, Burstable, and BestEffort behavior, checked 2026-09-05.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Pod anti-affinity spreads peers away from each other</title><link>https://www.xbcatm.com/posts/k8s-pod-anti-affinity-spreads-workloads/</link><pubDate>Thu, 18 Mar 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/k8s-pod-anti-affinity-spreads-workloads/</guid><description>Anti-affinity tells the scheduler to avoid placing a pod where its peers already live, useful for spreading replicas across failure domains.</description><content:encoded>&lt;p&gt;Anti-affinity is the scheduler rule that keeps replicas apart. Where affinity says &amp;ldquo;run me next to these pods&amp;rdquo;, anti-affinity says &amp;ldquo;keep me away from them&amp;rdquo;, and the reason is almost always failure domains: if all three replicas land on one node, one node failure is a full outage.&lt;/p&gt;
&lt;p&gt;The tension lives in a single word. &lt;code&gt;required&lt;/code&gt; makes the rule a filter, so a pod with nowhere legal to go stays Pending forever; &lt;code&gt;preferred&lt;/code&gt; makes it a score, so the same rule degrades gracefully. One YAML shape produces either a deadlock or a soft preference depending on which you typed.&lt;/p&gt;
&lt;h2 id="a-rule-is-a-selector-plus-a-topology"&gt;A rule is a selector plus a topology&lt;/h2&gt;
&lt;p&gt;The rule has two parts. A &lt;code&gt;labelSelector&lt;/code&gt; chooses which pods count as peers, and a &lt;code&gt;topologyKey&lt;/code&gt; names the node label that defines a domain. &lt;code&gt;kubernetes.io/hostname&lt;/code&gt; makes every node its own domain; &lt;code&gt;topology.kubernetes.io/zone&lt;/code&gt; groups nodes into zones, so &amp;ldquo;keep apart&amp;rdquo; comes to mean &amp;ldquo;keep to different zones&amp;rdquo;. The &lt;code&gt;namespaces&lt;/code&gt; field defaults to the pod&amp;rsquo;s own namespace, so anti-affinity never accidentally spans tenants.&lt;/p&gt;
&lt;p&gt;Nodes that lack the topologyKey label are not part of any domain. The docs warn that anti-affinity requires consistently labelled nodes and that a missing key leads to unintended behaviour. In practice an unlabelled node is invisible to the rule: neither filtered out nor counted, so it can collect exactly the replicas the rule was meant to spread. One more wrinkle: preferred rules attached to pods that already exist are ignored when a new pod is scheduled. Only the incoming pod&amp;rsquo;s own rules count, so retrofitting a spread rule onto a running workload does not reshuffle what is already placed.&lt;/p&gt;
&lt;h2 id="required-is-a-cap-not-a-guarantee"&gt;Required is a cap, not a guarantee&lt;/h2&gt;
&lt;p&gt;Required anti-affinity is a node filter, so it caps a workload at one replica per domain. Nine replicas across six nodes leaves three Pending forever, no matter how much spare capacity exists. Across three zones, four replicas leave one Pending. Every rejected pod reports FailedScheduling, the autoscaler keeps reporting that it cannot help, and nothing ever errors.&lt;/p&gt;
&lt;p&gt;That is why the docs warn against required anti-affinity except where the hard constraint is real, such as one instance per node for a &lt;code&gt;hostPort&lt;/code&gt; or a local SSD. They also note that inter-pod affinity and anti-affinity need substantial processing and are not recommended above several hundred nodes, which is a second reason to reach for something cheaper. A required rule is also not retroactive: it filters placement at admission time, so a node label that changes later does not evict pods already running, which is the &lt;code&gt;IgnoredDuringExecution&lt;/code&gt; half of the name.&lt;/p&gt;
&lt;h2 id="spread-constraints-say-the-same-thing-more-cheaply"&gt;Spread constraints say the same thing more cheaply&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;topologySpreadConstraints&lt;/code&gt; expresses the same intent with an explicit tolerance. &lt;code&gt;maxSkew&lt;/code&gt; is the permitted difference between the fullest domain and the global minimum; &lt;code&gt;topologyKey&lt;/code&gt; names the domain; &lt;code&gt;whenUnsatisfiable&lt;/code&gt; decides whether an unsatisfiable skew is fatal (&lt;code&gt;DoNotSchedule&lt;/code&gt;, the default) or merely a scoring penalty (&lt;code&gt;ScheduleAnyway&lt;/code&gt;). Nine replicas over three zones with &lt;code&gt;maxSkew: 1&lt;/code&gt; land as three, three, three.&lt;/p&gt;
&lt;p&gt;The docs&amp;rsquo; worked example is worth internalising: two zones holding two and one matching pods, with &lt;code&gt;maxSkew: 1&lt;/code&gt;, forces the next pod into the emptier zone, because placing it in the fuller one makes the skew two. &lt;code&gt;minDomains&lt;/code&gt; raises the global minimum so a cluster with fewer zones than expected cannot call itself balanced, and it is only valid with &lt;code&gt;DoNotSchedule&lt;/code&gt;. The reason to prefer constraints over anti-affinity is cost as much as clarity: spread counts pods per domain, while anti-affinity evaluates a term against every node. Constraints also compose, so one per topology key can spread across zones and nodes at the same time.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;Required anti-affinity with more replicas than domains. The workload scales up to the number of nodes and stops there, every further pod sits Pending, and the horizontal autoscaler keeps asking for more. The same trap appears in a small cluster: a two-node test cluster with three replicas and a required hostname rule can never run the third. Switch to &lt;code&gt;preferred&lt;/code&gt; or to a spread constraint with &lt;code&gt;ScheduleAnyway&lt;/code&gt;, and keep a hard rule only where one pod per node is genuinely required.&lt;/p&gt;
&lt;p&gt;Nodes without the topology label. A node pool that never received &lt;code&gt;topology.kubernetes.io/zone&lt;/code&gt; is outside every domain, so it is invisible to the constraint: replicas pile onto the labelled nodes while a third of the cluster sits unused. Verify with &lt;code&gt;kubectl get nodes -L topology.kubernetes.io/zone&lt;/code&gt; that every node carries a value.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Confirm the topology is real before trusting the spread:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kubectl get nodes -L topology.kubernetes.io/zone -L kubernetes.io/hostname
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,PHASE:.status.phase
kubectl get events --field-selector reason=FailedScheduling --sort-by=.lastTimestamp | tail -5
kubectl get pods -o jsonpath=&amp;#39;{range .items[*]}{.spec.nodeName}{&amp;#34;\n&amp;#34;}{end}&amp;#39; | sort | uniq -c
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;A blank cell in the first command means that node belongs to no domain. FailedScheduling events naming anti-affinity rules mean the required rule has no eligible node left. The last command shows the real distribution: a count of one per node means required anti-affinity is capping the workload, while uneven counts mean a spread constraint is not being honoured.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/"&gt;Assigning Pods to Nodes&lt;/a&gt;, inter-pod affinity and anti-affinity rules, topology keys and scheduling behaviour, checked 2026-09-16.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/"&gt;Pod Topology Spread Constraints&lt;/a&gt;, maxSkew, whenUnsatisfiable, minDomains and the worked skew example, checked 2026-09-16.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item><item><title>Taints repel pods that do not tolerate them</title><link>https://www.xbcatm.com/posts/k8s-taints-and-tolerations-reserve-nodes/</link><pubDate>Thu, 25 Feb 2027 00:00:00 +0000</pubDate><guid>https://www.xbcatm.com/posts/k8s-taints-and-tolerations-reserve-nodes/</guid><description>A taint marks a node so the scheduler keeps workloads off it unless a pod declares the matching toleration, which is how you reserve nodes for specialists.</description><content:encoded>&lt;p&gt;Taint is the &amp;rsquo;no admission&amp;rsquo; sign and toleration is the permit, so you can pin paying platforms, GPUs, or spot nodes to exactly the pods that fit.&lt;/p&gt;
&lt;h2 id="a-sign-for-specialists"&gt;A sign for specialists&lt;/h2&gt;
&lt;p&gt;A taint marks a node as off-limits to ordinary pods, and it is fundamentally a sign, not a command: applying a taint does not move anything that is already running. New pods avoid the node unless they carry a matching toleration, which is the &amp;lsquo;permit.&amp;rsquo; That is how you reserve a subset of the cluster, GPU machines, spot nodes, nodes with special hardware, for exactly the workloads that fit, while everything else schedules as if those nodes did not exist. Because it is scoped by opt-in, the same physical fleet can host both the general pool and the specialist pool without label soup.&lt;/p&gt;
&lt;h2 id="effect-decides-the-action"&gt;Effect decides the action&lt;/h2&gt;
&lt;p&gt;The taint&amp;rsquo;s effect field is what turns a mark into behavior. &lt;code&gt;NoSchedule&lt;/code&gt; keeps new pods away but leaves running ones alone, the default you want for capacity you plan to use. &lt;code&gt;PreferNoSchedule&lt;/code&gt; is a soft bias that discourages, but does not forbid, scheduling; useful for cost-sensitive pools where you prefer cheap nodes rather than a hard rule. &lt;code&gt;NoExecute&lt;/code&gt; goes further and evicts pods already on the node, which also lets you cordon a node for maintenance: taint it &lt;code&gt;NoExecute&lt;/code&gt; and the scheduler drains it on its own. Choose the effect that matches the action you really intend, because software and hard exclusion are very different guarantees.&lt;/p&gt;
&lt;h2 id="pair-is-a-contract"&gt;Pair is a contract&lt;/h2&gt;
&lt;p&gt;Tolerations are always paired against a taint by key, value, and effect, so the match is explicit and it is a contract between two objects. A toleration expresses &amp;lsquo;I can live on nodes bearing this taint&amp;rsquo;; it does not express a preference or force placement, so a tolerant pod can still land elsewhere. That is why you can mix GPU nodes and general nodes in one cluster without accidental misplacement: pods that cannot tolerate the taint simply never get scheduled there, and specialists stay on the hardware they need, not the budget you guessed. Design the pairing deliberately, with an owner and a reason, or the contract drifts into label soup.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;The most expensive failure is the over-broad toleration that pollutes the specialist pool. A toleration with &lt;code&gt;operator: Exists&lt;/code&gt; and no value, or one copied into a shared chart, matches every value of that key, so ordinary Deployments suddenly qualify for GPU or spot nodes. The reserved pool fills with cheap pods while the specialists it was built for stay Pending. This is the mirror image of the DaemonSet failure: there a missing toleration leaves special nodes uncovered, here an extra toleration leaves them unprotected. Treat tolerations like RBAC wildcards.&lt;/p&gt;
&lt;p&gt;The second failure is applying &lt;code&gt;NoExecute&lt;/code&gt; when you meant &lt;code&gt;NoSchedule&lt;/code&gt;. &lt;code&gt;NoSchedule&lt;/code&gt; steers only new pods, while &lt;code&gt;NoExecute&lt;/code&gt; evicts running pods without a matching toleration, optionally after &lt;code&gt;tolerationSeconds&lt;/code&gt; expires. A maintenance taint with the wrong effect drains production in seconds. Unlike affinity, which never evicts, the taint effect carries destructive power, so rehearse on one node and watch &lt;code&gt;kubectl get pods -o wide&lt;/code&gt; before repeating across the pool.&lt;/p&gt;
&lt;p&gt;The third failure is spelling drift between taint and toleration. &lt;code&gt;dedicated=gpu:NoSchedule&lt;/code&gt; never meets &lt;code&gt;dedicated=GPU:NoSchedule&lt;/code&gt;, and a toleration naming &lt;code&gt;PreferNoSchedule&lt;/code&gt; does not cover a &lt;code&gt;NoSchedule&lt;/code&gt; taint. Specialists sit Pending with &lt;code&gt;had taint that the pod didn't tolerate&lt;/code&gt; while the pool looks healthy. Compare both sides with &lt;code&gt;kubectl describe pod&lt;/code&gt; against &lt;code&gt;kubectl get node &amp;lt;name&amp;gt; -o jsonpath='{.spec.taints}'&lt;/code&gt;, and keep the canonical triple in one documented place.&lt;/p&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Save the following as &lt;code&gt;taint_toleration_sim.py&lt;/code&gt; and run &lt;code&gt;python3 taint_toleration_sim.py&lt;/code&gt;. It uses only the standard library, touches no cluster, and models the rules above: key plus operator plus value plus effect, with soft taints never blocking.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tolerates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;One toleration matches one taint on key, value, operator, and effect.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;taint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;operator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Equal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Equal&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;taint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;effect&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;effect&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;taint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;schedulable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taints&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tolerations&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;A pod schedules only if every hard taint is tolerated.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;taint&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;taints&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;taint&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PreferNoSchedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tolerates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tolerations&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;: got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, want &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;OK: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; -&amp;gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gpu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dedicated&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gpu&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NoSchedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;exact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dedicated&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gpu&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NoSchedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedulable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;exact&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;exact match schedules&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedulable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;missing toleration is repelled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;wide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dedicated&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;operator&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Exists&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NoSchedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dedicated&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;spot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NoSchedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedulable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;wide&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Exists toleration matches any value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedulable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;gpu&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;dedicated&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;spot&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NoSchedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}]),&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;wrong value is repelled&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;evict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;maintenance&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;drain&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NoExecute&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;any_effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;maintenance&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;drain&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedulable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;evict&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;any_effect&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;empty effect matches any effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedulable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;evict&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;untolerated NoExecute would evict&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;soft&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cost&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cheap&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;effect&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PreferNoSchedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;schedulable&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;soft&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[]),&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PreferNoSchedule never blocks&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;PASS: 7 scenarios; allow and repel verified&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The seven scenarios prove an exact toleration allows scheduling while a missing one repels, an &lt;code&gt;Exists&lt;/code&gt; toleration matches any value but a wrong value is refused, an empty effect covers &lt;code&gt;NoExecute&lt;/code&gt;, an untolerated &lt;code&gt;NoExecute&lt;/code&gt; would evict, and &lt;code&gt;PreferNoSchedule&lt;/code&gt; never blocks. Expected output ends with PASS: 7 scenarios; allow and repel verified.&lt;/p&gt;
&lt;p&gt;The assertions passed with Python 3.12.6 on macOS. The script checks matching logic only, not capacity, affinity, or live taints. Before changing production, compare pod events against the node&amp;rsquo;s actual taint list.&lt;/p&gt;
&lt;h2 id="further-reading"&gt;Further reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/posts/k8s-node-affinity-pins-workloads/"&gt;Node affinity places workloads on matching nodes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.xbcatm.com/posts/k8s-daemonset-runs-everywhere/"&gt;A DaemonSet puts one Pod on every eligible node&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/"&gt;Kubernetes taints and tolerations&lt;/a&gt;, documents effects and scheduling, checked 2026-06-01.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/"&gt;Kubernetes node affinity&lt;/a&gt;, documents matching pods to node labels, checked 2026-06-01.&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>