目录

Spark的Dockerfile分析

1 Overview

Spark 容器化的前提是需要 Spark 的镜像文件,那么怎么 build 呢?Spark 官方是提供了 Dockerfile 的,并且也提供了脚本工具,可以自行 build 并发布到自己的 Restry 里。

2 Spark里的Kubernetes

2.1 Dockerfile

Spark 提供的 Dockerfile 可以在类似目录找到。

1
2
# tmp 是我放 Spark 的目录
/tmp/spark-2.4.2-bin-hadoop2.7/kubernetes/dockerfiles/spark

查看 Dockerfile 的内容,需要注意一下注释的内容,关键点在于构建命令 docker build...,需要在哪个目录去执行,注释写的很清楚,这里就不赘述了。然后一句句看下来再分析。

首先看第一部分是基础镜像。

1
FROM openjdk:8-alpine

第二部分是一些内部的临时变量 ARG。

1
2
3
ARG spark_jars=jars
ARG img_path=kubernetes/dockerfiles
ARG k8s_tests=kubernetes/tests

第三部分是一些命令。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# -e: 若指令传回值不等于0,则立即退出shell
# -x: 执行指令后,会先显示该指令及所下的参数
set -ex
# apk 是 alpine 提供的软件包管理工具
# upgrade --no-cache 重新更新已安装的软件包
# add 命令从仓库中安装最新软件包,并自动安装必须的依赖包,这里注意一下 tini,在 entrypoint.sh 中是很重要的角色
apk upgrade --no-cache
apk add --no-cache bash tini libc6-compat linux-pam nss
# 创建 Spark 相关的一些文件夹用来存放文件
mkdir -p /opt/spark
mkdir -p /opt/spark/work-dir
touch /opt/spark/RELEASE
# 这个是 linux 系统经常存在的配置,详细可以自行找找 /bin/sh 和 /bin/bash 的区别 
rm /bin/sh
ln -sv /bin/bash /bin/sh
echo "auth required pam_wheel.so use_uid" >> /etc/pam.d/su
chgrp root /etc/passwd
chmod ug+rw /etc/passwd

第四部分是将构建 Docker 镜像文件的时候,将本地文件 Copy 到镜像中。

1
2
3
4
5
6
7
COPY ${spark_jars} /opt/spark/jars
COPY bin /opt/spark/bin
COPY sbin /opt/spark/sbin
COPY ${img_path}/spark/entrypoint.sh /opt/
COPY examples /opt/spark/examples
COPY ${k8s_tests} /opt/spark/tests
COPY data /opt/spark/data

第五部分是设置环境变量和工作目录以及容器启动的脚本(后面也会分析这个脚本)。

1
2
3
4
5
ENV SPARK_HOME /opt/spark

WORKDIR /opt/spark/work-dir

ENTRYPOINT [ "/opt/entrypoint.sh" ]

2.2 Entrypoint

这个是容器的入口点,执行的是 COPY 进去镜像的 entrypoint.sh 文件。而这个脚本最后的命令是这样的。

1
exec /sbin/tini -s -- "${CMD[$]}"

打开内容可以看到,首先会根据容器启动的类型来初始化一些环节变量,并且根据启动的参数,来执行镜像里的脚本,并启动 Driver 或者 Executor,具体可以参看脚本内容。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 篇幅有限,有删减
case "$SPARK_K8S_CMD" in
  driver)
    CMD=(
      "$SPARK_HOME/bin/spark-submit"
      --conf "spark.driver.bindAddress=$SPARK_DRIVER_BIND_ADDRESS"
      --deploy-mode client
      "$@"
    )
    ;;
    # 删减开始
    ...
    ...
    ...
    # 删减结束
  executor)
    CMD=(
      ${JAVA_HOME}/bin/java
      "${SPARK_EXECUTOR_JAVA_OPTS[@]}"
      -Xms$SPARK_EXECUTOR_MEMORY
      -Xmx$SPARK_EXECUTOR_MEMORY
      -cp "$SPARK_CLASSPATH"
      org.apache.spark.executor.CoarseGrainedExecutorBackend
      --driver-url $SPARK_DRIVER_URL
      --executor-id $SPARK_EXECUTOR_ID
      --cores $SPARK_EXECUTOR_CORES
      --app-id $SPARK_APPLICATION_ID
      --hostname $SPARK_EXECUTOR_POD_IP
    )
    ;;

  *)
    echo "Unknown command: $SPARK_K8S_CMD" 1>&2
    exit 1
esac

关于 tini 的使用,我也是最近才了解的,给两个参考链接。

  1. http://yunke.science/2018/04/09/Tini-command/
  2. https://github.com/krallin/tini/issues/8

3 Summary

我们可以根据 Spark 提供的镜像构建的方式,来构建自己的 Spark 镜像,适合有定制化需求的团队。看完文章,大家可能会好奇,Spark 如何通过 K8S 的 Java 客户端来拉起镜像创建 Pod 的,后面会通过源码来继续分析。

警告
本文最后更新于 2017年2月1日,文中内容可能已过时,请谨慎参考。