如何用 docker 在本地端建立 Spark Cluster

Spark 的特點之一就是 Unified,代表我們只有準備好 Spark cluster 的話,可以直接把 第一個 Spark Application 程式 run 在 cluster 上面,弄一套 Spark cluster 的選項有很多種,有錢的話直接找個雲服務商用他們的服務建立一個就搞定了,像是 AWS 的 EMR 或是 GCS 的 Dataproc,沒錢的話我們只能在 local 端以容器化的方式模擬 Spark cluster 出來了。

找找巨人的肩膀

準備 Spark 環境是最麻煩的地方!感謝 mvillarrealb 寫的 docker-spark-cluster github repo,按造他的步驟可以快速的建立成功 Spark Cluster,但因為我們想執行的版本為 Spark 3.5.4 和 python 3.12,原作者用的 openjdk image openjdk:11.0.11-jre-slim-buster 的 apt install 只能安裝到 python 3.9,

然後 openjdk 11 的 image 最新的 ubuntu 版本看起來只到 11 Bullseye,這個版的 apt install python 也是只到 3.9,

所以我只能基於 repo docker-spark-cluster 的內容,升級 python 和 Spark 版本,升級後的內容可參考我的 gitHub repo docker-spark-cluster

Docker Image 準備

基於方便,我順便把 第一個 Spark Application 程式中需要用到的 python library 直接在 dockerfile 中用 pip install 的方式裝好,Dockerfile 內容如下,

FROM openjdk:11-jre-slim-bullseye AS builder

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

RUN apt update -y && apt upgrade -y && apt install -y curl vim wget ssh net-tools ca-certificates build-essential gdb lcov pkg-config libbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev lzma lzma-dev tk-dev uuid-dev zlib1g-dev libmpdec-dev
RUN cd /opt && \
    wget "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz" && \
    tar -xzvf "Python-${PYTHON_VERSION}.tgz" && \
    cd Python-${PYTHON_VERSION} && \
    ./configure --enable-optimizations && \
    make -j `nproc` && \
    ln -s /opt/Python-${PYTHON_VERSION}/python /usr/local/bin/python && \
    ln -s /opt/Python-${PYTHON_VERSION}/python /usr/local/bin/python3 && \
    ln -s /opt/Python-${PYTHON_VERSION}/python /usr/local/bin/python3.12 && \
    rm ../Python-${PYTHON_VERSION}.tgz && \
    python -m ensurepip --upgrade && \
    pip3 install --upgrade setuptools && \
    pip3 install matplotlib==3.10.0 pandas==2.2.3




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 /opt/spark \
&& tar -xf apache-spark.tgz -C /opt/spark --strip-components=1 \
&& rm apache-spark.tgz


FROM builder AS apache-spark

WORKDIR /opt/spark

ENV SPARK_MASTER_PORT=7077 \
SPARK_MASTER_WEBUI_PORT=8080 \
SPARK_LOG_DIR=/opt/spark/logs \
SPARK_MASTER_LOG=/opt/spark/logs/spark-master.out \
SPARK_WORKER_LOG=/opt/spark/logs/spark-worker.out \
SPARK_WORKER_WEBUI_PORT=8080 \
SPARK_WORKER_PORT=7000 \
SPARK_MASTER="spark://spark-master:7077" \
SPARK_WORKLOAD="master" \
SPARK_WORKER_DIR="/opt/workspace"


EXPOSE 8080 7077 7000

RUN mkdir -p $SPARK_LOG_DIR && \
touch $SPARK_MASTER_LOG && \
touch $SPARK_WORKER_LOG && \
ln -sf /dev/stdout $SPARK_MASTER_LOG && \
ln -sf /dev/stdout $SPARK_WORKER_LOG

COPY start-spark.sh /

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

在來你就可以使用以下命令建立 image。

docker build -t cluster-apache-spark:3.5.4 .

使用 docker compose 啟動 Spark cluster

image 準備好了後,我們就可以試著用 docker compose 啟動 cluster,這次就先來啟動個 1 driver 2 executor 共 3 台機器的環境好了,

services:
  spark-master:
    image: cluster-apache-spark:3.5.4
    ports:
      - "9090:8080"
      - "7077:7077"
    volumes:
       - ./apps:/opt/workspace/apps
       - ./data:/opt/workspace/data
    environment:
      - SPARK_LOCAL_IP=spark-master
      - SPARK_WORKLOAD=master
  spark-worker-a:
    image: cluster-apache-spark:3.5.4
    ports:
      - "9091:8080"
      - "7002:7000"
    depends_on:
      - spark-master
    environment:
      - SPARK_MASTER=spark://spark-master:7077
      - SPARK_WORKER_CORES=1
      - SPARK_WORKER_MEMORY=1G
      - SPARK_DRIVER_MEMORY=1G
      - SPARK_EXECUTOR_MEMORY=1G
      - SPARK_WORKLOAD=worker
      - SPARK_LOCAL_IP=spark-worker-a
    volumes:
      - ./apps:/opt/workspace/apps
      - ./data:/opt/workspace/data
  spark-worker-b:
    image: cluster-apache-spark:3.5.4
    ports:
      - "9092:8080"
      - "7003:7000"
    depends_on:
      - spark-master
    environment:
      - SPARK_MASTER=spark://spark-master:7077
      - SPARK_WORKER_CORES=1
      - SPARK_WORKER_MEMORY=1G
      - SPARK_DRIVER_MEMORY=1G
      - SPARK_EXECUTOR_MEMORY=1G
      - SPARK_WORKLOAD=worker
      - SPARK_LOCAL_IP=spark-worker-b
    volumes:
      - ./apps:/opt/workspace/apps
      - ./data:/opt/workspace/data

docker-compose.yml 中可以看到,我把 repo 的 appdata 資料夾 mount 到 Spark cluster 的工作目錄下 /opt/workspace/,代表我們可以同步的修改程式以及取得輸出結果,

在來就可以用以下命令啟動 Spark cluster,

docker compose up -d

啟動完成後,你就可以用 http://localhost:9090/ 連到 Spark cluster 的 driver 上了,在 UI 上我們也可以看到這個 cluster 中有 2 台 worker 節點可以來執行任務。

spark submit application

最後,我們就可以把我們在 第一個 Spark Application 寫的程式,submit 在這個 Spark cluster 中執行了,

submit application 前,請先參考 在 第一個 Spark Application 文章,把 movie lens 的資料準備好才行!

命令如下:

docker exec docker-spark-cluster-spark-master-1 /opt/spark/bin/spark-submit \
--master spark://spark-master:7077 \
--driver-memory 1G \
--executor-memory 1G \
/opt/workspace/apps/main.py

submit 後,再進一次Spark UI http://localhost:9090/ 去看,就能看到 Running Application 中有我們剛剛 submit 進去的記錄了,

等它跑完,就去檢查一下 data/output 下有沒有結果輸出成功,

有的話,恭喜你已成功將你寫的程式執行在 Spark cluster 中了!

結語

我們基於 mvillarrealb 寫的 docker-spark-cluster github repo,升級 python 和 spark 版本到 3.12 和 3.5.2,然後用 docker compose 在 local 端啟動了 1 台 Driver 2 台 worker 的 Spark cluster 來執行我們在 第一個 Spark Application 寫的程式。

其實還有挺多可以優化的,像是 Java 版本我們可以用 15,還有就是打包小一點的 python 3.12 出來,現在這個 image 打包出來的 python 太大了,34 M 的樣子,導致整個 docker image size 到 2G 😅

看後面有沒有機會啦!

tshine73
tshine73
文章: 58

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *