1. 为什么需要DaemonSet:一批“长在节点上”的守护进程
先从一个最朴素的问题说起:Kubernetes里已经有Deployment、StatefulSet、Job这些工作负载,它们能把Pod调度到集群的各个节点上,为什么还要单独搞一个DaemonSet控制器?我自己的理解是,有一类应用的需求和“副本数量”这个概念根本不匹配——它不是“我想跑几个实例”,而是“每个节点上必须且只能跑一个实例”。最典型的例子就是日志采集、监控探针和网络插件,比如Prometheus Node Exporter、Fluentd、Calico的节点组件。这类程序如果漏掉某个节点,那个节点的日志就丢了、指标就断了、网络策略就废了,这不是副本数能解决的,是覆盖度的问题。
DaemonSet(以下简称DS)解决的就是这个“全覆盖”需求。你定义一个DS,控制器会保证集群里符合条件的每个节点上都运行一个Pod副本,节点加入集群时自动补上,节点被删掉时对应的Pod也会被清理。它本质上是一个“节点级”的工作负载控制器,和Deployment这种“集群级”的副本调度器是两套逻辑。
如果你刚开始接触K8s,建议先把DS和Deployment的区别刻在脑子里:Deployment关心“我要跑3个副本,跑在哪都行”,DS关心的是“每个节点都得有一个,缺一不可”。这两个模型的服务目标完全不同,所以适用的业务场景也完全不同。后面面试的时候,面试官问“Deployment和DaemonSet有什么区别”,本质上考的就是你有没有理解这个“节点绑定”的核心思想。
另外补充一个容易混淆的点:StatefulSet也常被拿来和DS比较,但StatefulSet的关键词是“稳定标识”和“有序部署”,DS的关键词是“节点覆盖”,两者方向完全不同。理解了它们各自的出发点,对于控制器家族的整体认知会清晰很多。
2. DaemonSet的核心运作机制与设计逻辑
2.1 控制器如何实现“每节点一个Pod”
DS控制器的工作流程并不复杂,但里面有几个关键细节值得展开讲。它主要做三件事:监听节点变化、监听DS对象变化、监听Pod变化,然后通过一个调谐循环确保实际状态和期望状态一致。
具体来说,控制器会为每一个符合条件的节点计算“这个节点上是否已经存在属于这个DS的Pod”。如果没有,就创建;如果有了但Pod异常,就重建;如果节点被标记为不可调度(比如kubectl cordon),DS默认不会在那个节点上新建Pod,但已经存在的Pod不会被主动驱逐,这一点很多人会记混。
这里有个非常核心的设计:DS创建的Pod会自动设置nodeAffinity,把Pod绑定到具体的节点上。你可以用kubectl get pod -o yaml去看一个DS管理的Pod,会发现它的spec里有类似这样的片段:
affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchFields: - key: metadata.name operator: In values: - node-01这个字段是DS控制器自动注入的,不是你在YAML里写的。它的作用是把Pod和节点死死绑定,就算你手动删掉这个Pod,控制器重建时依然会把它调度回原节点。这个机制保证了“节点级覆盖”的语义不会被破坏——你没法把一个DS的Pod漂移到别的节点上。
另一个容易被忽略的细节是:DS在创建Pod时使用的默认RestartPolicy是Always(和普通Pod一样),但它在Pod template里还默认设置了hostNetwork相关的能力支持(具体是否启用取决于模板配置)。这意味着DS非常适合运行那些需要访问宿主机网络栈或文件系统的组件,这也是为什么CNI插件、kube-proxy这类基础组件几乎全部用DS来部署。
2.2 Pod名称、调度与更新策略:DS和Deployment的三个关键区别
对比一下DS和Deployment,有三个一眼就能看出来的差异,这也是排查问题时的切入点。
第一,Pod命名规则。Deployment创建的Pod名称是<deployment-name>-<replicaset-hash>-<pod-hash>,随机性很强;DS创建的Pod名称是<daemonset-name>-<pod-hash>,更短,而且没有ReplicaSet这一层。为什么?因为DS根本不需要ReplicaSet。ReplicaSet的作用是管理“一组相同副本”,而DS的副本粒度是“节点”,每个Pod和节点一一对应,没有副本组的概念,所以直接由DS控制器管理Pod,逻辑更直接。
第二,调度语义。Deployment的Pod调度由kube-scheduler基于资源请求、亲和性、污点容忍等综合打分;DS的Pod同样走调度器,但它额外带了强制的节点亲和性。并且DS对nodeSelector和tolerations的处理更敏感——你在DS的template里配置了污点容忍,它会应用到所有节点的Pod上。
第三,更新策略。Deployment默认滚动更新,先起新Pod再淘汰旧Pod,靠ReplicaSet完成新旧交替;DS的滚动更新有两种模式:OnDelete(删除旧Pod后才创建新Pod)和RollingUpdate(默认,逐节点滚动替换)。这个我们下一节详细拆解,简单说就是DS的滚动更新要谨慎很多,因为一个节点只有一个Pod,更新失败影响的是整节点。
2.3 选择DaemonSet而不是Deployment的三类典型场景
什么时候应该用DS?我总结了三类典型场景,基本覆盖了生产环境的绝大多数需求,你可以对照检查自己的业务。
第一类是集群基础设施组件。比如kube-proxy、calico-node、flannel、coredns(虽然CoreDNS在部署形态上更像Deployment,但在某些网络方案中也有DS形态)。这类组件的特点是:每个节点都必须有,缺了节点网络就不通、Service就不可用。它们常需要访问宿主机的网络命名空间或iptables规则,用DS让Pod直接跑在节点上是最合适的。
第二类是日志采集与监控采集。比如Fluentd、Fluent Bit、Filebeat、Prometheus Node Exporter、Datadog Agent。这类组件的核心诉求是“采集本节点的数据”,包括容器日志、系统指标、宿主机状态等。如果用Deployment部署,Pod分布不均匀,有的节点没有采集器,有的节点有两个,数据就乱了。DS天然保证每节点一个,采集逻辑简单清晰。
第三类是本地存储管理或设备管理类组件。比如某些GPU管理插件、本地磁盘的PV provisioner、节点清理工具。这类组件需要对宿主机上的物理设备做操作,必须运行在宿主机上,而且每个节点一个就够,多了反而冲突。
所以回头看:如果你要部署的是一个无状态API服务,副本数3个即可,用Deployment;如果你要部署的是一套“节点级系统组件”,用DS。这个判断标准比背任何文档都管用。
3. 从零定义一个DaemonSet:YAML拆解与关键参数说明
3.1 一个最小可运行的DS示例
直接上一个我平时用的最小示例,部署一个node-exporter风格的采集器,用nginx镜像代替,方便你观察行为:
apiVersion: apps/v1 kind: DaemonSet metadata: name: node-logger namespace: default spec: selector: matchLabels: app: node-logger template: metadata: labels: app: node-logger spec: tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule containers: - name: logger image: nginx:1.24 resources: requests: cpu: 100m memory: 64Mi limits: cpu: 200m memory: 128Mi这里有几个关键点要说明。第一,spec.selector必须和template.metadata.labels匹配,而且一旦创建后selector不可修改(和Deployment一样),想改只能删除重建。第二,这个示例里加了容忍control-plane节点的污点,目的是让DS也调度到master节点上——生产环境里监控采集器通常确实需要采集master节点的指标,但如果你不想让DS跑在master上,就把tolerations去掉。第三,resources建议一定写上,否则DS在节点上创建的Pod没有资源上限,高负载时可能挤占系统组件资源。
创建的命令很简单:
kubectl apply -f node-logger.yaml kubectl get ds kubectl get pods -o wide你可以明显看到Pod数量等于可调度节点的数量,Pod分布在每个节点上,名称都是node-logger-xxxxx格式。
3.2 控制DS调度范围的三种方式
有些场景你并不想让DS跑遍所有节点,比如只想在带有SSD的节点上运行本地存储插件,或者只想在特定的业务节点上跑日志采集器。有三种方式可以限定调度范围,我按使用频率排序。
第一种是nodeSelector,最简单粗暴。在template的spec里加一个字段:
spec: template: spec: nodeSelector: disk-type: ssd只有当节点带有disk-type=ssd这个标签时,DS才会在上面创建Pod。注意这个和nodeAffinity是兼容的,DS自动注入的节点亲和性依然存在,你写的nodeSelector是叠加条件。
第二种是nodeAffinity,表达力更强,支持In、NotIn、Exists等操作符。举个例子:
spec: template: spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-type operator: In values: - gpu - high-memory这里用的是“硬性亲和”,意思是Pod必须调度到带有node-type为gpu或high-memory的节点上。如果你想要“优先但不强制”,可以用preferredDuringSchedulingIgnoredDuringExecution。
第三种是nodeName,直接指定节点,这招比较少见,一般用来排查单个节点上的问题或者临时把某个DS定向到指定节点。我不建议在生产环境使用,因为写死了节点名,节点挂了Pod就起不来了。
除此之外,污点容忍(tolerations)是反向控制的关键手段。如果你有节点打了NoSchedule污点,默认情况下DS不会调度上去;但如果你在template里加了对应的容忍,DS就会无视这个污点。这里有个很容易踩坑的点:DS默认对node.kubernetes.io/not-ready和node.kubernetes.io/unreachable这类节点异常污点是有默认容忍的,所以节点短暂NotReady时DS Pod不会立刻被驱逐,这是合理的,不要试图用tolerationSeconds强制它快速离开——否则节点抖动一次你的采集器就全没了。
3.3 更新策略详解:RollingUpdate和OnDelete怎么选
DS的更新策略是生产环境里影响面最大的配置,值得单独说。
先看默认的RollingUpdate模式。它有一个非常重要的参数:maxUnavailable,默认值是1。含义是“更新过程中最多允许几个节点上的DS Pod处于不可用状态”。这个值和Deployment里的maxUnavailable语义类似,但因为DS是每节点一个Pod,所以这里的“个数”直接对应节点数,影响面更直观。
举个例子,你有10个节点跑DS,maxUnavailable=1时,控制器会先更新一个节点上的Pod,等它变Ready并Available,再更新下一个。这样虽然整体更新慢,但始终最多只有一个节点的采集/监控是空窗期。如果你对更新速度有要求,可以把maxUnavailable调大,比如2或者3,但要清楚代价:同一时间可能有多个节点的采集任务中断。
OnDelete模式就更有意思了。它表示“我不会主动更新任何Pod,你自己手动删掉某个节点的旧Pod,我才会在对应节点创建新版本”。这个模式特别适合两种场景:一是节点分批维护时,按节点维度灰度更新;二是配合节点cordon/drain流程,先腾空节点再更新Pod,避免更新期间业务影响。
我个人的建议:对于日志采集和监控组件,默认用RollingUpdate,maxUnavailable保持1,稳字当头;对于CNI插件或kube-proxy这类基础组件,更新要极端谨慎,建议用OnDelete,配合节点维护窗口手动推进,出了问题可以逐节点回滚,不至于一次更新全军覆没。
4. 调度细节与污点容忍:为什么DS默认不调度到master节点
4.1 污点、容忍和DS的“默认行为”
很多新手第一次创建DS时都会问:为什么我的DS没有在master节点上创建Pod?答案很简单:master节点默认带有node-role.kubernetes.io/control-plane:NoSchedule污点,而DS默认没有对应的容忍,所以控制器不会把Pod调度上去。
这个设计是刻意的。Kubernetes把控制平面和数据平面在调度上做了隔离,正常情况下业务负载不应该运行在master节点上,避免资源竞争影响控制平面稳定性。但DS这类基础设施组件是个例外——比如监控组件需要采集master节点自身的指标,那就必须显式添加容忍。
在3.1的示例里,我写的容忍是:
tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule这个写法比operator: Equal和value: ""更简洁,含义是“只要能匹配到这个key的污点,不管值是什么,都容忍”。因为master节点的这个污点本身没有value,用Exists最稳妥。
还有一类污点需要特别留意:NoExecute。这个污点一旦打上,不但阻止新Pod调度,还会驱逐节点上已有的Pod。如果DS要容忍NoExecute,建议同时设置tolerationSeconds,比如:
tolerations: - key: node.kubernetes.io/not-ready operator: Exists effect: NoExecute tolerationSeconds: 60这样节点进入NotReady状态后,DS Pod会在60秒后被驱逐,而不是永久停留在那个节点。对于采集器这类应用,60秒左右的容忍时间是合理折中——既能容忍短暂抖动,又不会在节点长时间异常时继续空转。
另外注意:不要轻易移除系统级的默认容忍。比如node.kubernetes.io/not-ready:NoExecute是kubelet自动注入的默认容忍,你可以在自己的DS里覆盖它,但除非特别清楚后果,否则保持默认是更安全的选择。
4.2 节点标签管理与定向调度的最佳实践
节点标签是控制DS调度的核心工具,建议提前规划好命名规范。我见过不少团队用node-role.kubernetes.io/worker、node-type=ssd、zone=cn-east-1这类标签来区分节点池,然后DS通过nodeSelector或nodeAffinity精准匹配。
给节点打标签的命令很简单:
kubectl label node node-01 disk-type=ssd kubectl label node node-02 node-type=gpu但这里要提醒一个细节:标签修改后,已经存在的DS Pod不会自动被删除重建。比如你给一个节点加了disk-type=ssd标签,想让某个DS调度上去,控制器会在这个节点上新建Pod,但原本DS在其他节点上的Pod依然存在(除非它们还有别的容忍条件)。反过来,你如果删掉了一个节点的标签,K8s不会自动把之前调度上去的DS Pod驱逐掉。这个“只增不删”的特性经常让人困惑,我建议在节点做大规模标签变更后,手动检查一遍DS Pod分布,必要时删除不符合预期的Pod让它重建。
还有一种常见做法是给节点同时打多个标签,用nodeAffinity的In操作符做“或”匹配,或者用matchExpressions多个条件做“且”匹配。这块建议在测试环境多试几组组合,别上来就在生产改调度,影响面太大。
4.3 优先级、资源预留与节点压力的关系
DS Pod在节点上运行,必然占用节点资源。如果资源请求设置得太低,采集器可能因为OOM被频繁杀掉;设置得太高,又会挤压业务Pod的调度空间。这里分享一个在监控采集场景下比较稳妥的资源配比:
resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mirequests是调度依据,limits是运行上限。像日志采集器这类应用,CPU通常波动不大,100m的request足够调度;内存弹性大,limit给到512Mi可以应对突发日志量。需要注意的是,DS Pod的QoS等级由requests和limits决定,如果两者设置相同就是Guaranteed,优先级最高;如果不一致就是Burstable,在节点压力大时可能被驱逐。基础设施组件建议设置成Guaranteed,避免在节点内存紧张时被优先杀掉。
另外,DS控制器本身不参与节点压力驱逐的判断,驱逐逻辑在kubelet侧。也就是说,你就算给DS Pod设置了很高的priorityClassName,在节点MemoryPressure时kubelet仍可能按QoS和优先级决定驱逐顺序。所以不要迷信“基础设施组件不会被杀”,资源预留才是保命符。
5. 真实案例:用DaemonSet部署节点级监控与日志采集
5.1 案例背景与容器镜像的选择思路
我去年帮一个客户搭建了一套多集群监控系统,要求是“每个节点的宿主机指标、容器日志都能采集到,且不能漏节点、不能重复采集”。当时我们第一反应就是用DS部署采集器,而不是Deployment。
这里有个经验之谈:采集器镜像的选择要结合你对“侵入性”的容忍度。像Node Exporter这种只需要暴露指标的,官方镜像即可,挂载宿主机的/proc和/sys目录就行;像Fluentd这种要采集容器日志的,除了挂载/var/log,还得挂载/var/lib/docker/containers目录(或者containerd的日志目录,取决于你的运行时),同时需要hostPath类型挂载。
我建议你在选镜像前,先搞清楚三个问题:采集对象是宿主机的还是容器内的?需要访问宿主机哪些路径?是否需要修改宿主机网络配置?答案不同,挂载volume的方式完全不同。
5.2 部署Node Exporter的完整步骤
直接上我们生产环境用的Node Exporter配置(简化版):
apiVersion: apps/v1 kind: DaemonSet metadata: name: node-exporter namespace: monitoring labels: app.kubernetes.io/name: node-exporter spec: selector: matchLabels: app.kubernetes.io/name: node-exporter template: metadata: labels: app.kubernetes.io/name: node-exporter spec: hostNetwork: true hostPID: true tolerations: - operator: Exists containers: - name: node-exporter image: prom/node-exporter:v1.6.1 args: - --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|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|iso9660|jffs2|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tmpfs|tracefs)$ ports: - containerPort: 9100 hostPort: 9100 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 200m memory: 256Mi volumeMounts: - name: proc mountPath: /host/proc readOnly: true - name: sys mountPath: /host/sys readOnly: true - name: root mountPath: /host/root mountOnly: true readOnly: true volumes: - name: proc hostPath: path: /proc - name: sys hostPath: path: /sys - name: root hostPath: path: /这里面有几个细节值得注意:hostNetwork: true让Node Exporter直接使用宿主机网络,9100端口直接监听在宿主机上,方便Prometheus用固定IP:9100抓取;hostPID: true允许它看到宿主机上的所有进程,方便采集进程级指标;tolerations里写operator: Exists不带key和effect,表示容忍所有污点,这样master节点也会被采集到。
volumeMounts挂载宿主机的/proc、/sys、/根目录,是为了让采集器能读取宿主机层面的文件系统数据,这是Node Exporter的常见玩法。readOnly: true是为了安全,采集器没必要写宿主机文件。
最后给Service对象选配一段,让Prometheus能通过DNS稳定访问:
apiVersion: v1 kind: Service metadata: name: node-exporter namespace: monitoring spec: clusterIP: None selector: app.kubernetes.io/name: node-exporter ports: - name: metrics port: 9100 targetPort: 9100用clusterIP: None做headless service,Prometheus做服务发现时就拿到所有Pod的IP列表,配合hostNetwork模式下Pod IP等于节点IP的特性,天然实现了“每节点一个采集端点”的语义。
5.3 采集日志时hostPath挂载的常见坑
日志采集比指标采集复杂得多,最大的坑是你不知道容器运行时把日志写到哪里。如果是docker运行时,路径通常是/var/lib/docker/containers/<id>/*-json.log;如果是containerd,路径则是/var/log/containers/<pod-name>_<namespace>_<container-name>-xxx.log,其实是软链到/var/log/pods/...的。
不同运行时挂载路径完全不同,所以DS的hostPath配置必须和运行时匹配。我踩过这个坑:第一次用Fluentd采集时,挂载了/var/lib/docker/containers,结果换一个节点用containerd后,日志全采不到。后来统一改成挂载/var/log和/var/lib/docker/containers并用hostPath的type: DirectoryOrCreate兜底,才算稳定。
挂载路径建议用DirectoryOrCreate而不是Directory,这样即使某些节点上不存在目录,kubelet会自动创建,Pod不会直接CrashLoopBackOff。但要注意,自动创建出来的目录属主是root,可能引发权限问题,日志采集器一般要用root用户或者有特权的ServiceAccount运行。
日志采集还有一个隐藏问题:Pod的日志文件会因容器重启而变化,采集器要能感知文件的新旧替换,利用file的tail支持或者inotify机制来处理。这个属于采集器自身能力,不赘述了,但选型时一定要看它是否支持“文件rotate后持续跟踪”。
6. DaemonSet滚动更新实操:流程、参数与踩坑记录
6.1 更新流程模拟与关键参数说明
假设你要升级集群里的node-exporter,从v1.6.1升到v1.7.0。最朴素的操作用kubectl edit ds node-exporter把镜像版本改掉,保存后DS控制器自动开始滚动更新。但生产环境我建议用kubectl set image,更可控:
kubectl set image ds/node-exporter node-exporter=prom/node-exporter:v1.7.0 -n monitoring改完之后可以用kubectl rollout status ds/node-exporter -n monitoring观察进度。DS的rollout状态和Deployment不太一样,它不会显示“ReplicaSet”相关的信息,而是显示节点级别的完成情况。大概长这样:
daemon set "node-exporter" successfully rolled out如果更新失败,kubectl rollout undo ds/node-exporter -n monitoring可以回滚到上一个版本,这个命令在DS上同样有效。
关键是maxUnavailable的取值。默认1是最保守的,但当你节点很多时(比如50个),一个一个更新真的很慢。我实测过,如果每个Pod启动时间2秒左右,50个节点串行更新大约要3-5分钟,还能接受;但如果Pod启动要10秒(比如需要加载大量规则),串行更新就太慢了。这时候可以适度调高maxUnavailable,比如30%。DS支持设置百分比,它会换算成节点数量并向上取整:
updateStrategy: rollingUpdate: maxUnavailable: 30% type: RollingUpdate注意这里的百分比语义是“基于DS管理的Pod总数”,不是集群节点总数。你管理10个节点的DS,30%就是同一时间最多3个节点不可用。
6.2 更新过程中Pod一直Pending的排查流程
滚动更新中最常见的故障现象是:某个节点的旧Pod被删了,新Pod却一直Pending。我梳理一个标准排查路径,建议按顺序执行。
第一步,先看Pod事件:
kubectl describe pod <ds-pod-name> -n monitoring事件里如果出现0/N nodes are available,说明调度器找不到满足条件的节点。原因通常是nodeSelector或nodeAffinity限制了调度范围,但节点标签不满足;或者节点上有污点而DS没有容忍。
第二步,检查节点状态和污点:
kubectl get nodes kubectl describe node <node-name> | grep -A5 Taints如果只想让DS忽略所有污点,在template的tolerations里加上:
tolerations: - operator: Exists这个配置意味着匹配所有污点,包括NoSchedule和NoExecute。对于监控采集器这种基础设施组件是合理的,但如果你跑的是业务型Pod,千万别这么干,被驱逐时没有任何保护。
第三步,检查DS状态的Conditions。DS有个NumberUnavailable字段,可以直接看:
kubectl get ds -n monitoring -o wide kubectl get ds node-exporter -n monitoring -o jsonpath='{.status}'如果NumberUnavailable长期不为0且DesiredNumberScheduled稳定,说明更新卡住了,优先看那个不可用节点上的Pod事件。
我印象最深的一次故障是:节点上kubelet版本和集群版本有差异,导致Pod调度成功但kubelet拒绝创建容器,事件里只显示Failed to create pod sandbox,排查了半天才发现是节点kubelet版本落后。这类问题常规事件里不明显,建议在DS更新前先确认所有节点kubelet版本一致,省掉很多奇怪问题。
6.3 OnDelete模式下的手动灰度与回滚技巧
我在一些关键基础组件上(比如CNI插件)更倾向用OnDelete模式。操作方式是这样的:
先用kubectl edit ds把updateStrategy改成:
updateStrategy: type: OnDelete然后你更新template里的镜像版本。这时候控制器不会动任何Pod,只是把新模板存起来。接着你挑一个节点验证,比如节点node-03:
kubectl delete pod -n monitoring <ds-pod-on-node-03>控制器检测到这个节点上“属于该DS的Pod不存在”,就会用新模板创建Pod。于是node-03上的组件先完成了升级。你观察几分钟,确认新版本在这个节点上工作正常(日志正常、指标正常、网络正常),再去扫其他节点。
这种模式的好处是每步都在掌控中,灰度范围是你手动决定的。配合节点维护流程,先cordon节点,再drain节点,然后让DS在新模板下重建Pod,几乎不影响业务。
回滚也是一样的思路:把template的镜像版本改回旧的,然后逐个删除节点上的新Pod,控制器会用旧模板重建。不需要一次性回滚全局,哪个节点出问题就回滚哪个,非常灵活。
7. 常见问题与故障排查实录
7.1 DaemonSet Pod总是被驱逐或反复重启
这个问题有一种常见表现:Pod的RestartCount持续增长,或者反复处于Error状态。排查时先看日志:
kubectl logs <pod-name> -n monitoring --previous通过--previous查看上一次容器退出前的日志,往往能直接看到OOM或权限报错。
如果是OOM,就要调大内存limit;如果是权限报错,检查Pod是否使用了privileged: true(很多节点级采集器需要),以及ServiceAccount是否有权限。需要特权时,要在container配置里加上:
securityContext: privileged: true加上之后Pod会以root身份在宿主机namespace中运行,能访问宿主机设备,但也意味着权限很大,要评估风险后再决定。
7.2 节点加进集群后DS没有自动创建Pod
集群扩容是DS的高光时刻——新节点加入后,DS应该自动在新节点上创建Pod。如果没创建,按这个顺序排查:
先看新节点是否Ready:kubectl get nodes;再看节点是否有污点:如果新节点打了污点且DS没有对应容忍,就不会调度;再看DS的nodeSelector或nodeAffinity是否匹配:新节点可能缺少对应标签;最后看节点资源是否充足:kubectl describe node里看Allocatable和Requests,如果新节点CPU或内存已满,调度器会跳过。
还有一个容易忽略的点:新节点加入集群后,如果DS的Pod模板里有hostPort端口占用冲突,可能表现为创建成功但一直CrashLoopBackOff,因为端口被宿主机其他进程占了。这种问题事件里能看到failed to bind port,解决方式是换端口或者排查占用进程。
7.3 手动删除了DS Pod后为什么又被创建了
这个是DS的正常行为,不是故障。前面说过,DS控制器发现“某个节点上缺少属于该DS的Pod”时,会自动补建。所以你想“手动删掉某个DS Pod让它不再出现”是不可能的,除非删除DS本身,或者修改DS的调度范围让这个节点不再匹配。
如果你只是想临时停掉某个节点上的DS Pod,正确做法是kubectl cordon节点标记不可调度,或者给节点加一个node.kubernetes.io/unschedulable相关调度条件。但注意:cordon只影响新Pod调度,已存在的Pod不会被驱逐。想彻底让该节点没有DS Pod,要么修改DS的调度范围,要么自定义一个“禁止该节点”的nodeAffinity,要么删除节点。
7.4 速查表:DS常见故障与定位建议
| 故障现象 | 常见原因 | 排查命令/手段 |
|---|---|---|
| Pod Pending | 节点标签不匹配、污点未容忍、资源不足 | kubectl describe pod、kubectl describe node |
| Pod反复重启 | OOM、privileged缺失、配置错误 | kubectl logs --previous、查看limit配置 |
| 新节点无Pod | 污点、标签不匹配、节点未Ready | kubectl get nodes -A检查Taints/Labels |
| 更新卡住 | maxUnavailable过小、节点有Pod一直不Ready | kubectl rollout status ds、逐节点检查 |
| 手动删Pod又被建 | DS控制器正常调谐行为 | 需要修改DS或节点调度属性 |
| Pod调度到错误节点 | nodeSelector/affinity配置错误 | 检查template的调度字段 |
| 更新后老版本还在 | OnDelete模式下未删除旧Pod | 手动删除对应节点Pod触发重建 |
这张表基本覆盖了我日常排查DS问题的大部分路径,建议收藏,遇到问题先按表格定位,再深入展开。
8. 生产环境的实战建议与经验总结
8.1 关于资源与可观测性的配置清单
我最后总结一份在真实环境落地DS时的配置清单,全部是踩坑后的经验,不是理论推演。
第一,所有DS Pod都要显式配置resources,不要省略。尤其是监控采集器,limit给得太小会丢数据,request给得太大影响调度密度。推荐配置范围:CPU request50m-100m,limit200m-500m;内存request64Mi-128Mi,limit256Mi-512Mi。具体数值按业务吞吐量调整,采集器在日志量突增时的表现是判断limit是否合理的关键。
第二,对于需要访问宿主机资源的DS,把hostPID、hostNetwork、privileged等参数一次性想清楚。安全性和功能性要平衡,比如node-exporter我一般不开privileged,但Fluentd需要读日志文件,通常需要root或特权模式。这块没有统一答案,一定要结合组件本身的权限需求。
第三,DS的监控不要只看Pod状态,要看“决策覆盖度”。我建议部署后立刻做一个校验:列出节点列表和DS Pod所在节点列表,对比差异。命令可以这样:
kubectl get nodes -o name kubectl get pods -n monitoring -o wide | grep node-exporter | awk '{print $8}' | sort两条命令的输出差异一眼就能看出来,节点有但Pod没有的,就是覆盖缺口。这个校验动作在节点扩容后必须执行一次。
第四,涉及DS的变更建议走完整的发布流程,不要直接在生产环境edit。就算只是改个镜像tag,也要先在测试环境验证。原因很简单,DS影响的是全节点,回滚虽然容易但要逐节点确认,变更失败的成本比Deployment高得多。
8.2 不要把DaemonSet用错场景
最后聊一个理念问题。我在技术咨询中见过不少团队把“非节点级”的业务负载也用DS部署,理由是“省事,每个节点都有”。比如把某个缓存组件用DS部署,希望每个节点都有一份本地缓存——这听起来合理,但实际上会带来两个问题:一是缓存组件的副本数和节点数绑定,节点扩容时缓存加倍,节点缩容时缓存减半,容量规划很难;二是缓存组件之间如果要做数据同步,DS模型完全没有“固定身份”的概念,Pod重建后名字会变,服务发现和状态维护都很麻烦。
这类场景正确的选择要么是DaemonSet配合专属存储和固定协调逻辑(复杂度较高),要么是StatefulSet(如果有状态且需要稳定标识),要么就是Deployment加节点亲和(如果是无状态的服务,只是想均匀分布)。关键判断标准就一句话:你的核心诉求是“节点覆盖”还是“副本可用”。前者用DS,后者用其他控制器。
这个判断标准我百试不爽,也在培训中分享给不少学员。掌握好这个分界点,你对Kubernetes工作负载的理解已经超过了不少所谓“经验丰富”的开发者。