使用 Pod 模板 - Amazon EMR
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

使用 Pod 模板

从 Amazon EMR 版本 5.33.0 或 6.3.0 开始,Amazon EMR on EKS 支持 Spark 的 Pod 模板功能。Pod 是包含一个或多个容器的容器组,包含共享存储和网络资源,以及如何运行容器的规范。Pod 模板是决定如何运行每个容器的规范。您可以使用 Pod 模板文件定义 Spark 配置不支持的驱动程序或执行程序 Pod 的配置。有关 Spark 的 Pod 模板功能的更多信息,请参阅 Pod 模板

注意

Pod 模板功能仅适用于驱动程序和执行程序 Pod。不能使用 Pod 模板配置任务控制器 Pod。

常见场景

您可以通过将 Pod 模板与 Amazon EMR on EKS 结合使用,来定义如何在共享 EKS 集群上运行 Spark 任务,从而节省成本并提高资源利用率和性能。

  • 为了降低成本,您可以安排 Spark 驱动程序任务在 Amazon EC2 按需型实例上运行,同时安排 Spark 执行程序任务在 Amazon EC2 Spot 实例上运行。

  • 为了提高资源利用率,您可以支持多个团队在同一 EKS 集群上运行其工作负载。每个团队将获得一个指定的 Amazon EC2 节点组,以便在上运行其工作负载。您可以使用 Pod 模板对其工作负载应用相应的容忍度。

  • 为了改进监控,您可以运行单独的日志记录容器,将日志转发到现有的监控应用程序。

例如,以下 Pod 模板文件演示了常见的使用场景。

apiVersion: v1 kind: Pod spec: volumes: - name: source-data-volume emptyDir: {} - name: metrics-files-volume emptyDir: {} nodeSelector: eks.amazonaws.com/nodegroup: emr-containers-nodegroup containers: - name: spark-kubernetes-driver # This will be interpreted as driver Spark main container env: - name: RANDOM value: "random" volumeMounts: - name: shared-volume mountPath: /var/data - name: metrics-files-volume mountPath: /var/metrics/data - name: custom-side-car-container # Sidecar container image: <side_car_container_image> env: - name: RANDOM_SIDECAR value: random volumeMounts: - name: metrics-files-volume mountPath: /var/metrics/data command: - /bin/sh - '-c' - <command-to-upload-metrics-files> initContainers: - name: spark-init-container-driver # Init container image: <spark-pre-step-image> volumeMounts: - name: source-data-volume # Use EMR predefined volumes mountPath: /var/data command: - /bin/sh - '-c' - <command-to-download-dependency-jars>

Pod 模板完成以下任务:

  • 添加一个新的 init 容器(初始化容器),并在 Spark 主容器启动之前执行。该初始化容器将名为 source-data-volumeEmptyDir 卷与 Spark 主容器一起使用。您可让您的 init 容器运行初始化步骤,例如下载依赖项或生成输入数据。然后 Spark 主容器使用数据。

  • 添加其它 Sidecar 容器(边车容器),与 Spark 主容器一起执行。这两个容器共享另一个称为 metrics-files-volumeEmptyDir 卷。您的 Spark 任务可以生成指标,例如 Prometheus 指标。然后,Spark 任务可以将指标放入文件中,并让边车容器将文件上载到您自己的 BI 系统,以供将来分析。

  • 将新的环境变量添加到 Spark 主容器中。您可以让您的任务使用环境变量。

  • 定义节点选择器,以便该 Pod 仅安排在 emr-containers-nodegroup 节点组。这有助于各任务和团队隔离计算资源。

利用 Amazon EMR on EKS 启用 Pod 模板

要使用 Amazon EMR on EKS 启用 Pod 模板功能,请配置 Spark 属性 spark.kubernetes.driver.podTemplateFilespark.kubernetes.executor.podTemplateFile,以指向 Amazon S3 中的 Pod 模板文件。然后,Spark 下载 Pod 模板文件并使用它来构建驱动程序和执行程序 Pod。

注意

Spark 使用任务执行角色加载 Pod 模板,因此任务执行角色必须具有访问 Amazon S3 才能加载 Pod 模板的权限。有关更多信息,请参阅创建任务执行角色

您可以使用 SparkSubmitParameters 来指定 Pod 模板的 Amazon S3 路径,如以下任务运行 JSON 文件所示。

{ "name": "myjob", "virtualClusterId": "123456", "executionRoleArn": "iam_role_name_for_job_execution", "releaseLabel": "release_label", "jobDriver": { "sparkSubmitJobDriver": { "entryPoint": "entryPoint_location", "entryPointArguments": ["argument1", "argument2", ...], "sparkSubmitParameters": "--class <main_class> \ --conf spark.kubernetes.driver.podTemplateFile=s3://path_to_driver_pod_template \ --conf spark.kubernetes.executor.podTemplateFile=s3://path_to_executor_pod_template \ --conf spark.executor.instances=2 \ --conf spark.executor.memory=2G \ --conf spark.executor.cores=2 \ --conf spark.driver.cores=1" } } }

此外,您也可以使用 configurationOverrides 来指定 Pod 模板的 Amazon S3 路径,如以下任务运行 JSON 文件所示。

{ "name": "myjob", "virtualClusterId": "123456", "executionRoleArn": "iam_role_name_for_job_execution", "releaseLabel": "release_label", "jobDriver": { "sparkSubmitJobDriver": { "entryPoint": "entryPoint_location", "entryPointArguments": ["argument1", "argument2", ...], "sparkSubmitParameters": "--class <main_class> \ --conf spark.executor.instances=2 \ --conf spark.executor.memory=2G \ --conf spark.executor.cores=2 \ --conf spark.driver.cores=1" } }, "configurationOverrides": { "applicationConfiguration": [ { "classification": "spark-defaults", "properties": { "spark.driver.memory":"2G", "spark.kubernetes.driver.podTemplateFile":"s3://path_to_driver_pod_template", "spark.kubernetes.executor.podTemplateFile":"s3://path_to_executor_pod_template" } } ] } }
注意
  1. 将 Pod 模板功能与 Amazon EMR on EKS 结合使用时,您需要遵循安全指南,例如隔离不受信任的应用程序代码。有关更多信息,请参阅Amazon EMR on EKS 安全最佳实践

  2. 您无法通过使用 spark.kubernetes.driver.podTemplateContainerNamespark.kubernetes.executor.podTemplateContainerName 来更改 Spark 主容器名称,因为这些名称已被硬编码为 spark-kubernetes-driverspark-kubernetes-executors。如果要自定义 Spark 主容器,则必须使用这些硬编码名称在 Pod 模板中指定容器。

Pod 模板字段

使用 Amazon EMR on EKS 配置 Pod 模板时,请考虑以下字段限制。

  • Amazon EMR on EKS 仅允许 Pod 模板中的以下字段启用合适的任务调度。

    以下是所允许的 Pod 级别字段:

    • apiVersion

    • kind

    • metadata

    • spec.activeDeadlineSeconds

    • spec.affinity

    • spec.containers

    • spec.enableServiceLinks

    • spec.ephemeralContainers

    • spec.hostAliases

    • spec.hostname

    • spec.imagePullSecrets

    • spec.initContainers

    • spec.nodeName

    • spec.nodeSelector

    • spec.overhead

    • spec.preemptionPolicy

    • spec.priority

    • spec.priorityClassName

    • spec.readinessGates

    • spec.runtimeClassName

    • spec.schedulerName

    • spec.subdomain

    • spec.terminationGracePeriodSeconds

    • spec.tolerations

    • spec.topologySpreadConstraints

    • spec.volumes

    这些是所允许的 Spark 主容器级别字段:

    • env

    • envFrom

    • name

    • lifecycle

    • livenessProbe

    • readinessProbe

    • resources

    • startupProbe

    • stdin

    • stdinOnce

    • terminationMessagePath

    • terminationMessagePolicy

    • tty

    • volumeDevices

    • volumeMounts

    • workingDir

    当您在 Pod 模板中使用任何不允许的字段时,Spark 将引发异常,并且任务将失败。以下示例显示,由于不允许字段而导致 Spark 控制器日志中出现错误消息。

    Executor pod template validation failed. Field container.command in Spark main container not allowed but specified.
  • Amazon EMR on EKS 预定义了 Pod 模板中的以下参数。您在 Pod 模板中指定的字段不得与这些字段重叠。

    这些是预定义的卷名称:

    • emr-container-communicate

    • config-volume

    • emr-container-application-log-dir

    • emr-container-event-log-dir

    • temp-data-dir

    • mnt-dir

    • home-dir

    • emr-container-s3

    这些是仅适用于 Spark 主容器的预定义卷挂载:

    • 名称:emr-container-communicate;MountPath:/var/log/fluentd

    • 名称:emr-container-application-log-dir;MountPath:/var/log/spark/user

    • 名称:emr-container-event-log-dir;MountPath:/var/log/spark/apps

    • 名称:mnt-dir;MountPath:/mnt

    • 名称:temp-data-dir;MountPath:/tmp

    • 名称:home-dir;MountPath:/home/hadoop

    这些是仅适用于 Spark 主容器的预定义环境变量:

    • SPARK_CONTAINER_ID

    • K8S_SPARK_LOG_URL_STDERR

    • K8S_SPARK_LOG_URL_STDOUT

    • SIDECAR_SIGNAL_FILE

    注意

    您仍然可以使用这些预定义卷,并将它们挂载到其它边车容器中。例如,您可以使用 emr-container-application-log-dir,并将其挂载到您在 Pod 模板中定义的边车容器上。

    如果指定的字段与 Pod 模板中的任何预定义字段冲突,Spark 将引发异常,并且任务将失败。以下示例显示,由于与预定义字段冲突,Spark 应用程序日志出现了错误消息。

    Defined volume mount path on main container must not overlap with reserved mount paths: [<reserved-paths>]

边车容器注意事项

Amazon EMR 控制由 Amazon EMR on EKS 预置的 Pod 生命周期。边车容器应遵循与 Spark 主容器相同的生命周期。如果您在 Pod 中注入额外的边车容器,我们建议您与 Amazon EMR 定义的 Pod 生命周期管理集成,以便在 Spark 主容器退出时,边车容器可以自行停止。

为了降低成本,我们建议您实施一个流程,以防止存在边车容器的驱动程序 Pod 在任务完成后继续运行。完成执行程序后,Spark 驱动程序会删除执行程序 Pod。但是,当驱动程序完成后,其它边车容器将继续运行。在 Amazon EMR on EKS 清除 Pod 驱动程序之前,Pod 都会产生费用,通常在驱动程序 Spark 主容器完成后不到一分钟内产生费用。为了降低成本,您可以将更多边车容器与生命周期管理机制集成,以便 Amazon EMR on EKS 为驱动程序和执行程序 Pod 定义生命周期管理机制,如下节所述。

在驱动程序和执行程序 Pod 中的 Spark 主容器每两秒钟将 heartbeat 发送到文件 /var/log/fluentd/main-container-terminated。通过向您的边车容器添加 Amazon EMR 预定义的 emr-container-communicate 卷挂载,您可以定义 Sidecar 容器的子进程,以定期跟踪此文件的上次修改时间。然后,如果子进程发现 Spark 主容器在较长持续时间内停止使用 heartbeat,则子进程会自行停止。

以下示例演示了跟踪检测信号文件并自行停止的子进程。将 your_volume_mount 替换为挂载预定义卷的路径。脚本捆绑在边车容器使用的镜像内部。在 Pod 模板文件中,您可以使用以下命令 sub_process_script.shmain_command 来指定边车容器。

MOUNT_PATH="your_volume_mount" FILE_TO_WATCH="$MOUNT_PATH/main-container-terminated" INITIAL_HEARTBEAT_TIMEOUT_THRESHOLD=60 HEARTBEAT_TIMEOUT_THRESHOLD=15 SLEEP_DURATION=10 function terminate_main_process() { # Stop main process } # Waiting for the first heartbeat sent by Spark main container echo "Waiting for file $FILE_TO_WATCH to appear..." start_wait=$(date +%s) while ! [[ -f "$FILE_TO_WATCH" ]]; do elapsed_wait=$(expr $(date +%s) - $start_wait) if [ "$elapsed_wait" -gt "$INITIAL_HEARTBEAT_TIMEOUT_THRESHOLD" ]; then echo "File $FILE_TO_WATCH not found after $INITIAL_HEARTBEAT_TIMEOUT_THRESHOLD seconds; aborting" terminate_main_process exit 1 fi sleep $SLEEP_DURATION; done; echo "Found file $FILE_TO_WATCH; watching for heartbeats..." while [[ -f "$FILE_TO_WATCH" ]]; do LAST_HEARTBEAT=$(stat -c %Y $FILE_TO_WATCH) ELAPSED_TIME_SINCE_AFTER_HEARTBEAT=$(expr $(date +%s) - $LAST_HEARTBEAT) if [ "$ELAPSED_TIME_SINCE_AFTER_HEARTBEAT" -gt "$HEARTBEAT_TIMEOUT_THRESHOLD" ]; then echo "Last heartbeat to file $FILE_TO_WATCH was more than $HEARTBEAT_TIMEOUT_THRESHOLD seconds ago at $LAST_HEARTBEAT; terminating" terminate_main_process exit 0 fi sleep $SLEEP_DURATION; done; echo "Outside of loop, main-container-terminated file no longer exists" # The file will be deleted once the fluentd container is terminated echo "The file $FILE_TO_WATCH doesn't exist any more;" terminate_main_process exit 0