<?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>DaemonSet · XB Field Notes</title><link>https://hk.xbcatm.com/tags/daemonset/</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://hk.xbcatm.com/tags/daemonset/index.xml" rel="self" type="application/rss+xml"/><item><title>A DaemonSet puts one Pod on every eligible node</title><link>https://hk.xbcatm.com/posts/k8s-daemonset-runs-everywhere/</link><pubDate>Mon, 29 Mar 2027 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/posts/k8s-daemonset-runs-everywhere/</guid><description>A DaemonSet schedules one copied replica per node, the natural home for log agents, metrics scrapers, and the small helpers every machine needs.</description><content:encoded>&lt;p&gt;A Deployment targets a replica count; a DaemonSet targets coverage of eligible nodes. That is useful for node-level log collectors, network agents, and device plugins. A newly eligible node creates reconciliation work, not an immediate guarantee that its agent is running or ready. Selection rules, scheduling, image pulls, startup, and node health can all delay useful coverage.&lt;/p&gt;
&lt;p&gt;The controller creates and reconciles Pods from a shared template. Its desired state is normally one managed Pod per eligible node, rather than an operator-maintained replica count. Desired coverage and observed readiness are separate measurements. Keep an independently defined intended node population so a mistaken selector cannot make excluded machines disappear from the audit.&lt;/p&gt;
&lt;h2 id="one-per-node-by-design"&gt;One per node, by design&lt;/h2&gt;
&lt;p&gt;The controller evaluates node selectors and required affinity, creates a Pod targeted to each eligible node, and normally lets the scheduler bind it. Nodes joining or leaving change that desired population. DaemonSet Pods automatically tolerate the unschedulable taint, so cordoning a node alone does not exclude them. Registration does not make scheduling, image retrieval, or initialization instantaneous.&lt;/p&gt;
&lt;p&gt;Priority can help an important daemon preempt lower-priority workloads, but it does not create capacity or satisfy incompatible placement constraints. Choose priority and resource requests according to actual operational importance and workload impact. A Pod can remain Pending or fail after scheduling; neither a high PriorityClass nor a toleration proves that it is ready or immune to node-pressure eviction.&lt;/p&gt;
&lt;h2 id="coverage-over-count"&gt;Coverage over count&lt;/h2&gt;
&lt;p&gt;RollingUpdate replaces Pods in a controlled sequence; OnDelete waits for old Pods to be deleted before replacing them from the changed template. With maxSurge zero, maxUnavailable one permits an unavailable node during rollout, not uninterrupted per-node coverage. A nonzero maxSurge can temporarily place old and new Pods on one node and needs extra resources. Version skew during updates is expected, and blocked rollouts can prolong it. &lt;code&gt;kubectl rollout status ds/agent&lt;/code&gt; reports controller rollout progress, not continuity of delivered logs.&lt;/p&gt;
&lt;p&gt;Selectors and affinity can divide heterogeneous fleets into deliberate subsets. A GPU device plugin and an edge log forwarder need not share one DaemonSet. Check the intended membership of each subset, and distinguish a node that is intentionally excluded from one whose expected agent is missing.&lt;/p&gt;
&lt;h2 id="rides-the-node-lifecycle"&gt;Rides the node lifecycle&lt;/h2&gt;
&lt;p&gt;The controller adds documented tolerations for conditions including not-ready, unreachable, several pressure taints, and unschedulable. Custom control-plane or hardware taints may still require explicit tolerations. A toleration permits placement past its matching taint; it does not select that node or overcome every other constraint. Label changes can alter membership, so observe the actual reconciliation rather than assuming an atomic handover between daemon fleets.&lt;/p&gt;
&lt;p&gt;Ordinary kubectl drain refuses DaemonSet-managed Pods unless &amp;ndash;ignore-daemonsets is supplied; that option skips them rather than deleting them. Cordoning and draining are therefore different from stopping these node services. Configure graceful termination for actual Pod deletion, shutdown, or other supported maintenance paths, and test the intended node-lifecycle procedure.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;A new tainted pool can be silently excluded while desiredNumberScheduled still equals numberReady. Those fields describe the DaemonSet controller’s population, not every machine the operator intended to cover, and they are not per-pool counters. Compare node identities, selectors, taints, owned Pods, readiness, and update revision. A mismatch identifies a gap; events and Pod conditions are needed to distinguish missing tolerance, insufficient resources, image failure, or application startup failure.&lt;/p&gt;
&lt;p&gt;A bad daemon update can remove observability on several nodes. Choose disruption and surge budgets, test a selector-scoped canary, and verify collection continuity and resource use before widening the rollout. Keep a usable previous revision and confirm rollback actually converges; a command returning successfully does not recover missing logs or guarantee that an old image remains available.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-k8s-daemonset-runs-everywhere.detail.en.svg" alt="An illustrative intended-node set is compared with controller eligibility and owned Pod readiness, with rollout, toleration and held diagnostic limits." width="560" height="2474"&gt;
&lt;figcaption&gt;DaemonSet coverage audit: mechanism and evidence boundaries.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;Use Bash, Python 3, an authorized context/namespace/DaemonSet, and INTENDED_NODES: an independently maintained JSON map of intended node names to UIDs. Do not derive that roster from this DaemonSet’s selector. The script reads JSON rather than table columns:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&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;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;: &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CONTEXT&lt;/span&gt;&lt;span class="p"&gt;:?Set the authorized kubectl context&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;NAMESPACE&lt;/span&gt;&lt;span class="p"&gt;:?Set the DaemonSet namespace&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DAEMONSET&lt;/span&gt;&lt;span class="p"&gt;:?Set the DaemonSet name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;INTENDED_NODES&lt;/span&gt;&lt;span class="p"&gt;:?Set the independent node-name to UID JSON file&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;umask&lt;/span&gt; &lt;span class="m"&gt;077&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d&lt;span class="k"&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;trap&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;rm -rf -- &amp;#34;$tmp&amp;#34;&amp;#39;&lt;/span&gt; EXIT
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;kubectl --context &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$CONTEXT&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --request-timeout&lt;span class="o"&gt;=&lt;/span&gt;15s&lt;span class="o"&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;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; get ds &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DAEMONSET&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -o json &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/before.json&amp;#34;&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;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; get nodes -o json &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/nodes.json&amp;#34;&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;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; get pods -o json &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/pods.json&amp;#34;&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;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;k&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; get ds &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DAEMONSET&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -o json &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;/after.json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 - &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$tmp&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$INTENDED_NODES&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$NAMESPACE&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$DAEMONSET&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt;&amp;#39;PY&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;import collections, json, pathlib, sys
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;root, roster, namespace, name = sys.argv[1:]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;def require(ok, message):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; if not ok:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; raise SystemExit(message)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;def unique(pairs):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; result = {}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; for key, value in pairs:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; require(key not in result, &amp;#34;Duplicate JSON key&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; result[key] = value
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; return result
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;def load(path):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; return json.loads(pathlib.Path(path).read_text(), object_pairs_hook=unique)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;def ready(obj):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; return any(c.get(&amp;#34;type&amp;#34;) == &amp;#34;Ready&amp;#34; and c.get(&amp;#34;status&amp;#34;) == &amp;#34;True&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; for c in obj.get(&amp;#34;status&amp;#34;, {}).get(&amp;#34;conditions&amp;#34;, []))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;a, b = (load(pathlib.Path(root) / (x + &amp;#34;.json&amp;#34;)) for x in (&amp;#34;before&amp;#34;, &amp;#34;after&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;for ds in (a, b):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; require(ds[&amp;#34;kind&amp;#34;] == &amp;#34;DaemonSet&amp;#34; and ds[&amp;#34;apiVersion&amp;#34;] == &amp;#34;apps/v1&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;Wrong resource type&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; require(ds[&amp;#34;metadata&amp;#34;][&amp;#34;namespace&amp;#34;] == namespace and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; ds[&amp;#34;metadata&amp;#34;][&amp;#34;name&amp;#34;] == name, &amp;#34;Wrong DaemonSet identity&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; require(ds[&amp;#34;metadata&amp;#34;].get(&amp;#34;uid&amp;#34;) and not ds[&amp;#34;metadata&amp;#34;].get(&amp;#34;deletionTimestamp&amp;#34;),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;Missing UID or deleting DaemonSet&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;require(all(a[&amp;#34;metadata&amp;#34;][k] == b[&amp;#34;metadata&amp;#34;][k] for k in (&amp;#34;uid&amp;#34;, &amp;#34;generation&amp;#34;)),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; &amp;#34;DaemonSet changed during collection; retry&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;uid = a[&amp;#34;metadata&amp;#34;][&amp;#34;uid&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;expected = load(roster)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;require(isinstance(expected, dict) and expected and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; all(isinstance(k, str) and k and isinstance(v, str) and v
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; for k, v in expected.items()), &amp;#34;Use a nonempty node-name to UID map&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;node_items = load(pathlib.Path(root) / &amp;#34;nodes.json&amp;#34;)[&amp;#34;items&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;nodes = {n[&amp;#34;metadata&amp;#34;][&amp;#34;name&amp;#34;]: n for n in node_items}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;require(len(nodes) == len(node_items), &amp;#34;Duplicate node names&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;wrong_nodes = sorted(n for n in expected if n not in nodes or
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; nodes[n][&amp;#34;metadata&amp;#34;][&amp;#34;uid&amp;#34;] != expected[n])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;unready_nodes = sorted(n for n in expected if n in nodes and not ready(nodes[n]))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;rows, seen = [], set()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;for pod in load(pathlib.Path(root) / &amp;#34;pods.json&amp;#34;)[&amp;#34;items&amp;#34;]:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; m = pod[&amp;#34;metadata&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; require(m[&amp;#34;namespace&amp;#34;] == namespace, &amp;#34;Wrong Pod namespace&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; if not any(o.get(&amp;#34;controller&amp;#34;) is True and o.get(&amp;#34;uid&amp;#34;) == uid and
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; o.get(&amp;#34;kind&amp;#34;) == &amp;#34;DaemonSet&amp;#34; and o.get(&amp;#34;apiVersion&amp;#34;) == &amp;#34;apps/v1&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; and o.get(&amp;#34;name&amp;#34;) == name for o in m.get(&amp;#34;ownerReferences&amp;#34;, [])):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; continue
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; require(m.get(&amp;#34;uid&amp;#34;) and m[&amp;#34;uid&amp;#34;] not in seen, &amp;#34;Missing or duplicate Pod UID&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; seen.add(m[&amp;#34;uid&amp;#34;])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; node = pod[&amp;#34;spec&amp;#34;].get(&amp;#34;nodeName&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="s"&gt; phase = pod.get(&amp;#34;status&amp;#34;, {}).get(&amp;#34;phase&amp;#34;, &amp;#34;Unknown&amp;#34;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; terminating = bool(m.get(&amp;#34;deletionTimestamp&amp;#34;))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; rows.append(dict(pod=m[&amp;#34;name&amp;#34;], uid=m[&amp;#34;uid&amp;#34;], node=node, phase=phase,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; ready=ready(pod) and phase == &amp;#34;Running&amp;#34; and not terminating,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; terminating=terminating,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; revision=m.get(&amp;#34;labels&amp;#34;, {}).get(&amp;#34;controller-revision-hash&amp;#34;)))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;counts = collections.Counter(r[&amp;#34;node&amp;#34;] for r in rows if r[&amp;#34;node&amp;#34;])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;ready_nodes = {r[&amp;#34;node&amp;#34;] for r in rows if r[&amp;#34;ready&amp;#34;] and r[&amp;#34;node&amp;#34;]}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;issues = dict(node_identity_mismatch=wrong_nodes, node_not_ready=unready_nodes,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; missing_ready=sorted(set(expected) - ready_nodes),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; unexpected_owned_nodes=sorted(set(counts) - set(expected)),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; multiple_pods=sorted(n for n, count in counts.items() if count &amp;gt; 1),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; unassigned=[r[&amp;#34;uid&amp;#34;] for r in rows if not r[&amp;#34;node&amp;#34;]],
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; unready_pods=[r[&amp;#34;uid&amp;#34;] for r in rows if not r[&amp;#34;ready&amp;#34;]])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;print(json.dumps(dict(namespace=namespace, daemonset=name, uid=uid,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; intended=expected, pods=rows, issues=issues), indent=2))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;sys.exit(1 if any(issues.values()) else 0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;PY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Zero means this collected snapshot has one nonterminating Running/Ready owned Pod per intended, Ready node with matching UID. Nonzero requires investigation: surge duplicates can be legitimate; unassigned Pods remain unresolved. Pod UIDs and revisions are reported, but target revision, placement eligibility and log delivery are not proved. Separate API reads are not atomic; recheck during change. Node UIDs do not prove current physical execution. Only syntax and synthetic file controls were exercised here. No cluster, rollout or drain ran; technical and runtime validation remain held.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/node-pressure-eviction/"&gt;Kubernetes node-pressure eviction&lt;/a&gt;, DaemonSet priority does not confer eviction immunity, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/daemon-set-v1/"&gt;Kubernetes DaemonSet API&lt;/a&gt;, desired and ready status plus surge and unavailable budgets, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/"&gt;Kubernetes node drain&lt;/a&gt;, drain skips DaemonSet Pods when ignore-daemonsets is selected, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/"&gt;Kubernetes DaemonSet rolling updates&lt;/a&gt;, update strategies, disruption budget and surge configuration, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/"&gt;Kubernetes DaemonSet&lt;/a&gt;, documents daemonset behavior, checked 2026-06-01.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/"&gt;Kubernetes taints and tolerations&lt;/a&gt;, documents reaching tainted nodes, checked 2026-09-05.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>