从 Dockerfile CMD 调用 sh 文件时无法在 docker 容器内设置环境变量

Unable to set environment variable inside docker container when calling sh file from Dockerfile CMD

我正在关注 this link 创建一个 spark 集群。我能够 运行 spark 集群。但是,我必须给出一个绝对路径开始spark-shell。我正在尝试设置环境变量,即 PATHstart-shell.sh 中的一些其他变量。但是,它没有在容器内设置它。我尝试在容器内使用 printenv 打印它。但这些变量从未反映出来。

我是否尝试错误地设置环境变量?不过,Spark 集群已 运行ning 成功。

我正在使用 docker-compose.yml 构建和重新创建图像和容器。

docker-compose up --build

Docker 文件

# builder step used to download and configure spark environment
FROM openjdk:11.0.11-jre-slim-buster as builder

# Add Dependencies for PySpark
RUN apt-get update && apt-get install -y curl vim wget software-properties-common ssh net-tools ca-certificates python3 python3-pip python3-numpy python3-matplotlib python3-scipy python3-pandas python3-simpy

# JDBC driver download and install
ADD https://go.microsoft.com/fwlink/?linkid=2168494 /usr/share/java

RUN update-alternatives --install "/usr/bin/python" "python" "$(which python3)" 1

# Fix the value of PYTHONHASHSEED
# Note: this is needed when you use Python 3.3 or greater
ENV SPARK_VERSION=3.1.2 \
HADOOP_VERSION=3.2 \
SPARK_HOME=/opt/spark \
PYTHONHASHSEED=1

# Download and uncompress spark from the apache archive
RUN wget --no-verbose -O apache-spark.tgz "https://archive.apache.org/dist/spark/spark-${SPARK_VERSION}/spark-${SPARK_VERSION}-bin-hadoop${HADOOP_VERSION}.tgz" \
&& mkdir -p ${SPARK_HOME} \
&& tar -xf apache-spark.tgz -C ${SPARK_HOME} --strip-components=1 \
&& rm apache-spark.tgz

我的 Dockerfile-spark

在 Dockerfile 中的 ENV 下使用 SPARK_BIN="${SPARK_HOME}/bin/ 时,环境变量已设置。使用 printenv

在 docker 容器内可见
FROM apache-spark

WORKDIR ${SPARK_HOME}

ENV SPARK_MASTER_PORT=7077 \
SPARK_MASTER_WEBUI_PORT=8080 \
SPARK_LOG_DIR=${SPARK_HOME}/logs \
SPARK_MASTER_LOG=${SPARK_HOME}/logs/spark-master.out \
SPARK_WORKER_LOG=${SPARK_HOME}/logs/spark-worker.out \
SPARK_WORKER_WEBUI_PORT=8080 \
SPARK_MASTER="spark://spark-master:7077" \
SPARK_WORKLOAD="master"        

COPY start-spark.sh /

CMD ["/bin/bash", "/start-spark.sh"]

开始-spark.sh

#!/bin/bash
. "$SPARK_HOME/bin/load-spark-env.sh"

export SPARK_BIN="${SPARK_HOME}/bin/" # This doesn't work here
export PATH="${SPARK_HOME}/bin/:${PATH}" # This doesn't work here      

# When the spark work_load is master run class org.apache.spark.deploy.master.Master
if [ "$SPARK_WORKLOAD" == "master" ];
then

export SPARK_MASTER_HOST=`hostname`           # This works here

cd $SPARK_BIN && ./spark-class org.apache.spark.deploy.master.Master --ip $SPARK_MASTER_HOST --port $SPARK_MASTER_PORT --webui-port $SPARK_MASTER_WEBUI_PORT >> $SPARK_MASTER_LOG. 

我的文件结构是

从主容器内部

root@3abbd4508121:/opt/spark# export
declare -x HADOOP_VERSION="3.2"
declare -x HOME="/root"
declare -x HOSTNAME="3abbd4508121"
declare -x JAVA_HOME="/usr/local/openjdk-11"
declare -x JAVA_VERSION="11.0.11+9"
declare -x LANG="C.UTF-8"
declare -x OLDPWD
declare -x PATH="/usr/local/openjdk-11/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
declare -x PWD="/opt/spark"
declare -x PYTHONHASHSEED="1"
declare -x SHLVL="1"
declare -x SPARK_HOME="/opt/spark"
declare -x SPARK_LOCAL_IP="spark-master"
declare -x SPARK_LOG_DIR="/opt/spark/logs"
declare -x SPARK_MASTER="spark://spark-master:7077"
declare -x SPARK_MASTER_LOG="/opt/spark/logs/spark-master.out"
declare -x SPARK_MASTER_PORT="7077"
declare -x SPARK_MASTER_WEBUI_PORT="8080"
declare -x SPARK_VERSION="3.1.2"
declare -x SPARK_WORKER_LOG="/opt/spark/logs/spark-worker.out"
declare -x SPARK_WORKER_WEBUI_PORT="8080"
declare -x SPARK_WORKLOAD="master"
declare -x TERM="xterm"
root@3abbd4508121:/opt/spark# 

在 Docker 中设置环境变量有几种不同的方法,运行 进程也有几种不同的方法。一个容器通常 运行 一个进程,由图像的 ENTRYPOINTCMD 设置控制。如果您 docker exec 容器中的第二个进程,它不会 运行 作为主进程的子进程,并且不会看到由该主进程设置的环境变量。

在此处显示的设置中,start-spark.sh 脚本是主要的容器进程(它是图像的 CMD)。如果您 docker exec your-container printenv,它将看到 Dockerfile 中设置的内容,但看不到此脚本中设置的内容。

每次您 运行 容器时,文件系统路径之类的东西通常都会被修复,无论您在那里 运行 使用什么命令,因此您可以在 Docker文件

ENV SPARK_BIN=${SPARK_HOME}/bin PATH=${SPARK_BIN}:${PATH}

您可以在 Docker 文件中同时指定 ENTRYPOINTCMD;如果这样做,CMD 将作为参数传递给 ENTRYPOINT。这导致了一个有用的模式,其中 CMD 是一个标准的 shell 命令,而 ENTRYPOINT 是一个包装器,它进行首次设置然后 运行s 它。您可以将脚本分成两部分:

#!/bin/sh
# spark-env.sh
. "${SPARK_BIN}/load-spark-env.snh"
exec "$@"
#!/bin/sh
# start-spark.sh
spark-class org.apache.spark.deploy.master.Master \
  --ip "$SPARK_MASTER_HOST" \
  --port "$SPARK_MASTER_PORT" \
  --webui-port "$SPARK_MASTER_WEBUI_PORT"

然后在您的Docker文件中指定这两个部分

COPY spark-env.sh start-spark.sh /
ENTRYPOINT ["/spark-env.sh"] # must be JSON-array syntax
CMD ["/start-spark.sh"] # or any other valid CMD

这对您的调试很有用,因为可以直接覆盖 docker rundocker-compose run 指令中的 CMD,而保留 ENTRYPOINT

docker-compose run spark \
  printenv

这将基于所有相同的 Docker 文件设置启动一个新容器。当它 运行s 时,它 运行s printenv 而不是图像中的 CMD 。这将在 ENTRYPOINT 脚本中进行首次设置,然后最后的 exec "$@" 行将 运行 printenv 而不是启动 Spark 应用程序。这将向您显示应用程序启动时的环境。