<?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 现场笔记</title><link>https://hk.xbcatm.com/zh/tags/daemonset/</link><description>基于 HTTP、DNS、TLS、Caddy、SSH、发布与恢复的可复现运维现场笔记。</description><generator>Hugo</generator><language>zh-CN</language><atom:link href="https://hk.xbcatm.com/zh/tags/daemonset/index.xml" rel="self" type="application/rss+xml"/><item><title>DaemonSet 在每个合格节点各起一个 Pod</title><link>https://hk.xbcatm.com/zh/posts/k8s-daemonset-runs-everywhere/</link><pubDate>Mon, 29 Mar 2027 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/zh/posts/k8s-daemonset-runs-everywhere/</guid><description>DaemonSet 让每个合格节点各跑一个副本，这正是日志、指标与每台机都必须有的小助手的天然归宿。</description><content:encoded>&lt;p&gt;Deployment 主要表达副本总数，DaemonSet 主要表达合格节点的覆盖范围，适合节点日志采集、网络代理和设备插件。新节点符合条件后会触发协调工作，不代表服务立刻运行或就绪。节点选择、调度容量、镜像拉取、初始化和节点故障，都可能延迟真正有用的覆盖；这些环节需要分别观察。&lt;/p&gt;
&lt;p&gt;控制器根据共享模板创建并协调 Pod，通常以每个合格节点一个受管理 Pod 为期望，而不是依赖手工维护副本总数。期望覆盖与实际就绪是两种指标。还应独立定义运维希望覆盖的节点集合，避免选择器写错后，被排除的机器同时从期望数和审计视野中消失。&lt;/p&gt;
&lt;h2 id="每节点一个天生如此"&gt;每节点一个、天生如此&lt;/h2&gt;
&lt;p&gt;控制器评估节点选择器与必要亲和条件，为每个合格节点创建定向 Pod，通常再由调度器绑定。节点加入和离开会改变期望集合。DaemonSet Pod 自动容忍 unschedulable 污点，因此只执行 cordon 不会排除它们。节点注册并不保证调度、镜像获取和初始化在同一时刻完成，也不保证每台机器已经具备可用日志采集能力。&lt;/p&gt;
&lt;p&gt;优先级可以帮助重要守护进程抢占低优先级工作负载，却不会创造容量或消除冲突的放置条件。应按真实重要性和业务影响选择 PriorityClass 与资源请求。Pod 可能一直 Pending，也可能调度后启动失败；高优先级和容忍都不能证明已经就绪，更不意味着免受节点压力驱逐。&lt;/p&gt;
&lt;h2 id="覆盖优先于数量"&gt;覆盖优先于数量&lt;/h2&gt;
&lt;p&gt;RollingUpdate 会按策略替换 Pod；OnDelete 则等待旧 Pod 被删除后，才使用更新模板创建替代者。maxSurge 为零时，maxUnavailable 为一允许某个节点在更新期间暂时不可用，并不保证每节点不断档。非零 maxSurge 可以让同一节点暂时同时运行新旧 Pod，需要额外资源。更新期间存在版本差异是正常现象，阻塞会使差异持续。&lt;code&gt;kubectl rollout status ds/agent&lt;/code&gt; 报告控制器进度，不能证明日志交付连续。&lt;/p&gt;
&lt;p&gt;选择器与亲和可以把异构节点分成有意定义的子集，GPU 设备插件与边缘日志转发器不一定使用同一个 DaemonSet。检查各子集真正应该包含哪些节点，区分有意排除与预期代理缺席。跨池比较前还要确认标签来源可靠，不能把一个看起来合理的标签直接当成硬件或运行能力证明。&lt;/p&gt;
&lt;h2 id="跟着节点生命周期走"&gt;跟着节点生命周期走&lt;/h2&gt;
&lt;p&gt;控制器会自动添加文档规定的容忍，包括 not-ready、unreachable、若干压力污点及 unschedulable。控制平面或硬件自定义污点仍可能需要显式容忍。容忍只是允许跨过匹配污点，不会主动选择节点，也不保证满足其他约束。标签变化可能改变成员集合；应观察实际协调结果，不能假定不同守护集合之间是原子交接。&lt;/p&gt;
&lt;p&gt;普通 kubectl drain 遇到 DaemonSet 管理的 Pod 时会拒绝继续，除非指定 &amp;ndash;ignore-daemonsets；这个选项跳过它们，不负责删除它们。因此 cordon、drain 与停止节点守护服务是不同动作。实际删除 Pod、节点关闭或其他受支持维护路径，仍需要合理终止宽限与信号处理，并验证具体生命周期流程。
新节点池交付时，先把独立定义的预期节点身份与实际受管理 Pod 对照，再检查调度事件和就绪状态。不能一看到数量分叉就直接补容忍，因为资源、镜像、启动失败也可能造成缺口；盲目扩大容忍范围还可能让代理进入原本不应覆盖的节点。
守护镜像先在明确限定的节点池灰度，验证真实采集与恢复行为后再扩大。保留可用旧版本和对应配置，回滚操作也需要观察结果，不能把提交回滚请求等同于所有节点已经恢复。&lt;/p&gt;
&lt;p&gt;灰度期间检查新旧 Pod 的节点身份、就绪与版本，确认日志连续性及 CPU、内存变化。发现异常先停止扩大范围并排查。旧镜像和配置保留多久，应依据恢复要求、镜像供应与变更政策，而不是把固定一周当成通用规则。&lt;/p&gt;
&lt;h2 id="坏掉的样子"&gt;坏掉的样子&lt;/h2&gt;
&lt;p&gt;新污点节点池可能被整体排除，此时 desiredNumberScheduled 仍然等于 numberReady。这些字段描述控制器认为应该覆盖的集合，不代表运维原本希望覆盖的全部机器，也不是按池分组的计数。应比较预期节点、选择器、污点、所有者 UID、就绪与版本；缺口需要结合事件才能判断具体原因。&lt;/p&gt;
&lt;p&gt;坏版本可能让多个节点失去可观测性。应选择合适的不可用与增量预算，先验证选择器限定的灰度池，再扩大更新。保留能实际取得的旧镜像和配置，确认回滚最终收敛。命令返回成功不能补回已经丢失的日志，也不能保证旧版本仍然能够启动。&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.zh.svg" alt="比较示意预期节点集合、控制器资格及受管理 Pod 就绪状态，并说明更新、容忍与暂缓诊断的边界。" width="560" height="2092"&gt;
&lt;figcaption&gt;DaemonSet 覆盖审计：机制与证据边界。&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="快速验证"&gt;快速验证&lt;/h2&gt;
&lt;p&gt;使用 Bash、Python 3，并明确设置获准查询的 CONTEXT、NAMESPACE 与 DAEMONSET。INTENDED_NODES 指向独立维护的 JSON 对象，键是预期节点名，值是对应 UID；不能从本 DaemonSet 的选择器反推该名单，否则选错节点仍可能自证成功。下面读取 JSON 字段，不再依赖表头、位置列或管道计数：&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;退出零表示所收集快照中，每个预期节点 UID 匹配且 Ready，并恰有一个非终止中、Running 且 Ready 的受管理 Pod。非零需要调查：surge 造成的重复可能合法，尚未绑定节点的 Pod 仍待诊断。输出记录 Pod UID 与版本标签，但不证明目标版本、放置资格或日志交付。不同 API 读取不是原子快照，变更期间应重新核对；节点名到 UID 的映射也不能证明 Pod 当前确在该机器执行。本轮只检查语法与合成文件控制，没有运行集群、更新或 drain，技术与运行验证仍暂缓。&lt;/p&gt;
&lt;h2 id="来源"&gt;来源&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 优先级不能保证免于节点压力驱逐，已核验 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;，期望与就绪状态及 surge 和 unavailable 预算，已核验 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;，ignore-daemonsets 使 drain 跳过受 DaemonSet 管理的 Pod，已核验 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;，更新策略、不可用预算与增量配置，已核验 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;，说明 DaemonSet 行为，已核验 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 与 tolerations&lt;/a&gt;，说明进入被 taint 节点，已核验 2026-09-05。&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>