一、引言
Kubernetes 把 Job 定义为"一次性运行到完成并停止"的任务,Job 会创建一个或多个 Pod,并持续重试直到达到指定成功次数;同时它也支持并行 Job、固定完成数、工作队列等模式。这正好对应大数据离线计算的基本语义:任务有开始、有结束,失败可以重跑,资源可以按批次申请和释放。相反,StatefulSet 面向需要稳定网络标识、稳定持久存储、有序部署和有序伸缩的应用,这更接近流处理引擎中 JobManager、TaskManager、状态后端和恢复元数据的治理诉求。Service 则用于把一组动态变化的 Pod 暴露成稳定访问入口,解耦客户端和后端实例,这是在线查询服务必须依赖的基础能力。

Kubernetes 提供的是资源与控制面的统一抽象,而不是负载语义的统一抽象。Pod 是最小运行单元,调度器根据容器的 resource requests 选择节点,kubelet 和容器运行时根据 limits 执行资源约束;CPU limit 通常表现为 CPU throttling,memory limit 则在内存压力下可能触发 OOM kill 。这意味着 K8s 能回答"放在哪里跑、给多少资源、如何约束",但不会自动回答"任务失败是否可重放、状态是否可丢、请求是否能中断"。
Kubernetes 会根据 CPU、内存的 requests 与 limits 把 Pod 分成 Guaranteed、Burstable、BestEffort 三类,并在节点资源压力下优先驱逐 BestEffort,然后是 Burstable,最后才是 Guaranteed 。这套机制对所有 Pod 都适用,但同样一次驱逐,对离线 Spark executor、Flink TaskManager、Trino worker 的业务影响完全不同。

因此,大数据上 K8s 的核心设计原则不是"所有东西都容器化",而是"把负载语义翻译成合适的 K8s 控制模式"。离线计算要把一个 DAG 或作业批次映射为可结束、可重试、可清理的运行单元;实时流处理要把长生命周期应用映射为可声明、可恢复、可有状态升级的控制对象;在线查询服务要把查询入口、协调器、工作节点、弹性和优雅下线组合成服务化系统。
二、离线计算
离线作业最像 Kubernetes 原生 Job 的语义:一次提交、一组 Pod、完成后释放资源。spark-submit 会创建运行在 Kubernetes Pod 中的 driver,driver 再创建 executor Pod 并与它们连接执行应用代码;应用完成后 executor Pod 终止并清理,driver Pod 保留日志并以 completed 状态留在 API 中,直到被垃圾回收或手动清理 ,这说明 Spark 离线作业天然适合"作业生命周期驱动资源生命周期"。
运行模式可以简化成下面的图:

离线作业的资源特征是"脉冲式"和"阶段式"。一个 Spark 作业可能在读数据阶段 I/O 密集,在 shuffle 阶段网络和本地盘密集,在聚合阶段内存压力上升,在写出阶段又回到 I/O;同一个作业内部不同 stage 对 CPU、内存、磁盘、网络的需求并不均匀。Kubernetes 的 requests 会影响调度器是否能把 Pod 放到节点上,limits 会影响运行时资源上限;如果内存 limit 设置过紧,容器超过限制后可能被 OOM kill,而 CPU limit 过紧则可能导致任务被 throttling,表现为作业变慢而不是直接失败 。
离线计算的稳定性诉求不是"每个 Pod 永不失败",而是"失败可诊断、可重试、可清理、可补数"。Kubernetes Job 会在 Pod 失败或被删除时启动新的 Pod,并可通过 backoffLimit 控制失败重试次数 。Spark 自身也有 executor 丢失重试、task retry、stage retry 等机制;在 K8s 上,治理重点往往是避免把基础设施重试和计算引擎重试叠加成失控风暴。

治理离线作业时,最常见的错误是套用在线服务的思路:给每个 Pod 配强健康检查、追求常驻副本、用请求延迟指标衡量稳定性。离线作业更需要的是容量池、队列公平性、优先级、作业级别重试、产出校验和历史日志。对于大规模并行作业,还要关注 gang scheduling 或批调度能力,因为一个 1000 executor 的作业如果只能零散调度到 50 个 executor,可能既占住资源又迟迟跑不完;WorkloadWithJobalpha 能力尝试为符合条件的并行 Job 创建Workload与PodGroup,用于原生 gang scheduling,但该能力仍是 alpha,默认关闭,生产使用需谨慎核验集群版本与特性开关。
三、流式任务
流式任务表面上也是一组 Pod,但它的语义完全不同:任务不是"跑完",而是持续处理不断到来的数据。Flink 可以原生在 Kubernetes 上启动 JobManager 和 TaskManager 并提交作业,但长期运行的 streaming application 还需要处理不健康重启、升级前 savepoint、回滚、按负载 resize 等生命周期问题;Operator 通过 Kubernetes 自定义资源声明期望状态,并持续 reconcile 运行状态与声明状态 。这正是流式任务区别于离线作业的核心:流任务的治理对象不是一次执行,而是一个持续演化的有状态应用。
Flink Operator 的用户侧 API 包括 FlinkDeployment、FlinkSessionJob、FlinkStateSnapshot、FlinkBlueGreenDeployment 等 namespaced Custom Resources;其中 FlinkDeployment 可运行一个 Application cluster 或 Session cluster,FlinkSessionJob 可向已有 Session cluster 提交单个作业,FlinkStateSnapshot 表示一次 savepoint 或 checkpoint 操作 。这说明在 K8s 上治理流式任务,通常不应只盯着 Pod,而要把"作业、状态、快照、升级、恢复"提升为一等对象。

流式任务的资源特征更接近"稳定基线 + 负载波动"。和离线作业相比,Flink 任务通常有固定并行度、固定 slot、持续内存占用和持续网络背压;扩缩容不能只看 CPU 或内存,还要看 source lag、backpressure、checkpoint duration、state size、operator utilization 等流处理语义指标。autoscaler 会基于观测到的利用率对每个 job vertex 的 parallelism 做 right-size,并在负载上升时扩容、负载下降时缩容;同时还有 TaskManager memory autotuning 能力 。
稳定性诉求上,流式任务的关键不是"失败后重新跑一遍",而是"从一致状态恢复,尽量少丢、少重、少停"。Flink Operator 支持生命周期管理、stateful/stateless upgrade、声明式 snapshot、失败升级回滚、自动重启不健康作业等能力 。FlinkStateSnapshot 用于表达对目标 FlinkDeployment 或 FlinkSessionJob 的 savepoint/checkpoint 操作,状态会从 TRIGGER_PENDING 到 IN_PROGRESS,再到 COMPLETED、FAILED 或 ABANDONED,并在 status.path 中记录最终路径 。

流式任务在 K8s 上最怕两类"看似正常"的治理动作。第一类是把它当 Deployment 服务滚动更新,直接杀旧 Pod 起新 Pod,绕过 savepoint 或 checkpoint 恢复路径;第二类是用普通 HPA 只按 CPU 伸缩 TaskManager,导致并行度、slot、状态重分布和 source 消费能力不匹配。更合理的方式是让 Operator 处理有状态升级、快照、恢复和并行度调整,把 K8s 的声明式控制与 Flink 的状态语义绑定起来。
四、在线查询服务
在线查询服务的代表是 Trino、Presto、ClickHouse 查询入口、Druid Broker/Router、SQL Gateway、数据 API 服务等。它们与离线、流式最大的区别是:用户或上游系统会在任意时刻发起请求,治理目标从"任务完成"和"状态连续"转向"服务可达、查询低延迟、失败隔离、滚动发布不中断"。Kubernetes Service 正好对应这类诉求:Service 把一个或多个 Pod 暴露成稳定网络应用入口,使客户端无需感知后端 Pod 的创建、销毁和 IP 变化 。
Trino 建议使用 Helm chart 部署,会产生 coordinator、worker、Service、Deployment、ReplicaSet 等对象;Trino 不像某些 Kubernetes 应用那样适合大量小 Pod,它更适合较少数量、每个 Pod 拥有更多资源,并强烈建议避免多个 Trino Pod 落在同一物理主机上以减少资源竞争 。这句话很重要:在线查询服务虽然看起来是"无状态服务",但它的执行引擎往往对内存、CPU cache、本地网络、磁盘 spill、节点间 shuffle 很敏感,不能简单按 Web 服务模式水平扩成许多小副本。

在线查询服务的资源特征是"并发驱动 + 查询复杂度驱动"。同样 100 个查询,如果都是小维表点查,压力可能主要在 coordinator、元数据访问和连接池;如果是大表 join、聚合、排序,压力会转移到 worker 的内存、CPU、网络 exchange 和 spill。Trino 通过 .server.workers 设置 worker 数,通过 coordinator 和 worker 的 JVM max heap size 调整内存,其中某些内存参数需要谨慎调优,否则可能导致 Trino 启动失败 。
在线查询服务的稳定性治理重点是"请求不中断"和"节点退出不伤害运行中查询"。Trino 提供了 worker graceful shutdown API:调用后 worker 进入 SHUTTING_DOWN,等待 shutdown.grace-period,让 coordinator 感知并停止向该 worker 分配 task,然后等待活跃 task 完成,再次等待 grace period,最后关闭应用;默认 grace period 是 2 分钟 。这意味着查询服务的 Pod 删除不能只依赖 Kubernetes 默认 SIGTERM,还要把引擎自己的退服协议接入 preStop、termination grace period 或运维流程。

如果说离线计算的危险动作是"无限重试烧资源",流式任务的危险动作是"无状态滚动更新破坏状态恢复",那么在线查询服务的危险动作就是"把 worker 当普通 Web Pod 直接缩容"。对在线查询服务来说,扩容要考虑 coordinator 调度能力、worker 资源规格、数据源连接数、缓存预热、查询队列和资源组;缩容要考虑退服、正在运行的 query、spill 文件、连接保持和客户端重试。
五、三类负载对照
三类负载跑在 K8s 上时,差异可以压缩成一张工程决策表:
| 维度 | 离线计算 | 实时流处理 | 在线查询服务 |
|---|---|---|---|
| 时间语义 | 有开始、有结束 | 长期持续运行 | 长期对外服务 |
| 典型引擎 | Spark、Hive on Tez、MR、批式 Flink | Flink、Kafka Streams、Spark Structured Streaming | Trino、Presto、Druid、ClickHouse 查询层 |
| K8s 映射 | Job、CronJob、Spark Operator、批调度器 | FlinkDeployment、FlinkSessionJob、Operator、Stateful/HA 组件 | Deployment、StatefulSet、Service、Ingress/Gateway、HPA/KEDA |
| 资源曲线 | 峰谷明显,按作业申请释放 | 稳定基线,随吞吐平滑波动 | 随并发和查询复杂度突增突降 |
| 主要瓶颈 | Shuffle、I/O、数据倾斜、排队 | Backpressure、checkpoint、state size、source lag | Coordinator、worker memory、exchange、spill、连接数 |
| 失败语义 | 可重跑、可补数、可回填 | 从 checkpoint/savepoint 恢复 | 请求尽量不中断,失败隔离 |
| 弹性方式 | 作业级并行度、executor 动态伸缩、队列容量 | 基于 lag/utilization/backpressure 的并行度调整 | worker 数、资源组、查询队列、服务副本 |
| 发布方式 | 新作业提交,失败重跑 | savepoint/checkpoint 后有状态升级 | 滚动发布、优雅下线、灰度入口 |
| 稳定性指标 | 成功率、耗时、重试数、产出正确性、资源成本 | lag、checkpoint 成功率、恢复时间、端到端延迟 | P95/P99 延迟、查询失败率、队列等待、可用性 |
| 治理目标 | 吞吐、成本、公平性 | 连续性、一致性、可恢复 | 低延迟、高可用、请求不中断 |
再进一步抽象,三者真正不同的是"资源治理的主语":
diff
+----------------------+--------------------------+
| 负载类型 | 治理主语 |
+----------------------+--------------------------+
| 离线计算 | 一次作业 / 一个批次 |
| 实时流处理 | 一个持续应用 / 一份状态 |
| 在线查询服务 | 一个服务入口 / 一组请求 |
+----------------------+--------------------------+
这也是为什么不能用一套 Kubernetes 模板治理所有大数据工作负载。模板可以统一镜像规范、标签、日志采集、监控接入、RBAC、网络策略,但不能统一失败语义、扩缩容依据和发布策略。越是成熟的大数据云原生平台,越会在统一底座之上提供按负载分类的控制器、队列、配额、监控面板和运维流程。
六、运行模式设计
设计 K8s 运行模式时,可以先问四个问题:任务是否会结束,状态能否重建,请求能否中断,资源是否可预测。答案基本决定了它应该落入哪种范式。

离线计算的 K8s 模式应围绕作业编排、资源池、队列和作业生命周期设计。Spark on Kubernetes driver 会创建 executor Pod,executor 完成后退出并清理,driver completed 状态保留日志 。因此平台层应重点提供作业模板、镜像依赖管理、队列准入、失败重试边界、日志归档、历史服务和成本归因,而不是把 executor 当成长期服务副本保护起来。
流式处理的 K8s 模式应围绕 Operator、状态后端、快照和有状态升级设计。Flink Operator 把应用声明为自定义资源,并提供 lifecycle management、snapshot management、blue/green deployment、autoscaling、metrics、events 等能力 。因此平台层应重点控制 savepoint 路径、checkpoint 可靠性、升级模式、恢复策略、状态大小、版本兼容和自动扩缩容边界。
在线查询服务的 K8s 模式应围绕稳定入口、容量水位、灰度发布和优雅下线设计。Kubernetes Service 会持续维护匹配 Pod 的 EndpointSlice,使客户端不需要跟踪后端 Pod IP 变化 ;Trino 的 graceful shutdown 则说明引擎级退服流程必须与 Pod 终止流程结合 。因此平台层应重点提供 Service/Gateway、资源组、连接池、查询排队、熔断限流、worker 退服、灰度发布和容量压测。
七、资源治理差异
Kubernetes 的 requests 和 limits 是三类负载都会用到的基本语言,但写法应有差异。requests 决定调度时的资源预留,limits 决定运行时上限;CPU 超过 limit 会被限制 CPU 时间,memory 超过 limit 可能被 OOM kill 。在大数据场景中,这个差异会直接影响性能诊断:CPU limit 过低常表现为慢,memory limit 过低常表现为失败。
离线作业可以接受更激进的资源超卖和 Burstable 策略,因为单个 executor 被驱逐或 OOM 后通常可以由计算引擎重试补偿。但 driver、shuffle-heavy executor、关键提交阶段不应盲目 BestEffort,否则作业会在资源压力下频繁失败,拖垮队列吞吐。对离线计算来说,合理的策略通常是:driver requests 更稳,executor 按历史画像分层,长尾作业进入单独队列,超大 shuffle 作业要求本地盘或远程 shuffle 服务能力。
流式任务对资源抖动更敏感。TaskManager 被驱逐不是简单丢一个计算片段,而是触发 failover、状态恢复、checkpoint 回退和吞吐下降;如果频繁发生,source lag 会持续扩大。流式任务的核心 Pod 更适合使用明确 requests,必要时提高 QoS 等级,并把扩缩容交给理解 Flink 作业图和运行指标的控制器,而不是单纯按 Pod CPU 横向扩缩。
在线查询服务需要同时避免两种极端:资源给少会导致排队、OOM、查询失败;资源切得太碎又会导致节点间 shuffle 和内存竞争放大。Trino 建议少量较大 Pod,并避免多个 Trino Pod 在同一物理主机上造成资源争用 。因此在线查询服务常需要 pod anti-affinity、拓扑分散、专用节点池、大内存规格、spill 存储规划和连接数保护。

八、稳定性治理
稳定性不是一个统一指标。离线作业的稳定性可以用"是否按时产出正确结果"衡量;流式任务的稳定性要看"是否持续处理并保持状态一致";在线查询服务的稳定性要看"用户请求是否快速、成功、不中断"。
离线计算的故障治理应从作业视角出发。Kubernetes Job 支持失败后重建 Pod,并通过完成数判断任务完成 。但作业平台还要在 K8s 之外补上数据语义:输入分区是否完整、输出是否幂等、补数是否覆盖正确时间窗口、失败重试是否会产生重复写入。
流式任务的故障治理应从状态视角出发。Flink Operator 中 FlinkStateSnapshot 对 savepoint/checkpoint 的声明式表达,说明状态快照已不只是引擎内部细节,而是 K8s 控制面可观察、可追踪的对象 。这类任务的稳定性看板应把 checkpoint 成功率、checkpoint duration、restart 次数、恢复耗时、source lag、backpressure 放在 Pod restart 之前。
在线查询服务的故障治理应从请求视角出发。Service 解决的是访问入口稳定,不保证单次查询一定成功;Deployment 滚动更新解决的是副本替换,不保证查询引擎内部任务不被中断。Trino graceful shutdown 把 worker 退服拆成"进入关闭状态、等待 coordinator 停止派发、等待活跃 task 完成、再关闭"的过程 ,这说明在线查询服务必须把引擎协议纳入 Kubernetes 生命周期钩子。
rust
三类稳定性指标分层
离线计算:
作业成功率 -> 产出时效 -> 重试次数 -> 成本 -> 数据正确性
实时流处理:
存活状态 -> checkpoint 成功率 -> lag -> backpressure -> 恢复时间
在线查询服务:
可用性 -> 查询成功率 -> P95/P99 延迟 -> 队列等待 -> 优雅退服成功率
九、常见误区
- 把所有大数据负载都塞进 Deployment 。Deployment 很适合无状态、可替换、长期运行的服务,但离线作业需要 completion、parallelism、backoff、TTL、历史追踪;流式任务需要 savepoint、checkpoint、stateful upgrade;把它们都当普通 Deployment,会让运维动作失去业务语义。
- 把 HPA 当万能弹性方案。HPA 按 CPU 或内存扩服务副本,对 Web 服务常常有效;但对 Spark executor,弹性要和 driver 调度、shuffle、stage 进度有关;对 Flink,弹性要和并行度、slot、状态重分布、checkpoint 有关;对 Trino,弹性要和 coordinator 调度、worker 退服、查询队列、数据源连接有关。
- 把 Pod restart 当稳定性指标。Pod restart 对在线服务可能是严重事件,对离线 executor 可能只是可重试失败,对流式 TaskManager 则可能意味着一次状态恢复。稳定性看板应该从负载语义出发,而不是从 Kubernetes 对象事件出发。
- 把"上 K8s"理解成"成本必然下降"。K8s 提供更细粒度的调度和释放能力,但如果 requests 估算偏大、长尾作业占队列、流任务过度预留、查询服务规格切碎,成本可能上升。成本优化必须按负载设计:离线看队列利用率和作业画像,流式看基线容量与 lag,查询看并发水位和资源组。