伦敦制造me

返回索引

我第一次发表这里

§1 Apache Spark 和 Docker

Apache Spark 最近新增的主要功能之一是对 Kubernetes 的完整支持。当 Kubernetes 被选为集群管理器时,用户必须提交容器化的应用程序。 Kubernetes 支持多种容器格式,但 Docker 是最受欢迎的选项。本文介绍了如何将包含 Python 和 Scala 源以及混合第三方依赖项的双语代码库容器化并作为 Docker 应用程序运行:首先,程序在单个 Docker 容器中以本地模式执行。第二部分展示了如何在多容器 Docker 应用程序中使用 Spark 的集群模式启动小型集群,下面贴上一个偷偷摸摸的高峰:

SneakPeak.png

本文的实际部分使用来自 bdrecipes 项目的代码,序言描述了如何轻松地在本地设置此代码库。或者,可以在此处获取包含所有应用程序代码和工件的完整 Docker 映像。

父 Docker 镜像

Docker image 是 Docker 引擎可以用来实例化容器的蓝图。根据官方文档,容器是“您机器上的另一个进程,它已与主机上的所有其他进程隔离开来”。图像定义不必从头开始,但可以导入现有图像并对其进行修改。此处将使用这种定制方法:DataMechanics 最近发布了一组免费的 Docker 镜像。这些图像中的每一个都包含一个 Spark 分发和一些额外的花里胡哨,如 Jupyter 笔记本支持或连接到对象存储(如 S3)的连接器。

数十个 Docker 镜像是可用因此需要确定最适合给定代码库的 docker 化的镜像:bdrecipesPOM 文件指定 Spark 版本 3.1.1、Scala 2.12.10 和 Java 8。可以在python文件夹下找到,我在本地使用 Python 3.8.2。除此之外,我在这里从下载了 Spark 发行版spark-3.1.1-bin-hadoop3.2以供本地使用。这种组合的最佳匹配似乎是带有注释3.1.1-hadoop-3.2.0-java-8-scala-2.12-python-3.8-dm13的 Docker 映像,这是 bdrecipes 的第一个条目Dockerfile(稍后描述)将指定。

派生的 Docker 镜像

下一节将详细介绍从3.1.1-hadoop-3.2.0-java-8-scala-2.12-python-3.8-dm13等父映像派生自定义映像时所涉及的步骤。对于想要跳过必要的 bdrecipes 安装和映像构建过程的不耐烦的读者,可以使用一个快捷方式:完成的自定义映像被推送到 docker存储库并且可以使用docker pull bdrecipes/spark-on-docker获取

$ docker pull bdrecipes/spark-on-docker
   Using default tag: latest
   latest: Pulling from bdrecipes/spark-on-docker 
[...]
$ docker images 
   REPOSITORY                  TAG       IMAGE ID    CREATED       SIZE
   bdrecipes/spark-on-docker   latest    e6fcd107d   6 hours ago   2.34GB

如果使用这个预构建的镜像,下面示例中的 docker 命令需要稍作修改:示例将使用bdrecipes/spark-on-docker,而不是参考下一节中手动组装的镜像my-app:dev,例如命令docker run --rm -i -t my-app:dev bash更改为docker run --rm -i -t bdrecipes/spark-on-docker bash。以下部分解释了bdrecipes/spark-on-docker映像在推送到存储库之前是如何组装的:

Dockerfile

代码库 docker 化的第一步是在项目的根目录中创建一个 Dockerfile。这是一个包含指令的简单文本文件,Docker 守护进程运行此文件来组装图像。 bdrecipes 的Dockerfile包含以下条目:

FROM gcr.io/datamechanics/spark:3.1.1-hadoop-3.2.0-java-8-scala-2.12-python-3.8-dm13

COPY tutorials/ tutorials/

COPY resources/ resources/

COPY target/bdrecipes-phil.jar .

COPY scripts/launch_spark_daemon.sh .

COPY requirements.txt .

RUN pip3 install -r requirements.txt

COPY setup.py setup.py

RUN pip3 install -e .

以下段落更详细地解释了此 Dockerfile 中的各个说明。第一行初始化一个新的构建阶段并声明我们的自定义图像派生自的父图像。上一节已经确定最合适的父级是spark:3.1.1-hadoop-3.2.0-java-8-scala-2.12-python-3.8-dm13,这导致以下初始指令:

FROM gcr.io/datamechanics/spark:3.0.2-hadoop-3.2.0-java-8-scala-2.12-python-3.8-dm13

bdrecipes 项目的所有 Spark 程序都放在教程文件夹中,而输入文件位于资源文件夹中。 Dockerfile 中接下来的两个 copy 行使这两个文件夹在容器中可用:

COPY tutorials/ tutorials/
COPY resources/ resources/

通过这些指令,可以在容器文件系统的/opt/spark/work-dir/tutorials/module1/python/query_plans.py下找到像/Users/me/IdeaProjects/bdrecipes/tutorials/module1/python/query_plans.py这样的本地文件。类似地,像/Users/me/IdeaProjects/bdrecipes/resources/warc.sample这样的输入文件参数必须作为/opt/spark/work-dir/resources/warc.sample传递给query_plans.py的 dockerized 运行,如下所示。

在执行 Scala 或 Java 源代码之前,需要通过mvn clean install组装一个 fat JAR。之前的文章解释了如何正确设置 bdrecipes 代码库。 Maven 构建过程完成后,可以在target/bdrecipes-phil.jar找到胖 JAR。使用以下指令将此程序集添加到容器文件系统中:

COPY target/bdrecipes-phil.jar .

创建了一个帮助脚本launch_spark_daemon.sh,这将减少本文后半部分启动 Spark 守护程序时所需的手动工作。下一行添加了这个脚本:

COPY scripts/launch_spark_daemon.sh .

Pyspark 和 pythonic 项目依赖项由以下两条指令处理:

COPY requirements.txt .
RUN pip3 install -r requirements.txt

Docker 映像的最后一层安装了 bdrecipes Python 包。这将使诸如和之类的 Python 模块中的自定义功能可用于容器化 PySpark 程序:

COPY setup.py setup.py
RUN pip3 install -e .

容器创建

以下部分的实际部分依赖于 Docker 引擎,可以从下载。一旦 Docker 守护进程启动并运行,我们的自定义镜像可以通过导航到 bdrecipes 项目根目录并执行命令docker build -t my-app:dev .来构建

$ cd ~/IdeaProjects/bdrecipes
$ docker build -t my-app:dev .
[+] Building 1.0s (14/14) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 37B
 => [internal] load .dockerignore
 => => transferring context: 2B
 => [internal] load metadata for gcr.io/datamechanics/spark:3.1.1-hadoop-3.2.0-java-8-scala-2.12-python-3.8-dm13
 => [1/9] FROM gcr.io/datamechanics/spark:3.1.1-hadoop-3.2.0-java-8-scala-2.12-python-3.8-dm13@sha256:4ec1977ea8...
 => [internal] load build context
 => => transferring context: 5.63kB
 => CACHED [2/9] COPY tutorials/ tutorials/
 => CACHED [3/9] COPY resources/ resources/
 => CACHED [4/9] COPY target/bdrecipes-phil.jar .
 => CACHED [5/9] COPY scripts/launch_spark_daemon.sh .
 => CACHED [6/9] COPY requirements.txt .
 => CACHED [7/9] RUN pip3 install -r requirements.txt
 => CACHED [8/9] COPY setup.py setup.py
 => CACHED [9/9] RUN pip3 install -e .
 => exporting to image
 => => exporting layers
 => => writing image sha256:6a...
 => => naming to docker.io/library/my-app:dev

这个终端输出看起来很有希望,创建了九层,对应于Dockerfile中的九条指令。我们的新自定义镜像my-app:dev可以通过执行 dockerized 版本的强制性 HelloWorld 示例来测试: 两个源文件是HelloWorld.scala和helloworld.py,这个 HelloWorld 的相关目录和文件的本地布局例子是

bdrecipes/
   target/
      bdrecipes-phil.jar
   tutorials/
      module1/
         scala/
            HelloWorld.scala
         python/
            helloworld.py

让我们通过在终端窗口中输入命令docker run --rm -i -t my-app:dev bash来研究正在运行的 Docker 容器中的布局。这将启动一个容器并将其以交互方式(-i标志)附加到本地终端会话(-t标志):

$ docker run --rm -i -t my-app:dev bash

$ pwd
   /opt/spark/work-dir
$ ls -l
   -rw-r--r-- 1 root root 113942259 Jun 19 09:53 bdrecipes-phil.jar
   drwxr-xr-x 2 185  root      4096 Jun 19 10:00 bdrecipes.egg-info
   -rwxr-xr-x 1 root root       149 Jun 13 20:49 launch_master.sh
   -rwxr-xr-x 1 root root       151 Jun 13 14:29 launch_worker.sh
   -rw-r--r-- 1 root root        19 Jun 19 10:00 requirements.txt
   drwxr-xr-x 2 root root      4096 Jun 19 09:54 resources/
   -rw-r--r-- 1 root root        78 May 10 10:08 setup.py
   drwxr-xr-x 4 root root      4096 Jun 19 09:54 tutorials/
$ exit;
   exit
$

使用预建镜像bdrecipes/spark-on-docker时,run 命令变为docker run --rm -i -t bdrecipes/spark-on-docker bash。从上面的终端输出中,我们可以推断出从 Docker 镜像my-app:dev实例化的容器中相关文件的布局如下:

/opt/spark/work-dir/
               bdrecipes-phil.jar
               tutorials/
                  module1/
                     scala/
                        HelloWorld.scala
                     python/
                        helloworld.py

现在我们拥有了将 HelloWorld 程序作为 Docker 应用程序执行所需的所有信息。docker run命令应在本地终端会话中输入以下参数(即,容器的“外部”):

执行HelloWorld.scala(如果需要,将my-app:dev更改为bdrecipes/spark-on-docker):

$ docker run my-app:dev driver --class module1.scala.HelloWorld local:///opt/spark/work-dir/bdrecipes-phil.jar
[...]
+ CMD=("$SPARK_HOME/bin/spark-submit" --conf "spark.driver.bindAddress=$SPARK_DRIVER_BIND_ADDRESS" --deploy-mode client "$@")
+ exec /usr/bin/tini -s -- /opt/spark/bin/spark-submit --conf spark.driver.bindAddress= --deploy-mode client 
   --class module1.scala.HelloWorld local:///opt/spark/work-dir/bdrecipes-phil.jar
WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

@@@ HELLO WORLD @@@

执行helloworld.py(如果需要,将my-app:dev更改为bdrecipes/spark-on-docker):

$ docker run my-app:dev driver local:///opt/spark/work-dir/tutorials/module1/python/helloworld.py
[...]
+ CMD=("$SPARK_HOME/bin/spark-submit" --conf "spark.driver.bindAddress=$SPARK_DRIVER_BIND_ADDRESS" --deploy-mode client "$@")
+ exec /usr/bin/tini -s -- /opt/spark/bin/spark-submit --conf spark.driver.bindAddress= --deploy-mode client 
  local:///opt/spark/work-dir/tutorials/module1/python/helloworld.py
WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

@@@ HELLO WORLD @@@

Spark 应用程序作为 Docker 应用程序

在上一节中,两个 HelloWorld 程序作为 Docker 应用程序成功执行。涉及到相当多的设置工作,所以对于这些琐碎的例子来说,使用 Docker 似乎有点过头了。但是,当QueryPlans.scala/query_plans.pyfrom§10.1之类的不那么琐碎的程序进入图片时,容器化的好处已经变得显而易见。它们是解析输入文件、依赖第三方库并应用打包在同一代码库中的业务逻辑的 Spark 应用程序。直接从以前的成功执行中恢复命令会导致运行时错误:

QueryPlans.scala运行失败(如果需要,将my-app:dev更改为bdrecipes/spark-on-docker):

$ docker run my-app:dev driver --class module1.scala.QueryPlans local:///opt/spark/work-dir/bdrecipes-phil.jar

[...]
org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: file:/Users/me/IdeaProjects/bdrecipes/
      resources/warc.sample
   at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.singleThreadedListStatus(FileInputFormat.java:332)
   at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.listStatus(FileInputFormat.java:274)

query_plans.py运行失败(如果需要,将my-app:dev更改为bdrecipes/spark-on-docker):

$ docker run my-app:dev driver local:///opt/spark/work-dir/tutorials/module1/python/query_plans.py

[...]
py4j.protocol.Py4JJavaError: An error occurred while calling z:org.apache.spark.api.python.PythonRDD.newAPIHadoopFile.
InvalidInputException: Input path does not exist: file:/Users/me/IdeaProjects/bdrecipes/resources/warc.sample
  at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.singleThreadedListStatus(FileInputFormat.java:332)
  at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.listStatus(FileInputFormat.java:274)
  at org.apache.hadoop.mapreduce.lib.input.FileInputFormat.getSplits(FileInputFormat.java:396)

QueryPlans.scala/query_plans.py的输入处理逻辑是导致此失败的原因:程序解析文本文件并执行与内存中映射的连接。如果输入文件位置未作为参数传递,则将使用默认位置/Users/me/IdeaProjects/bdrecipes/resources/warc.sample。我们的 Dockerfile 应该将此输入文件放在/opt/spark/work-dir/resources/下,因此当位置/opt/spark/work-dir/resources/warc.sample作为参数传递给程序时,运行时错误将消失:

成功运行QueryPlans.scala(如果需要,将my-app:dev更改为bdrecipes/spark-on-docker):

$ docker run my-app:dev driver --class module1.scala.QueryPlans \
   local:///opt/spark/work-dir/bdrecipes-phil.jar \
   /opt/spark/work-dir/resources/warc.sample

[...]
   :                 +- Scan[obj#27]
   +- BroadcastExchange HashedRelationBroadcastMode(List(input[0, string, true]),false), [id=#95]
      +- LocalTableScan [tag#7, language#8]

成功运行query_plans.py(如果需要,将my-app:dev改为bdrecipes/spark-on-docker):

$ docker run my-app:dev driver local:///opt/spark/work-dir/tutorials/module1/python/query_plans.py \
   /opt/spark/work-dir/resources/warc.sample

[...]
   +- *(4) Sort [tag#0 ASC NULLS FIRST], false, 0
      +- Exchange hashpartitioning(tag#0, 200), true, [id=#115]
         +- *(3) Filter (isnotnull(tag#0) AND NOT (tag#0 = ))
            +- *(3) Scan ExistingRDD[tag#0,language#1]

刚刚执行的程序取自§10.1,它探索了 Catalyst 的查询计划。执行在最后被冻结 10 分钟,以便有足够的时间调查 Spark UI。要跳过此等待时间并停止 Spark 应用程序(和容器),始终可以手动中止运行(例如,通过在 macOS 上按controlc)。

但是,访问 Spark 的 WebUI(通常为localhost:4040)的 URL 将导致失望,即使 UI 服务已成功启动,如执行开始时的日志输出所示:

INFO Utils: Successfully started service 'SparkUI' on port 4040.
INFO SparkUI: Bound SparkUI to 0.0.0.0, and started at http://59a0411f4157:4040

这可以通过将容器的端口发布到 Docker 主机(我的 MacBook)的标志-p 4040:4040来修复,从而产生以下增强的docker run命令:

  • Scala 执行与可在localhost:4040访问的 Web UI:

docker run -p 4040:4040 my-app:dev driver --class module1.scala.QueryPlans local:///opt/spark/work-dir/bdrecipes-phil.jar /opt/spark/work-dir/resources/warc.sample

  • PySpark 执行与可在localhost:4040访问的 Web UI:

docker run -p 4040:4040 my-app:dev driver local:///opt/spark/work-dir/tutorials/module1/python/query_plans.py /opt/spark/work-dir/resources/warc.sample

半场战绩

下图试图可视化上次执行以及涉及的最相关的 Spark 和 Docker 实体:

Docker1.png

Both QueryPlans.scala and query_plans.py delegate the creation of the SparkSession object to a utility method (createSession and create_session) which sets the master URL as.master("local[3]").通常,Spark DriverWorkerMaster 是在不同主机上产生的不同进程,但在这种本地、非分布式模式下,它们重合并在单个 Docker 容器中运行的单个 JVM 中创建。这是本地使用的常规方法,但 Docker 允许我们通过将程序作为多容器 Docker 应用程序运行来轻松创建小型 Spark 集群。这是本文后半部分的主题:

多容器 Docker 应用

如果我们的两个程序应该在多个容器上作为集群应用程序运行,则需要对源代码进行少量修改:SparkSession 对象的主 URL 必须指向集群主节点,因此不能以local开头。本文其余部分将使用的两个稍作修改的程序是QueryPlansDocker.scala/query_plans_docker.py。与上一节的兄弟姐妹的唯一区别是在创建_SparkSession_ 对象时没有显式设置主 URL,该 URL 将从命令行作为参数提供给spark-submit

到目前为止,不需要明确指定networking 选项。要使多容器方案正常工作,必须使用docker network命令创建网络myapp_network(至少在 MacBook 上):

$ docker network create myapp_network

现在可以手动启动模拟小型 Spark 集群的多容器 Docker 应用程序:从同一个 Docker 映像my-app:dev(或bdrecipes/spark-on-docker)启动多个容器后,必须在它们上生成不同的 Spark 守护进程:我们需要一个实例生成一个 Spark Master(使用org.apache.spark.deploy.master.Master)和一个或多个启动org.apache.spark.deploy.worker.Worker并成功注册到 Spark Master 的“Worker 实例”。然而,正如本文开头已经提到的,Dockerfile 将帮助脚本launch_spark_daemon.sh复制到容器中,这将使生活变得更加轻松。

Docker 编写

launch_spark_daemon.sh脚本在位于 Dockerfile 旁边的特殊 YAML 文件docker-compose.yml中被引用(在其他几个实体中)。此文件由Docker Compose使用,这是一个用于协调多容器 Docker 应用程序的工具。 bdrecipes 项目的 compose文件的内容是:

services:
  spark-master:
    image: my-app:dev
    container_name: spark-master
    hostname: spark-master
    ports:
      - "4040:4040"
      - "7077:7077"
      - "8080:8080"
    networks:
      - myapp_network
    environment:
      - UI_PORT=8080
      - MASTER_PORT=7077
    command: "sh launch_spark_daemon.sh master"
  worker1:
    container_name: worker1
    hostname: worker1
    image: my-app:dev
    depends_on:
      - spark-master
    ports:
      - "8081:8081"
    networks:
      - myapp_network
    environment:
      - UI_PORT=8081
      - MASTER_URL=spark://spark-master:7077
      - CORES=1
      - MEM=800m
    command: "sh launch_spark_daemon.sh worker"
  worker2:
    container_name: worker2
    hostname: worker2
    image: my-app:dev
    depends_on:
      - spark-master
    ports:
      - "8082:8082"
    networks:
      - myapp_network
    environment:
      - UI_PORT=8082
      - MASTER_URL=spark://spark-master:7077
      - CORES=1
      - MEM=800m
    command: "sh launch_spark_daemon.sh worker"
networks:
  myapp_network:

预建镜像bdrecipes/spark-on-docker的稍微修改的文件被粘贴到此处。 compose 文件定义了三个由容器支持并最终一起运行的服务,主要的是 Spark Master(spark-master),两个 Worker(worker1,worker2)依赖于它。实际的计算工作将在两个 Worker 上执行,Master 将充当协调器,稍后将通过spark-submit启动集群应用程序。 compose 文件中的大多数条目应该是不言自明的,一些一般性说明:

  • 所有三个服务都从同一个底层 Docker 镜像my-app:dev(或bdrecipes/spark-on-docker)实例化,它们都使用之前设置的网络myapp_network

  • environment部分设置了几个在launch_spark_daemon.sh中引用的环境变量。脚本本身由三个服务使用command指令调用。

  • 只要 Spark 守护进程处于活动状态,三个服务容器就会处于活动状态,因此必须手动停止它们,因为没有超时。

  • 每个由 Worker 托管的 Spark 执行器只能使用一个内核和最多 800 MB 的内存。这些是环境变量CORESMEM指定的最大资源,当然可以增加。

集群模式和Docker

如前所述,使用 Docker Compose 来设置本地 Spark 集群并不是必须的,几个手动步骤可以达到相同的效果:例如,可以通过以下方式生成 Spark Master 守护程序

  • 使用docker run --rm -it --network myapp_network --name spark-master --hostname spark-master -p 7077:7077 -p 8080:8080 my-app:dev bash创建并进入一个新的容器实例

  • 使用如下命令启动镜像中的Master进程:/opt/spark/bin/spark-class org.apache.spark.deploy.master.Master --host`hostname`--port 7077 --webui-port 8080

显然,当多容器 Docker 应用程序在没有 Compose 工具的情况下运行时,会涉及很多样板文件。有了它,用户除了编写一个撰写文件外,只需要做两件事,即导航到包含该文件的目录并输入命令docker-compose up -d:

$ cd ~/IdeaProjects/bdrecipes 
$ docker-compose up -d
   Creating network "bdrecipes_myapp_network" with the default driver
   Creating spark-master ... done
   Creating worker1      ... done
   Creating worker2      ... done

$ docker ps
CONTAINER ID   IMAGE        COMMAND                  CREATED          STATUS         NAMES
f993eb56b525   my-app:dev   "/opt/dm_entrypoint.…"   24 seconds ago   Up 20 seconds  worker1
5bc05f7260c3   my-app:dev   "/opt/dm_entrypoint.…"   24 seconds ago   Up 19 seconds  worker2
41f2f6482419   my-app:dev   "/opt/dm_entrypoint.…"   30 seconds ago   Up 24 seconds  spark-master

如果使用的是预建镜像bdrecipes/spark-on-docker,则应该将compose文件的这个版本复制到一个新目录下。进入目录后,启动服务的命令不变,即docker-compose up -d

Docker 在行动

根据终端输出,一个mini Spark集群启动成功。这可以通过访问三个 Spark 守护程序的 UI 来确认,这要归功于 compose 文件的端口转发部分,可以通过以下方式访问:

  • localhost:8080用于主 UI

  • localhost:8081用于第一个 Spark Worker 的 UI

  • localhost:8082用于第二个 Spark Worker 的 UI

http://localhost:8080/显示的页面应该表明 Spark Master 已启动并运行,并且两个 Worker 已注册成功:

SparkMaster.png

下面是在localhost:8081访问的第一个工作人员的 UI 截图:

Worker1.png

最后一个屏幕截图底部的 Running Executors 表是空的,因为尚未启动 Spark 应用程序,因此 Spark Worker 处于空闲状态。这很快就会改变,QueryPlansDocker.scala/query_plans_docker.py现在可以作为集群应用程序从spark-master容器中运行,只需两个命令:

  • 使用docker exec -it spark-master bashspark-master容器附加到本地命令行会话

  • Scala:使用/opt/spark/bin/spark-submit --master spark://spark-master:7077 --executor-memory=768m --class module1.scala.QueryPlansDocker /opt/spark/work-dir/bdrecipes-phil.jar /opt/spark/work-dir/resources/warc.sample启动应用程序

  • PySpark:使用/opt/spark/bin/spark-submit --master spark://spark-master:7077 --executor-memory=768m /opt/spark/work-dir/tutorials/module1/python/query_plans_docker.py /opt/spark/work-dir/resources/warc.sample启动应用程序

使用docker exec -it spark-master bash附加主容器后执行QueryPlansDocker.scala:

$ /opt/spark/bin/spark-submit --master spark://spark-master:7077 --executor-memory=768m \
   --class module1.scala.QueryPlansDocker \
   /opt/spark/work-dir/bdrecipes-phil.jar \
   /opt/spark/work-dir/resources/warc.sample

WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Using Spark`s default log4j profile: org/apache/spark/log4j-defaults.properties
INFO SparkContext: Running Spark version 3.1.1
INFO ResourceUtils: ==============================================================
INFO ResourceUtils: No custom resources configured for spark.driver.
INFO ResourceUtils: ==============================================================
INFO SparkContext: Submitted application: Query Plans Docker
INFO ResourceProfile: Default ResourceProfile created, executor resources: Map(cores -> name: ...
INFO ResourceProfile: Limiting resource is cpu
[...]
INFO Utils: Successfully started service 'sparkDriver' on port 37959.
INFO Utils: Successfully started service 'SparkUI' on port 4040.
INFO SparkUI: Bound SparkUI to 0.0.0.0, and started at http://spark-master:4040
INFO StandaloneAppClient$ClientEndpoint: Connecting to master spark://spark-master:7077...
INFO TransportClientFactory: Successfully created connection to spark-master/172.xxxxxxx:7077 after 55 ms (0 ms spent in bootstraps)
[...]
   :                 +- Scan[obj#27]
   +- BroadcastExchange HashedRelationBroadcastMode(List(input[0, string, true]),false), [id=#95]
      +- LocalTableScan [tag#7, language#8]

使用docker exec -it spark-master bash附加主容器后执行query_plans_docker.py:

/opt/spark/bin/spark-submit --master spark://spark-master:7077 --executor-memory=768m \
   /opt/spark/work-dir/tutorials/module1/python/query_plans_docker.py \
   /opt/spark/work-dir/resources/warc.sample 

WARN NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
Using Sparks default log4j profile: org/apache/spark/log4j-defaults.properties
INFO SparkContext: Running Spark version 3.1.1
INFO ResourceUtils: ==============================================================
INFO ResourceUtils: No custom resources configured for spark.driver.
INFO ResourceUtils: ==============================================================
INFO SparkContext: Submitted application: Query Plans Docker
[...]
INFO Utils: Successfully started service 'sparkDriver' on port 40105.
INFO Utils: Successfully started service 'SparkUI' on port 4040.
INFO SparkUI: Bound SparkUI to 0.0.0.0, and started at http://spark-master:4040
INFO StandaloneAppClient$ClientEndpoint: Connecting to master spark://spark-master:7077...
INFO TransportClientFactory: Successfully created connection to spark-master/172.19.0.2:7077 after 49 ms (0 ms spent in bootstraps)
INFO StandaloneSchedulerBackend: Connected to Spark cluster with app ID app-20210621204759-0003
[...]
   +- *(4) Sort [tag#0 ASC NULLS FIRST], false, 0
      +- Exchange hashpartitioning(tag#0, 200), ENSURE_REQUIREMENTS, [id=#115]
         +- *(3) Filter (NOT (tag#0 = ) AND isnotnull(tag#0))
            +- *(3) Scan ExistingRDD[tag#0,language#1]

如前所述,本文中使用的程序在计算结果后将执行冻结 10 分钟,从而留出足够的时间来探索 SparkUI。访问localhost:4040/executors确认应用程序以分布式方式执行:

4040.png

截图后半部分的 Executors 表有两行用于两个执行器,一行用于驱动程序。根据 Completed Tasks 列,Executor 0 上完成了 104 个任务,Executor 1 上完成了 102 个任务,驱动程序上没有。 Executors 表将包含 PySpark 运行的类似条目,任务总数略高,但它们也应该相当均匀地分布在两个执行器之间。

总结

使用上一节中介绍的其他 Docker 和 Spark 实体更新之前的图表会导致图片非常拥挤:

Docker2.png

后续文章将演示我们的自定义镜像如何在 Kubernetes 上运行。

Logo

云原生社区为您提供最前沿的新闻资讯和知识内容

更多推荐