如何在K8s集群的Java应用中高效配置并提交Spark作业?
基于Rancher Desktop的本地Kubernetes集群,已通过Bitnami Helm Chart部署Spark集群(含1个Master节点、2个Worker节点)。同一集群内还部署了Quarkus Java应用,该应用暴露REST GET接口,调用此接口时需触发Spark作业提交——作业提交性能是核心需求(后续需频繁提交大量作业)。
使用Spark核心包的SparkLauncher类提交作业时,需配置sparkHome和master属性,存在以下两个疑问:
- 此场景下如何配置
sparkHome和master以实现最优作业提交性能? - 大小约2GB的自定义Spark作业JAR包应存储在何处,才能让Spark集群快速访问?
当前实现方式:将Spark的bin和jars目录复制到Java应用的Resources目录,代码如下:
new SparkLauncher() .setSparkHome("src/main/resources/spark") .setDeployMode("cluster") .setMaster("spark://my-spark.default.svc.cluster.local:7077") .setAppResource("src/main/resources/spark-examples_2.13-3.5.1.jar") .setMainClass("org.apache.spark.examples.SparkPi") .startApplication(this);
本地在Intellij IDEA中运行正常,但部署到K8s集群后出现错误:
"java.io.IOException","message":"Cannot run program "src/main/resources/spark/bin/spark-submit": error=2, No such file or directory"
先解决当前部署报错问题
你现在的问题很明确:K8s容器里根本不存在src/main/resources/spark这个路径——本地开发时的资源目录结构在构建容器镜像后会被重构,不会保留src/main/resources这个原始路径。而且把Spark的bin和jars打包进Quarkus镜像完全是冗余操作,既增大镜像体积,又没必要。
正确思路是:不要把Spark二进制文件打包进Quarkus应用镜像,而是让Quarkus容器直接使用集群内的Spark客户端环境,或者改用更适合K8s的作业提交方式。
一、sparkHome与master的最优配置(兼顾性能与稳定性)
1. sparkHome的正确配置
放弃在Quarkus里打包Spark,改用以下两种方式之一:
- 基于Spark客户端镜像构建Quarkus镜像:用Bitnami提供的Spark客户端镜像(比如
bitnami/spark:3.5.1)作为Quarkus镜像的基础镜像,这样容器里自带完整的Spark环境,sparkHome直接设为镜像默认的安装路径/opt/bitnami/spark即可。这种方式最稳定,也能保证spark-submit命令正常执行。 - 通过K8s存储挂载共享Spark客户端:创建一个存储卷存放Spark客户端文件,然后让Quarkus Pod挂载这个卷。但这种方式维护成本高,不如第一种省心。
2. master的配置(影响作业提交性能的核心)
- 当前Spark Standalone模式:你用的
spark://my-spark.default.svc.cluster.local:7077是正确的,这是集群内Spark Master的Service地址,集群内访问延迟最低,适合当前部署架构。 - 追求极致批量作业性能?切换到Spark on K8s模式:如果后续要提交大量作业,建议把Spark集群从Standalone模式切换为Spark on Kubernetes模式(直接用K8s作为资源管理器),此时
master设为k8s://https://kubernetes.default.svc.cluster.local。这种模式下,Spark作业由K8s原生调度,作业启动速度更快、资源隔离更彻底,批量作业的吞吐量会显著提升。
二、2GB自定义JAR包的存储方案(让Spark Worker快速访问)
2GB的大JAR绝对不能放在Quarkus应用里,否则每次提交作业都要从Quarkus容器传输到Spark Master,严重拖慢提交速度。推荐以下三种方案:
- 方案1:集群共享PersistentVolume(PV):创建一个可多Pod挂载的PV,把JAR包上传到PV中。提交作业时
setAppResource设为file:///挂载路径/your-custom.jar,同时配置Spark Worker节点挂载同一个PV。Worker可以直接读取本地文件,速度最快,适合JAR包频繁更新的场景。 - 方案2:集群内HTTP文件服务器:在K8s里部署一个简单的Nginx服务,把JAR包放在Nginx的静态文件目录。提交作业时
setAppResource设为http://nginx-service.default.svc.cluster.local/jars/your-custom.jar,Spark Worker通过集群内Service地址下载,延迟低,无需挂载存储,维护简单。 - 方案3:内嵌到Spark Worker镜像:把JAR包打包进自定义的Spark Worker镜像,提交作业时
setAppResource设为local:///镜像内的JAR路径。这种方式Worker启动时直接读取本地文件,速度最快,但JAR包更新需要重新构建镜像,适合JAR包不常变动的场景。
内容的提问来源于stack exchange,提问作者PowerfullDeveloper

