使用 Docker Compose 部署大数据集群:Spark + Hadoop + Hive + JupyterLab

麻雀虽小,五脏俱全。

GitHub 项目地址:bigdata-env

引子

我有两台树莓派。

凭借超高颜值,安安静静地摆在那里,就是超绝桌搭。担心没有实用性?有的兄弟,包有的。装上 JupyterLab,它是数据科学工作站;装上 Kali Linux,它是黑客界的瑞士军刀;装上 llama.cpp,它是你的私有 LLM 推理服务器。

raspberry_pi_cluster

你可能不屑一顾,这些事一台树莓派也能搞定,何必弄两台呢?看来只有做一些两台机器才能办到的事,才算物尽其用。因此,我计划部署一个大数据集群。部署大数据集群是个技术活,不要期待一开始就在硬件上部署。可以先在 Docker Compose 上跑通,再尝试硬件部署。

Note: 如果对装机过程感兴趣,可以参考我的博客《树莓派 5 装机指南》

一、集群功能

「大数据集群」是个宽泛的词儿。

装了 Spark 的,可以是大数据集群;装了 Spark + Hadoop 的,也可以是大数据集群。那么到底要装到什么程度呢?这取决于我们的需求。作为常年耕作在数据一线的工程师,我认为基础需求包括:

  • 必须能分布式计算
  • 必须能分布式存储
  • 必须可以建分区表
  • 必须能使用分布式机器学习库 pyspark.ml
  • 必须有调度工具(当然 crontab 也算)
  • 必须有 JupyterLab

我的目标是搭建一个满足这些需求的集群。当然,我们没有必要从零开始搭建,因为前人已经有很优秀的工作。只需要基于前人的工作做一些拓展,就能实现我们想要的功能。s1mplecc 大佬的《使用 Docker 快速部署 Spark + Hadoop 大数据集群》就是一个很棒的基础工作。

大佬已经实现了 Spark + Hadoop 集群,我只需要新增以下内容:

  1. 沿用一主(Master)二从(Worker)的配置
  2. 启用 Worker 节点的 DataNode 以支持数据同步
  3. 安装 Hive 环境
  4. 安装并初始化 miniconda3
  5. 安装与 Spark 版本配适的 Python 和 PySpark
  6. 安装 JupyterLab

主要增加了 Hive、JupyterLab 和 miniconda3。Hive 让我们更轻松地管理分区表;JupyterLab 提供了用于调试开发的浏览器界面;miniconda3 用作 Python 包管理。

二、稍作讲解

Docker Compose 使用 docker-compose.yml 文件定义了容器的环境变量、数据卷、网络、依赖关系等配置。并允许用户使用 Dockerfile 文件制作自定义镜像。这两个文件是本项目的核心,下面我将介绍这两个文件的具体作用。

Note: 完整部署代码见 GitHub 仓库:luochang212/bigdata-env

1)docker-compose.yml 文件

下面是 docker-compose.yml 文件:

services:
  mysql:
    image: mysql:8.0
    # platform: linux/amd64
    hostname: mysql
    container_name: hive-mysql
    networks:
      - bigdata-base-network
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=hive_metastore
      - MYSQL_USER=hive
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - '3306:3306'
    command: --default-authentication-plugin=mysql_native_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      timeout: 10s
      retries: 3
      interval: 10s
      start_period: 20s

  spark:
    build:
      context: ./
      dockerfile: Dockerfile
    image: spark-hadoop-hive:latest
    # platform: linux/amd64
    hostname: master
    container_name: spark-hadoop-hive-master
    networks:
      - bigdata-base-network
    depends_on:
      mysql:
        condition: service_healthy
    environment:
      - SPARK_MODE=master
      - SPARK_RPC_AUTHENTICATION_ENABLED=no
      - SPARK_RPC_ENCRYPTION_ENABLED=no
      - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no
      - SPARK_SSL_ENABLED=no
      - HIVE_DB_USER=hive
      - HIVE_DB_PASSWORD=${HIVE_DB_PASSWORD}
    volumes:
      - ./share:/opt/share
    ports:
      - '8080:8080'
      - '4040:4040'
      - '7077:7077'
      - '8088:8088'
      - '8042:8042'
      - '9870:9870'
      - '19888:19888'
      - '9083:9083'
      - '10000:10000'
      - '8888:8888'
    command: bash /opt/start-master.sh

  spark-worker-1:
    image: spark-hadoop-hive:latest
    # platform: linux/amd64
    hostname: worker1
    container_name: spark-hadoop-hive-worker-1
    networks:
      - bigdata-base-network
    depends_on:
      - spark
    environment:
      - SPARK_MODE=worker
      - SPARK_MASTER_URL=spark://master:7077
      - SPARK_WORKER_MEMORY=1G
      - SPARK_WORKER_CORES=1
      - SPARK_RPC_AUTHENTICATION_ENABLED=no
      - SPARK_RPC_ENCRYPTION_ENABLED=no
      - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no
      - SPARK_SSL_ENABLED=no
    volumes:
      - ./share:/opt/share
    ports:
      - '8081:8081'
    command: bash /opt/start-worker.sh

  spark-worker-2:
    image: spark-hadoop-hive:latest
    # platform: linux/amd64
    hostname: worker2
    container_name: spark-hadoop-hive-worker-2
    networks:
      - bigdata-base-network
    depends_on:
      - spark
    environment:
      - SPARK_MODE=worker
      - SPARK_MASTER_URL=spark://master:7077
      - SPARK_WORKER_MEMORY=1G
      - SPARK_WORKER_CORES=1
      - SPARK_RPC_AUTHENTICATION_ENABLED=no
      - SPARK_RPC_ENCRYPTION_ENABLED=no
      - SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED=no
      - SPARK_SSL_ENABLED=no
    volumes:
      - ./share:/opt/share
    ports:
      - '8082:8081'
    command: bash /opt/start-worker.sh

networks:
  bigdata-base-network:
    driver: bridge

volumes:
  mysql_data:

该文件创建了四个容器、一个网络、一个数据卷。

  • 容器:
    • mysql:用于存储 Hive 表的元数据
    • spark:主节点
    • spark-worker-1:工作节点 1 号
    • spark-worker-2:工作节点 2 号
  • 网络:
    • bigdata-base-network:启用桥接(bridge)模式
  • 数据卷:
    • mysql_datamysql 容器挂载的数据卷

2)Dockerfile 文件

下面是 Dockerfile 文件:

FROM --platform=$BUILDPLATFORM docker.io/bitnami/spark:3
LABEL maintainer="luochang212"
LABEL description="Docker image with Spark (3.3.0) and Hadoop (3.3.2), based on bitnami/spark:3. \
For more information, please visit https://github.com/luochang212/bigdata-env."

USER root

ENV HADOOP_HOME="/opt/hadoop"
ENV HADOOP_CONF_DIR="$HADOOP_HOME/etc/hadoop"
ENV HADOOP_LOG_DIR="/var/log/hadoop"
ENV HIVE_HOME="/opt/hive"
ENV HIVE_CONF_DIR="$HIVE_HOME/conf"
ENV MINICONDA_HOME="/opt/miniconda3"
ENV PATH="$MINICONDA_HOME/bin:$HADOOP_HOME/sbin:$HADOOP_HOME/bin:$HIVE_HOME/bin:$PATH"

WORKDIR /opt

RUN apt-get update && apt-get install -y openssh-server

# Create spark user for Spark services
RUN useradd -r -m -s /bin/bash spark && \
    usermod -aG sudo spark

RUN ssh-keygen -t rsa -f /root/.ssh/id_rsa -P '' && \
    cat /root/.ssh/id_rsa.pub >> /root/.ssh/authorized_keys

# RUN curl -OL https://archive.apache.org/dist/hadoop/common/hadoop-3.3.2/hadoop-3.3.2.tar.gz
RUN curl -OL https://mirrors.huaweicloud.com/apache/hadoop/common/hadoop-3.3.2/hadoop-3.3.2.tar.gz
RUN tar -xzvf hadoop-3.3.2.tar.gz && \
  mv hadoop-3.3.2 hadoop && \
  rm -rf hadoop-3.3.2.tar.gz && \
  mkdir /var/log/hadoop

# Install Hive
RUN curl -OL https://mirrors.huaweicloud.com/apache/hive/hive-3.1.3/apache-hive-3.1.3-bin.tar.gz
RUN tar -xzvf apache-hive-3.1.3-bin.tar.gz && \
  mv apache-hive-3.1.3-bin hive && \
  rm -rf apache-hive-3.1.3-bin.tar.gz

# Install Miniconda
RUN curl -OL https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh && \
    bash Miniconda3-latest-Linux-x86_64.sh -b -p $MINICONDA_HOME && \
    rm -rf Miniconda3-latest-Linux-x86_64.sh && \
    . $MINICONDA_HOME/bin/activate && \
    conda init --all
    # conda config --set auto_activate_base false

# Install JupyterLab and common data science packages
RUN /bin/bash -c "source $MINICONDA_HOME/bin/activate && \
    conda config --set channel_priority flexible && \
    conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main && \
    conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r && \
    conda install -n base -y python=3.9 jupyterlab ipykernel && \
    pip install --no-cache-dir pyspark==3.3.0 && \
    conda clean -a -y"

# Install MySQL connector for Hive metastore
RUN curl -OL https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar && \
  mv mysql-connector-j-8.0.33.jar $HIVE_HOME/lib/mysql-connector-java.jar

RUN mkdir -p /root/hdfs/namenode && \
    mkdir -p /root/hdfs/datanode 

COPY config/* /tmp/

RUN mv /tmp/ssh_config /root/.ssh/config && \
    mv /tmp/hadoop-env.sh $HADOOP_CONF_DIR/hadoop-env.sh && \
    mv /tmp/hdfs-site.xml $HADOOP_CONF_DIR/hdfs-site.xml && \
    mv /tmp/core-site.xml $HADOOP_CONF_DIR/core-site.xml && \
    mv /tmp/mapred-site.xml $HADOOP_CONF_DIR/mapred-site.xml && \
    mv /tmp/yarn-site.xml $HADOOP_CONF_DIR/yarn-site.xml && \
    mv /tmp/workers $HADOOP_CONF_DIR/workers && \
    mv /tmp/hive-site.xml $HIVE_CONF_DIR/hive-site.xml

COPY start-master.sh /opt/start-master.sh
COPY start-worker.sh /opt/start-worker.sh
COPY set-conda-env.sh /opt/set-conda-env.sh
COPY set-jupyter-env.sh /opt/set-jupyter-env.sh
COPY jupyter_server_config.py /opt/jupyter_server_config.py

RUN chmod +x /opt/start-master.sh && \
    chmod +x /opt/start-worker.sh && \
    chmod +x $HADOOP_HOME/sbin/start-dfs.sh && \
    chmod +x $HADOOP_HOME/sbin/start-yarn.sh && \
    chmod +x /opt/set-conda-env.sh && \
    chmod +x /opt/set-jupyter-env.sh && \
    chmod +x /opt/jupyter_server_config.py

RUN hdfs namenode -format
RUN sed -i "1 a /etc/init.d/ssh start > /dev/null &" /opt/bitnami/scripts/spark/entrypoint.sh

ENTRYPOINT [ "/opt/bitnami/scripts/spark/entrypoint.sh" ]
CMD [ "/opt/bitnami/scripts/spark/run.sh" ]

使用 Dockerfile 文件可以制作自定义镜像,从而创建自定义容器。从 docker-compose.yml 文件可以看出 sparkspark-worker-1spark-worker-2 三个容器共用一个 Dockerfile 文件。也就是说,这三个容器的镜像是相同的,只是环境变量和启动命令有所不同。

Dockerfile 的主要工作是软件安装,它在 bitnami/spark:3 镜像的基础上安装了 Hadoop、Hive、miniconda3、JupyterLab 等软件。

三、本地运行

如果你希望在本地运行本文创建的大数据集群,请参考以下步骤。

1)克隆仓库

git clone https://github.com/luochang212/bigdata-env.git
cd bigdata-env

2)配置 .env 文件

.env.example 为模板,创建你的 .env 文件:

cp .env.example .env

编辑 .env 文件,改成你的密码(可选):

MYSQL_ROOT_PASSWORD=[your-mysql-root-password]
MYSQL_PASSWORD=[your-mysql-password]
HIVE_DB_PASSWORD=[your-hive-db-password]

⚠️ 注意

  • 若修改 .envMYSQL_PASSWORD 的密码,需要将 hive-site.xmljavax.jdo.option.ConnectionPassword 的密码同步修改为你的新密码
  • 由于 hive-site.xml 无法使用 .env 配置环境变量,因此该文件中依然包含明文密码。请注意不要在上传 hive-site.xml 文件时泄漏密码

3)启动 docker 服务

启动服务之前,请确保已经安装并启动了 docker 服务。中国大陆地区需要额外配置一下镜像源,否则无法正常下载镜像文件。配置镜像源的方法在 《耶是 ClickHouse!我们有救了!!》 一文中有详细介绍。

⚠️ 注意

为避免脚本在 docker 中运行出错,Windows 用户 请先运行以下脚本:

.\convert.ps1

此脚本 将项目下 Windows 风格的换行符转换为 Unix 风格的换行符。

构建镜像:

docker compose build --no-cache

启动所有服务:

docker compose up -d

完全启动大约需要 30 秒,如果觉得等待无聊,可以打印实时日志:

docker compose logs -f

4)打开 JupyterLab

在浏览器打开 JupyterLab:http://localhost:8888/lab

这里需要输入 token 以获得访问权限,执行以下命令获取 token:

docker compose exec -it spark jupyter lab list

进入 JupyterLab 后,使用 JupyterLab 的终端即可访问大数据环境。

# 验证 Hive 服务可用
hive -e 'SHOW DATABASES;'

# 确认 Spark 服务可用
pyspark

# 也可启动 Spark 交互式命令行
spark-shell