使用SparkLauncher发布k8s作业

一.背景

        在大数据处理场景规模化、复杂化的当下,企业对 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 作业部署痛点的最优解,各环节形成互补:

  1. SparkLauncher 作为标准化发布入口:通过编程接口统一封装作业提交参数(如主类、jar 包路径、资源配置、K8s 专属配置),替代手工spark-submit命令,避免配置遗漏或格式错误,同时支持与企业现有运维系统(如监控平台、权限系统)集成,实现发布流程的可管控、可审计;
  2. K8s 作为资源调度底座:为 Spark 作业提供容器化运行环境,支持按作业需求弹性分配 CPU / 内存资源,通过 Pod 副本机制保障作业高可用,利用 K8s 的命名空间实现多租户隔离,解决传统集群资源争抢、利用率低的问题;
  3. 编程式发布适配自动化场景: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));
                        }

});

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路边草随风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值