一、1. 背景
针对许多在线业务集群 CPU 利用率偏低或存在显著的潮汐特性,可以利用 TKE 集群混部技术,实现 Spark 离线任务与在线任务的混合部署。在不影响在线业务 QoS 的前提下,进一步提升节点资源的利用率,从而有效降低整体成本。
具体来说,Spark 首先通过客户端构建 driver pod 对象,并向 Kubernetes 的 apiserver 发送请求以创建 driver pod。Spark 的 driver 进程运行在 driver pod 中。driver 启动后,会在内部构建并创建这些 executor pod,同时通过 watch 和 list 等机制持续监听各 executor pod 的状态。任务执行结束后,executor pod 会被自动清理,而 driver pod 则会以 completed 状态继续保留。
二、2. 环境准备
参考文档 TKE 集群混部原理与部署介绍,准备 TKE 集群,确保集群包含可用的原生节点,并正确安装 craned 和 QoSAgent 组件。请确保 QoSAgent 组件已勾选 “CPU 使用优先级”、 “CPU 超线程隔离”、“网络 QoS 增强”三项能力。
四、3.1 安装 Spark 环境
在准备好的 TKE 集群的一台节点上,或任一可访问集群 ApiServer 的 CVM 上部署 Spark 环境:
mkdir /usr/local/service/cd /usr/local/service/wget https://archive.apache.org/dist/spark/spark-3.3.2/spark-3.3.2-bin-hadoop3.tgztar -xzvf spark-3.3.2-bin-hadoop3.tgzmv spark-3.3.2-bin-hadoop3 spark
在集群上部署 Spark 任务使用的 ServiceAccount:
首先创建名为 “workspark” 的 namespace。这里我们假设 Spark 相关的 pod 运行在该 namespace 中。
apiVersion: v1kind: ServiceAccountmetadata: name: spark namespace: workspark—apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata: name: spark-cluster-admin-binding-worksparksubjects:- kind: ServiceAccount name: spark namespace: worksparkroleRef: kind: ClusterRole name: cluster-admin apiGroup: rbac.authorization.k8s.io
五、3.2 准备 podTemplate 文件
假设我们需要为 driver pod 申请2核2G的资源,创建 driver-pod-template.yaml:
apiVersion: v1kind: Podspec: containers: – name: spark-kubernetes-driver resources: limits: gocrane.io/cpu: "2" gocrane.io/memory: 2Gi requests: gocrane.io/cpu: "2" gocrane.io/memory: 2Gi
注意:这里需要使用 gocrane.io/cpu 和 gocrane.io/memory 扩展资源,K8s 才会把相关资源只调度到有离线资源可用的节点上。
假设我们需要为 executor pod 申请2核2G的资源,创建 executor-pod-template.yaml:
apiVersion: v1kind: Podspec: containers: – name: spark-kubernetes-executor resources: limits: gocrane.io/cpu: "2" gocrane.io/memory: 2Gi requests: gocrane.io/cpu: "2" gocrane.io/memory: 2Gi
注意:这里需要使用 gocrane.io/cpu 和 gocrane.io/memory 扩展资源,K8s 才会把相关资源只调度到有离线资源可用的节点上。
六、3.3 准备 PodQOS 规则
apiVersion: ensurance.crane.io/v1alpha1kind: PodQOSmetadata: name: podqos-sparkspec: labelSelector: matchExpressions: – key: spark-role operator: In values: – driver – executor resourceQOS: cpuQOS: cpuPriority: 7
注意:说明:
注意:这里通过匹配 driver pod 和 executor pod 的 label 来区分 spark-submit 提交后创建的 spark pod。
七、3.4 准备 spark-submit 命令
可以通过以下方式查看 Apiserver 的 URL:
$ kubectl cluster-infoKubernetes control plane is running at https://10.2.16.107……
在上述情况下,我们可以通过将 –master k8s://http://10.2.16.107 作为参数传递给 spark-submit 来将 spark 任务提交至该 Kubernetes 集群。
通过 spark-submit 提交以下任务:
/usr/local/service/spark/bin/spark-submit \\ –master k8s://https://10.2.16.107 \\ –deploy-mode cluster \\ –name spark-pi \\ –class org.apache.spark.examples.SparkPi \\ –conf spark.executor.instances=2 \\ –conf spark.kubernetes.namespace=workspark \\ –conf spark.kubernetes.driver.container.image=ccr.ccs.tencentyun.com/use-test/spark:v3.3.2 \\ –conf spark.kubernetes.executor.container.image=ccr.ccs.tencentyun.com/use-test/spark:v3.3.2 \\ –conf spark.kubernetes.authenticate.driver.serviceAccountName=spark \\ –conf spark.kubernetes.driver.podTemplateFile=./driver-pod-template.yaml \\ –conf spark.kubernetes.executor.podTemplateFile=./executor-pod-template.yaml \\ –conf spark.kubernetes.driver.limit.cores=2 \\ –conf spark.kubernetes.driver.request.cores=0 \\ –conf spark.kubernetes.executor.limit.cores=2 \\ –conf spark.kubernetes.executor.request.cores=0 \\ –conf spark.driver.memory=2g \\ –conf spark.executor.memory=2g \\ local:///opt/spark/examples/jars/spark-examples_2.12-3.3.2.jar 20000
命令参数说明如下:
八、3.4.1 基础部分
1. 通过 –conf spark.executor.instances 指定 executor pod 的数量。
2. 通过 –conf spark.kubernetes.namespace 指定的 namespace 应与创建 ServiceAccount 时一致,此处使用 “workspark”。
九、3.4.2 镜像部分
通过 –conf spark.kubernetes.driver.container.image 和 –conf spark.kubernetes.executor.container.image 指定自己编译的要运行的镜像。
十、3.4.3 Pod 模板部分
我们需要让 spark pod 使用集群的离线资源。因为 gocrane.io/cpu 和 gocrane.io/memory 属于扩展资源,spark-submit 要求这类扩展资源放在 pod 模板文件中。请根据实际资源需求,变更相关 pod 模板中资源的值。
十一、3.4.4 resources 要求说明
1. CPU 资源部分:
(1)通过 –conf spark.kubernetes.{driver,executor}.request.cores 指定为0。
(2)通过 –conf spark.kubernetes.{driver,executor}.limit.cores 指定为规划的 limit 的值。
2. 内存资源部分:
需要主动通过 –conf spark.driver.memory 指定内存 limit 要求的大小。
十二、3.5 提交命令并查看 pod 状态
1. 查看运行的 spark pod 的 yaml,可以确认 driver pod 和 executor pod 是否按预期使用了 “gocrane.io/cpu” 和 “gocrane.io/memory” 的资源。
2. 查看 spark pod 是否包含 key 为 “gocrane.io/cpu-qos” 和 “gocrane.io/qos” 的 Annotation。当 pod 有了这些特定的 Annotation,代表已经正确使用了对应的离线资源。
十四、3.6.1 部署 Volcano
参考 Volcano 页面,可以通过如下方式对 Volcano 进行安装。
helm repo add volcano-sh https://volcano-sh.github.io/helm-chartshelm repo updatehelm install volcano volcano-sh/volcano -n volcano-system –create-namespace
注意:说明:
注意:安装完成后通过 kubectl get all -n volcano-system 确认 volcano-controllers、volcano-scheduler 等 pod 已经处于 Running 状态。
十五、3.6.2 准备支持 Volcano 的 Spark 版本
因为需要通过 spark-submit 使用 Volcano 相关高级特性,需要参阅 Apache Spark 文档中 https://dlcdn.apache.org/spark/docs/3.3.2/running-on-kubernetes.html#build 小节,重新编译部署的 spark 版本:
注意:说明:
注意:为通过实验验证功能,我们也可以通过将 spark-kubernetes_2.12-3.3.2.jar、volcano-client-5.12.2.jar、volcano-model-v1beta1-5.12.2.jar 三个 jar 包拷贝进我们对应 jars 目录(如 /usr/local/service/spark/jars)的方法,来满足这里 Spark 版本需求。
十六、3.6.3 准备支持 Volcano 的 Spark 镜像
因为需要使用 Volcano 高级特性,Spark 镜像需包含相关 jar 包。
这里作为实验,我们可以通过以下 Dockerfile.patch 来构建我们测试使用的支持 Volcano 的镜像:
FROM ccr.ccs.tencentyun.com/use-test/spark:v3.3.2COPY spark-kubernetes_2.12-3.3.2.jar /opt/spark/jars/COPY volcano-client-5.12.2.jar /opt/spark/jars/COPY volcano-model-v1beta1-5.12.2.jar /opt/spark/jars/
十七、3.6.4 准备 Volcano 队列
按照实际情况,创建我们要使用的 Volcano 队列。这里假设我们的 Volcano 队列名字为 “sparkqueue”。
apiVersion: scheduling.volcano.sh/v1beta1kind: Queuemetadata: name: sparkqueuespec: weight: 1 reclaimable: false capability: gocrane.io/cpu: 20 gocrane.io/memory: 100Gi
十八、3.6.5 准备 podGroup 模板文件
apiVersion: scheduling.volcano.sh/v1beta1kind: PodGroupspec: # Specify minMember to 1 to make a driver pod minMember: 1 # Specify minResources to support resource reservation (the driver pod resource and executors pod resource should be considered) # It is useful for ensource the available resources meet the minimum requirements of the Spark job and avoiding the # situation where drivers are scheduled, and then they are unable to schedule sufficient executors to progress. minResources: gocrane.io/cpu: "6" gocrane.io/memory: "6Gi" # Specify the priority, help users to specify job priority in the queue during scheduling. priorityClassName: system-node-critical # Specify the queue, indicates the resource queue which the job should be submitted to queue: sparkqueue
注意:说明:
注意:1. 如注释所要求:minMember 这里需要设置为 1,请勿修改。
注意:2. minResources 中 gocrane.io/cpu 和 gocrane.io/memory 可以按实际情况进行配置。这里指定为 driver pod 和 executor pod 对资源需求的和。
注意:3. 通过 queue 可以指定使用的 Volcano 队列。这里我们指定使用我们创建的名字为 “sparkqueue” 的队列。
十九、3.6.6 准备 spark-submit 命令
/usr/local/service/spark/bin/spark-submit \\ –master k8s://https://10.2.16.107 \\ –deploy-mode cluster \\ –name spark-pi \\ –class org.apache.spark.examples.SparkPi \\ –conf spark.executor.instances=2 \\ –conf spark.kubernetes.namespace=workspark \\ –conf spark.kubernetes.driver.container.image=ccr.ccs.tencentyun.com/use-test/spark:v3.3.2-volcano-patch3 \\ –conf spark.kubernetes.executor.container.image=ccr.ccs.tencentyun.com/use-test/spark:v3.3.2-volcano-patch3 \\ –conf spark.kubernetes.authenticate.driver.serviceAccountName=spark \\ –conf spark.kubernetes.driver.podTemplateFile=./driver-pod-template.yaml \\ –conf spark.kubernetes.executor.podTemplateFile=./executor-pod-template.yaml \\ –conf spark.kubernetes.driver.limit.cores=2 \\ –conf spark.kubernetes.driver.request.cores=0 \\ –conf spark.kubernetes.executor.limit.cores=2 \\ –conf spark.kubernetes.executor.request.cores=0 \\ –conf spark.driver.memory=2g \\ –conf spark.executor.memory=2g \\ –conf spark.kubernetes.scheduler.name=volcano \\ –conf spark.kubernetes.driver.pod.featureSteps=org.apache.spark.deploy.k8s.features.VolcanoFeatureStep \\ –conf spark.kubernetes.executor.pod.featureSteps=org.apache.spark.deploy.k8s.features.VolcanoFeatureStep \\ –conf spark.kubernetes.scheduler.volcano.podGroupTemplateFile=./podgroup-template-gocrane.yaml \\ local:///opt/spark/examples/jars/spark-examples_2.12-3.3.2.jar 20000
注意:说明:
注意:这里增加了如下几行配置:
注意:–conf spark.kubernetes.scheduler.name=volcano \\
注意:–conf spark.kubernetes.driver.pod.featureSteps=org.apache.spark.deploy.k8s.features.VolcanoFeatureStep \\
注意:–conf spark.kubernetes.executor.pod.featureSteps=org.apache.spark.deploy.k8s.features.VolcanoFeatureStep \\
注意:–conf spark.kubernetes.scheduler.volcano.podGroupTemplateFile=./podgroup-template-gocrane.yaml
注意:其中这里最后一行配置需要指定上文中准备好的 podGroup 模板文件的路径。
注意:另外,需要注意的是,这里我们需要使用新构建的支持 Volcano 的镜像。
20、3.6.7 提交命令并查看 pod 状态
提交命令,可以看到首先会创建一个 driver pod,然后会创建出来两个 executor pod。我们依次验证相关状态,可以看到:
1. 在driver pod 和 executor pod 的 yaml 中会指定 schedulerName: volcano。表明 spark pod 已经在使用 Volcano 调度器了。
2. 通过 kubectl describe 命令查看 driver pod 和 executor pod,可以看到 “Normal Scheduled 29m volcano Successfully assigned workspark/xxxx-xxxxx to xxxxNode” 类似的 Event(事件)。
3. 在 driver pod 和 executor pod 的 Annotation中,都可以看到 key 为 “scheduling.k8s.io/group-name” 的行,类似如下:
scheduling.k8s.io/group-name: spark-d41b280c285741b9b6d3a2d3f3cf6eec-podgroup
这表明 driver pod 和 executor pod 共同使用的是同一个 podGroup。查看该 podGroup,类似如下,相关参数正是我们 podGroup 模板指定的:
apiVersion: scheduling.volcano.sh/v1beta1kind: PodGroupmetadata: name: spark-0ed23d5712a04c76ab15935d7fd6c49b-podgroup namespace: worksparkspec: minMember: 1 minResources: gocrane.io/cpu: "6" gocrane.io/memory: 6Gi priorityClassName: system-node-critical queue: sparkqueuestatus: conditions: – lastTransitionTime: "2025-11-04T08:53:06Z" reason: tasks in gang are ready to be scheduled status: "True" transitionID: f8e70311-3b30-4a01-8b00-4c0de38fced4 type: Scheduled phase: Completed succeeded: 1
在 driver pod 和 executor pod 运行期间,查看 “sparkqueue” 这个Volcano 队列的状态,表明我们的 spark pod 已经占用了本队列的部分资源:
apiVersion: scheduling.volcano.sh/v1beta1kind: Queuemetadata: annotations: name: sparkqueue resourceVersion: "1305744284" uid: b88f3034-0e5f-487d-9ad1-a3e9b9781f66spec: capability: gocrane.io/cpu: 10 gocrane.io/memory: 20Gi parent: root reclaimable: false weight: 4status: allocated: gocrane.io/cpu: "6" gocrane.io/memory: "6442450944" memory: 7296Mi pods: "3" tke.cloud.tencent.com/eni-ip: "3" reservation: {} state: Open
21、4. 使用 Spark on K8s Operator
Spark Operator 是专为在 Kubernetes 集群中运行 Spark 工作负载而设计,旨在自动化管理 Spark 作业的生命周期。通过 SparkApplication 等 CRD 资源,你可以灵活提交和管理 Spark 作业。利用 Kubernetes 的自动扩展、健康检查和资源管理等特性,Spark Operator 可以更有效地监控和优化 Spark 作业的运行。
相比 spark-submit 的使用优势:
·简化管理:通过 Kubernetes 的声明式作业配置,自动化部署 Spark 作业并管理作业的生命周期。
·弹性资源供给:利用原生节点池等弹性资源,可在业务高峰期快速获得大量弹性资源,平衡性能和成本。
适用场景:
·数据分析:数据科学家可以利用 Spark 进行交互式数据分析和数据清洗等。
·批量数据计算:运行定时批处理作业,处理大规模数据集。
·实时数据处理:Spark Streaming 库提供了对实时数据进行流式处理的能力。
22、4.1 部署 Spark Operator 环境
依次执行如下命令,部署 Spark Operator 运行相关资源。
helm repo add spark-operator https://kubeflow.github.io/spark-operatorhelm repo updatehelm install spark-operator spark-operator/spark-operator –version 2.3.0 –namespace spark-operator –create-namespace –set controller.batchScheduler.enable=true
部署后,检查确认 spark-operator-controller、spark-operator-webhook pod 已经处于 Running 状态。
注意:这里有可能遇到镜像拉取的问题,原始镜像地址为 ghcr.io/kubeflow/spark-operator/controller:2.3.0,如果该地址不能访问,可以变更为 ccr.ccs.tencentyun.com/use-test/spark-operator:controller-2.3.0 用于测试。
23、4.2 准备 SparkApplication 任务
apiVersion: "sparkoperator.k8s.io/v1beta2"kind: SparkApplicationmetadata: name: spark-pi namespace: defaultspec: arguments: – "10000" type: Scala mode: cluster image: "apache/spark:v3.3.2" imagePullPolicy: Always mainClass: org.apache.spark.examples.SparkPi mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.12-3.3.2.jar" sparkVersion: "3.3.2" restartPolicy: type: Never volumes: – name: "test-volume" hostPath: path: "/tmp" type: Directory driver: memory: "2g" template: spec: containers: – name: spark-kubernetes-driver resources: limits: gocrane.io/cpu: "2" gocrane.io/memory: "2Gi" requests: gocrane.io/cpu: "2" gocrane.io/memory: "2Gi" labels: version: 3.3.2 serviceAccount: spark volumeMounts: – name: "test-volume" mountPath: "/tmp" executor: instances: 2 memory: "2g" template: spec: containers: – name: spark-kubernetes-executor resources: limits: gocrane.io/cpu: "2" gocrane.io/memory: "2Gi" requests: gocrane.io/cpu: "2" gocrane.io/memory: "2Gi" labels: version: 3.3.2 volumeMounts: – name: "test-volume" mountPath: "/tmp"
部署该应用后:
1. 查看 spark pod 的资源已经按预期使用了 “gocrane.io/cpu” 和 “gocrane.io/memory” 相关扩展资源。
2. 查看 driver pod 和 executor pod 是否有 key 为 “gocrane.io/cpu-qos” 和 “gocrane.io/qos” 的Annotation,如果有这些 Annotation,代表已经正确使用了相关的离线资源。
25、4.3.1 部署 Volcano
参考 章节 3.6.1 的方式部署 Volcano。
helm repo add spark-operator https://kubeflow.github.io/spark-operatorhelm install my-release spark-operator/spark-operator –version 2.3.0 \\ –namespace spark-operator \\ –create-namespace \\ –set webhook.enable=true \\ –set controller.batchScheduler.enable=true
注意:这里需要依赖 Volcano 打开 batchScheduler 特性:controller.batchScheduler.enable=true
26、4.3.2 准备 Volcano 队列
参考 章节 3.6.4,创建名为“sparkqueue”的 Volcano 队列。
27、4.3.3 准备使用 Volcano 的 SparkApplication 任务
apiVersion: "sparkoperator.k8s.io/v1beta2"kind: SparkApplicationmetadata: name: spark-pi namespace: defaultspec: arguments: – "10000" type: Scala mode: cluster image: "apache/spark:v3.3.2" imagePullPolicy: Always mainClass: org.apache.spark.examples.SparkPi mainApplicationFile: "local:///opt/spark/examples/jars/spark-examples_2.12-3.3.2.jar" sparkVersion: "3.3.2" batchScheduler: volcano # 新增 batchSchedulerOptions: # 新增 queue: "sparkqueue" restartPolicy: type: Never volumes: – name: "test-volume" hostPath: path: "/tmp" type: Directory driver: memory: "2g" template: spec: containers: – name: spark-kubernetes-driver resources: limits: gocrane.io/cpu: "2" gocrane.io/memory: "2Gi" requests: gocrane.io/cpu: "2" gocrane.io/memory: "2Gi" labels: version: 3.3.2 serviceAccount: spark volumeMounts: – name: "test-volume" mountPath: "/tmp" executor: instances: 2 memory: "2g" template: spec: containers: – name: spark-kubernetes-executor resources: limits: gocrane.io/cpu: "2" gocrane.io/memory: "2Gi" requests: gocrane.io/cpu: "2" gocrane.io/memory: "2Gi" labels: version: 3.3.2 volumeMounts: – name: "test-volume" mountPath: "/tmp"
注意:说明:
注意:其中以下部分为新增:
注意:1. batchScheduler: Volcano 指定了 driver pod 和 executor pod 使用 Volcano 调度器。
注意:2. batchSchedulerOptions: queue: "sparkqueue" 部分,指定了使用名字为 “sparkqueue” 的 Volcano queue。
28、4.3.4 部署 SparkApplication 任务并查看 pod 状态
1. 在 driver pod 和 executor pod 的 yaml 可发现 schedulerName 已经设置为 Volcano,表明已经在使用 Volcano 调度器了。
2. 通过 kubectl describe 命令查看 driver pod 和 executor pod,可以看到 “Normal Scheduled 10m Volcano Successfully assigned workspark/xxxx-xxxxx to xxxxNode” 类似的 Event(事件)。
3. 在 driver pod 和 executor pod 的 Annotation中,都可以看到 key 为 “scheduling.k8s.io/group-name” 的行:
scheduling.k8s.io/group-name: spark-spark-pi-pg
这表明 driver pod 和 executor pod 已经共同使用了同一个 podGroup。
查看这个 podGroup,类似如下,相关参数正是我们 podGroup 模板指定的:
apiVersion: scheduling.volcano.sh/v1beta1kind: PodGroupmetadata: name: spark-spark-pi-pg namespace: defaultspec: minMember: 1 minResources: {} queue: sparkqueuestatus: conditions: – lastTransitionTime: "2025-11-04T10:07:20Z" reason: tasks in gang are ready to be scheduled status: "True" transitionID: 28ba9ce7-c84c-438e-a71d-a02ef3459011 type: Scheduled phase: Running running: 3
4. 在 driver pod 和 executor pod 运行期间,查看 “sparkqueue” 这个 Volcano 队列的状态,表明我们的 spark pod 已经占用了本队列的部分资源:
apiVersion: scheduling.volcano.sh/v1beta1kind: Queuemetadata: name: sparkqueuespec: capability: gocrane.io/cpu: 10 gocrane.io/memory: 20Gi parent: root reclaimable: false weight: 4status: allocated: gocrane.io/cpu: "6" gocrane.io/memory: "6442450944" memory: 7296Mi pods: "3" tke.cloud.tencent.com/eni-ip: "3" reservation: {} state: Open