Deployment 主要表达副本总数,DaemonSet 主要表达合格节点的覆盖范围,适合节点日志采集、网络代理和设备插件。新节点符合条件后会触发协调工作,不代表服务立刻运行或就绪。节点选择、调度容量、镜像拉取、初始化和节点故障,都可能延迟真正有用的覆盖;这些环节需要分别观察。
控制器根据共享模板创建并协调 Pod,通常以每个合格节点一个受管理 Pod 为期望,而不是依赖手工维护副本总数。期望覆盖与实际就绪是两种指标。还应独立定义运维希望覆盖的节点集合,避免选择器写错后,被排除的机器同时从期望数和审计视野中消失。
每节点一个、天生如此
控制器评估节点选择器与必要亲和条件,为每个合格节点创建定向 Pod,通常再由调度器绑定。节点加入和离开会改变期望集合。DaemonSet Pod 自动容忍 unschedulable 污点,因此只执行 cordon 不会排除它们。节点注册并不保证调度、镜像获取和初始化在同一时刻完成,也不保证每台机器已经具备可用日志采集能力。
优先级可以帮助重要守护进程抢占低优先级工作负载,却不会创造容量或消除冲突的放置条件。应按真实重要性和业务影响选择 PriorityClass 与资源请求。Pod 可能一直 Pending,也可能调度后启动失败;高优先级和容忍都不能证明已经就绪,更不意味着免受节点压力驱逐。
覆盖优先于数量
RollingUpdate 会按策略替换 Pod;OnDelete 则等待旧 Pod 被删除后,才使用更新模板创建替代者。maxSurge 为零时,maxUnavailable 为一允许某个节点在更新期间暂时不可用,并不保证每节点不断档。非零 maxSurge 可以让同一节点暂时同时运行新旧 Pod,需要额外资源。更新期间存在版本差异是正常现象,阻塞会使差异持续。kubectl rollout status ds/agent 报告控制器进度,不能证明日志交付连续。
选择器与亲和可以把异构节点分成有意定义的子集,GPU 设备插件与边缘日志转发器不一定使用同一个 DaemonSet。检查各子集真正应该包含哪些节点,区分有意排除与预期代理缺席。跨池比较前还要确认标签来源可靠,不能把一个看起来合理的标签直接当成硬件或运行能力证明。
跟着节点生命周期走
控制器会自动添加文档规定的容忍,包括 not-ready、unreachable、若干压力污点及 unschedulable。控制平面或硬件自定义污点仍可能需要显式容忍。容忍只是允许跨过匹配污点,不会主动选择节点,也不保证满足其他约束。标签变化可能改变成员集合;应观察实际协调结果,不能假定不同守护集合之间是原子交接。
普通 kubectl drain 遇到 DaemonSet 管理的 Pod 时会拒绝继续,除非指定 –ignore-daemonsets;这个选项跳过它们,不负责删除它们。因此 cordon、drain 与停止节点守护服务是不同动作。实际删除 Pod、节点关闭或其他受支持维护路径,仍需要合理终止宽限与信号处理,并验证具体生命周期流程。 新节点池交付时,先把独立定义的预期节点身份与实际受管理 Pod 对照,再检查调度事件和就绪状态。不能一看到数量分叉就直接补容忍,因为资源、镜像、启动失败也可能造成缺口;盲目扩大容忍范围还可能让代理进入原本不应覆盖的节点。 守护镜像先在明确限定的节点池灰度,验证真实采集与恢复行为后再扩大。保留可用旧版本和对应配置,回滚操作也需要观察结果,不能把提交回滚请求等同于所有节点已经恢复。
灰度期间检查新旧 Pod 的节点身份、就绪与版本,确认日志连续性及 CPU、内存变化。发现异常先停止扩大范围并排查。旧镜像和配置保留多久,应依据恢复要求、镜像供应与变更政策,而不是把固定一周当成通用规则。
坏掉的样子
新污点节点池可能被整体排除,此时 desiredNumberScheduled 仍然等于 numberReady。这些字段描述控制器认为应该覆盖的集合,不代表运维原本希望覆盖的全部机器,也不是按池分组的计数。应比较预期节点、选择器、污点、所有者 UID、就绪与版本;缺口需要结合事件才能判断具体原因。
坏版本可能让多个节点失去可观测性。应选择合适的不可用与增量预算,先验证选择器限定的灰度池,再扩大更新。保留能实际取得的旧镜像和配置,确认回滚最终收敛。命令返回成功不能补回已经丢失的日志,也不能保证旧版本仍然能够启动。
快速验证
使用 Bash、Python 3,并明确设置获准查询的 CONTEXT、NAMESPACE 与 DAEMONSET。INTENDED_NODES 指向独立维护的 JSON 对象,键是预期节点名,值是对应 UID;不能从本 DaemonSet 的选择器反推该名单,否则选错节点仍可能自证成功。下面读取 JSON 字段,不再依赖表头、位置列或管道计数:
(
set -euo pipefail
: "${CONTEXT:?Set the authorized kubectl context}"
: "${NAMESPACE:?Set the DaemonSet namespace}"
: "${DAEMONSET:?Set the DaemonSet name}"
: "${INTENDED_NODES:?Set the independent node-name to UID JSON file}"
umask 077
tmp=$(mktemp -d)
trap 'rm -rf -- "$tmp"' EXIT
k=(kubectl --context "$CONTEXT" --request-timeout=15s)
"${k[@]}" -n "$NAMESPACE" get ds "$DAEMONSET" -o json > "$tmp/before.json"
"${k[@]}" get nodes -o json > "$tmp/nodes.json"
"${k[@]}" -n "$NAMESPACE" get pods -o json > "$tmp/pods.json"
"${k[@]}" -n "$NAMESPACE" get ds "$DAEMONSET" -o json > "$tmp/after.json"
python3 - "$tmp" "$INTENDED_NODES" "$NAMESPACE" "$DAEMONSET" <<'PY'
import collections, json, pathlib, sys
root, roster, namespace, name = sys.argv[1:]
def require(ok, message):
if not ok:
raise SystemExit(message)
def unique(pairs):
result = {}
for key, value in pairs:
require(key not in result, "Duplicate JSON key")
result[key] = value
return result
def load(path):
return json.loads(pathlib.Path(path).read_text(), object_pairs_hook=unique)
def ready(obj):
return any(c.get("type") == "Ready" and c.get("status") == "True"
for c in obj.get("status", {}).get("conditions", []))
a, b = (load(pathlib.Path(root) / (x + ".json")) for x in ("before", "after"))
for ds in (a, b):
require(ds["kind"] == "DaemonSet" and ds["apiVersion"] == "apps/v1",
"Wrong resource type")
require(ds["metadata"]["namespace"] == namespace and
ds["metadata"]["name"] == name, "Wrong DaemonSet identity")
require(ds["metadata"].get("uid") and not ds["metadata"].get("deletionTimestamp"),
"Missing UID or deleting DaemonSet")
require(all(a["metadata"][k] == b["metadata"][k] for k in ("uid", "generation")),
"DaemonSet changed during collection; retry")
uid = a["metadata"]["uid"]
expected = load(roster)
require(isinstance(expected, dict) and expected and
all(isinstance(k, str) and k and isinstance(v, str) and v
for k, v in expected.items()), "Use a nonempty node-name to UID map")
node_items = load(pathlib.Path(root) / "nodes.json")["items"]
nodes = {n["metadata"]["name"]: n for n in node_items}
require(len(nodes) == len(node_items), "Duplicate node names")
wrong_nodes = sorted(n for n in expected if n not in nodes or
nodes[n]["metadata"]["uid"] != expected[n])
unready_nodes = sorted(n for n in expected if n in nodes and not ready(nodes[n]))
rows, seen = [], set()
for pod in load(pathlib.Path(root) / "pods.json")["items"]:
m = pod["metadata"]
require(m["namespace"] == namespace, "Wrong Pod namespace")
if not any(o.get("controller") is True and o.get("uid") == uid and
o.get("kind") == "DaemonSet" and o.get("apiVersion") == "apps/v1"
and o.get("name") == name for o in m.get("ownerReferences", [])):
continue
require(m.get("uid") and m["uid"] not in seen, "Missing or duplicate Pod UID")
seen.add(m["uid"])
node = pod["spec"].get("nodeName", "")
phase = pod.get("status", {}).get("phase", "Unknown")
terminating = bool(m.get("deletionTimestamp"))
rows.append(dict(pod=m["name"], uid=m["uid"], node=node, phase=phase,
ready=ready(pod) and phase == "Running" and not terminating,
terminating=terminating,
revision=m.get("labels", {}).get("controller-revision-hash")))
counts = collections.Counter(r["node"] for r in rows if r["node"])
ready_nodes = {r["node"] for r in rows if r["ready"] and r["node"]}
issues = dict(node_identity_mismatch=wrong_nodes, node_not_ready=unready_nodes,
missing_ready=sorted(set(expected) - ready_nodes),
unexpected_owned_nodes=sorted(set(counts) - set(expected)),
multiple_pods=sorted(n for n, count in counts.items() if count > 1),
unassigned=[r["uid"] for r in rows if not r["node"]],
unready_pods=[r["uid"] for r in rows if not r["ready"]])
print(json.dumps(dict(namespace=namespace, daemonset=name, uid=uid,
intended=expected, pods=rows, issues=issues), indent=2))
sys.exit(1 if any(issues.values()) else 0)
PY
)
退出零表示所收集快照中,每个预期节点 UID 匹配且 Ready,并恰有一个非终止中、Running 且 Ready 的受管理 Pod。非零需要调查:surge 造成的重复可能合法,尚未绑定节点的 Pod 仍待诊断。输出记录 Pod UID 与版本标签,但不证明目标版本、放置资格或日志交付。不同 API 读取不是原子快照,变更期间应重新核对;节点名到 UID 的映射也不能证明 Pod 当前确在该机器执行。本轮只检查语法与合成文件控制,没有运行集群、更新或 drain,技术与运行验证仍暂缓。
来源
Kubernetes node-pressure eviction,DaemonSet 优先级不能保证免于节点压力驱逐,已核验 2026-10-18。
Kubernetes DaemonSet API,期望与就绪状态及 surge 和 unavailable 预算,已核验 2026-10-18。
Kubernetes node drain,ignore-daemonsets 使 drain 跳过受 DaemonSet 管理的 Pod,已核验 2026-10-18。
Kubernetes DaemonSet rolling updates,更新策略、不可用预算与增量配置,已核验 2026-10-18。
Kubernetes DaemonSet,说明 DaemonSet 行为,已核验 2026-06-01。
Kubernetes taints 与 tolerations,说明进入被 taint 节点,已核验 2026-09-05。