Skip to content

AliyunContainerService/benchmark-for-spark

Repository files navigation

Spark on ACK 最佳实践和基准测试

Apache Spark 是一种专门为大规模数据处理而设计的快速且通用的计算引擎,并已经广泛应用于各行各业的大数据处理场景中。

阿里云容器服务 Kubernetes 版(Container Service for Kubernetes,简称容器服务 ACK)提供高性能可伸缩的容器应用管理服务,支持企业级 Kubernetes 容器化应用的生命周期管理。

本文将介绍在容器服务 ACK 集群中运行 Spark 工作负载的最佳实践和基准测试结果。

为什么要在 Kubernetes 上运行 Spark 作业?

Spark 自 2.3 版本开始支持将作业提交至 Kubernetes 集群中,详情参见 [SPARK-18278]。在此之前,Spark 支持的集群管理器只有 Apache Hadoop Yarn、Apache Mesos 以及 Spark 自身实现的一个集群管理器 。

在 Kubernetes 上运行 Spark 工作负载有以下优点:

  • 标准化和可移植性:通过把 Spark 应用及其依赖打包成容器镜像,享受容器的各种优点,很容易的解决 Hadoop 版本不匹配和兼容性问题。还可以给容器镜像打上标签控制版本,这样如果需要测试不同版本的Spark或者依赖项的话,选择对应的版本做到了。
  • 资源按需供给:Spark 可以按需申请资源,例如申请 10 个 Executor,每个 Executor 分配 8 个 CPU core 和 32GB 内存,避免资源浪费。
  • 认证和鉴权:由于 Driver pod 负责创建多个 Executor pods,可以利用 Kubernetes 的 ServiceAccount 和 RBAC 资源(Role、ClusterRole 等)做权限控制,从而保证集群的安全性。
  • 支持多租户:可利用 Kubernetes 的命名空间机制(Namespace)和资源配额机制(ResourceQuota)做用户粒度资源隔离和资源分配,并利用 Kubernetes 的节点选择机制(NodeSelector)保证 Spark 工作负载可以获得专用的资源。
  • 复用 Kubernetes 生态:重用 Kubernetes 生态的各种组件,例如 Prometheus 监控、日志等。通过把 Spark 工作负载部署在已有的 Kubernetes 集群中,能够快速开始工作,大大降低运维成本。
  • Spark 多版本支持:在传统的部署方式下,客户端使用的 Spark 版本必须和集群中使用的 Spark 版本一致。而在 Kubernetes 环境下,用户可以自定义 Spark 作业所使用的容器镜像,从而可以实现在一个 Kubernetes 集群中同时运行多个版本的 Spark 作业。
  • 工作流编排:把 Spark 和管理数据生命周期的应用运行在同一个集群中,可以使用业界流行的工作流编排解决方案(例如 Apache Airflow 和 Argo Workflow)构建端到端的解决方案,并能很容易的复制到其他区域部署,甚至是在私有化环境中部署。

如何在 Kubernetes 上运行 Spark 作业?

在 Spark 中,作业通常由一个 Driver 进程和若干个 Executor 进程构成。在 Kubernetes 中,Pod 是集群中最小的可部署单元和执行单元。

将 Spark 作业运行在 Kubernetes 上的大致想法就是在单独的 Pod 中分别运行 Driver 进程和 Executor 进程。从 Spark Core 代码实现上来看,其最主要的组件是 KubernetesClusterSchedulerBackend,它是 CoarseGrainedSchedulerBackend 的一个子类,通过调用 Kubernetes API 创建和删除 Pod 来启动和停止 Spark 作业。

向 Kubernetes 集群提交 Spark 作业常见的有两种方式,一是使用 Spark 中自带的 spark-submit 脚本来提交作业,二是使用 Spark Operator 提交作业,下面将分别介绍这两种提交方式。

spark-submit

客户端可以直接使用 spark-submit 命令提交 Spark 作业到 Kubernetes 集群中。

首先,我们需要给 Spark 作业中的 Driver pod 创建一个服务账号(ServiceAccount)并对其授予一定的权限,否则 Driver pod 会由于权限不足无法创建出 Executor pods,从而导致作业执行失败。

# spark-rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: spark

---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: spark-role
rules:
- apiGroups:
  - ""
  resources:
  - pods
  - services
  - configmaps
  verbs:
  - "*"

---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: spark-rolebinding
subjects:
- kind: ServiceAccount
  name: spark
  namespace: default
roleRef:
  kind: Role
  name: spark-role
  apiGroup: rbac.authorization.k8s.io

将上述 RBAC 资源清单文件保存为 spark-rbac.yaml,并执行如下命令创建相应的资源:

$ kubectl apply -f spark-rbac.yaml
serviceaccount/spark created
role.rbac.authorization.k8s.io/spark-role created
rolebinding.rbac.authorization.k8s.io/spark-rolebinding created

然后,我们需要获取 Kubernetes 集群的 API Server 的访问 URL:

KUBERNETES_API_SERVER_URL=`kubectl cluster-info | grep "Kubernetes control plane" | egrep -o "https://[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+"`

最后, 调用 spark-submit 脚本提交 Spark 作业:

# Spark 镜像
SPARK_IMAGE=apache/spark:3.5.0

# 提交作业
${SPARK_HOME}/bin/spark-submit \
    --master k8s://${KUBERNETES_API_SERVER_URL} \
    --deploy-mode cluster \
    --name spark-pi \
    --class org.apache.spark.examples.SparkPi \
    --conf spark.executor.instances=2 \
    --conf spark.kubernetes.container.image=${SPARK_IMAGE} \
    --conf spark.kubernetes.authenticate.driver.serviceAccountName=spark \
    local:///opt/spark/examples/jars/spark-examples_2.12-3.5.0.jar

Spark Operator

Kubernetes 中的 Operator 模式 允许用户在不修改 Kubernetes 自身代码的情况下,通过为一个或多个自定义资源(Custom Resource,CR)关联控制器(Controller),从而扩展集群的能力。

使用 Operator 可以实现非常多的功能,例如自动化部署应用和管理应用的生命周期。Spark Operator 定义了 SparkApplicationScheduledSparkApplication 两种 CRD(CustomResourceDefinition)并为它们实现了相应的控制器,用户只需要编写相应的清单文件,就可以像管理 Kubernetes 中内置的资源一样去管理 Spark 作业。

例如,用户在集群中部署好 Spark Operator 之后,可以编写如下作业清单文件:

# spark-pi.yaml

apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
  name: spark-pi
  namespace: default
spec:
  type: Scala
  mode: cluster
  image: apache/spark:3.5.0
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.5.0.jar
  sparkVersion: "3.5.0"
  driver:
    cores: 1
    coreLimit: 1200m
    memory: 512m
    serviceAccount: spark
  executor:
    cores: 1
    instances: 1
    memory: 512m

上述清单文件定义了一个 SparkApplication 对象,其中定义了 Spark 作业所需的参数,Driver/Executor Pod 的资源配置等,将其保存为 spark-pi.yaml,然后可以执行如下操作:

# 提交作业
kubectl apply -f spark-pi.yaml

# 查看作业
kubectl get sparkapplication spark-pi

# 删除作业
kubectl delete -f spark-pi.yaml

关于使用 Spark Operator 提交作业的更多细节,请参考 Spark Operator 用户文档

快速开始

关于如何在 ACK 集群中运行 Spark 工作负载,请参考快速开始

性能优化

为了提高在 Kubernetes 运行工作负载时的性能和易用性,并降低成本,阿里云 EMR 和 ACK 团队做了很多优化工作,主要有以下这些:

最佳实践

基准测试

关于 TPC-DS 基准测试

TPC-DS 由第三方社区创建和维护,是事实上的做性能压测,协助确定解决方案的工业标准。这个测试集包含对大数据集的统计、报表生成、联机查询、数据挖掘等复杂应用,测试用的数据和值是有倾斜的,与真实数据一致。可以说 TPC-DS 是与真实场景非常接近的一个测试集,也是难度较大的一个测试集。

TPC-DS 基准测试有以下几个主要特点:

  • 遵循 SQL 2003 的语法标准,SQL 案例比较复杂;
  • 分析的数据量大,并且测试案例是在回答真实的商业问题;
  • 测试案例中包含各种业务模型(如分析报告型,迭代式的联机分析型,数据挖掘型等);
  • 几乎所有的测试案例都有很高的 IO 负载和 CPU 计算需求。

TPC-DS 基准测试总包含 99 条压测查询,其中有 4 条查询包含 2 个变体(14、23、24、39),另外还有一个 s_max 查询进行全量扫描和最大的一些表的聚合,因此一共是 104 条查询语句,这些查询覆盖了 SQL 2003 语法的大部分标准。

由于上述原因,本文将采用 TPC-DS 基准测试来评测 Spark 在 ACK 上的性能。

如何运行 TPC-DS 基准测试

基准测试采用 terraform 管理基准测试环境,保证测试环境可以被轻松一键复现。同时,相关 Spark 作业已经被制作成 Helm Chart,可以通过 Helm 命令行工具一键配置并提交基准作业。关于如何运行 TPC-DS 基准测试的具体步骤,请参考运行 TPC-DS 基准测试

Spark on ACK TPC-DS 基准测试

为了了解在 Kubernetes 容器环境下运行 Spark 作业的性能,本次基准测试创建了包含 1个 ecs.g7.2xlarge 实例和 6 个 ecs.g7.8xlarge 实例的 ACK 集群,并运行了 5 轮数据量为 3 TB(SF=3072)的 TPC-DS 基准测试。

关于基准测试的详细步骤和测试结果,请参考 Spark on ACK TPC-DS 基准测试

Apache Spark v.s. EMR Spark

阿里云 EMR Spark 基于开源的 Apache Spark 做了大量优化,下面将在同一 ACK 集群环境中分别使用 Apache Spark 和 EMR Spark 运行相同的规模的 TPC-DS 基准测试,基准测试的配置和结果请参考 Apache Spark v.s. EMR Spark

Spark on ECS v.s. on ACK

Spark 作业可以以非容器化的方式直接运行在 ECS 中,也可以以容器化的方式运行在 ACK 集群中,下面将在相同数量和相同规格的 ECS 集群和 ACK 集群中分别运行相同规模的 TPC-DS 基准测试,以对比容器化和非容器化环境下的性能差异,基准测试的配置和结果请参考 Spark on ECS v.s. on ACK

Spark x86 v.s. on arm64

为了对比 Spark on ACK 在 x86/arm64 架构下的性能和成本,下面将在相同数量和相同规模的 x86/arm64 架构集群中分别运行相同规模的 TPC-DS 基准测试,它们具有相同的节点数量、vCPU 数量和内存大小,并挂载了相同数量和规模的云盘,区别主要在于 x86 架构集群使用了 g7.8xlarge 实例规格族 ECS,arm64 架构集群使用了 g8y.8xlarge 实例规格族 ECS,基准测试的配置和结果请参考 Spark x86 v.s. on arm64