亚马逊AWS官方博客

在 Amazon EKS 上部署 Spark 作业

越来越多的用户开始使用 Kubernetes 将应用程序部署到生产环境中的容器内,因为它提供了强大的抽象功能,可以管理容器生命周期、优化基础设施资源、提高交付流程的灵活性,以及促进依赖项管理。

如今,在适用于 Kubernetes 的自定义 Spark 调度器推出之际,许多 AWS 客户都在询问如何使用 Amazon Elastic Kubernetes Service (Amazon EKS) 来处理分析工作负载,特别是 Spark ETL 作业。本文将说明 Spark Kubernetes 调度器的当前状况,介绍在 Amazon EKS 上部署 Spark 作业的不同使用案例,并指导您完成在 Amazon EKS 上部署 Spark ETL 示例作业的步骤。

Spark 2.4 提供的功能

在实现于 Kubernetes 中原生集成 Spark 之前,开发人员使用的是 Spark 独立部署。在这种配置下,Spark 集群会持久存在,并会使用 Kubernetes Replication Controller。由于这种配置是静态的,因而作业并行性比较固定且并发性有限,从而会导致资源浪费。

在 Spark 2.3 中,Kubernetes 已经成为一个原生的 Spark 资源调度器。Spark 应用程序可与 Kubernetes API 交互以配置和预置 Kubernetes 对象,包括适用于不同 Spark 组件的 Pod 和服务。如此一来,我们可以借助 Kubernetes 的原生功能简化 Spark 集群的管理,从而实现更高的弹性、可扩展性和安全性。

Spark Kubernetes 调度器仍处在实验阶段。它提供了一些前景良好的功能,不过也有些功能未囊括其中。Spark 2.4 版当前支持:

  • 在客户端和集群模式下使用 Spark 应用程序。
  • 在具有可定制配置的 Kubernetes Pod 中启动驱动程序和执行程序。
  • 通过 TLS 身份验证与 Kubernetes API 服务器进行交互。
  • 在驱动程序和执行程序中挂载 Kubernetes 密钥,以保护敏感信息。
  • 挂载 Kubernetes 卷(hostPath、emptyDir 和 persistentVolumeClaim)。
  • 通过 Kubernetes 服务显示 Spark UI。
  • 与 Kubernetes RBAC 集成,从而使 Spark 作业作为 serviceAccount 运行。

该版本不支持:

  • 驱动程序弹性。Spark 驱动程序在 Kubernetes Pod 中运行。如果某个节点发生故障,Kubernetes 不会将这些 Pod 重新安排用于任何其他节点。Kubernetes restartPolicy 仅指在相同的 Kubelet(同一节点)上重新启动相关容器。通过使用 Kubernetes 自定义控制器监控驱动程序 Pod 的状态并在集群级别应用重启策略,可以缓解此问题。
  • 动态资源分配。Spark 应用程序需要借助一种 External Shuffle 服务来实现自动扩展,以便在 Spark 执行程序外部持久保存 Shuffle 数据(请参阅 SPARK-24432)。

有关更多信息,请参考官方 Spark 文档

此外,对于流式处理应用程序的弹性,Spark 使用检查点目录来存储元数据和数据以及恢复其状态。检查点目录需要能够从 Spark 驱动程序和执行程序访问。常用的方法是使用 HDFS,但这无法在 Kubernetes 上使用。Kubernetes 上的 Spark 部署可以使用 PersistentVolumes(在 Pod 终止后会继续存在),同时还可使用 readWriteMany 访问模式来实现并发访问。Amazon EKS 支持将 Amazon EFSAmazon FSX Lustre 作为 PersistentVolume 类。

使用案例 – 在 Amazon EKS 上部署 Spark 作业

使用 Amazon EKS 运行 Spark 作业对以下类型的使用案例颇有助益:

  • 具有高可用性需求的工作负载。与 Amazon EKS 工作节点一样,Amazon EKS 控制平面也部署在多个可用区中。
  • 能够实现工作负载之间的隔离并减少资源消耗的多租户环境。Amazon EKS 支持 Kubernetes Namespace、Network Policy、Quota、Pod Priority 和 Preemption。
  • 在 Kubernetes 中使用 Docker 和现有工作负载的开发环境。Spark on Kubernetes 在大数据和已经容器化的工作负载之间提供了一种统一的方法。
  • 专注于应用程序开发而不必担心集群的大小调整和配置。Amazon EKS 是一项完全托管的服务,可以简化集群维护,包括 Kubernetes 版本升级和安全补丁。
  • 需要快速自动调整响应时间的尖峰工作负载。Amazon EKS 支持 Kubernetes Cluster Autoscaler,可在数分钟内提供更高的计算能力。

在 EKS 上部署 Spark 应用程序

在本节中,我将运行一个演示,向您展示在 Amazon EKS 上部署 Spark 应用程序的步骤。该演示应用程序是一个用 Scala 编写的 ETL 作业,用于处理纽约的出租车乘坐情况,以便按小时计算对司机最有价值的区域。该应用程序从公共 NYC Amazon S3 存储桶读取数据,然后将输出写入其他 Amazon S3 存储桶,再在 AWS Glue Data Catalog 中创建表,以通过 Amazon Athena 分析结果。该演示应用程序需满足以下先决条件:

  • 源代码的副本,可从以下链接获得:

git clone https://github.com/aws-samples/amazon-eks-apache-spark-etl-sample.git

  • 计算机上装有 17.05 以上版本的 Docker,用于构建 Docker 镜像。
  • 现有 Amazon S3 存储桶的名称。
  • 有权向该 Amazon S3 存储桶执行写入的 AWS IAM 策略。
  • 经过配置有权通过编辑 ClusterRole 创建 Kubernetes ServiceAccount 的 Amazon EKS 集群以及附加有之前的 AWS IAM 策略的工作节点实例角色。

如果您没有 Amazon EKS 集群,可以(在更新 AWS IAM 策略 ARN 后)使用 EKSCTL 工具按照以下示例创建一个:

eksctl create cluster -f example/eksctl.yaml

第一步,我将 Spark 应用程序打包到 Docker 镜像中。我的 Dockerfile 是一个多阶段镜像:第一阶段用于使用 SBT 工具编译和构建二进制文件,第二阶段用作 Spark 基础层,最后一个阶段用于我的最终可部署镜像。SBT 和 Apache Spark 都没有 Docker 基础镜像,因此我需要在层中加入必要文件:

  • 为应用程序构建 Docker 镜像并将其推送到我的存储库:

docker build -t <REPO_NAME>/<IMAGE_NAME>:v1.0 .

docker push <REPO_NAME>/<IMAGE_NAME>:v1.0

现在,我已拥有要部署为 Docker 镜像的包,接下来我需要准备用于部署的基础设施:

  • 创建 Kubernetes 服务账户和集群角色绑定,以向 Kubernetes 授予 Spark 作业编辑权限:

kubectl apply -f example/kubernetes/spark-rbac.yaml

最后,我需要将我的 Spark 应用程序部署到 Amazon EKS。将 Spark 作业部署到 Kubernetes 的方法有多种:

  • 通过负责部署的服务器使用 spark-submit 命令。Spark 目前只支持通过 SSL 证书进行 Kubernetes 身份验证。此方法与 Amazon EKS 不兼容,因为后者仅支持 IAM 和持有者令牌身份验证。
  • 使用将从 Kubernetes 集群运行 spark-submit 命令的某个 Kubernetes 作业,并确保该作业处于成功状态。在此方法中,spark-submit 通过 Kubernetes Pod 运行,身份验证则依靠与 Amazon EKS 完全兼容的 Kubernetes RBAC 进行。
  • 使用 Kubernetes 自定义控制器(也称为 Kubernetes Operator)基于 (CRD) 的声明性方法管理 Spark 作业生命周期。此方法与 Amazon EKS 兼容,并添加了一些目前 Spark Kubernetes 调度器不支持的功能,例如驱动程序弹性和高级资源调度。

我将使用 Kubernetes 作业方法来避免对自定义控制器的额外依赖。为此,我需要一个包含 Spark 二进制文件的 Spark 基础镜像,包括 spark-submit 命令:

  • 为 Spark 构建 Docker 镜像并将其推送到我的存储库:

docker build --target=spark -t <REPO_NAME>/spark:v2.4.4 .

docker push <REPO_NAME>/spark:v2.4.4

  • 使用 kubectl 基于描述文件创建作业,修改目标 Amazon S3 存储桶及要从中提取镜像的 Docker 存储库:

kubectl apply -f example/kubernetes/spark-job.yaml

  • 使用 Kubernetes 端口转发通过托管在 Spark 驱动程序上的 Spark UI 监控作业。首先获取驱动程序 Pod 名称,然后通过让浏览器指向 http://localhost:4040 来在 localhost 上转发它的端口并访问 UI:

kubectl get pods | awk '/spark-on-eks/{print $1}'

kubectl port-forward <SPARK_DRIVER_POD_NAME> 4040:4040

作业完成后,我可以创建表并对其进行爬网和查询,通过 Amazon Athena 控制台查看结果:

  • 在 AWS Glue 控制台中添加爬网程序,以对 Amazon S3 存储桶中的数据进行爬网,然后创建两个表,一个用于原始乘坐情况,一个用于每个地方的乘坐统计数据:
    • 输入爬网程序名称,然后单击下一步
    • 选择数据存储,然后单击下一步
    • 选择之前使用的 Amazon S3 存储桶,然后单击下一步
    • 输入 AWS Glue IAM 角色的名称,然后单击下一步
    • 选择按需运行,然后单击下一步
    • 选择您要添加表的数据库,选择为每个 Amazon S3 路径创建单个架构,单击下一步,然后单击完成
  • 运行爬网程序并等待操作执行完毕。
  • 在 Amazon Athena 控制台中选择合适的数据库后,执行以下查询来预览原始乘车情况,并查看哪个区域最适合出租车司机接车:

SELECT * FROM RAW_RIDES LIMIT 100;

SELECT locationid, yellow_avg_minute_rate, yellow_count, pickup_hour FROM "test"."value_rides" WHERE pickup_hour=TIMESTAMP '2018-01-01 5:00:00.000' ORDER BY yellow_avg_minute_rate DESC; 

小结

在本博文中,您了解了 Apache Spark 如何使用 Amazon EKS 在 Kubernetes 上运行 Spark 应用程序。您还了解了 Amazon EKS 的当前状况、Kubernetes 与 Spark 的集成、这种方法的局限性,以及构建和运行 ETL 的步骤,包括 Docker 镜像构建、Amazon EKS RBAC 配置和 AWS 服务配置。