Spark on Kubernetes
Spark on Kubernetes 是 Apache Spark 支持的原生集群部署模式(自 Spark 2.3 引入),允许 Spark 应用程序(批处理、流处理、机器学习)直接在 Kubernetes 集群上运行,利用 Kubernetes 强大的容器编排、资源调度和基础设施管理能力。以下是其部署架构和关键细节的详解:
核心架构与工作原理
-
提交 Spark 应用 (
spark-submit
):- 用户使用
spark-submit
命令(或通过 API)提交 Spark 应用。 - 指定
--master k8s://<api-server-url>
(如k8s://https://<k8s-apiserver>:6443
). - 提供应用 JAR、主类、配置(Executor 数量、内存、CPU 等)、Driver/Executor 的 Docker 镜像。
- 用户使用
-
创建 Driver Pod:
spark-submit
直接与 Kubernetes API Server 通信。- Kubernetes API Server 根据提交的参数,在指定 Namespace 中创建一个 Driver Pod。
- 这个 Pod 运行 Spark Driver 程序(包含
SparkContext
)。
-
Driver 管理 Executor:
- Driver Pod 启动后,
SparkContext
初始化。 - Driver 向 Kubernetes API Server 请求创建 Executor Pods。
- 请求基于用户配置(
spark.executor.instances
,spark.executor.memory
,spark.executor.cores
等)。
- Driver Pod 启动后,
-
创建 Executor Pods:
- Kubernetes API Server 接收请求,调度器(Scheduler)根据资源请求、节点亲和性等策略,选择合适的工作节点(Node)。
- Kubelet 在选定的节点上拉取指定的 Executor Docker 镜像并启动 Executor Pod。
- 每个 Executor Pod 运行一个 Spark Executor JVM 进程。
-
应用执行:
- Executor Pods 启动后,主动向 Driver Pod 注册。
- Driver 将 Task 分发给注册的 Executors。
- Executors 执行 Task,与 Driver 通信(汇报状态、获取数据/指令)。
-
应用结束与清理:
- 应用完成后(成功或失败),Driver Pod 的状态会更新。
- Driver Pod 负责清理其创建的 Executor Pods(通过向 Kubernetes API Server 发送删除请求)。
- Driver Pod 本身根据其
restartPolicy
(通常是Never
或OnFailure
)决定是否保留(用于日志查看或调试)。
关键组件详解
spark-submit
: 提交入口。配置 Kubernetes 认证信息(kubeconfig)、API Server URL、镜像、资源等。- Kubernetes API Server: 核心枢纽。接收
spark-submit
请求创建 Driver Pod;接收 Driver Pod 请求创建/删除 Executor Pods。 - Driver Pod:
- 核心管理者: 包含 Spark Driver 程序(
SparkSession
/SparkContext
)。 - 职责:
- 应用初始化。
- 向 Kubernetes 申请 Executor 资源。
- 任务调度与分发。
- 跟踪 Executor 状态和任务执行进度。
- 管理 Executor Pod 的生命周期(创建、删除)。
- 应用完成后的清理工作。
- 配置要点:
- 镜像 (
spark.kubernetes.driver.container.image
):必须包含 Spark 发行版、JDK 和应用程序依赖。 - 资源 (
spark.driver.memory
,spark.driver.cores
,spark.kubernetes.driver.request.cores
,spark.kubernetes.driver.limit.cores
等)。 - 服务账号 (
spark.kubernetes.authenticate.driver.serviceAccountName
):用于 Pod 访问 Kubernetes API 的权限。 restartPolicy
:通常设为Never
或OnFailure
。
- 镜像 (
- 核心管理者: 包含 Spark Driver 程序(
- Executor Pods:
- 任务执行者: 每个 Pod 运行一个 Executor JVM 进程。
- 职责: 执行 Driver 分配的 Task,管理本地数据和 shuffle 数据。
- 配置要点:
- 镜像 (
spark.kubernetes.executor.container.image
):通常与 Driver 镜像相同或兼容。 - 资源 (
spark.executor.memory
,spark.executor.cores
,spark.kubernetes.executor.request.cores
,spark.kubernetes.executor.limit.cores
等)。 - 数量 (
spark.executor.instances
)。 - 本地存储 (
spark.kubernetes.executor.volumes
+.volumeMounts
):用于 shuffle 溢出、临时文件等。 - 重要特性:
- 动态分配 (
spark.dynamicAllocation.enabled=true
): 根据工作负载自动增减 Executor 数量(需配合 External Shuffle Service 或 Kubernetes Shuffle Manager)。 - Pod 亲和性/反亲和性: 优化调度(如将 Executor 分散在不同节点)。
- 动态分配 (
- 镜像 (
- Kubernetes Scheduler: 负责将 Driver Pod 和 Executor Pod 调度到合适的 Worker Node 上运行(基于资源请求、约束、亲和性规则等)。
- Kubelet: 在工作节点上运行,负责拉取镜像、启动/停止容器、管理 Pod 生命周期。
- kube-proxy & CNI Plugin: 提供 Pod 网络通信(Driver 与 Executor 之间、Executor 之间 shuffle 数据传输)。
- (可选) Kubernetes Shuffle Manager: 替代传统的 Spark External Shuffle Service。直接在 Kubernetes 上管理 shuffle 数据的存储和获取(如使用本地卷或分布式存储),是实现动态分配的关键。常见实现:
KubernetesShuffleManager
(Spark 内置实验性功能)。- Apache Celeborn (Incubating): 高性能、云原生的 Shuffle 服务,与 Spark on K8s 集成良好。
部署模式详解
Spark on K8s 主要支持两种部署模式:
-
Cluster 模式 (推荐):
spark-submit
在客户端机器运行,提交后立即返回。- Driver Pod 在 Kubernetes 集群内部创建和运行。
- 所有交互(日志、状态)通过 Kubernetes API (
kubectl logs/describe
) 或 Spark UI (需配置 Ingress/Service) 访问。 - 优点: 资源管理统一,Driver 高可用性更易实现(配合 K8s 机制),适合生产环境。
- 缺点: 调试稍复杂(需访问集群内日志/UI)。
-
Client 模式:
spark-submit
在客户端机器运行,Driver 进程也在该客户端 JVM 中运行。- 仅 Executor Pod 在 Kubernetes 集群中创建。
- 优点: Driver 日志/UI 直接在客户端可见,调试方便。
- 缺点:
- 客户端机器必须能访问 Kubernetes API Server 和 Executor Pods 网络(常受网络策略限制)。
- 客户端机器资源需足够运行 Driver。
- 客户端不稳定会导致整个应用失败。
- 不推荐用于生产环境。
核心优势
- 统一资源管理与调度: 利用 Kubernetes 强大的调度器(支持 bin packing、亲和性/反亲和性、污点容忍、GPU 等),实现 Spark 与其他工作负载(如数据库、Web服务)共享集群资源,提高利用率。
- 简化基础设施: 无需单独维护 Hadoop/YARN 集群。Kubernetes 成为统一的基础设施层。
- 容器化与隔离: 每个 Spark 应用(Driver + Executors)运行在独立 Pod 中,环境隔离性好,依赖管理通过容器镜像解决。
- 弹性伸缩: 结合 Kubernetes HPA 和 Spark 动态分配,实现更精细的资源自动扩缩容。
- 高可用性: 可借助 Kubernetes 机制(如 Pod Disruption Budgets, 健康检查)提升 Driver/Executor 的可用性。
- 云原生集成: 无缝集成 Prometheus(监控)、Fluentd(日志)、Helm(包管理)、Service Mesh 等云原生生态工具。
- 多租户与命名空间: 利用 Kubernetes Namespace 实现资源配额、网络策略隔离,支持多团队/项目共享集群。
- 存储集成: 原生支持 Kubernetes Persistent Volumes (PV) / Persistent Volume Claims (PVC),方便访问 HDFS、S3、NFS、Local PV 等存储。
关键挑战与注意事项
- 镜像构建与管理:
- 需要构建包含 Spark 发行版、JDK、应用代码及依赖的 Docker 镜像。
- 优化镜像大小(分层构建、精简基础镜像)。
- 建立镜像仓库(如 Harbor, Docker Hub, ECR, GCR)管理流程。
- Shuffle 管理:
- 动态分配依赖可靠、高效的 Shuffle 服务。传统 External Shuffle Service 在 K8s 中部署复杂。
- 优先考虑 Kubernetes Shuffle Manager (如 Celeborn) 或确保稳定 ESS 部署。
- 网络性能:
- Pod 间通信(Driver-Executor, Executor-Executor shuffle)依赖 CNI 插件性能。
- 选择高性能 CNI (如 Calico, Cilium) 并优化网络策略。
- 考虑节点本地性(减少跨节点 shuffle)。
- 存储配置:
- 为 Executor 配置
emptyDir
卷用于 shuffle 溢出和临时文件(注意大小限制)。 - 访问外部存储(HDFS, S3)需正确配置卷挂载、Secret (认证信息)、ConfigMap。
- 为 Executor 配置
- 权限控制 (RBAC):
- Driver Pod 需要足够权限(通过 ServiceAccount + Role + RoleBinding)来创建/删除 Executor Pods。
- 遵循最小权限原则。
- 日志与监控:
- 日志: 配置
spark.kubernetes.container.image.pullPolicy
避免缓存旧日志,使用kubectl logs
或集成集群日志方案(EFK/Loki+Promtail+Grafana)集中收集 Pod 日志。设置spark.kubernetes.driver.label.[LabelName]
/.annotation.[AnnotationName]
便于筛选。 - 监控: 暴露 Spark Metrics(通过
spark.metrics.conf
或 JMX Exporter),集成 Prometheus + Grafana 进行可视化。利用 Kubernetes 原生监控(Metrics Server)。
- 日志: 配置
- 资源配额与限制:
- 使用 Kubernetes ResourceQuotas 限制 Namespace 级别的总资源使用。
- 为 Driver/Executor Pod 设置合理的
requests
和limits
(spark.kubernetes.driver/executor.request.cores/memory
,.limit.cores/memory
) 防止单个应用耗尽节点资源。
- Spark 版本与 K8s 版本兼容性: 关注官方文档,确保使用的 Spark 版本与 Kubernetes 集群版本兼容。
- 安全:
- 使用 Secrets 管理敏感信息(密码、密钥)。
- 考虑启用 Pod 安全策略(PSP)或 Pod Security Admission (PSA)。
- 镜像扫描。
部署流程示例 (Cluster 模式)
-
准备 Docker 镜像:
- 基于官方 Spark 镜像或自定义镜像(添加应用 JAR 和依赖)。
- 构建镜像并推送到可访问的镜像仓库。
FROM apache/spark:3.5.0-scala2.12-java17-python3.10-ubuntu COPY target/my-spark-app.jar /opt/spark/work-dir/ # (可选) 添加额外依赖、配置文件
docker build -t myregistry.com/myteam/spark-app:3.5.0 . docker push myregistry.com/myteam/spark-app:3.5.0
-
配置 Kubernetes 权限 (RBAC):
- 创建 ServiceAccount (e.g.,
spark-driver-sa
)。 - 创建 Role 赋予权限(
create
,get
,list
,watch
,delete
pods)。 - 创建 RoleBinding 绑定 ServiceAccount 和 Role。
apiVersion: v1 kind: ServiceAccount metadata: name: spark-driver-sa namespace: spark-apps --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: spark-driver-role namespace: spark-apps rules: - apiGroups: [""] resources: ["pods"] verbs: ["create", "get", "list", "watch", "delete"] - apiGroups: [""] resources: ["configmaps"] verbs: ["create", "get", "delete"] # 可能需要用于某些配置 --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: spark-driver-rb namespace: spark-apps roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: spark-driver-role subjects: - kind: ServiceAccount name: spark-driver-sa namespace: spark-apps
- 创建 ServiceAccount (e.g.,
-
提交 Spark 应用 (
spark-submit
):bin/spark-submit \ --master k8s://https://<k8s-apiserver>:6443 \ --deploy-mode cluster \ --name my-spark-pi \ --class org.apache.spark.examples.SparkPi \ --conf spark.executor.instances=5 \ --conf spark.kubernetes.container.image=myregistry.com/myteam/spark-app:3.5.0 \ --conf spark.kubernetes.namespace=spark-apps \ --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark-driver-sa \ --conf spark.kubernetes.driver.request.cores=1 \ --conf spark.kubernetes.driver.memory=2g \ --conf spark.kubernetes.executor.request.cores=1 \ --conf spark.kubernetes.executor.memory=4g \ --conf spark.dynamicAllocation.enabled=false \ # 简化示例,禁用动态分配 local:///opt/spark/work-dir/my-spark-app.jar # 注意 'local://' 表示镜像内路径
-
监控与管理:
- 查看 Pods:
kubectl get pods -n spark-apps -w
- 查看 Driver 日志:
kubectl logs -f <driver-pod-name> -n spark-apps
- 访问 Spark UI:
- 临时端口转发:
kubectl port-forward <driver-pod-name> 4040:4040 -n spark-apps
,然后访问http://localhost:4040
- 创建 NodePort/LoadBalancer Service + Ingress 暴露 UI(生产环境推荐)。
- 临时端口转发:
- 应用结束: Executor Pods 会被自动清理,Driver Pod 保留(状态
Completed
或Error
)供查日志。
- 查看 Pods:
总结
Spark on Kubernetes 提供了将 Spark 深度集成到现代云原生基础设施的标准方式。它利用 Kubernetes 的容器编排、资源调度和声明式 API 优势,实现了:
- 统一资源池: Spark 与其他工作负载共享 K8s 集群资源。
- 敏捷部署与运维: 容器化封装依赖,K8s 管理生命周期。
- 弹性与效率: 结合 K8s 和 Spark 的动态伸缩能力。
- 云原生集成: 无缝对接监控、日志、存储、网络等云原生生态。
成功部署的关键在于解决 镜像管理、Shuffle 性能、网络配置、RBAC 权限、存储访问和监控日志集成 等挑战。随着 Kubernetes Shuffle Manager(如 Celeborn)的成熟和 Spark 对 K8s 支持度的不断提升,Spark on Kubernetes 已成为生产环境部署 Spark 的重要选择,尤其适用于混合云/多云环境和追求统一技术栈的场景。