一.背景
在大数据处理场景规模化、复杂化的当下,企业对 Spark 作业的 “标准化发布、弹性调度、资源管控、全生命周期管理” 提出了更高要求。Spark 作为大数据批处理、流处理的核心引擎,广泛应用于数据仓库构建、离线分析、实时计算等场景,但传统的 Spark 作业部署方式(如 YARN 集群模式、本地提交)已难以适配云原生时代的运维诉求,痛点日益突出:
传统部署模式的核心问题集中在三方面:一是作业发布碎片化,多依赖手工脚本、运维平台定制化工具,不同环境(测试 / 生产)的配置不一致,易引发部署故障,且缺乏统一的发布入口和标准化流程;二是资源调度效率低,YARN 等传统调度系统与云原生基础设施(K8s)割裂,无法充分利用 K8s 的容器化弹性扩缩容、资源隔离、多租户管理能力,硬件资源利用率低,且难以应对突发的计算资源需求;三是作业运维成本高,作业提交后缺乏统一的状态监控、日志归集、故障自愈能力,跨环境部署需重复适配底层资源,运维复杂度随集群规模增长呈指数级上升。
与此同时,K8s 已成为云原生时代的核心容器编排平台,凭借容器化部署、弹性调度、资源隔离、高可用等特性,成为企业统一的资源管理底座。将 Spark 作业迁移至 K8s 集群运行,能充分发挥云原生架构的优势,但原生的spark-submit命令行提交方式存在配置繁琐、集成性差、难以嵌入自动化流程(如 CI/CD)的问题,无法满足企业级标准化、自动化发布的需求。
SparkLauncher 作为 Spark 官方提供的 Java/Scala 编程式作业提交工具,恰好弥补了这一短板:它通过 API 化的方式封装 Spark 作业提交逻辑,支持灵活配置 K8s 集群的运行参数(如容器镜像、资源配额、命名空间、PVC 挂载等),可无缝集成至企业内部的运维平台、CI/CD 流水线,实现 Spark 作业的标准化、自动化发布。在此背景下,“SparkLauncher + K8s” 的组合成为解决传统 Spark 作业部署痛点的最优解,各环节形成互补:
- SparkLauncher 作为标准化发布入口:通过编程接口统一封装作业提交参数(如主类、jar 包路径、资源配置、K8s 专属配置),替代手工
spark-submit命令,避免配置遗漏或格式错误,同时支持与企业现有运维系统(如监控平台、权限系统)集成,实现发布流程的可管控、可审计; - K8s 作为资源调度底座:为 Spark 作业提供容器化运行环境,支持按作业需求弹性分配 CPU / 内存资源,通过 Pod 副本机制保障作业高可用,利用 K8s 的命名空间实现多租户隔离,解决传统集群资源争抢、利用率低的问题;
- 编程式发布适配自动化场景:SparkLauncher 的 API 特性使其可嵌入 Java/Scala 编写的运维平台、自动化脚本,轻松对接 CI/CD 流水线(如 Jenkins、GitLab CI),实现 “代码提交 - 编译打包 - 作业发布 - 状态校验” 的全流程自动化,大幅降低人工干预成本。
采用 SparkLauncher 发布 K8s 作业,本质是构建 “云原生 + 标准化” 的 Spark 作业发布体系:既充分利用 K8s 的弹性调度、资源隔离能力,提升大数据处理的资源利用率和运维灵活性,又通过 SparkLauncher 的编程式接口实现作业发布的标准化、自动化,解决传统部署模式的碎片化、低效率问题。该方案可广泛应用于企业级大数据平台的作业发布、跨环境 Spark 作业迁移、云原生大数据集群建设等场景,帮助企业打通 “Spark 计算引擎” 与 “K8s 资源底座” 的衔接壁垒,实现大数据作业的云原生转型,降低运维成本的同时提升资源调度效率,为大规模、高并发的大数据处理场景提供稳定、可扩展的发布支撑。
二.具体实现
1.创建一个java工程,引入依赖
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_2.12</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-yarn_2.12</artifactId>
<version>3.2.2</version>
</dependency>
2.定义SparkLauncher实例
SparkLauncher sparkLauncher = new SparkLauncher()
.setAppName("xxx")
.setMaster("k8s://https://apiproxy.xxx.com:8001/proxy/k8s/xxx")
.setDeployMode("cluster")
.setConf("spark.user", "hadoop");
3.定义参数,并设置
Map<String, Object> baseConfigMap = new HashMap<>();
baseConfigMap.put("spark.kubernetes.namespace", k8s命名空间);
baseConfigMap.put("spark.kubernetes.file.upload.path", "hdfs://xxx/temp");
baseConfigMap.put("spark.kubernetes.container.image.pullSecrets", "default-secret");
baseConfigMap.put("spark.kubernetes.authenticate.driver.serviceAccountName", "xxx");
baseConfigMap.put("spark.kubernetes.container.image", "spark作业镜像");
baseConfigMap.put("spark.kubernetes.driver.podTemplateFile","hdfs://xxx/driver.yml");
baseConfigMap.put("spark.kubernetes.executor.podTemplateFile","hdfs://xxx/executor.yml");
baseConfigMap.put("spark.kubernetes.authenticate.caCertFile","/var/run/secrets/kubernetes.io/serviceaccount/ca.crt");
baseConfigMap.put("spark.kubernetes.authenticate.oauthTokenFile","/var/run/secrets/kubernetes.io/serviceaccount/token");
baseConfigMap.put("spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-local-dir-1.options.storageClass", "csi-disk-ssd-topology");
baseConfigMap.put("spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-local-dir-1.mount.path", "/var/data");
baseConfigMap.put("spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-local-dir-1.options.sizeLimit","30Gi");
baseConfigMap.put("spark.kubernetes.driver.volumes.persistentVolumeClaim.spark-local-dir-1.options.claimName","OnDemand");
baseConfigMap.put("spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-local-dir-1.options.storageClass", "csi-disk-ssd-topology");
baseConfigMap.put("spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-local-dir-1.mount.path", "/var/data");
baseConfigMap.put("spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-local-dir-1.options.sizeLimit","30Gi");
baseConfigMap.put("spark.kubernetes.executor.volumes.persistentVolumeClaim.spark-local-dir-1.options.claimName","OnDemand");
baseConfigMap.put("spark.kubernetes.driver.pod.name", "pod名称");
baseConfigMap.put("spark.kubernetes.driver.limit.cores", 2);
baseConfigMap.put("spark.kubernetes.driver.request.cores", 2);
baseConfigMap.put("spark.driver.memory", "4g");
baseConfigMap.put("spark.executor.memory", "4g");
baseConfigMap.put("spark.kubernetes.executor.limit.cores", 2);
baseConfigMap.put("spark.kubernetes.executor.request.cores", 2);
baseConfigMap.put("spark.executor.instances", 5);
for (Map.Entry<String, Object> baseConfig : baseConfigMap.entrySet()) {
sparkLauncher.setConf(baseConfig.getKey(), String.valueOf(baseConfig.getValue()));
}
4.设置作业的信息
sparkLauncher.setMainClass("镜像里面jar的主类")
.setAppResource("镜像里面jar的地址")
.addAppArgs("镜像里面jar的主类的参数");
5.发布作业
sparkLauncher.redirectError(new File("/xxx/sparkLauncher.log"))
// 这里将标准错误输出重定向到标准输出
.startApplication(new SparkAppHandle.Listener() {
// 状态监听没什么用,主要使用SparkAppHandle 的 stop/kill/getState 等操作
@Override
public void stateChanged(SparkAppHandle sparkAppHandle) {
logger.info("info ----- "+ JSON.toJSONString(sparkAppHandle));
}
@Override
public void infoChanged(SparkAppHandle sparkAppHandle) {
logger.info("info ----- "+JSON.toJSONString(sparkAppHandle));
}
});
7099

被折叠的 条评论
为什么被折叠?



