ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Kubernetes工作负载实战:DaemonSet、Job与CronJob全面解析

Kubernetes工作负载实战:DaemonSet、Job与CronJob全面解析 1. 先搞清楚这三类工作负载到底在解决什么问题Kubernetes 里跑应用很多人一开始只接触 Deployment 和 Service觉得“能起 Pod、能暴露端口”就够了。但真正把业务铺到集群里之后你会发现有一类需求是 Deployment 根本接不住的比如每个节点上都必须有一个日志采集进程、一个监控探针比如每天凌晨要跑一次数据清洗任务再比如某个批处理任务跑完就结束不需要常驻。我刚接触这块时也绕了不少弯子总想着用 Deployment 加副本数硬凑结果要么节点上多跑了重复进程要么任务结束后 Pod 还在重启要么定时任务根本没法表达。后来把 DaemonSet、Job 和 CronJob 这三类工作负载真正吃透才觉得 Kubernetes 的调度模型其实是把“常驻服务”“节点级守护”“一次性任务”“定时任务”分得清清楚楚每种负载对应一种生命周期理解了这个后面排障会顺手很多。这篇文章就专门拆这三类负载DaemonSet 负责节点级守护Job 负责一次性批处理CronJob 负责定时触发。我会把它们的适用场景、核心原理、YAML 写法、实操中踩过的坑全部讲透并附上排查思路。内容偏实战新手可以直接照着用老手也可以当速查手册翻。2. 节点级守护DaemonSet 的设计逻辑与实操细节2.1 为什么需要 DaemonSet它和 Deployment 的本质区别Deployment 的调度单位是“副本数”你声明 replicas: 3调度器会尽量把 3 个 Pod 分散到不同节点但具体落在哪几个节点、每个节点跑几个你没法精确控制。DaemonSet 恰恰相反它的语义是“每个匹配的节点上且仅运行一个 Pod”。这个“且仅”非常关键意味着集群里新增节点时DaemonSet 会自动在新节点上拉起 Pod节点被移除时对应 Pod 也会被清理。整个生命周期跟着节点走而不是跟着副本数走。用生活化的类比Deployment 像公司里的客服团队总共招了 20 个人谁在哪个办公室不重要只要总人数够、服务可用就行DaemonSet 像每个楼层必须配备的消防员不管楼层里有多少员工每一层都必须且只能有一个消防员驻守。所以 DaemonSet 特别适合那些“基础设施必须遍地开花”的场景比如日志采集器Filebeat、Fluentd、Logstash每个节点一个收集宿主机容器日志。监控探针Node Exporter、Datadog Agent每个节点一个采集节点指标。网络插件Calico、Flannel 的 agent 组件每个节点一个负责转发配置。存储挂载组件如 Ceph 的 csi-node-driver每个节点一个负责本地卷挂载。反过来说如果你的应用是业务型 Web 服务节点数不固定、缩容扩容频繁那 DaemonSet 就不合适硬用只会导致业务 Pod 数量跟着节点数量上下抖动。2.2 DaemonSet 的调度逻辑演进从 default scheduler 到 nodeAffinity很多人以为 DaemonSet 的“每节点一个”是调度器按某种算法算出来的其实早期版本里 DaemonSet 是绕开默认调度器的。DaemonSet Controller 会直接读取节点列表然后在每个节点上创建一个 Pod并且把nodeName字段指定好调度器看到这个字段就直接跳过。这带来一个好处即使集群里没有可用调度器DaemonSet 也能把 Pod 放到目标节点上。但坏处也很明显它没法感知节点资源是否充足、是否有端口冲突导致有些 Pod 可能调度失败。后来 Kubernetes 演进后DaemonSet 开始默认使用默认调度器并且通过 nodeAffinity 来限定 Pod 只能调度到指定节点。下面是现代 Kubernetes 中 DaemonSet 产生的 Pod 里自动注入的一个 nodeAffinity 片段affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchFields: - key: metadata.name operator: In values: - node-01这个片段意味着 Pod 必须调度到 metadata.name 为 node-01 的节点。如果你用kubectl get pod -o yaml查看 DaemonSet 管理的 Pod会发现这个字段。这点在排障时很有用因为如果你手动修改了 DaemonSet 的 affinity可能会和自动注入的 nodeAffinity 冲突导致 Pod 无法调度。2.3 一个完整的 DaemonSet 示例日志采集器配置与字段解释实际部署中DaemonSet 的 YAML 和 Deployment 很像但有几个关键差异。下面是一个采集节点日志的 DaemonSet 示例apiVersion: apps/v1 kind: DaemonSet metadata: name: log-collector namespace: kube-system spec: selector: matchLabels: app: log-collector template: metadata: labels: app: log-collector spec: hostNetwork: true hostPID: true tolerations: - operator: Exists containers: - name: collector image: fluentd:latest resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi volumeMounts: - name: varlog mountPath: /var/log - name: docker mountPath: /var/lib/docker/containers readOnly: true volumes: - name: varlog hostPath: path: /var/log这个例子里有几个关键点值得展开说。第一个是tolerations。默认情况下Kubernetes 主节点control-plane带有污点普通 Deployment 的 Pod 不会调度到主节点上。但日志采集这类基础组件通常希望覆盖所有节点包括主节点所以我会加上operator: Exists的容忍表示容忍所有污点。如果你不希望收集主节点日志也可以去掉这个容忍或者用更精细的key/value/effect组合。第二个是hostNetwork: true。因为日志采集器要访问宿主机上的容器日志目录和 socket使用宿主机网络往往更稳妥否则某些环境下容器网络转发会出问题。但要注意hostNetwork 意味着端口直接占用宿主机端口多个 DaemonSet 如果都监听相同端口就会冲突。第三个是hostPath卷。日志采集器要读宿主机日志只能通过挂载宿主机目录实现。这里有个隐患宿主机目录权限、SELinux、只读挂载等问题都可能导致采集失败所以在测试环境一定要先验证挂载路径是否可访问。2.4 DaemonSet 的滚动更新与回滚策略DaemonSet 更新不像 Deployment 那么可以大范围滚动因为它每个节点只有一个 Pod如果一次性全部更新极端情况下所有节点同时重启基础服务可能中断。所以 DaemonSet 默认的更新策略是RollingUpdate且maxUnavailable默认是 1意思是同一时刻最多只能有一个节点的 DaemonSet Pod 处于不可用状态。你可以通过spec.updateStrategy来调整updateStrategy: type: RollingUpdate rollingUpdate: maxUnavailable: 2如果你的集群节点很多想让更新快一点可以把maxUnavailable调大如果对可用性要求极高甚至希望逐个节点确认可以先把maxUnavailable设置为 0然后配合maxSurge实现“先起新的、再删旧的”不过 DaemonSet 的maxSurge只在部分版本支持并且要求控制器版本足够新。我建议在关键基础设施上不要贪快默认值最稳。回滚操作和 Deployment 类似kubectl rollout undo daemonset/log-collector -n kube-system但回滚前最好先看一眼历史版本kubectl rollout history daemonset/log-collector -n kube-system2.5 节点选择与定向部署虽然 DaemonSet 默认面向所有节点但很多时候你只想让它在某些节点上运行比如只有 GPU 节点需要跑 GPU 监控只有 Windows 节点需要跑 Windows 日志采集。这时需要给 DaemonSet 加 nodeSelector 或 nodeAffinityspec: template: spec: nodeSelector: disktype: ssd或者更灵活的 nodeAffinityaffinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: gpu operator: In values: - true注意 nodeSelector 和 nodeAffinity 是“且”的关系如果同时配置Pod 必须同时满足两者。有一个实操心得如果你在 DaemonSet 中配置了 nodeSelector但是发现某些节点一直处于 Pending第一反应别去查调度器先用kubectl describe pod看 Events里面通常会明确提示节点不匹配。很多时候是自己给某个节点打错了标签或者标签 key 大小写不一致。3. 跑完就结束Job 的设计目标与批处理场景3.1 Job 的核心语义保证一定数量的 Pod 成功完成DaemonSet 关注的是常驻Job 关注的则是“到达终态”。所谓终态就是 Pod 中的主容器进程正常退出退出码为 0。Deployment 会无限重启容器来维持期望副本数Job 则恰恰相反它要的是一次成功成功了就不会再重启。Job 的典型场景包括一次性的数据迁移脚本。批量图片压缩处理。数据库备份任务。需要并行处理大量数据的 MapReduce 类任务配合并行度。Job 的 YAML 很简洁核心是restartPolicy必须为Never或OnFailure不能是Always否则 Pod 永远不会“完成”。3.2 Job 的三种使用模式和参数计算Job 最让人困惑的是它怎么控制“何时算成功”。其实它背后由三个参数决定completions期望成功完成的 Pod 数量。parallelism同时运行的 Pod 数量。activeDeadlineSeconds整个 Job 最多运行多久超时则强制终止。三种使用模式对应不同的参数组合模式completionsparallelism适用场景单工作队列11跑一次脚本不需要并发固定完成数52需要执行 5 次任务但允许同时跑 2 个工作队列模式1N一个队列多个 worker 同时消费任务谁完成谁算成功前两种好理解第三种“工作队列模式”很多人会迷糊completions1 且 parallelismN到底几个 Pod 算成功实际语义是只要有 1 个 Pod 成功完成Job 就整体成功其他 Pod 会被清理。这种模式适合“多个 worker 抢一个任务队列谁做完了谁提交结果”。再手动算一个例子假设我要处理 100 个文件每个文件处理耗时 1 分钟设置 parallelism10那么理想情况下 10 个 Pod 同时处理每个处理 10 个文件总耗时约 10 分钟。参数可以直接设为spec: parallelism: 10 completions: 100这里有个误区completions不一定等于任务数它只是“Pod 成功次数”。如果你每个 Pod 内部自己循环处理 10 个文件那么 completions10 即可。这个由你的脚本逻辑决定。3.3 Job 重试、超时与背压控制实际跑批任务时脚本不可能永远一次成功网络抖动、数据格式异常、资源不足都可能让 Pod 失败。Job 的重试机制是嵌套的需要理清楚容器内部进程退出码非 0Pod 状态变为 Failed。Pod 是否会重启取决于restartPolicy。设为OnFailureKubernetes 会在同一 Pod 内重启容器设为Never则不会重启Job 会新建一个 Pod。Job 的重试次数由backoffLimit控制默认是 6。这里的 6 不是指“Pod 内重启 6 次”而是指“Job 最多允许失败多少次按 Pod 计数”。超过这个次数Job 会被标记为 Failed。我常用的 Job 配置模板apiVersion: batch/v1 kind: Job metadata: name: batch-processor spec: template: spec: restartPolicy: Never containers: - name: worker image: my-registry/batch-worker:latest args: [--input, data.txt] backoffLimit: 4 activeDeadlineSeconds: 600 ttlSecondsAfterFinished: 300activeDeadlineSeconds: 600控制整个 Job 最长运行 600 秒超时直接终止。注意它和 Pod 的activeDeadlineSeconds不同Job 级的是总预算Pod 级的是单个 Pod 的预算。ttlSecondsAfterFinished: 300表示 Job 完成后 300 秒自动清理避免历史 Job 堆积。3.4 Job 的并行扩缩容与外部队列集成如果你要处理大量数据手动设 parallelism 不够灵活。Kubernetes 支持 Job 的并行扩缩容但这需要外部控制器配合比如 Knative 或 Kubeflow 的控制器。不过我们也可以借助 Job 自身的索引完成数据分片。从 Kubernetes 1.21 开始Job 支持completionMode: Indexed这样每个 Pod 会获得一个从 0 到 completions-1 的索引通过环境变量或 hostname 暴露spec: completionMode: Indexed在容器内可以通过$JOB_COMPLETION_INDEX拿到索引。比如索引 0~9分别处理第 0~9 个分片这样就实现了“并行批处理 分片”。我自己用这个方式写过数据同步任务10 个并发 Pod 各处理一块 ID 范围效果非常稳定。和小知识如果 Job 没有设置 completionMode默认是NonIndexed所有 Pod 等价谁成功都一样。4. 定时触发CronJob 的调度原理与生产实践4.1 CronJob 的底层机制CronJob Controller 与 Job 的关系CronJob 本质上是“定时生成 Job”的控制器。它不直接创建 Pod而是根据 cron 表达式到点后创建 Job再由 Job 管理 Pod。理解这一层关系很多问题就好解释了你排查一个 CronJob 没跑起来先看 Job 有没有被创建再看 Pod 有没有创建而不是直接看 Pod。CronJob 的核心字段apiVersion: batch/v1 kind: CronJob metadata: name: daily-cleanup spec: schedule: 0 2 * * * concurrencyPolicy: Forbid startingDeadlineSeconds: 100 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: template: spec: restartPolicy: OnFailure containers: - name: cleanup image: busybox:1.36 args: - /bin/sh - -c - echo cleanup sleep 10concurrencyPolicy是必须关注的参数它决定了上次 Job 还没结束时这次到点是否允许并发创建。三个取值Allow允许并发默认值。Forbid不允许如果上次 Job 还在运行本次跳过。Replace把上次的 Job 杀掉重新创建一个。startingDeadlineSeconds用来处理“错过调度时间”的情况。比如集群或 CronJob Controller 在计划时间点不可用错过之后如果还在 deadline 之内会补创建超过 deadline 就不再创建。successfulJobsHistoryLimit和failedJobsHistoryLimit控制历史 Job 保留数量避免 etcd 里堆积过多 Job 对象。生产上建议设置小一点比如成功保留 3 个、失败保留 1 个否则时间久了 Job 会撑爆 apiserver 的存储。4.2 时区与 Cron 表达式最容易踩的坑Cron 表达式里的时间基准取决于 CronJob Controller 所在容器使用的时区。在 Kubernetes 1.27 之前默认是 UTC所以国内用户写0 2 * * *其实就是 UTC 凌晨 2 点对应北京时间早上 10 点。这个坑我踩过明明配了每天凌晨 2 点清理临时文件实际上却是每天上午 10 点才执行白白多占了一上午存储。从 Kubernetes 1.27 开始CronJob 支持spec.timeZone字段可以直接指定时区spec: timeZone: Asia/Shanghai schedule: 0 2 * * *但在老版本集群中这个字段会被忽略。所以如果你没法升级集群建议统一使用 UTC 换算后的时间或者在容器内通过环境变量指定时区不过 CronJob Controller 的时间基准不会因为容器内时区而改变。最稳妥的办法还是升级到 1.27 并显式设置timeZone。关于 Cron 表达式我建议写成 5 位标准格式分 时 日 月 周。比如0 */2 * * *每 2 小时执行一次。30 4 * * 1每周一凌晨 4:30 执行。0 0 1 * *每月 1 号零点执行。注意CronJob 的 schedule 不支持秒级配置。如果你有秒级或分钟级调度需求应该找其他方案。4.3 错过调度与挂起策略know如何避免重复执行除了 concurrencyPolicy还有suspend字段控制 CronJob 是否暂停。设置为 true 后CronJob 不会创建任何 Job适合在维护窗口临时暂停定时任务kubectl patch cronjob/daily-cleanup -p {spec:{suspend:true}}但 suspend 不会影响已经创建的 Job。如果实际执行时发现任务重复跑很可能是因为某个 Job 运行时间超过了 Cron 周期而 concurrencyPolicy 又设置了Allow。生产环境我建议默认用Forbid除非你有明确理由允许并发。另外CronJob 的“错过了到底补不补”是很多人纠结的点。触发漏执行的条件通常是 CronJob Controller 在计划时间点无法访问 apiserver或者 Controller 重启导致时间窗口被跳过。startingDeadlineSeconds只对“控制器恢复后”的补执行有效超过这个秒数漏掉就漏掉了不会无限补。所以关键任务不要只指望 CronJob 的补执行机制更可靠的做法是使用外部监控判断“某个时间点应该产生某个 Job”是否真的产生了。4.4 CronJob 与 Job 的清理策略时间长了CronJob 会产生大量 Job 和 Pod。若没有清理策略你会看到kubectl get job刷屏。下面是推荐的清理配置spec: successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3如果你希望彻底不保留任何历史可以都设为 0但这样会有一个问题Job 被立刻清理后日志对应的 Pod 也没了排查失败任務的依据就会丢失。除非配合 Fluentd 之类的日志系统否则不建议设为 0。还有一种情况CronJob 创建的 Job 占用了大量资源但 Job 完成后的 Pod 默认会保留一段时间由 Pod 的terminationGracePeriodSeconds和 Job 的ttlSecondsAfterFinished控制。建议给每个 CronJob 模板加上ttlSecondsAfterFinished: 3600这样任务完成后 1 小时Pod 和 Job 自动清理避免资源泄漏。5. 三类负载对比与选型决策5.1 直接了当的对比表把三类负载放到一张表里选型时一眼就清楚维度DaemonSetJobCronJob生命周期常驻跟节点走一次性成功即终态周期性每次创建一个 Job调度目标每个匹配节点一个 Pod总成功数 / 并行数由 Cron 时间触发生成 Job重启策略Always默认Never / OnFailure由 Job 决定失败处理持续重启维持“每一个节点一个”backoffLimit 控制Job 失败后再等下次周期典型场景日志、监控、网络组件数据迁移、批处理定时备份、清理、报表更新策略RollingUpdate不可更新只能重建修改模板后重建后续 Job资源释放Pod 常驻不释放完成后可自动清理依赖 historyLimit 和 ttl5.2 从工作负载生命周期角度选型我见过不少人在选型时纠结“我这个功能用 DaemonSet 跑个循环检测还是用 CronJob 更好”其实只要抓住生命周期答案就清楚了如果进程需要“7x24 小时一直存在”比如日志采集、指标暴露、网络转发选 DaemonSet 或 Deployment取决于是否每节点必须一个。如果进程“跑完一个明确任务就要退出”且这个退出就是成功选 Job。如果进程“需要按某种时间规律反复执行”选 CronJob。还有一类任务介于 Job 和 CronJob 之间比如“每周批量跑一次报表跑完退出”。显然 CronJob 更合适。如果哪天需求变成“报表任务随时手动触发”你依然可以用 CronJob 的 suspend Job 来做也可以直接创建一个普通 Job。5.3 混合使用场景DaemonSet CronJob 的经典组合实际生产里这三类负载经常组合出现。举一个我维护过的大数据平台的例子DaemonSet 部署节点级监控 Agent实时采集每个节点的内存、磁盘、GPU 使用率。CronJob 每天凌晨跑一次历史数据归档把几天前的监控数据从热存储搬到冷存储。归档任务使用 Job 内部的 Indexed 分片并发 5 个 Pod 同时归档不同节点的数据。这个组合的好处是实时数据流由 DaemonSet 保证周期性任务由 CronJob 驱动每个批次内部分片由 Job 的并行能力承担。三者在调度语义上互不干扰非常清晰。如果你也在设计类似的系统建议一开始就把这层关系定好不要想着用一个 Deployment 全包后面改造成本很高。6. 实操过程一个完整的“节点巡检 定时备份”案例这一节我带你完整走一遍把 DaemonSet、Job、CronJob 串到一个具体任务里方便你照着搭。6.1 环境准备与目标定义假设我们有一个 3 节点的 Kubernetes 集群需要实现两个需求在每个节点上运行一个巡检脚本每 5 分钟检查根分区磁盘使用率超过 80% 则输出告警日志到宿主机/var/log/disk-health.log。每天晚上 23:30 执行一次数据库备份备份完成后把结果文件归档若失败则重试最多 2 次。第一个需求用 DaemonSet 实现第二个需求用 CronJob Job 实现。6.2 用 DaemonSet 部署节点磁盘巡检巡检脚本用 shell 实现镜像直接基于 busyboxapiVersion: apps/v1 kind: DaemonSet metadata: name: disk-checker namespace: ops spec: selector: matchLabels: app: disk-checker template: metadata: labels: app: disk-checker spec: hostNetwork: true tolerations: - operator: Exists containers: - name: check image: busybox:1.36 command: - /bin/sh - -c - | while true; do usage$(df / | awk NR2 {print $5} | sed s/%//) echo $(date) disk usage: ${usage}% /host-log/disk-health.log if [ $usage -gt 80 ]; then echo $(date) WARNING: disk usage 80% /host-log/disk-health.log fi sleep 300 done volumeMounts: - name: hostlog mountPath: /host-log volumes: - name: hostlog hostPath: path: /var/log这里有几个细节hostNetwork: true保证脚本访问宿主机/的 df 数据而不是容器文件系统。如果去掉它df /看到的是容器层不是真实根分区。脚本里sleep 300实现 5 分钟一次但 daemonSet 本身是常驻容器不会退出。日志写入宿主机目录/var/log/disk-health.log采集端可以直接读取。部署后检查kubectl apply -f disk-checker.yaml kubectl get daemonset -n ops kubectl get pods -n ops -o wide正常情况下 3 个节点各有 1 个 Pod Running。6.3 用 CronJob Job 实现数据库备份备份任务用 CronJobCronJob 内部引用 Job 模板。这里用一个模拟备份任务来说明apiVersion: batch/v1 kind: CronJob metadata: name: db-backup namespace: ops spec: schedule: 30 23 * * * timeZone: Asia/Shanghai concurrencyPolicy: Forbid startingDeadlineSeconds: 120 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 3 jobTemplate: spec: backoffLimit: 2 activeDeadlineSeconds: 600 template: spec: restartPolicy: OnFailure containers: - name: backup image: my-registry/mysql-backup:latest env: - name: BACKUP_TIME value: $(date %Y%m%d%H%M%S) command: - /backup.sh这里有几个关键点restartPolicy: OnFailure表示如果容器内命令失败Kubernetes 会在同一个 Pod 内重启容器不算新建 Pod。backoffLimit: 2控制 Job 级别的失败重试次数。如果第一次容器运行失败重启一次还是失败这个 Pod 记为一次失败再新建 Pod 直至失败总次数超过 2Job 标记为 Failed。理解这个嵌套关系很重要。activeDeadlineSeconds: 600防止某些备份卡死超时强制终止。实际运行时如果 CronJob 触发但没有看到 Job先查 CronJob 状态kubectl get cronjob db-backup -n ops -o yaml看status.lastScheduleTime是否有值以及status.lastSuccessfulTime。如果没有 lastScheduleTime说明 Controller 还没触发重点查 CronJob Controller 日志如果有 lastScheduleTime 但没有 Job可能是suspend为 true 或startingDeadlineSeconds过期。6.4 验证任务结果与日志清理Job 完成后查看结果kubectl get job -n ops kubectl logs job/db-backup-xxxx -n ops如果失败通过kubectl describe job查看 Event通常能看到失败原因。为了不积攒历史我建议在脚本内部把备份文件保留时间也控制好Kubernetes 层面只保留 Job 历史记录 3 条备份文件可以通过外部存储生命周期管理。7. 常见问题与排查技巧实录我在这里整理几个实操中反复出现的典型问题并按“现象、原因、排查、解决”的方式梳理成速查表。现象可能原因排查命令解决办法DaemonSet Pod 一直 Pending节点标签不匹配、污点未容忍或资源不足kubectl describe pod pod查看 Events检查 nodeSelector 与 nodeAffinity添加 tolerationsDaemonSet Pod 被驱逐或频繁重启节点资源压力、limit 设置过小kubectl top pod、kubectl logs调整 resources requests/limits优化容器脚本内存占用Job 一直不结束Pod 反复失败脚本逻辑问题、镜像拉取失败、backoffLimit 未生效kubectl get job -o yaml、kubectl logs修复脚本确认镜像 tag 存在适当调大 backoffLimitCronJob 到点不执行时区错乱、Controller 异常、suspendtruekubectl get cronjob -o yaml检查 suspend 和 lastScheduleTime显式设置 timeZone确保 controller-manager 正常CronJob 任务重复执行concurrencyPolicyAllow 且前一个 Job 未结束kubectl get job观察 job 创建时间设置 concurrencyPolicyForbid 或 Replace历史 Job 堆积过多未配置 historyLimit 或 ttlSecondsAfterFinishedkubectl get job -n ns配置 successfulJobsHistoryLimit 和 failedJobsHistoryLimitJob 模板加 ttlPod 一直 CrashLoopBackOff 但 Job 不失败restartPolicyOnFailure 导致容器持续重启尚未超过 backoffLimitkubectl describe pod确认业务脚本能正确定义“成功退出”必要时改为 restartPolicyNever7.1 DaemonSet 更新阻塞的排查DaemonSet 滚动更新有时会卡住比如你改了镜像版本但kubectl rollout status一直等待。原因通常有两种某个节点的 Pod 处于 Pending 或 CrashLoopBackOff导致maxUnavailable1的限制下无法继续更新。节点上有资源抢占或其他 Pod 占用了关键端口。处理方式kubectl rollout status daemonset/disk-checker -n ops --timeout60s kubectl get pods -n ops -l appdisk-checker -o wide找到那个不是 Running 的 Poddescribe 查看具体原因。如果因为节点资源不足可以临时降低 DaemonSet 的 requests或者先排空该节点。7.2 Job 完成但“卡在 Completed”的假象有时候你看到 Job 状态是 Completed但 Pod 一直还在不消失。这其实是正常的因为 Pod 保留了日志直到ttlSecondsAfterFinished到期才被清理。如果你不希望看到这些 Pod显式添加ttlSecondsAfterFinished字段即可。另外有些容器主进程退出后Pod 状态为 Completed 但 Job 的status.complete却不更新这通常是因为 Job Controller 版本和 apiserver 版本不匹配或者 Job 用了selector手动指定却与 controller 生成的 label 冲突。正常情况下不要手动设置 Job 的 selector让它自动生成。7.3 CronJob 时区问题深度排查我已在前文说过时区问题这里再补充一个排障套路。如果 CronJob 看起来没按预期执行按以下顺序排查kubectl get cronjob -o yaml查看spec.schedule和spec.timeZone。确认 Controller Manager 日志中有没有关于 CronJob 的报错kubectl logs -n kube-system controller-manager-pod | grep -i cronjob。用date命令确认当前节点时间与期望是否一致。如果 schedule 中写了?之类的特殊字符在标准 5 位 cron 中是不合法的也需检查。7.4 Job 并行度与任务分片的一个经验用 Job 做数据分片时我发现直接使用JOB_COMPLETION_INDEX环境变量是最方便的。但要注意该环境变量只在completionMode: Indexed时注入。如果你想要更多灵活性比如按日期分片可以在命令行参数中通过$(JOB_COMPLETION_INDEX)传递args: - --index$(JOB_COMPLETION_INDEX)容器 entrypoint 里再解析。这种方式的优点是完全不用改代码只需要在启动参数里传入索引。8. 最后再分享几个小技巧DaemonSet、Job、CronJob 这三类负载虽然概念不复杂但真正常用后我觉得有几个点值得额外留意。第一DaemonSet 的 Pod 数量不一定是节点总数。如果你给 DaemonSet 加了 nodeSelector它只在匹配节点上运行。排查的时候别老想着“集群有几个节点就有几个 Pod”先看节点标签。第二Job 的 backoffLimit 和 Pod 的 restartPolicy 是两层独立机制。很多人误以为 restartPolicyAlways 也能用于 Job实际上创建 Job 时如果你写了 Always会被 API Server 直接拒绝。如果任务失败我建议优先改成restartPolicy: Never然后靠 Job 的重试创建新 Pod这样日志更干净不容易混淆。第三CronJob 不要依赖startingDeadlineSeconds来保证 100% 不丢执行。它只是一个补偿窗口如果集群宕机超过该时间任务就真的错过了。对关键业务建议在外部增加一个“运行结果检查”机制比如每天检查备份文件是否生成或者使用独立调度器触发一次性 Job。第四合理利用kubectl create job --fromcronjob/name。当你临时想立即执行一次 CronJob 的任务时不需要修改 schedule直接用这个命令创建一次性 Job命名也可能覆盖。这是我最常用的调试手段。kubectl create job --fromcronjob/db-backup manual-backup-001 -n ops第五DaemonSet 和 CronJob 的资源限制务必配置。因为 DaemonSet 的 Pod 数量随节点数线性增长如果不设置 resources requests某些低配置节点可能直接被占满CronJob 如果不设置 activeDeadlineSeconds一个卡死的任务可能会一直挂在那里不断产生新的 Job最终耗尽集群资源。把这些小技巧记录下来至少能帮你少踩一半以上的坑。三类工作负载用好了Kubernetes 里的“常驻守护”和“批处理”这两大类需求基本都能优雅落地。
返回列表