上一篇【第22篇】StatefulSet——有状态应用的“私人管家“
下一篇【第24篇】Job和CronJob——一次性任务和定时任务
摘要
你有没有想过一个问题——10个Node的K8s集群,怎么保证每个Node上都跑着日志收集器(Filebeat)、监控Agent(Prometheus node_exporter)、网络插件(Calico)?用Deployment设replicas=10?万一调度器把两个Pod塞到同一台机器上呢?万一有台机器没分到呢?DaemonSet就是这种场景的标准答案——它保证每个符合条件的Node上都恰好运行一个Pod副本。
这篇文章从DaemonSet的典型应用场景切入,讲清楚它跟Deployment/StatefulSet的本质区别(绕过Scheduler直通kubelet),拆解RollingUpdate和OnDelete两种更新策略的差异,用NodeSelector和Toleration控制部署范围,最后实战部署一个日志收集DaemonSet和监控Agent。
一、DaemonSet的典型场景——“哪里需要哪里搬”
1.1 三大主力场景
【DaemonSet的经典应用场景】 ┌─────────────────────────────────────────────────────────────┐ │ K8s 集群(3个Node) │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Node-1 │ │ Node-2 │ │ Node-3 │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ │ │ │Filebeat │ │ │ │Filebeat │ │ │ │Filebeat │ │ ← 日志收集 │ │ │收集本机日志│ │ │ │收集本机日志│ │ │ │收集本机日志│ │ │ │ │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ │ │ │node_exprt│ │ │ │node_exprt│ │ │ │node_exprt│ │ ← 监控Agent │ │ │采集本机指标│ │ │ │采集本机指标│ │ │ │采集本机指标│ │ │ │ │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ ┌──────────┐ │ │ │ │ │ CNI 插件 │ │ │ │ CNI 插件 │ │ │ │ CNI 插件 │ │ ← 网络插件 │ │ │(Calico) │ │ │ │(Calico) │ │ │ │(Calico) │ │ │ │ │ └──────────┘ │ │ └──────────┘ │ │ └──────────┘ │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ 共同特点:每个Node一个,不需要更多,不能更少1.2 具体场景分析
| 场景类别 | 典型应用 | 为什么必须是DaemonSet |
|---|---|---|
| 日志收集 | Filebeat/Fluentd | 每个Node上所有Pod的日志都写到Node本地磁盘,需要本地Agent把日志推送到ES/Kafka |
| 监控Agent | Prometheus node_exporter / Datadog Agent | 采集Node级别的指标(CPU/内存/磁盘),只能在Node本机上采集 |
| 网络插件 | Calico/Flannel/Cilium | 每个Node都需要网络代理来处理Pod网络流量 |
| 存储插件 | CSI Node Plugin | 每个Node上需要存储驱动来挂载远程存储卷 |
| 安全审计 | Falco/Sysdig | 每个Node上需要内核级事件监控 |
| GPU管理 | NVIDIA device plugin | 有GPU的Node需要安装GPU驱动和管理插件 |
要点:DaemonSet的本质是"守护进程的K8s版本"。在传统运维里,你登录每台机器手动安装filebeat、node_exporter;在K8s里,一个DaemonSet YAML搞定——节点自动加入自动部署,节点离开自动清理。
二、DaemonSet的工作原理——“不走Scheduler,直通kubelet”
2.1 调度差异——DaemonSet为什么不经过Scheduler?
【Deployment调度 vs DaemonSet调度】 Deployment Pod 创建流程: DaemonSet Pod 创建流程: ┌────────────────────┐ ┌────────────────────┐ │ Deployment │ │ DaemonSet │ │ Controller │ │ Controller │ └────────┬───────────┘ └────────┬───────────┘ │ 创建Pod │ 直接指定Node ▼ ▼ ┌────────────────────┐ ┌────────────────────┐ │ Scheduler │ │ 每个Node的 │ │ ┌──────────────┐ │ │ kubelet │ │ │ 打分 → 选出 │ │ ← 跳过! │ │ │ │ 最优Node │ │ │ "来了个DaemonSet │ │ └──────────────┘ │ │ Pod,跑在我身上" │ └────────┬───────────┘ └────────────────────┘ │ 分配给某Node ▼ ┌────────────────────┐ │ kubelet 启动Pod │ └────────────────────┘ 区别: • Deployment:Pod创建后,Scheduler挑一个合适的Node • DaemonSet:每个Node上都跑一个,不需要挑——每个Node都要# DaemonSet——基本YAMLapiVersion:apps/v1kind:DaemonSetmetadata:name:filebeatlabels:app:filebeatspec:selector:matchLabels:app:filebeattemplate:metadata:labels:app:filebeatspec:containers:-name:filebeatimage:docker.elastic.co/beats/filebeat:8.10.0volumeMounts:-name:varlogmountPath:/var/log/containersreadOnly:true-name:varlibdockercontainersmountPath:/var/lib/docker/containersreadOnly:true-name:configmountPath:/usr/share/filebeat/filebeat.ymlsubPath:filebeat.ymlvolumes:-name:varloghostPath:path:/var/log/containers-name:varlibdockercontainershostPath:path:/var/lib/docker/containers-name:configconfigMap:name:filebeat-config2.2 新Node加入——自动部署
【DaemonSet的自动化能力】 时刻T1:集群只有2个Node ┌──────────────┐ ┌──────────────┐ │ Node-1 │ │ Node-2 │ │ [filebeat] │ │ [filebeat] │ └──────────────┘ └──────────────┘ 时刻T2:新Node加入集群 ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Node-1 │ │ Node-2 │ │ Node-3 │ │ [filebeat] │ │ [filebeat] │ │ ???? │ ← 新Node,自动部署 └──────────────┘ └──────────────┘ └──────────────┘ │ 几秒钟后自动完成 ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Node-1 │ │ Node-2 │ │ Node-3 │ │ [filebeat] │ │ [filebeat] │ │ [filebeat] │ ← 自动部署完成! └──────────────┘ └──────────────┘ └──────────────┘要点:这是DaemonSet最让人省心的地方——不用操心节点扩容。加一个Node,DaemonSet Controller自动检测到新Node,秒级创建一个Pod上去。反过来也一样——Node下线/驱逐,DaemonSet Pod跟着清理。
三、精准控制部署范围——“不是所有Node都要”
3.1 NodeSelector——指定Node标签
apiVersion:apps/v1kind:DaemonSetmetadata:name:gpu-monitorspec:selector:matchLabels:app:gpu-monitortemplate:metadata:labels:app:gpu-monitorspec:nodeSelector:# ← 只在有GPU的Node上部署accelerator:nvidia-tesla-v100containers:-name:gpu-monitorimage:nvidia/dcgm-exporter:latest# 给Node打标签kubectl label nodes node-1accelerator=nvidia-tesla-v100 kubectl label nodes node-2accelerator=nvidia-tesla-v100# 看效果——只有打了标签的Node上才会跑kubectl get pods-lapp=gpu-monitor-owide# NAME NODE STATUS# gpu-monitor-xxxxx node-1 Running# gpu-monitor-yyyyy node-2 Running# node-3没有这个Pod(没打标签)3.2 NodeAffinity——更精细的调度策略
【NodeSelector vs NodeAffinity】 NodeSelector(简单): NodeAffinity(灵活): ┌──────────────────┐ ┌──────────────────────────┐ │ 只支持等于匹配 │ │ 支持 In/NotIn/Exists/ │ │ accelerator=nvidia │ │ DoesNotExist/Gt/Lt │ │ │ │ │ │ 硬性要求(必须满足) │ │ 硬性+软性(尽量满足) │ └──────────────────┘ └──────────────────────────┘spec:affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:# 硬性要求nodeSelectorTerms:-matchExpressions:-key:node-role.kubernetes.io/workeroperator:Invalues:-"true"-key:disktypeoperator:Invalues:-ssdpreferredDuringSchedulingIgnoredDuringExecution:# 软性偏好-weight:100preference:matchExpressions:-key:zoneoperator:Invalues:-zone-a3.3 Tolerations——“容忍污点”
有些Node打了"污点"(Taint),普通Pod不调度上去——但DaemonSet Pod可以通过Toleration"容忍"这些污点。
# Master/Control-Plane节点默认有污点kubectl describenodemaster-node|grepTaints# Taints: node-role.kubernetes.io/control-plane:NoSchedule# DaemonSet想跑在Master上?加Toleration:spec:template:spec:tolerations:-key:node-role.kubernetes.io/control-planeoperator:Existseffect:NoSchedule# 容忍这个污点# 监控Agent需要跑在所有Node上——包括Master【Taint + Toleration —— "你不嫌我脏,我就让你上"】 Node打上污点(Taint): Pod声明容忍(Toleration): ┌────────────────────────┐ ┌────────────────────────┐ │ Node: master-node │ │ Pod: monitoring-agent │ │ Taint: │ │ Toleration: │ │ control-plane: │ ←→ │ control-plane: │ │ NoSchedule │ 匹配! │ Exists │ │ │ │ │ │ "我是控制节点, │ │ "我不介意你是控制节点" │ │ 别随便往我身上调度" │ │ │ └────────────────────────┘ └────────────────────────┘要点:Toleration是让DaemonSet"全身覆盖"的关键手段。监控Agent、日志收集器、网络插件这些基础设施通常需要跑在所有Node上——包括打了污点的Master/Control-Plane节点。如果不加Toleration,Master节点就成盲区了。
3.4 部署范围控制总结
| 机制 | 作用 | 场景举例 |
|---|---|---|
nodeSelector | 简单标签匹配 | 只在GPU节点部署GPU监控 |
nodeAffinity | 复杂条件匹配 | 只在SSD节点的Zone-A部署 |
tolerations | 容忍Node污点 | 让DaemonSet跑在Master节点上 |
四、更新策略——“怎么安全地升级所有Node”
4.1 RollingUpdate——滚动更新(默认)
【DaemonSet RollingUpdate 流程——一个Node一个Node更新】 Node-1 [Filebeat v1] ─┬─ 删除 v1 ─┬─ 创建 v2 ─┬─ v2 Ready │ │ │ Node-2 [Filebeat v1] ─│── 等待… ──│── 删除v1 ─│── 创建v2 ─┬─ v2 Ready │ │ │ │ Node-3 [Filebeat v1] ─│── 等待… ──│── 等待… ──│── 等待… ──│── 删除v1 ─┬─ v2 Ready │ │ │ │ │ ────────────────────────┴───────────┴───────────┴───────────┴───────────┘ 一次只更新一个Node,等新Pod Ready再更新下一个apiVersion:apps/v1kind:DaemonSetmetadata:name:filebeatspec:updateStrategy:type:RollingUpdaterollingUpdate:maxUnavailable:1# 最多一个Node不可用maxSurge:0# DaemonSet不支持surge(跟Deployment不同!)template:spec:containers:-name:filebeatimage:docker.elastic.co/beats/filebeat:8.11.0# 最新版4.2 OnDelete——"手动挡"更新
spec:updateStrategy:type:OnDelete# 不自动更新——等你手动删Pod才更新# OnDelete策略下:更新DaemonSet模板后,已有的Pod不会变kubectl edit daemonset filebeat# 改镜像版本# Pod还是旧版本!kubectl get pods-lapp=filebeat-ojsonpath='{.items[*].spec.containers[*].image}'# filebeat:8.10.0 filebeat:8.10.0 filebeat:8.10.0# 手动删一个Pod——它重建后变成新版本kubectl delete pod filebeat-xxxxx# 删Node-1上的# 重建后:filebeat:8.11.0# 一个一个手动删除,逐个更新——完全由你掌控节奏| 策略 | 自动化 | 风险控制 | 适用场景 |
|---|---|---|---|
| RollingUpdate | ✅ 自动逐个更新 | maxUnavailable控制 | 常规更新——推荐 |
| OnDelete | ❌ 手动删除Pod才更新 | 完全手动控制 | 谨慎场景——更新前需要手动验证 |
要点:DaemonSet的RollingUpdate有一个重要限制——不支持maxSurge(跟Deployment不同)。原因很简单:每个Node上只能跑一个DaemonSet Pod,不能像Deployment那样"先创一个新的再删旧的"。所以DaemonSet的更新是删除旧Pod→创建新Pod,中间有短暂的空窗期。
五、实战:部署一个完整的日志收集DaemonSet
5.1 Filebeat DaemonSet——收集所有Pod日志
# ConfigMap——Filebeat配置apiVersion:v1kind:ConfigMapmetadata:name:filebeat-confignamespace:kube-systemdata:filebeat.yml:|filebeat.inputs: - type: container paths: - /var/log/containers/*.log processors: - add_kubernetes_metadata: host: ${NODE_NAME} matchers: - logs_path: logs_path: "/var/log/containers/"output.elasticsearch:hosts:['${ELASTICSEARCH_HOST:elasticsearch:9200}']index:"filebeat-%{[agent.version]}-%{+yyyy.MM.dd}"logging.level:infologging.to_files:truelogging.files:path:/var/log/filebeatname:filebeatkeepfiles:7---# DaemonSetapiVersion:apps/v1kind:DaemonSetmetadata:name:filebeatnamespace:kube-systemlabels:app:filebeatspec:selector:matchLabels:app:filebeatupdateStrategy:type:RollingUpdaterollingUpdate:maxUnavailable:1template:metadata:labels:app:filebeatspec:serviceAccountName:filebeatterminationGracePeriodSeconds:30hostNetwork:truednsPolicy:ClusterFirstWithHostNettolerations:-key:node-role.kubernetes.io/control-planeoperator:Existseffect:NoSchedulecontainers:-name:filebeatimage:docker.elastic.co/beats/filebeat:8.10.0args:-"-c"-"/usr/share/filebeat/filebeat.yml"-"-e"env:-name:ELASTICSEARCH_HOSTvalue:"elasticsearch.logging.svc.cluster.local:9200"-name:NODE_NAMEvalueFrom:fieldRef:fieldPath:spec.nodeNameresources:requests:memory:"200Mi"cpu:"100m"limits:memory:"500Mi"cpu:"200m"securityContext:runAsUser:0volumeMounts:-name:configmountPath:/usr/share/filebeat/filebeat.ymlsubPath:filebeat.yml-name:varlogmountPath:/var/log/containersreadOnly:true-name:varlibdockercontainersmountPath:/var/lib/docker/containersreadOnly:true-name:datamountPath:/usr/share/filebeat/datavolumes:-name:configconfigMap:name:filebeat-configdefaultMode:0644-name:varloghostPath:path:/var/log/containers-name:varlibdockercontainershostPath:path:/var/lib/docker/containers-name:datahostPath:path:/var/lib/filebeat-datatype:DirectoryOrCreate---# RBAC——Filebeat需要读取K8s API获取Pod元数据apiVersion:v1kind:ServiceAccountmetadata:name:filebeatnamespace:kube-system---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRolemetadata:name:filebeatrules:-apiGroups:[""]resources:["pods","namespaces"]verbs:["get","list","watch"]---apiVersion:rbac.authorization.k8s.io/v1kind:ClusterRoleBindingmetadata:name:filebeatsubjects:-kind:ServiceAccountname:filebeatnamespace:kube-systemroleRef:kind:ClusterRolename:filebeatapiGroup:rbac.authorization.k8s.io5.2 Prometheus node_exporter——每个Node的监控Agent
apiVersion:apps/v1kind:DaemonSetmetadata:name:node-exporternamespace:monitoringlabels:app:node-exporterspec:selector:matchLabels:app:node-exporterupdateStrategy:type:RollingUpdaterollingUpdate:maxUnavailable:1template:metadata:labels:app:node-exporterspec:hostNetwork:true# 直接使用Node网络hostPID:true# 访问Node的进程信息tolerations:-operator:Exists# 容忍所有污点containers:-name:node-exporterimage:prom/node-exporter:v1.7.0args:---path.procfs=/host/proc---path.sysfs=/host/sys---path.rootfs=/host/root---collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+)($|/)---collector.filesystem.fs-types-exclude=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefds|securityfs|sysfs|tracefs)$ports:-containerPort:9100hostPort:9100name:metricsvolumeMounts:-name:procmountPath:/host/procreadOnly:true-name:sysmountPath:/host/sysreadOnly:true-name:rootmountPath:/host/rootreadOnly:truemountPropagation:HostToContainervolumes:-name:prochostPath:path:/proc-name:syshostPath:path:/sys-name:roothostPath:path:/# 部署验证kubectl apply-fnode-exporter-daemonset.yaml# 看——每个Node一个Podkubectl get pods-nmonitoring-lapp=node-exporter-owide# NAME NODE STATUS# node-exporter-abc12 master-node Running# node-exporter-def34 worker-node-1 Running# node-exporter-ghi56 worker-node-2 Running# 测试metrics接口kubectlexec-nmonitoring node-exporter-abc12 --wget-qO- http://localhost:9100/metrics|head要点:node_exporter需要直接访问Node的
/proc和/sys文件系统——这些是宿主机的数据,不是Pod内部的。所以用hostPath挂载,而且设置了hostNetwork: true和hostPID: true让Pod直接使用宿主机的网络和进程命名空间。securityContext里需要privileged: true或者特定的capabilities才能访问内核数据。
六、DaemonSet常用命令和调试
# 查看所有DaemonSetkubectl get daemonset --all-namespaces# 查看某个DaemonSet详情kubectl describe daemonset filebeat-nkube-system# 看每个Node上的DaemonSet Pod分布kubectl get pods-nkube-system-lapp=filebeat-owide# 查看DaemonSet状态——关键字段kubectl get daemonset filebeat-nkube-system# NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR# filebeat 3 3 3 3 3 <none>## DESIRED: 应该运行Pod的Node数(符合条件的所有Node)# CURRENT: 当前运行的Pod数# READY: 就绪的Pod数# 滚动更新kubectlsetimage daemonset/filebeatfilebeat=filebeat:8.11.0-nkube-system# 查看更新进度kubectl rollout status daemonset/filebeat-nkube-system# 回滚到上一个版本kubectl rollout undo daemonset/filebeat-nkube-system# 查看更新历史kubectl rollouthistorydaemonset/filebeat-nkube-system# 调试——为什么某些Node上没有DaemonSet Pod?kubectl get nodes --show-labels# 检查Node标签是否匹配nodeSelectorkubectl describenode<node-name>|grepTaints# 检查Taint是否被Toleration覆盖kubectl get events-nkube-system --field-selectorinvolvedObject.name=<daemonset-pod-name># 看事件日志定位调度失败原因本篇小结
DaemonSet是K8s里最"自动化"的资源控制器:
- 核心价值:保证每个符合条件的Node上正好跑一个Pod,不多不少——Node加入自动部署,Node离开自动清理
- 三大场景:日志收集(Filebeat/Fluentd)、监控Agent(node_exporter/Datadog)、基础设施(CNI/CSI)
- 调度差异:绕过Scheduler直通kubelet——因为不需要"选Node",每个Node都要
- 部署控制:NodeSelector简单筛选,NodeAffinity复杂匹配,Toleration容忍污点覆盖Master节点
- 更新策略:RollingUpdate逐个替换(不支持maxSurge),OnDelete手动掌控节奏
- 安全要点:DaemonSet Pod通常需要特权访问(hostPath/hostNetwork),RBAC和securityContext要配好
下一篇咱们聊Job和CronJob——一次性任务和定时任务。数据库备份、数据清洗、批量处理这些"跑完就停"的任务在K8s里怎么搞?
上一篇【第22篇】StatefulSet——有状态应用的“私人管家“
下一篇【第24篇】Job和CronJob——一次性任务和定时任务