上一篇解决了流处理的时间语义。这一篇进入部署架构——Spark 如何在不同的集群管理器上运行。

Spark 的一个设计决策是把计算引擎和资源管理解耦。SparkContext 通过 SchedulerBackend 接口与集群管理器交互,不绑定任何特定的资源管理系统。这让同一份 Spark 代码可以运行在 Standalone、YARN、Kubernetes 或 Mesos 上,只需要切换启动参数。

本文只抓一个问题:Client Mode 和 Cluster Mode 的区别,以及 YARN 和 Kubernetes 两种主流部署模式各自的架构特点。

Driver 和 Executor 的角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
┌──────────────────────────────────────────────────┐
│ Driver (SparkContext 所在进程) │
│ ├── DAGScheduler: RDD → Stage │
│ ├── TaskScheduler: Task → Executor │
│ ├── BlockManagerMaster: 存储元数据 │
│ └── SparkUI: Web 监控 │
└──────────────────┬───────────────────────────────┘
│ RPC (Task 分发 / 结果收集)
┌──────────┼──────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│Executor │ │Executor │ │Executor │
│ Task 0 │ │ Task 1 │ │ Task 2 │
│ Task 3 │ │ Task 4 │ │ Task 5 │
└─────────┘ └─────────┘ └─────────┘

Driver 是 Spark 应用的控制中心:运行用户的 main 函数,创建 SparkContext,编排调度逻辑,收集计算结果。Executor 是执行单元:接收 Driver 分配的 Task,执行计算并把结果返回给 Driver。

Client Mode 和 Cluster Mode 的唯一区别是 Driver 运行在哪里:

1
2
3
4
5
6
7
Client Mode:  Driver 运行在提交作业的客户端机器上
→ 适合交互式开发(spark-shell、Jupyter Notebook)
→ 客户端断开 → 作业终止

Cluster Mode: Driver 运行在集群中(YARN 的 ApplicationMaster / K8s 的 Pod)
→ 适合生产作业(提交后客户端可以断开)
→ Driver 的生命周期由集群管理器管理

YARN 模式

YARN(Yet Another Resource Negotiator)是 Hadoop 生态的资源管理器。Spark on YARN 是目前最成熟的部署方式。

YARN Client Mode:

1
2
3
4
5
6
7
8
9
10
11
客户端机器                    YARN 集群
┌────────────┐ ┌─────────────────────────┐
│ Driver │ │ ResourceManager │
│ (main) │──申请资源───>│ │
│ │ │ NodeManager 1 │
│ │<──分配───────│ ├── Container(AM,轻量) │
│ │ │ └── Container(Executor)│
│ │ │ NodeManager 2 │
│ SparkUI │ │ └── Container(Executor)│
│ :4040 │ └─────────────────────────┘
└────────────┘

Driver 运行在客户端机器上。YARN 分配一个轻量级 ApplicationMaster(只负责向 ResourceManager 申请 Executor Container),实际的调度逻辑在客户端 Driver 中执行。

YARN Cluster Mode:

1
2
3
4
5
6
7
8
9
10
客户端机器                    YARN 集群
┌────────────┐ ┌─────────────────────────┐
│ 提交脚本 │──提交 JAR───>│ ResourceManager │
│ (退出) │ │ │
└────────────┘ │ NodeManager 1 │
│ ├── Container(AM=Driver)│
│ └── Container(Executor)│
│ NodeManager 2 │
│ └── Container(Executor)│
└─────────────────────────┘

Driver 运行在 ApplicationMaster 的 Container 中。客户端提交作业后可以退出,通过 yarn logs -applicationId 查看日志。SparkUI 通过 YARN 的代理 URL 访问。

Kubernetes 模式

Spark on Kubernetes(自 Spark 2.3 起,Spark 3.1 GA)把 Driver 和 Executor 都运行在 K8s Pod 中。

1
2
3
4
5
6
spark-submit 流程:
1. spark-submit 创建 Driver Pod (通过 K8s API)
2. Driver Pod 启动后,向 K8s API Server 申请 Executor Pod
3. Executor Pod 启动,向 Driver 注册
4. Task 执行...
5. 作业完成 → Executor Pod 自动删除,Driver Pod 保留日志

K8s 模式的关键差异:

容器隔离:每个 Executor 是一个独立的 Pod,有独立的资源限制(CPU/Memory request & limit),比 YARN Container 的隔离更彻底。

动态 Pod 管理:Spark 直接调用 K8s API 创建和删除 Executor Pod,不需要预先分配固定数量的 Container。配合 Dynamic Resource Allocation 可以做到按需伸缩。

存储卷:Executor Pod 可以挂载 PersistentVolumeClaim 作为 Shuffle 数据的存储目录,解决 Pod 临时文件系统空间不足的问题。

镜像打包:用户代码和依赖打包在 Docker 镜像中。spark-submit 通过 --conf spark.kubernetes.container.image 指定镜像。

四种集群管理器对比

1
2
3
4
5
6
7
                Standalone    YARN         Kubernetes    Mesos(已废弃)
资源管理器 Spark 内置 YARN RM K8s API Mesos Master
隔离级别 JVM 进程 YARN Container Pod/cgroup Mesos Container
动态资源分配 支持 支持 支持(3.0+) 部分支持
多租户 弱 强(队列) 强(namespace) 中等
生态集成 无 Hadoop 全家桶 云原生 无
运维复杂度 低 中 中~高 高

选择建议:已有 Hadoop 集群 → YARN。云原生环境或 K8s 集群 → Kubernetes。开发测试 → Standalone。

实验:对比 Client 和 Cluster 模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# YARN Client Mode
spark-submit \
--master yarn \
--deploy-mode client \
--num-executors 3 \
--executor-memory 2g \
my-app.jar

# YARN Cluster Mode
spark-submit \
--master yarn \
--deploy-mode cluster \
--num-executors 3 \
--executor-memory 2g \
my-app.jar

# K8s Cluster Mode
spark-submit \
--master k8s://https://<k8s-api-server> \
--deploy-mode cluster \
--conf spark.kubernetes.container.image=my-spark:latest \
--conf spark.executor.instances=3 \
local:///opt/spark/my-app.jar

Client Mode 下,Driver 的 stdout/stderr 直接输出到提交终端。Spark UI 在客户端的 4040 端口。

Cluster Mode 下,Driver 在集群内部运行。YARN 模式用 yarn logs -applicationId <id> 查看日志。K8s 模式用 kubectl logs <driver-pod> 查看。

模式提炼

Spark 的集群管理器插件架构体现了关注点分离模式:计算引擎只关心"需要多少资源、Task 发给谁",资源管理只关心"分配哪些容器、监控资源使用"。两者通过 SchedulerBackend 接口解耦。

同样的模式出现在其他系统中:Flink 的 ResourceManager 适配层支持 YARN/K8s/Standalone。数据库的存储引擎插件(如 MySQL 的 InnoDB/MyISAM)把查询处理和存储管理解耦。

工程迁移表

Spark 概念 YARN 概念 K8s 概念 传统部署对应
Driver ApplicationMaster Driver Pod 主进程
Executor Container Executor Pod 工作进程
spark-submit YARN Client kubectl + spark-operator 脚本提交
–num-executors Container 数量 spark.executor.instances 进程数
–executor-memory Container memory Pod memory request -Xmx
Dynamic Allocation YARN 动态 Container Pod autoscaling 进程池伸缩

常见误解

误解一:“Client Mode 的 Driver 运行在集群中”。Client Mode 的 Driver 运行在提交作业的机器上,不在集群中。如果这台机器网络不稳定或关机,整个作业失败。

误解二:“K8s 模式需要特殊版本的 Spark”。Apache Spark 3.1+ 原生支持 K8s,不需要特殊构建。但需要自行构建包含用户代码的 Docker 镜像(Spark 提供 docker-image-tool.sh 脚本辅助构建)。

误解三:“YARN 和 K8s 的性能差异很大”。在计算性能上,YARN 和 K8s 几乎没有差异——计算发生在 Executor JVM 内部,与外层的资源管理器无关。差异主要在启动时间(K8s Pod 调度比 YARN Container 分配稍慢)和运维复杂度。

练习

  1. 在 YARN 上分别以 Client 和 Cluster 模式提交同一个作业。对比两次执行的 Spark UI 访问方式和 Driver 日志位置。

  2. 在 K8s 上用 spark-submit 提交作业后,用 kubectl get pods 观察 Driver Pod 和 Executor Pod 的创建和销毁过程。记录从提交到第一个 Task 执行的时间。

  3. 配置 --executor-memory 分别为 1g 和 4g,提交同一个包含大 Shuffle 的作业。在 Spark UI 的 Executors 页面对比 GC Time 和 Shuffle Spill 指标。

系列导航

序号 主题 状态
00 导读:为什么 Spark 的核心是一张 DAG 已完成
01 RDD:弹性分布式数据集的五大属性 已完成
02 宽依赖与窄依赖:Stage 是怎样划分出来的 已完成
03 DAG Scheduler 与 Task Scheduler:从逻辑计划到物理执行 已完成
04 Shuffle 机制:数据跨分区交换的代价与优化 已完成
05 内存管理与 Tungsten:堆外内存、序列化与代码生成 已完成
06 存储体系:Block Manager、广播变量与累加器 已完成
07 容错机制:Lineage、Checkpoint 与推测执行 已完成
08 Spark SQL 与 Catalyst:从 SQL 文本到物理算子树 已完成
09 DataFrame 与 Dataset:类型安全与性能的折中 已完成
10 Adaptive Query Execution:运行时改写执行计划 已完成
11 数据源 API 与谓词下推:让存储层帮忙过滤 已完成
12 Structured Streaming:把流当成无界表 已完成
13 水位线与状态管理:流处理的时间语义 已完成
14 Spark on YARN / K8s:资源管理与部署模式 本篇
15 动态资源分配与作业调度:多租户集群的资源博弈 下一篇
16 性能诊断:Spark UI、Event Log 与调优思路
17 Spark 与 Flink:两种计算引擎的设计选择

参考资料