Kubernetes 基础体系 · 第 18/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
DaemonSet:节点级 Agent、调度、更新、容忍和资源治理
DaemonSet 是 Kubernetes 中用于运行“每个节点一份”或“每个符合条件的节点一份”Pod 的工作负载控制器。典型用途包括:
- 节点日志采集 Agent;
- 节点监控、指标和硬件探针;
- CNI、网络策略或存储相关的节点插件;
- 安全扫描、主机完整性检查;
- 需要访问节点文件系统、容器运行时或内核接口的 Agent。
DaemonSet 不是“把 Pod 直接绑到节点上的静态配置”。它仍然由控制器持续计算期望状态,并通过 API Server、调度器和 kubelet 协作完成创建、调度、运行、更新和恢复。
一、先建立正确模型:DaemonSet 维护的是节点集合上的副本
Deployment 的核心问题是:
集群中应该有多少个副本?
DaemonSet 的核心问题是:
哪些节点应该各自拥有一个符合模板的 Pod?
设集群节点集合为 ,DaemonSet 的节点筛选条件为 ,则理论上的目标节点集合可以写成:
这里的 通常包括:
nodeSelector;- Pod 的
nodeAffinity; - 节点是否被控制面纳入调度范围;
- 节点 taint 是否被 Pod toleration 接受;
- Pod 的资源请求、端口、卷和其他调度约束是否可满足。
对于每个 ,DaemonSet 控制器期望存在一个属于该 DaemonSet 的 Pod:
但这不等于任何时刻都能观察到:
原因包括:
- Pod 尚未被调度;
- 镜像尚未拉取完成;
- kubelet 不可用;
- 节点资源不足;
- Pod 正在终止或滚动更新;
- 节点处于压力状态并发生驱逐;
- DaemonSet 使用了
maxSurge,更新期间一个节点上可能暂时存在两个 Pod; - 控制器、调度器和 kubelet 的状态同步存在延迟。
因此,DaemonSet 的 desiredNumberScheduled、currentNumberScheduled、numberReady 和 numberAvailable 表达的是不同层次的状态,不能只看其中一个字段判断 Agent 是否正常。
二、组件关系和完整生命周期
DaemonSet 运行涉及四个主要组件:
- DaemonSet controller:监听 DaemonSet、Node 和 Pod,计算哪些节点需要哪些 Pod;
- kube-scheduler:为尚未绑定节点的 Pod 选择可行节点;
- kubelet:在目标节点上创建 Pod 沙箱、启动容器、执行探针并上报状态;
- API Server:保存期望状态和观察状态,是各组件交换状态的中心。
典型流程如下:
sequenceDiagram
participant U as 用户
participant API as API Server
participant DC as DaemonSet Controller
participant S as Scheduler
participant K as Kubelet
participant C as Container Runtime
U->>API: 创建或更新 DaemonSet
API-->>DC: Watch 到 DaemonSet 变化
DC->>API: 为目标节点创建 Pod
API-->>S: Watch 到未绑定节点的 Pod
S->>S: 过滤节点、检查资源和约束
S->>API: 写入 Pod.spec.nodeName
API-->>K: kubelet Watch 到目标节点上的 Pod
K->>C: 创建容器并启动
C-->>K: 返回容器状态
K->>API: 更新 Pod Status
API-->>DC: Watch 到 Pod 状态变化
DC->>API: 更新 DaemonSet Status
更新 DaemonSet 时,控制器会根据 Pod 模板计算模板哈希。模板中影响 Pod 身份的字段发生变化,例如镜像、环境变量、资源、探针或卷配置发生变化,通常会产生新的 ControllerRevision。RollingUpdate 控制器随后按照更新策略替换旧 Pod。
1. DaemonSet 控制器不负责让容器真正运行
控制器通常只负责:
- 判断目标节点;
- 创建或删除 Pod;
- 处理旧版本 Pod;
- 更新 DaemonSet 状态。
容器是否能启动,仍由 kubelet 和容器运行时负责。例如:
- 镜像拉取失败;
- 容器进程立即退出;
- 挂载路径不存在;
- 容器没有权限访问
/var/log; - liveness 探针失败;
这些问题不会由 DaemonSet 控制器直接修复。控制器可能看到 Pod 一直存在,但 Pod 的 ready 或 available 状态不成立。
2. 调度器仍然参与 DaemonSet 调度
现代 Kubernetes 中,DaemonSet 控制器通常创建尚未绑定节点的 Pod,并为 Pod 注入针对目标节点的节点亲和性,调度器再完成节点绑定。
这与“控制器直接设置 spec.nodeName”不同。调度器因此仍会检查:
- 节点资源请求;
- 节点亲和性;
- taint 和 toleration;
- 端口冲突;
- 卷拓扑;
- 其他调度插件。
这也是为什么 DaemonSet 的 Pod 可能处于 Pending:控制器已经创建了 Pod,但调度器找不到满足条件的节点。
需要区分两个概念:
- DaemonSet 的目标节点:控制器认为该节点应该有 Pod;
- DaemonSet Pod 的可调度节点:调度器认为 Pod 实际可以放置的节点。
目标节点和可调度节点可能不一致。例如节点匹配了标签,但节点剩余内存不足以满足 Pod 的 requests.memory。
三、节点筛选:不是所有节点都会运行 DaemonSet Pod
1. nodeSelector:最简单的等值筛选
例如:
spec:
template:
spec:
nodeSelector:
kubernetes.io/os: linux
node-role.example.com: worker
这要求 Pod 只能调度到同时满足两个标签的节点。nodeSelector 是等值匹配,表达能力有限。
如果节点没有标签:
node-role.example.com=worker
即使它是 Linux 节点,也不会成为目标节点。
可以使用命令检查:
kubectl get nodes --show-labels
kubectl get nodes -l 'kubernetes.io/os=linux,node-role.example.com=worker'
第二条命令列出的节点只是标签筛选结果,不代表这些节点一定能运行 Pod;资源、taint、卷和其他调度约束仍可能使 Pod 无法调度。
2. nodeAffinity:表达更复杂的节点条件
DaemonSet 可以使用 requiredDuringSchedulingIgnoredDuringExecution:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: topology.kubernetes.io/zone
operator: In
values:
- zone-a
- zone-b
这里的含义是:
- 节点必须是 Linux;
- 节点区域必须是
zone-a或zone-b; IgnoredDuringExecution表示 Pod 调度成功后,如果节点标签后来变化,Kubernetes 不会仅因为这个变化自动驱逐已经运行的 Pod。
这最后一点经常被误解。节点亲和性主要约束调度时机;它不是一个持续执行的“节点标签守卫”。若希望节点变化后移走 Pod,需要结合控制器行为、taint、驱逐或人工操作处理。
preferredDuringSchedulingIgnoredDuringExecution 是偏好而非硬约束。它会影响调度打分,但在没有合适节点时仍可能放置到不符合偏好的节点。
3. DaemonSet 自身不会提供“只在新增节点上运行”的语义
DaemonSet 的目标是当前符合条件的节点集合:
- 新节点加入并匹配条件时,控制器会创建 Pod;
- 节点标签变化后开始匹配时,控制器可能创建 Pod;
- 节点不再匹配时,控制器会删除对应 Pod;
- 节点被删除时,对应 Pod 也不再具有运行意义。
因此,节点标签和 taint 是 DaemonSet 部署范围的一部分,应当像 API 设计一样被谨慎管理。误加一个标签可能让大量节点同时接收一个高资源 Agent。
四、Taint 和 Toleration:允许调度,不是保证运行
1. Taint 的三种效果
节点 taint 由三个主要部分组成:
key=value:effect
常见效果为:
NoSchedule:不允许新的不匹配 Pod 调度到节点;PreferNoSchedule:尽量避免调度,但不是硬拒绝;NoExecute:不匹配的已运行 Pod 也可能被驱逐,同时阻止新的 Pod 调度。
Pod 的 toleration 只表示“可以接受某个 taint”。例如:
tolerations:
- key: workload.example.com
operator: Equal
value: node-agent
effect: NoSchedule
它允许 Pod 调度到:
workload.example.com=node-agent:NoSchedule
但并不表示:
- 节点一定有足够资源;
- 容器一定启动成功;
- Pod 一定 Ready;
- kubelet 一定可用;
- 节点压力下不会被驱逐。
2. DaemonSet Pod 的自动容忍
Kubernetes 会为 DaemonSet Pod 自动添加一些与节点状态相关的 toleration。具体自动注入项属于 Kubernetes 版本和实现行为的一部分,常见项目包括:
node.kubernetes.io/not-ready:NoExecute
node.kubernetes.io/unreachable:NoExecute
node.kubernetes.io/disk-pressure:NoSchedule
node.kubernetes.io/memory-pressure:NoSchedule
node.kubernetes.io/pid-pressure:NoSchedule
node.kubernetes.io/unschedulable:NoSchedule
这些容忍的设计目的,是让节点级 Agent 在节点暂时异常时仍有机会留在节点上或重新调度,而不是像普通业务 Pod 那样立即被排除。
但这不等于 Agent 能够跨越节点故障继续工作:
- kubelet 停止时,Pod 状态可能长时间不更新;
- 节点断网时,容器可能仍在运行,但控制面看不到最新状态;
- 节点发生磁盘压力时,kubelet 仍可能驱逐 Pod;
- 节点彻底宕机时,节点上的容器不会被迁移到另一个节点,因为 DaemonSet 的语义是“一节点一份”,而不是“始终保持全局副本数”。
3. NoExecute 的 tolerationSeconds
可以限制 Pod 对 NoExecute taint 的容忍时间:
tolerations:
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 300
这表示节点进入 NotReady 并施加对应 taint 后,Pod 最多容忍 300 秒;超过时间仍不恢复,Pod 可能被驱逐。
节点级基础设施 Agent 是否应设置有限时间,需要结合用途决定:
- 采集节点本地日志的 Agent 可能需要尽量留在节点上;
- 依赖控制面通信的安全 Agent 可能希望故障节点尽快被标记为不可服务;
- 过长容忍时间可能导致“控制面认为 Pod 存在,但节点实际上已失联”的状态持续更久。
Node Condition、Lease、NotReady 和恢复流程会直接影响这些观察结果。Ready=False 或 Unknown、节点 taint、kubelet 心跳和容器实际运行状态不是同一个状态源,诊断时必须分别检查。
五、一个可运行的 DaemonSet 示例
下面的示例部署一个节点级日志 Agent。它只运行在 Linux worker 节点,访问节点的容器日志目录,并设置了资源、探针和滚动更新策略。
hostPath会让容器访问宿主机文件系统,示例适合理解机制,不应未经安全评估直接用于生产。不同发行版和容器运行时的日志路径也可能不同。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-log-agent
namespace: observability
labels:
app: node-log-agent
spec:
revisionHistoryLimit: 5
minReadySeconds: 10
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 0
selector:
matchLabels:
app: node-log-agent
template:
metadata:
labels:
app: node-log-agent
spec:
serviceAccountName: node-log-agent
nodeSelector:
kubernetes.io/os: linux
node-role.example.com: worker
tolerations:
- key: workload.example.com
operator: Equal
value: node-agent
effect: NoSchedule
containers:
- name: agent
image: ghcr.io/example/node-log-agent:1.4.0
imagePullPolicy: IfNotPresent
args:
- --path=/var/log/pods
- --config=/etc/node-log-agent/config.yaml
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30
periodSeconds: 20
volumeMounts:
- name: pod-logs
mountPath: /var/log/pods
readOnly: true
- name: agent-config
mountPath: /etc/node-log-agent
readOnly: true
volumes:
- name: pod-logs
hostPath:
path: /var/log/pods
type: Directory
- name: agent-config
configMap:
name: node-log-agent-config
还需要创建命名空间、ServiceAccount 和配置:
kubectl create namespace observability
kubectl create serviceaccount node-log-agent \
-n observability
kubectl create configmap node-log-agent-config \
-n observability \
--from-literal=config.yaml='{"flush_interval":"5s"}'
kubectl apply -f node-log-agent.yaml
这些命令成立的前提是:
- 节点存在
kubernetes.io/os=linux; - 目标 worker 节点存在
node-role.example.com=worker; - 镜像仓库可访问;
- 节点上存在
/var/log/pods; - 镜像确实提供
/ready和/healthzHTTP 接口; - 镜像内部支持示例中的参数。
如果镜像、路径或健康接口不存在,DaemonSet 对象仍然可以创建,但 Pod 可能进入 ImagePullBackOff、CrashLoopBackOff 或因探针失败而不 Ready。
检查结果:
kubectl -n observability get daemonset node-log-agent
kubectl -n observability get pods -o wide -l app=node-log-agent
kubectl -n observability describe daemonset node-log-agent
可能看到类似状态:
DESIRED CURRENT READY UP-TO-DATE AVAILABLE
3 3 3 3 3
其含义不是“集群总共有三个节点”,而是“当前有三个节点满足 DaemonSet 的筛选条件,并且三个 Pod 都达到对应状态”。如果 DESIRED=3、CURRENT=3、READY=0,说明控制器已创建 Pod,但容器启动或就绪过程失败。
进一步诊断:
kubectl -n observability get events \
--sort-by=.lastTimestamp
kubectl -n observability describe pod <pod-name>
kubectl -n observability logs <pod-name> -c agent
如果 Pod 处于 Pending,重点看调度事件,例如:
0/5 nodes are available:
2 Insufficient memory,
2 node(s) didn't match Pod's node affinity,
1 node(s) had untolerated taint
这三类原因分别对应:
- 资源请求超过节点可分配资源;
- 标签或亲和性不匹配;
- Pod 没有对应 toleration。
六、资源治理:DaemonSet 的成本按节点线性增长
1. 资源请求如何影响调度
对一个节点,调度器主要根据 Pod 的资源请求进行可行性判断,而不是根据容器此刻的实际 CPU 使用率。
设节点可分配资源为:
该节点上已有 Pod 的资源请求总和为:
DaemonSet Pod 的资源请求为:
则至少要满足:
例如某节点可分配内存为 8 GiB,已有工作负载请求 7.95 GiB,DaemonSet Pod 请求 128 MiB:
即使节点当前监控到的实际内存使用只有 5 GiB,调度器也可能拒绝该 Pod,因为调度使用的是请求账本,而不是瞬时利用率。
DaemonSet 的全局请求成本可以近似表示为:
例如:
- 100 个目标节点;
- 每个 Agent 请求
200mCPU; - 每个 Agent 请求
256Mi内存。
那么该 DaemonSet 的调度请求总量约为:
这还没有计算容器运行时、日志缓存、页缓存、网络缓冲和 kubelet 本身的开销。
2. requests 和 limits 的区别
requests影响调度和 QoS 分类;limits约束容器可使用的资源上限;- CPU 超过 limit 时通常会被节流;
- 内存超过 limit 时可能触发 OOMKill;
- 节点内存压力下,Pod 的 QoS 和优先级会影响驱逐顺序。
如果一个节点 Agent 同时设置:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
调度器只按 100m CPU 和 128Mi 内存计入资源请求,但运行时可能允许它使用到 500m CPU 和 512Mi 内存。多个 Agent 叠加后,节点可能出现“请求账本允许,但实际运行时资源不足”的情况。
3. QoS 分类
Pod 的 QoS 类别由容器资源配置决定:
- 所有容器的 CPU、内存 request 和 limit 都设置且相等:
Guaranteed; - 至少有一个容器设置 request 或 limit,但不满足 Guaranteed 条件:
Burstable; - 所有容器都没有 request 和 limit:
BestEffort。
节点级 Agent 通常不应无条件使用 BestEffort,因为它可能在节点压力下最早被驱逐;但把所有 Agent 都设置为 Guaranteed 也会显著提高资源预留,必须基于实际工作负载和节点容量计算。
4. 节点级 Agent 的资源不是“免费旁路”
常见错误是把 Agent 视为系统组件,于是省略资源请求:
resources: {}
后果可能是:
- 调度器低估该 Agent 的资源占用;
- 业务 Pod 被调度到看似有余量的节点;
- Agent 与业务容器在运行时争抢 CPU 或内存;
- 节点进入 MemoryPressure;
- kubelet 驱逐 Pod;
- Agent 本身也可能丢失日志或指标。
生产治理通常应同时关注:
- CPU request 是否足以保证采集循环及时运行;
- 内存 request 是否覆盖正常缓存;
- 内存 limit 是否能限制失控缓存;
- 日志突发时是否会无限堆积;
- Agent 的本地队列是否使用
emptyDir,其大小是否受控; - 节点是否为系统组件预留了资源;
- Agent 是否需要高优先级,还是应该允许让位给关键业务。
priorityClassName 可以影响调度抢占和驱逐相关行为,但高优先级不是资源增加器。若每个节点都安装多个高优先级 DaemonSet,抢占可能反过来挤压业务,甚至造成系统组件之间互相竞争。
七、节点级日志 Agent 与日志架构
Kubernetes 中最常见的日志路径是:
应用进程 stdout/stderr
↓
容器运行时
↓
节点上的容器日志文件
↓
DaemonSet 日志 Agent
↓
远端日志系统
应用容器将日志写入 stdout 或 stderr,容器运行时负责保存和轮转。节点级 Agent 通常以 DaemonSet 运行,通过 hostPath 读取节点上的容器日志目录,再解析 Pod、Namespace、容器名等元数据。
这个架构的优点是:
- 每个节点只运行一个采集 Agent;
- 不需要修改每个业务镜像;
- 能够统一处理节点上的日志;
- Agent 可以读取容器日志文件并批量发送。
但它也有明确边界:
hostPath让容器获得主机文件访问能力;- 容器日志文件路径依赖容器运行时和节点系统;
- 日志轮转速度超过 Agent 读取速度时,可能发生丢失;
- Agent 重启期间的本地缓存策略决定了是否能恢复;
- 节点损坏时,本地尚未发送的日志通常无法恢复。
Sidecar 日志采集则把采集器放进业务 Pod,优点是隔离性和应用级配置更强,代价是每个业务 Pod 都增加一个容器,资源、网络连接和运维对象数量都会增加。DaemonSet 适合节点级统一采集,Sidecar 适合必须按应用隔离、读取非标准日志文件或需要独立生命周期的场景。
日志留存也分两层:
- 节点本地容器日志留存,由容器运行时轮转和节点磁盘容量约束;
- 远端日志留存,由日志后端的索引、对象存储和生命周期策略约束。
DaemonSet 不能替代日志后端留存策略,也不能保证节点故障时本地日志不丢失。若日志系统要求较强可靠性,需要明确发送确认、磁盘缓冲、重试上限、背压和丢弃策略。
八、更新机制:RollingUpdate 和 OnDelete
DaemonSet 的 updateStrategy 有两个稳定策略:
updateStrategy:
type: RollingUpdate
以及:
updateStrategy:
type: OnDelete
1. RollingUpdate
RollingUpdate 是默认策略。Pod 模板变化后,控制器逐步删除旧 Pod 并创建新 Pod。
常用配置:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 0
maxUnavailable
maxUnavailable 表示更新期间允许不可用的 DaemonSet Pod 数量,可以是整数或百分比。
如果目标节点有 10 个:
maxUnavailable: 2
通常表示更新过程中最多允许约 2 个节点上的 DaemonSet Pod 不可用。控制器会据此控制替换速度。
这里的“不可用”与 readinessProbe、minReadySeconds 和 DaemonSet 状态有关,不只是容器进程是否存在。一个 Pod 进程运行但尚未 Ready,不能简单视为已完成更新。
minReadySeconds
minReadySeconds: 30
表示新 Pod Ready 后,至少稳定 30 秒才被视为 Available。它可以避免新版本刚通过探针就立刻继续推进,从而暴露启动后延迟失败的问题。
但它不会验证业务功能正确性。例如 Agent 的 /ready 只检查 HTTP 服务是否启动,却没有检查是否能读取日志或连接远端后端,那么 minReadySeconds 只能延长“表面健康”的观察时间。
maxSurge
maxSurge 允许更新期间在一个节点上临时运行额外 Pod:
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
这可以先启动新 Pod,再终止旧 Pod,降低节点级 Agent 的空窗期。但需要注意:
- 同一节点临时存在两个 Agent;
- 两个 Agent 可能重复采集同一日志;
- 主机端口、独占设备、文件锁或节点级 socket 可能冲突;
- 资源峰值会增加;
- 只有较新的 Kubernetes 版本支持并稳定实现 DaemonSet 的
maxSurge行为,使用前应核对目标集群版本和 API 文档。
对于日志采集 Agent,maxSurge 尤其需要验证重复采集和偏移量竞争。很多 Agent 默认假设每个节点只有一个实例,不能直接开启 surge。
maxUnavailable 和 maxSurge 都不能只从“更新速度”考虑,还要从节点资源和数据一致性考虑。
2. OnDelete
updateStrategy:
type: OnDelete
更新 Pod 模板后,控制器不会主动替换现有 Pod。只有旧 Pod 被删除,新的 Pod 才会按新模板创建。
该策略适合:
- 需要人工逐节点验证;
- 由外部系统控制重启;
- 对自动更新有严格限制;
- Agent 更新必须避开某些维护窗口。
代价是配置变更不会自动生效。执行以下命令只能更新 DaemonSet 模板,不会立即替换所有现有 Pod:
kubectl -n observability patch daemonset node-log-agent \
--type='strategic' \
-p='{"spec":{"template":{"spec":{"containers":[{"name":"agent","image":"ghcr.io/example/node-log-agent:1.5.0"}]}}}}'
随后需要逐个删除 Pod:
kubectl -n observability delete pod <pod-name>
删除后,DaemonSet 控制器会创建符合当前模板的新 Pod。
3. 执行更新、观察和回滚
更新镜像:
kubectl -n observability set image daemonset/node-log-agent \
agent=ghcr.io/example/node-log-agent:1.5.0
观察滚动过程:
kubectl -n observability rollout status daemonset/node-log-agent
kubectl -n observability get pods -l app=node-log-agent -o wide
查看历史:
kubectl -n observability rollout history daemonset/node-log-agent
回滚:
kubectl -n observability rollout undo daemonset/node-log-agent
kubectl -n observability rollout status daemonset/node-log-agent
回滚只会恢复 Kubernetes 对象记录的旧 Pod 模板。它不会恢复:
- 已经删除的本地日志;
- 已经发送到远端的错误数据;
- Agent 在节点上写入的外部状态;
- 与镜像不兼容的外部配置。
如果新镜像因为启动失败而持续 CrashLoopBackOff,滚动更新可能长期无法完成。此时应先查看具体 Pod 的事件和容器日志,再决定暂停变更、回滚或修复配置。DaemonSet 没有 Deployment 那种“自动发现应用级功能错误并自行回滚”的能力。
九、Pod 替换、故障恢复和并发边界
1. kubelet 重启不一定导致 Pod 重建
如果 kubelet 短暂重启,容器运行时中的容器可能仍然存在。kubelet 恢复后会重新同步本地状态,Pod 不一定经历完整删除和重建。
如果节点被控制面标记为 NotReady:
- 节点可能被添加
node.kubernetes.io/not-readytaint; - 普通 Pod 可能因
NoExecute而被驱逐; - DaemonSet Pod 的自动 toleration 可能让它继续保留更长时间;
- 但 kubelet 不恢复时,Pod 并不能真正提供节点级服务。
因此,“Pod 对象仍然存在”不代表“Agent 可用”。
2. 节点被删除后,DaemonSet 不会把 Pod 搬到别的节点
DaemonSet Pod 与节点绑定。节点彻底从 API Server 删除后,控制器会在其他目标节点维持各自的 Pod,但不会为了弥补一个节点而在别的节点创建第二份。
这保证了节点级语义:
而不是:
3. 更新期间可能存在短暂重复或短暂缺失
不使用 surge 时,常见流程是:
- 删除节点上的旧 Pod;
- 等待旧 Pod 终止;
- 创建或调度新 Pod;
- 等待新 Pod Ready;
- 继续下一个节点。
因此会有短暂空窗。
使用 surge 时,流程可能变成:
- 在目标节点上创建新 Pod;
- 新 Pod 调度并启动;
- 新 Pod 达到可用条件;
- 删除旧 Pod。
因此会有短暂重复。
控制器需要处理 API Server 延迟、Pod 删除宽限期和节点状态变化,所以在极短时间内观察到旧 Pod 和新 Pod 同时存在,或状态字段尚未收敛,并不必然是控制器失效。应结合 Pod 的 ownerReferences、模板哈希、创建时间和事件判断。
十、selector 是不可随意修改的身份边界
DaemonSet 的选择器必须匹配 Pod 模板标签:
spec:
selector:
matchLabels:
app: node-log-agent
template:
metadata:
labels:
app: node-log-agent
apps/v1 中,DaemonSet 的 .spec.selector 是不可变字段。这样做是为了避免控制器突然接管或遗弃一批已有 Pod。
如果选择器和模板标签不匹配,创建请求会被 API Server 拒绝。若多个工作负载错误地使用相同标签选择器,则可能导致:
kubectl get pods -l ...混入其他工作负载;- 诊断结果被误读;
- 控制器管理边界混乱;
- 删除和滚动更新时难以区分 Pod 所属。
应当使用稳定且专属于该 DaemonSet 的标签,而不是复用宽泛标签。
十一、DaemonSet 与 Deployment、StatefulSet 的差异
Deployment
Deployment 以副本数为中心:
集群中运行 N 个 Pod
它适合无状态业务服务。Pod 可以被调度到任意满足条件的节点,副本通常不与节点一一对应。
StatefulSet
StatefulSet 以稳定身份和有序生命周期为中心:
- 稳定 Pod 名称;
- 稳定网络身份;
- 稳定存储绑定;
- 有序创建和终止。
它适合有状态应用,不等价于节点级 Agent。
DaemonSet
DaemonSet 以节点覆盖范围为中心:
每个符合条件的节点运行一个 Pod
当节点数量、节点标签或节点 taint 变化时,目标副本数随之变化。因此不能用 replicas 字段扩缩 DaemonSet;DaemonSet 的规模由目标节点集合决定。
十二、失败表现和诊断方法
情况一:DESIRED=0
先检查筛选条件:
kubectl get nodes --show-labels
kubectl -n observability get daemonset node-log-agent -o yaml
重点核对:
nodeSelector是否写错;nodeAffinity是否要求不存在的标签;- 节点操作系统是否符合条件;
- DaemonSet 是否部署在正确命名空间;
- 是否误以为 taint 会决定
DESIRED。
某些 taint 主要影响调度,不一定改变控制器对目标节点集合的判断。因此不能只看到 taint 就断定节点不会被计入期望集合。
情况二:DESIRED 大于 CURRENT
说明控制器期望有更多 Pod,但当前 Pod 尚未全部创建、调度或被 API Server 观察到。检查:
kubectl -n observability describe daemonset node-log-agent
kubectl -n observability get events --sort-by=.lastTimestamp
还应检查控制器日志和 API Server 健康状况。若是大规模节点变更,状态短暂滞后是可能的。
情况三:CURRENT 正常但 READY=0
这通常意味着 Pod 已存在,但容器尚未就绪。检查:
kubectl -n observability get pods -l app=node-log-agent
kubectl -n observability describe pod <pod-name>
kubectl -n observability logs <pod-name> -c agent --previous
常见原因:
- readinessProbe 路径错误;
- 依赖的远端日志系统不可达;
- 配置文件格式错误;
- Agent 无法读取
hostPath; - 运行用户没有访问文件的权限;
- 容器启动后立即退出。
情况四:Pod 长时间 Pending
用调度事件定位:
kubectl -n observability describe pod <pod-name>
如果事件为 Insufficient cpu 或 Insufficient memory,调整 Agent 请求、扩容节点或修改节点筛选范围。不能只降低 request 来“骗过”调度器,因为这会把真实资源压力推迟到运行时。
如果事件为 untolerated taint,应先确认该 taint 是否应该被容忍,而不是机械地加入:
tolerations:
- operator: Exists
无条件容忍所有 taint 会让节点 Agent 进入控制面、专用硬件或严重异常节点,扩大故障影响面。
情况五:更新卡住
检查:
kubectl -n observability rollout status daemonset/node-log-agent
kubectl -n observability get pods -l app=node-log-agent -o wide
kubectl -n observability describe daemonset node-log-agent
常见原因:
- 新镜像无法拉取;
- 新 Pod 的 readinessProbe 永远失败;
maxUnavailable过小,且已有 Pod 不可用;- 新版本资源请求无法在目标节点放置;
hostPort或设备资源冲突;- 节点本身处于 NotReady;
- Pod 删除受到宽限期或容器退出阻塞。
确认旧版本仍有服务能力后,再执行回滚。盲目删除全部 Pod 可能让每个节点同时失去 Agent。
十三、生产边界:安全、可靠性和维护窗口
1. hostPath 的安全风险
访问以下路径的 DaemonSet Pod 风险不同:
/var/log/pods:通常是日志采集所需的较窄权限;/var/lib/containerd:可能暴露容器运行时数据;/etc:可能读取主机敏感配置;/var/run:可能接触主机 socket;/:几乎等同于暴露整个主机文件系统。
应限制:
hostPath.path的范围;- 挂载是否只读;
- 容器是否需要
privileged; - 是否需要
hostNetwork、hostPID或hostIPC; - ServiceAccount 是否拥有过宽 RBAC 权限;
- Pod Security Admission 是否允许这些能力。
节点 Agent 需要的权限应按功能拆分,而不是因为“它是系统 Agent”就授予所有主机能力。
2. 更新策略要匹配数据语义
对于监控 Agent,短暂重启可能只造成少量指标缺口;对于日志 Agent,重启可能导致文件偏移、重复采集或丢失。
因此更新前应明确:
- Agent 的 checkpoint 存在哪里;
- checkpoint 是否在容器重建后保留;
- 新旧实例同时存在是否会重复发送;
- 远端系统如何去重;
- 节点维护时是否允许采集空窗;
- 是否需要按区域、节点池或标签分批更新。
DaemonSet 的 maxUnavailable 只控制 Kubernetes 层面的替换节奏,不理解日志事件是否重复,也不会验证远端数据完整性。
3. PDB 不是 DaemonSet 更新的替代品
PodDisruptionBudget 主要约束符合条件的 Pod 在自愿中断场景下的可用数量,例如节点排空使用 Eviction API 的过程。它不能代替 DaemonSet 的 maxUnavailable,也不应被当成滚动更新的唯一保护机制。
DaemonSet 更新使用自己的更新策略;节点排空还会受到 taint、驱逐、kubelet 和 Pod 终止行为影响。两者需要分别验证。
十四、版本和实现差异
以下内容应特别注意版本范围:
- DaemonSet 使用稳定的
apps/v1API;不要继续使用早期 beta API。 maxUnavailable、minReadySeconds、OnDelete等字段属于稳定 DaemonSet 能力,但具体默认值和校验行为应以目标集群版本的 OpenAPI schema 为准。- DaemonSet 的
maxSurge是较新的更新能力。老版本集群、发行版补丁版本或旧客户端可能不支持或行为不同,部署前应检查:kubectl explain daemonset.spec.updateStrategy.rollingUpdate - 节点日志目录不是所有系统都相同。容器运行时、Linux 发行版、日志配置和云厂商节点镜像都可能改变路径及轮转行为。
- 托管 Kubernetes 服务可能对控制面组件、系统节点、污点、标签和日志路径做额外处理。不能把某个云厂商节点池的标签或 taint 假设为 Kubernetes 的通用规范。
- 自动注入的 DaemonSet toleration 属于控制器行为的一部分,若依赖某个具体 taint 的容忍效果,应在目标版本上通过:
检查最终 Pod 规格,而不是只看提交的模板。kubectl -n observability get pod <pod-name> -o yaml
十五、用一组不变量理解 DaemonSet
可以用下面几条不变量检查设计是否合理:
节点覆盖不变量
对于每个目标节点 ,最终应存在一个该 DaemonSet 的可运行 Pod;但在调度、更新和故障期间允许暂时偏离。
资源可行不变量
对每个节点:
如果这个条件不成立,Pod 即使在逻辑上“应该存在”,也无法被调度。
容忍边界不变量
Toleration 只解除指定 taint 带来的调度或驱逐限制:
更新安全不变量
新版本被视为可用之前,必须满足实际需要的健康条件,而不仅是容器进程启动:
但反向关系通常不成立:
因为就绪探针只验证它实现的检查项。
DaemonSet 的本质不是“自动在每个节点执行一个容器”,而是把节点集合、Pod 调度、节点状态、资源预算和版本替换连接成一个持续收敛的控制过程。理解这一点后,nodeSelector 决定覆盖范围,调度器决定能否放置,toleration 决定能否接受特定 taint,资源请求决定账面容量,探针决定可用性,更新策略决定替换节奏,而 kubelet 和节点状态决定最终运行结果。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:StatefulSet:稳定身份、顺序、PVC、更新和分布式系统边界
- 下一篇:Job 与 CronJob:并行、重试、补跑、时区、幂等和清理
- 延伸:Kubernetes Node 生命周期:Condition、Lease、Taint、NotReady 和恢复
- 延伸:Kubernetes 日志架构:stdout、Node Agent、Sidecar、采集和留存
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论