GKE 上的 Dataproc 自定义容器映像

您可以指定要与 Dataproc on GKE 搭配使用的自定义容器映像。您的自定义容器映像必须使用 Dataproc on GKE 的某个Spark 基础映像

使用自定义容器映像

如需使用 Dataproc on GKE 自定义容器映像,请在创建 Dataproc on GKE 虚拟集群或向集群提交 Spark 作业时设置 spark.kubernetes.container.image property

  • gcloud CLI 集群创建示例:
    gcloud dataproc clusters gke create "${DP_CLUSTER}" \
        --properties=spark:spark.kubernetes.container.image=custom-image \
        ... other args ...
    
  • gcloud CLI 作业提交示例:
    gcloud dataproc jobs submit spark \
        --properties=spark.kubernetes.container.image=custom-image \
        ... other args ...
    

自定义容器映像要求和设置

基础映像

您可以使用 docker 工具根据已发布的 Dataproc on GKE 基础 Spark 映像之一构建自定义 Docker。

容器用户

Dataproc on GKE 以 Linux spark 用户身份通过 1099 UID 和 1099 GID 来运行 Spark 容器。使用 UID 和 GID 获得文件系统权限。 例如,如果您将映像中位于 /opt/spark/jars/my-lib.jar 中的 jar 文件添加为工作负载依赖项,则必须向 spark 用户授予该文件的读取权限。

组件

  • JavaJAVA_HOME 环境变量指向 Java 安装位置。当前默认值为 /usr/lib/jvm/adoptopenjdk-8-hotspot-amd64,可能会发生变化(如需了解最新信息,请参阅 Dataproc 版本说明)。

    • 如果您自定义 Java 环境,请确保将 JAVA_HOME 设置为正确的位置,并确保 PATH 包含二进制文件的路径。
  • Python:Dataproc on GKE 基本 Spark 映像/opt/conda 处安装了 Miniconda3。CONDA_HOME 指向此位置,${CONDA_HOME}/bin 包含在 PATH 中,PYSPARK_PYTHON 设置为 ${CONDA_HOME}/python

    • 如果您自定义了 Conda,请确保 CONDA_HOME 指向 Conda 主目录,${CONDA_HOME}/bin 包含在 PATH 中,并且 PYSPARK_PYTHON 设置为 ${CONDA_HOME}/python.

    • 您可以在默认的底层环境中安装、移除和更新软件包,也可以创建一个新环境,但强烈建议该环境包含在基础容器映像的底层环境中安装的所有软件包。

    • 如果您向容器映像添加 Python 模块(例如包含实用函数的 Python 脚本),请在 PYTHONPATH 中添加模块目录。

  • Spark:Spark 安装在 /usr/lib/spark 中,SPARK_HOME 指向此位置。Spark 无法自定义。如果发生更改,容器映像将被拒绝或无法正常运行。

    • 作业:您可以自定义 Spark 作业依赖项。SPARK_EXTRA_CLASSPATH 用于定义 Spark JVM 进程的额外类路径。建议:将 jar 放入 /opt/spark/jars 下,并将 SPARK_EXTRA_CLASSPATH 设置为 /opt/spark/jars/*

      如果您将作业 jar 嵌入到映像中,建议的目录是 /opt/spark/job。提交作业时,您可以使用本地路径(例如 file:///opt/spark/job/my-spark-job.jar)引用该作业。

    • Cloud Storage 连接器:Cloud Storage 连接器安装在 /usr/lib/spark/jars 中。

    • 实用程序:运行 Spark 时需要 procpstini 实用程序软件包。这些实用程序包含在基础 Spark 映像中,因此自定义映像无需重新安装它们。

    • 入口点GKE 上的 Dataproc 会忽略对容器映像中的 ENTRYPOINTCMD 基元所做的任何更改。

    • 初始化脚本:您可以在 /opt/init-script.sh 中添加可选的初始化脚本。初始化脚本可以从 Cloud Storage 下载文件、在容器中启动代理、调用其他脚本以及执行其他启动任务。

      入口点脚本会在启动 Spark 驱动程序、Spark 执行器和其他进程之前,使用所有命令行参数 ($@) 调用初始化脚本。初始化脚本可以根据第一个参数 ($1) 选择 Spark 进程的类型:可能的值包括:对于驱动程序容器,为 spark-submit;对于执行器容器,为 executor

  • 配置:Spark 配置位于 /etc/spark/conf 下。SPARK_CONF_DIR 环境变量指向此位置。

    请勿在容器映像中自定义 Spark 配置。请改为通过 Dataproc on GKE API 提交任何媒体资源,原因如下:

    • 某些属性(例如执行器内存大小)是在运行时确定的,而不是在容器映像构建时确定的;它们必须由 GKE 上的 Dataproc 注入。
    • Dataproc on GKE 会对用户提供的属性施加限制。GKE 上的 Dataproc 会将 configMap 中的配置挂载到容器中的 /etc/spark/conf,从而覆盖映像中嵌入的设置。

基础 Spark 映像

Dataproc 支持以下 Spark 基础容器映像:

  • Spark 2.4:${REGION}-docker.pkg.dev/cloud-dataproc/spark/dataproc_1.5
  • Spark 3.1:${REGION}-docker.pkg.dev/cloud-dataproc/spark/dataproc_2.0

自定义容器映像构建示例

示例 Dockerfile

FROM us-central1-docker.pkg.dev/cloud-dataproc/spark/dataproc_2.0:latest

# Change to root temporarily so that it has permissions to create dirs and copy
# files.
USER root

# Add a BigQuery connector jar.
ENV SPARK_EXTRA_JARS_DIR=/opt/spark/jars/
ENV SPARK_EXTRA_CLASSPATH='/opt/spark/jars/*'
RUN mkdir -p "${SPARK_EXTRA_JARS_DIR}" \
    && chown spark:spark "${SPARK_EXTRA_JARS_DIR}"
COPY --chown=spark:spark \
    spark-bigquery-with-dependencies_2.12-0.22.2.jar "${SPARK_EXTRA_JARS_DIR}"

# Install Cloud Storage client Conda package.
RUN "${CONDA_HOME}/bin/conda" install google-cloud-storage

# Add a custom Python file.
ENV PYTHONPATH=/opt/python/packages
RUN mkdir -p "${PYTHONPATH}"
COPY test_util.py "${PYTHONPATH}"

# Add an init script.
COPY --chown=spark:spark init-script.sh /opt/init-script.sh

# (Optional) Set user back to `spark`.
USER spark

构建容器映像

在 Dockerfile 目录中运行以下命令

  1. 设置映像(例如 us-central1-docker.pkg.dev/my-project/spark/spark-test-image:latest)并切换到 build 目录。
    IMAGE=custom container image \
        BUILD_DIR=$(mktemp -d) \
        cd "${BUILD_DIR}"
    
  2. 下载 BigQuery 连接器。

    gcloud storage cp \
        gs://spark-lib/bigquery/spark-bigquery-with-dependencies_2.12-0.22.2.jar .
    

  3. 创建一个 Python 示例文件。

    cat >test_util.py <<'EOF'
    def hello(name):
      print("hello {}".format(name))
    def read_lines(path):   with open(path) as f:     return f.readlines() EOF

  4. 创建一个示例初始化脚本。

    cat >init-script.sh <<EOF
    echo "hello world" >/tmp/init-script.out
    EOF
    

  5. 构建并推送映像。

    docker build -t "${IMAGE}" . && docker push "${IMAGE}"