这几年数据圈里真正在被反复验证的路线,就是“云原生 + 大数据”,而 Kubernetes 基本成了绕不开的底座。我前后花了小一年时间,把原来跑在裸机 Hadoop YARN 上的一堆 Spark、Flink、Hive 任务,慢慢迁到一套基于 K8s 的弹性数据处理平台上。这里不谈概念包装,只说真实落地的过程、拆解思路和踩坑记录,适合正在做大数据平台容器化、想把资源利用率提上去、又找不到“从哪下手”的团队参考。
这套方案最终解决的核心问题其实很朴素:计算资源不该再跟着固定几台机器走,而是跟着任务走。任务来了,Pod 秒级拉起,算完自动释放;深夜批处理高峰和白天实时流量高峰错开,靠队列策略承接;存储和计算彻底拆开,对象存储服务长期数据。整篇内容会按照“为什么这么设计、组件如何选型、具体怎么一步步搭、线上遇到什么鬼问题、成本和后续怎么控制”来展开。
1. 为什么我把大数据任务搬到 Kubernetes:方案思路与取舍
1.1 核心痛点:固定集群带来的资源诅咒
以前用 YARN 管理大数据任务,集群规模是“预配”出来的。你得预估半年后的业务峰值,然后为了那个峰值把几十台机器常驻在机房里。白天跑实时任务和即席查询,CPU 可能只用 30%;凌晨批量任务全开的时候,队列又开始互相抢资源。加机器要审批走采购流程,减机器又怕下次峰值来了撑不住,管理员每天就是做配额拉扯的裁判。
这样的固定资源池最大的问题还不是钱,是响应速度。业务的探索性分析和临时数据任务没有固定节奏,今天数据团队想跑一个半小时的 SparkETL,资源不够就得排队,等前面的大任务把队列释放,黄花菜都凉了。如果是在 Kubernetes 上跑,情况完全不同:任务描述从“提交到队列中的 application”变成了一组“Pod 模板”,调度器按真实请求分配资源,任务结束了 Pod 立刻被回收。机器不够时集群自动扩容,空闲时缩容到最小规模。
我说的资源利用率提升,不仅仅是 CPU 曲线好看,而是团队可以真正把硬件资源当成一个动态池子。同一个 Kubernetes 集群里,白天跑 Web 服务的 Pod、夜间跑数据清洗的 Spark Executor,只要通过节点亲和性和优先级划分好命名空间,就能共享同一批机器。这种混部模式比“大数据一套集群、业务一套集群”的物理隔离要省一半成本。
1.2 选择 Kubernetes 而非自研调度系统
去大数据化这个方向,行业内反复摇摆过。有人提出干脆把计算层直接让 Spark 自己调度,也有人想用自研的 Mesos 或者单纯的 Docker Swarm。但折腾一圈后,Kubernetes 的胜出不是因为它技术最前沿,而是因为它生态完整、API 统一、操作习惯深入人心。
就 Spark on K8s 而言,作业提交后请求的是 Kubernetes API,Pod 调度、节点故障转移、日志采集这些都是 K8s 生态现成的能力。相比自己在 YARN 上定制调度策略,K8s 的声明式 API 让大任务和普通容器任务可以接受同一套身份认证、网络策略、监控体检。团队里不用养一批只懂“大数据调度”的人,运维手里已有的 K8s 经验能无缝迁移过来。
选型另一个关键点是“存算分离已经不可逆转”。云原生时代如果继续坚持 HDFS 存储与计算节点捆绑,计算资源就得跟着存储副本的分布去调度,天然别扭。而对象存储和各类云盘服务已经成熟,把 HDFS 当“数据底座”反而成了成本大头。Kubernetes 的工作负载天然以无状态为假设,计算层容器随时可死、可重建,数据放在远端的对象存储里才合理。架构上想明白这一点,后面的组件选型几乎不纠结。
1.3 什么情况下不建议盲目上这套架构
让我泼一盆冷水。如果你们的业务量很小,每天跑几十个任务,集群规模五台以内,直接维持现有 YARN 甚至单机调度都不亏。因为迁到 K8s 在初期需要付出一笔不小的学习成本和基建改造费用:镜像构建、RBAC 权限、网络策略、持久卷,这些都不是白的。
另外,如果团队没有足够强的容器化运维经验,强上 K8s 会把问题放大。大数据任务比普通微服务更挑剔——镜像动辄几个 GB,Executor 的数量可能瞬间几十上百,Pod 频繁调度会暴露 DNS 性能问题和节点端口不足的资源管理问题。一个经验不足的团队很容易在“从机房到容器”的路上被一堆琐碎问题拖下水,造成排障成本上升。
我的观点是:这套方案最适合数据量中等偏上、任务峰谷明显、团队已经想清楚了“长期追求弹性”的技术路线。如果只是开会听到“云原生”这个词,想跟风改造,建议先拿一两个非核心 Spark 任务试运行半年,再决定是否全面切。技术选型是为了解决问题,不是为了追逐热搜。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 组件与架构拆解:Spark、Flink、存储与调度四张王牌
2.1 Spark on K8s 的两种姿势:Operator 和原生模式怎么选
Spark 跑在 K8s 上有两条常见路线。一条是直接用 Apache Spark 原生提供的 spark-submit --master k8s://https://...,另一条是用 Google 开源的 Spark Operator 来管理 SparkApplication 这一类自定义资源。
原生模式适合跑批任务相对固定、开发栈还停留在脚本提交方式的团队,你基本只需改一行提交命令和配置,就能把任务塞进 K8s。然而它有一个不便之处:任务的生命周期没有和 K8s 资源风格统一,作业结束后要写额外的 hook,失败后要自己处理重试。Operator 模式等于把 Spark 任务视作 K8s 里的一等公民,你在 YAML 里声明跑几个 Driver、几个 Executor、镜像地址是什么,Kubernetes 会帮你盯状态和重试。
我在生产中选的是 Operator 模式。理由有几个:重试逻辑可以内置,GitOps 友好,代码仓库里存的是描述任务状态的 YAML,而不是一段只能自己在终端跑的脚本。而且每次任务执行的全程状态(Submission、Running、Succeeding、Failing)都会作为自定义资源状态暴露给上层,后续做数据平台页面的任务管理会轻松很多。
如果你也想用 Operator,注意自己维护 CRD 版本。官方版本从 v1beta1 演进到 v1 时,许多配置字段变化较大,升级前需要仔细核对任务模板中的 API version。针对旧任务,我们迁移时专门做了一张“字段映射清单”,防止升级后任务明明写对了,却因为某个 API 字段废弃应用一直起不来。
2.2 Flink on K8s:实时计算如何保持稳定与状态一致性
Flink 实时任务对弹性的要求,和 Spark 批处理完全不一样。批任务可以重启重跑,流任务如果宕机恢复不及时,可能出现数据延迟或重复消费。K8s 环境里 Flink 的推荐部署形态是 Native Application 模式,也就是每个作业一个独立 JobManager,配合独立的 TaskManager 副本。这种模式能最大化隔离,避免多个实时作业挤在一个共享 Session 集群里互相干扰。
基于 K8s 的 Flink 优势在于快速扩缩容。我在处理大促流量时,只要调整 parallelism 并滚动更新 TaskManager Deployment 即可,资源随 Kafka 堆积量自动上下波动。需要注意:扩容之前要规划好资源请求,别把 TaskManager 建到只剩少量内存的节点上,否则 Flink 启动后频繁发生内存溢出。
状态一致性方面,Checkpoint 应该存到远端对象存储路径,不要设置到 Pod 本地,因为 Pod 随时可能因为节点故障被重建。代码里配好 state.checkpoints.dir 指向 S3/OSS/ MinIO 后,还要设置合理的 Checkpoint 间隔。我见过不少新手误以为“检查点越多越保险”,最后反而因为状态后端频繁读写拖慢了吞吐。间隔太短,反压会从存检查点开始向上游传播;间隔太长,故障后恢复窗口过大。
Flink on K8s 集群与外部系统之间的网络策略也值得提前设计。例如 Flink 的 JobManager 需要监听外部提交端口,TaskManager 各 Pod 之间需要互相建立 TCP 连接。若不关注 NetworkPolicy,某些严格的安全环境下任务会长时间处于“调度成功但无法建立连接”的状态,排查起来异常痛苦。
2.3 存储拆分:HDFS 退役,还是保留一部分
在弹性计算架构里,存储的选择决定任务迁移的适配成本。最稳妥的方案是把历史数据分阶段迁移到对象存储里,让 HDFS 慢慢退到冷数据和临时计算两个角色。对象存储与计算完全解耦,Pod 在哪个节点上都无所谓,数据读取走网络即可,容量没有上限。副本可靠性由存储侧保证,不再占用计算节点的磁盘空间。
不过生产上不能一刀切。Spark Shuffle 的临时数据并不适合直接写对象存储,毕竟 shuffle 阶段要求高带宽低延迟的中间数据交换。这块我用 K8s 的 EmptyDir 或独立的临时卷来解决,量大时挂载一块本地 SSD 盘。而像 Hive 元数据库这种强一致性组件,则不迁移容器,保留独立有状态服务,让 K8s 的 SparkAccess 通过外置 JDBC 连接。
存储权限管理容易被忽略。对象存储如果使用 AK/SK 直接写死在 Spark 配置文件里,镜像随时有泄露风险。我们的做法是让 Spark Executor、Driver 都加载 K8s 的 Secret 映射成环境变量,并配置成只读挂载。安全这件事,在弹性平台上的影响范围比传统内网集群大得多——容器可以在任意节点上跑,不是只有可信运维才能访问。
2.4 调度器:Volcano 和 YuniKorn 让资源不再靠抢
默认的 Kubernetes 调度器在处理 Spark、Flink 这类批量任务时,会出现“饿死”状况:先提交的大任务把资源全占了,小而紧急的任务一直 Pending。解决思路是引入支持批量调度的调度器,我们调研并测试过 Volcano 和 YuniKorn。
Volcano 的核心能力是 Gang Scheduling,也就是 PodGroup 协同调度。Spark Executor 必须整批同时启动,如果只启动了部分 Executor,任务就空转浪等。Volcano 会检查每组 Pod 是否能全部满足资源,如果不够,干脆整个组处于待调度状态,不分配一半资源给一组死等一半的脑死亡任务。生产实测中,对于需要 50 个 Executor 的任务,这个能力几乎必不可少。
YuniKorn 则偏重队列容量与优先级管理。如果你所在团队内部有多个子业务线同时使用集群,YuniKorn 允许管理员按部门配置资源上限,避免某个子团队提交了千个 Executor 的任务导致全平台其他任务无法提交。我们已经把两者混用:Volcano 负责单任务的内部调度,YuniKorn 处理跨业务的资源治理。目前国内不少团队也已经这么运作。
只要集群规模没有大到几千节点,这两个调度器部署难度都不高,建议直接用 Helm Chart 安装。但要注意监控调度指标,比如 Pending Pod 的等待时间和调度吞吐,这两项指标能第一时间反映资源碎片化严重还是调度器配置不合理。
3. 从 0 搭建弹性数据处理平台的实操过程
3.1 环境准备与镜像改造:一份镜像解决代码与依赖环境
先交代一个前提:平台不是单独搭建在测试集群里,而是在现有 K8s 集群上开了一个独立的 data-platform Namespace。网络策略按 Namespace 隔离,外部数据源通过内网 DNS 访问。搭建的第一步,是准备一套能够满足 Spark 和 Flink 运行的基础镜像。
Spark 官方提供的 spark:3.4.x 基础镜像体积不小,但欠了一个关键考虑:它不懂企业内网的数据源驱动。我们改造时基于官方镜像增加几个 JDBC Driver、Hadoop 的 OSS 和 S3 相关依赖,并预装了后期需要的 Python 环境。镜像唯一的要求是不能在启动后的 Pod 里再 pip install 或在 shell 里连网下载,因为生产 Pod 通常没有外网权限。
Flink 镜像与 Spark 类似,但更需要注意版本对应关系。最好将任务代码打进镜像里,或者是 Flink 镜像加上一个任务 JAR 的叠加层。这样可以保证每次任务代码版本与镜像 Tag 严格对应。在管理镜像 Tag 时,我建议建立一个 registry 目录规则:data/spark-py-v3.4.0-{版本日期},并在 Kubernetes 的 Job 模板里硬性引用。经验教训是:使用 latest 标签的镜像上线后,极易出现今天执行的任务和昨天执行的任务跑的代码根本不属于同一个版本,还没法回溯。
3.2 提交第一个 Spark 作业:从 YAML 开始的完整步骤
第一步是安装 Spark Operator。这里推荐使用 Helm 方式:
bash复制helm repo add spark-operator https://googlecloudplatform.github.io/spark-operator
helm install spark-operator spark-operator/spark-operator \
--namespace data-platform \
--set webhook.enable=true
注意开启 webhook,它会做 SparkApplication 配置的校验,直接拦截掉明显写错的资源字段,相当省事。
随后为任务创建一个 ServiceAccount,Spark jobs 默认需要权限去创建、删除自己的 Executor Pod:
yaml复制apiVersion: v1
kind: ServiceAccount
metadata:
name: spark-sa
namespace: data-platform
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: spark-role
namespace: data-platform
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: spark-rolebinding
namespace: data-platform
subjects:
- kind: ServiceAccount
name: spark-sa
namespace: data-platform
roleRef:
kind: Role
name: spark-role
apiGroup: rbac.authorization.k8s.io
写好后 kubectl apply -f spark-sa.yaml 创建账号与权限。我见过不少新手跳过 ServiceAccount 设置,直接任务提交,结果 Executor 一直无法创建,原因是驱动程序没有权限向 K8s API 请求 pod,报错提示往往却很含糊。
接着写你的第一个 SparkApplication YAML:
yaml复制apiVersion: "sparkoperator.k8s.io/v1beta2"
kind: SparkApplication
metadata:
name: spark-elt-demo
namespace: data-platform
spec:
type: Python
pythonVersion: "3"
mode: cluster
image: "registry.example.com/data/spark-py-v3.4.0:20250401"
imagePullPolicy: IfNotPresent
mainApplicationFile: local:///opt/spark/work-dir/etl_demo.py
sparkVersion: "3.4.0"
serviceAccount: spark-sa
driver:
cores: 1
coreLimit: "1200m"
memory: "2g"
serviceAccount: spark-sa
labels:
version: 3.4.0
executor:
cores: 2
instances: 4
coreLimit: "2400m"
memory: "4g"
labels:
version: 3.4.0
sparkConf:
"spark.hadoop.fs.oss.impl": "org.apache.hadoop.fs.aliyun.oss.AliyunOSSFileSystem"
"spark.hadoop.fs.oss.endpoint": "oss-cn-hangzhou-internal.aliyuncs.com"
"spark.hadoop.fs.oss.accessKeyId": "your-ak"
"spark.hadoop.fs.oss.accessKeySecret": "your-sk"
restartPolicy:
type: OnFailure
onFailureRetries: 2
onFailureRetryInterval: 20
用 kubectl apply -f spark-elt-demo.yaml 提交后,可以观察:
bash复制kubectl get sparkapplication -n data-platform
kubectl describe sparkapplication spark-elt-demo -n data-platform
通过 describe 能看到 Driver Pod 的创建状态,如果资源不足会在事件里显示 Pending 原因。第一次提交成功后,我通常会在代码里刻意写一个空 running 的 sleep 一段时间,直接登录 Pod 查看 Spark UI,确认 Executor 都注册成功后再退出。这一步可以避免很多任务假成功的情况。
3.3 让集群真正“弹性”:关键参数、节点池与自动扩缩
弹性不能只是“任务能跑到 K8s 上”就算完成,核心在于需要时能立即扩大资源池。国内自建集群上,我采用下面的组合:
节点层面,配置 Cluster Autoscaler,它监听集群中 Pending Pod,发现节点不足时向底层的 OpenStack/阿里云等调用接口扩容节点。如果你们的底层是物理机自建,则很难做到分钟级扩机器,这部分弹性空间有限,要提前向业务方说明。
应用层面,让工作负载的资源配置量尽量贴近真实值。很多人习惯将 requests 设成实际用量的两倍,节点看似吃饱,实际上资源浪费非常严重。我们在 Spark 的 sparkConf 中设置 spark.driver.memoryOverhead 和 spark.executor.memoryOverhead 为实际内存的 10%~15%,能帮助调度器更精确地规划节点。
Flink 实时任务则用 KEDA 配合。KEDA 的 ScaledObject 可以根据上游 Kafka 消息堆积量,或者自定义的 Flink 吞吐指标,决定将 TaskManager 副本从 2 拉大到 10。普通 HPA 只看 CPU 和内存,Flink 扩缩容真正应该看的是消费延迟和反压数值。
小技巧是单独创建“弹性任务专属节点池”,把实时流的 Pod 和批处理任务的 Pod 用 nodeSelector 分开。原因很简单:批处理任务往往吞吐非常高但时间短,适合抢占式便宜机器;实时任务则不能经常因为节点回收而重启,宁可跑在稳定的按量付费实例上。
3.4 观察与监控:没有可观测性的弹性等于盲人开车
容器化环境里任务跑着跑着就没了、节点自动切换等情况非常普遍,所以必须把可观测性放在第一位。我搭建的监控分三部分:
- 基础设施层:Prometheus + Grafana 监控每个节点的 CPU、内存、磁盘 IO、网络流量。
- 工作负载层:Spark 和 Flink 都支持 Prometheus Reporter,把任务自身的指标导进同一套 Prometheus。
- 事件与日志层:Kubernetes Events 接入告警通道,Pod 日志用 Loki 或 ES 收集。
Spark 开启 Prometheus 的方法很简单,在 Operator 的 SparkApplication 配置中添加:
yaml复制sparkPrometheus:
enable: true
然后再加上 podMonitor,就能在 Grafana 中直接看到 job 的 shuffle read/write 效率与执行耗时。以前在 YARN 上做资源调优多靠猜,现在每个 Executor 的计算曲线都暴露出来,定位数据倾斜的节点非常快。
这套监控还有一层重要作用:为节点池自动扩缩容提供触发依据。比如设定离线任务队列 Pending 超过 10 个且持续 2 分钟时触发扩容,比单纯看机房负载更贴近业务目标。
4. 实战中踩过的坑:问题排查与规避实录
4.1 Driver、Executor 启动失败排查清单
容器环境下,任务失败的第一现场很少会打印到 driver logs,而是以 Pod 状态的方式呈现。所以我建议按这份清单排查:
- 检查 Pod 是否一直
Pending:描述里会显示节点资源不足、污点不匹配、持久卷无法挂载等原因。节点资源不足时要看看是不是所有工作节点都被大任务占用得一点不剩,可以临时提高 Cluster Autoscaler 的扩容触发阈值。 - 检查
ImagePullBackOff:如果镜像仓库需要认证,要提前在命名空间内创建imagePullSecret,并配置到 SparkApplication 的imagePullSecrets字段。 - 检查
CrashLoopBackOff的 driver 日志:重点看是否有类路径问题。用 python 任务时,代码脚本必须放在镜像里且能通过local://访问,不能总指望 spark-submit 帮你把外部文件传进去,否则在 K8s 环境下文件传递经常失败。 No such file or directory的诡异报错:多半是 Kubernetes 容器默认的用户 UID 与镜像内文件权限不一致。可以设置securityContext的runAsUser与镜像直接保持相同。
有一个生产事故给我印象很深刻:某次 Spark 任务迁移后,Executor 能启动,但一直处于 Waiting 状态,注册不到 Driver。查了一下午发现是集群开了网络策略,Executor 所在节点无法访问 Driver 的某个高位数端口。最后是在 NetworkPolicy 打开 Spark 通信端口段解决。容器环境加了很多安全能力,每个都可能断送任务连接。
4.2 Flink 反压、Kafka 消费延迟与 Checkpoint 超时
Flink 流式任务比批任务运维复杂,因为问题往往不是“挂了没”,而是“慢得没完”。我刚切到 K8s 上时遇到的一个典型问题:某实时入仓作业运行半小时后,Kafka 消费延迟越来越高,而 Flink Web UI 显示的 CPU 与内存都很低,看起来一切正常。
从监控上排查,发现 Flink 对下游存储写入的吞吐被限制,瓶颈是在对象存储的限流或者 MySQL 单表写入锁。K8s 弹性在这种情况下无能为力,反而会误导你盲目扩容 TaskManager,结果只是增加上游读取压力,不会提高下游吞吐。这时候的正确操作是优化 Sink 并发与批量写入参数,或者增加下游存储分片。
Checkpoint 超时更容易发生在大状态任务上:状态后端 RocksDB 增量上传到远端对象存储的速度跟不上算子读写,整个任务会频繁 stop-the-world。此时优先增大 taskmanager.memory.managed.size,将状态内存配额调高,之后再考虑增加 TaskManager 个数。
为了快速感知流任务是否健康,我建议把 Flink 的 restart-strategy 设为 exponential-delay,并配上外部消息告警。要特别注意失败后的自动恢复和告警一定不能只靠人工盯 Web UI,否则节假日一个消费滞后没人管,等到周一看的时候,上游 Kafka 可能已经堆了几千万条数据,只能放弃部分历史数据重跑。
4.3 Pod 被驱逐、磁盘写满与优雅退出处理
K8s 集群中,Pod 因为资源压力被驱逐是日常事件:节点磁盘超过 85%、内存不足,kubelet 会挑选优先级低的 Pod 杀掉。当我们的大数据计算 Pod 被杀时,任务就会失败重试。有一种场景是 Executor 正在处理 shuffle 数据时被驱逐,之后 Driver 会尝试重新申请 Executor,整个任务多耗掉不少时间。
缓解的方法有几个层面。代码层:Spark 开启 spark.cleaner.periodicCleanup.interval 定期清理 shuffle 临时数据,避免 Executor 写太多临时文件。调度层:给实时作业和核心批作业配上专门的节点池,并将非核心 ELT 任务落入抢占式节点,让系统驱逐时优先牺牲非核心任务。
还有一个优雅退出的技巧:在 SparkApplication 中配置 terminationGracePeriodSeconds,并让 Driver 的 preStop Hook 触发时先检查是否有正在运行的 Spark 作业,若有则发出一次“任务请求优雅停止”的信号。否则当平台在做节点维护、重新调度时,一个正在跑的流任务就可能直接被 SIGKILL 杀掉,连最终的清理逻辑都没有机会执行。
5. 成本控制、运维模型与下一步演进
5.1 成本控制:弹性带来的钱账要算清
弹性不只是为了性能,本质是成本优化。但“动态资源和成本核算”也意味着预算模式需要改变。以前机房采购是一次性花费,现在是以容器资源计量计费。我们后来引入了每个 Namespace 的资源 Quota,并通过一套成本拆分工具,把集群的月度总成本按 Pod 资源使用量分摊到子部门。这里建议在实施平台初期就做好标签规范,让 Pod 模板强制带上业务团队与任务目的标签。
很多团队看见“对象存储很便宜”就无脑把大批量小文件丢进去,结果跑 Spark SQL 时需要反复读取文件元数据,任务慢且成本反而更高。计算弹性要配合存储治理:将小文件用 compaction 合并成大文件,再放到对象存储上。我们在迁移中期就做了一个小时级增量合并的算子,效果非常明显,读取性能提升接近一整倍。
5.2 运维模型:从救火队长转变成平台赋能
切到云原生架构后,后端机器学习时代的“谁主机出问题谁去修”的场景逐渐减少,取而代之的是大量“YAML 和镜像问题”。运维人员必须更了解镜像构建、CI/CD流水线和Application配置管理。团队里的数据开发人员、算法工程师直接在 Git 仓库提交任务代码,平台自动构建镜像并提交 SparkApplication,这就是把 YARN 时代只能靠管理员代跑的瓶颈打破了。
所以我认为最值得投入的其实是“平台化抽象”部分:允许用户提交一种“任务描述文件”,内部由平台转换为标准 SparkApplication 或 FlinkDeployment YAML。这样可以屏蔽掉底层 K8s 细节,Data Scientist 不需要了解什么 NodeSelector、Toleration 就能跑起模型训练。平台侧再配合一套权限体系,避免任何人写着花式 Yaml 把集群全搞挂。
这一步做得好,K8s 就从“额外复杂度”变成了“后台能力”,团队响应需求的速度会发生质变。
5.3 下一步演进:实时数仓、GPU 负载与统一资源池
现在这个弹性数据平台已经比较稳定,日常运行着数十个周期调度任务和十几个 Flink 实时作业。后续我将重点扩展三个方向:
- 实时数仓往 K8s 进一步迁移,结合 Iceberg 这类表格式,把实时与离线数仓真正在存储层统一。
- GPU 推理和大模型微调负载进入同一个 K8s 资源池。GPU 调度需要提前部署 Device Plugin,资源隔离和显存限制比 CPU 任务更细腻,很多经验能复用。
- 多集群联邦。随着数据中心跨地域的场景增加,可以考虑让任务按数据位置挑选最合适的集群运行,平台侧负责应用集群路由与资源调节。
这些都是演进路线上顺理成章的方向,因为底座弹性化以后,新增一种工作负载只是加一套 CRD 和镜像模板的事,不必再把整个架构推翻重来。
6. 几个关键经验总结(或者说,血泪教训)
最后唠叨几句我对这套方案的体会。如果你现在正要开始做类似改造,请记住几件小事:
- 不要一次性迁移所有任务。优先找一个调度周期固定、链路短的离线任务跑通全流程。从镜像构建到监控告警,每个环节都打磨顺了,再谈大规模迁移。
- 资源配额的“重要程度排序”一定要提前想好:哪个业务能抢占资源,哪个业务必须保稳定,哪个任务适合用抢占式实例,这些策略搞不清楚,容量再大也天天踩踏。
- 可观测系统要跟平台同步建设。没有顶层监控就放开弹性扩缩容,你相当于坐在一辆没有仪表盘的赛车上,速度越快越危险。
我把这套基于 Kubernetes 的弹性数据处理方案跑通后,最大的感受是:以前为了维护机房一套固定集群,半夜被电话叫醒是常事;现在任务出问题,监控告警会先走一遍规则,多数情况我自己还没醒,任务已经根据策略自动重试了。技术架构的变化最终是运维体验和工作重心的变化。真心建议有条件的朋友尽早向这个方向走一步,哪怕只拿一套非核心任务验证一下,也比站在原地听别人讲“云原生”要实在得多。
