3. 环境搭建:Docker Compose 部署 Hadoop、Spark、Flink、Kafka 集群
说实话,做实时数仓最头疼的往往不是写代码,而是搭环境。我见过太多人花了两周装集群,最后发现版本不兼容,又得重来。所以这一章,我带你用 Docker Compose 一把梭搞定。
你想想看,本地开发环境要是搞不定,后面所有实战都是空中楼阁。我个人习惯是把基础设施容器化,这样团队新同学拉起来就能跑,不用在每个人的电脑上折腾 JDK 版本、SSH 免密这些破事。
本章目标:用 Docker Compose 编排一套完整的 Lakehouse 底座,包含 HDFS、YARN、Hive Metastore、Spark、Flink、Kafka、ZooKeeper。全部容器化,一键启动。
3.1 整体架构与组件选型
先看整体拓扑。我画了一张图,帮你理清每个组件的位置和关系。
这张图分四层:存储层(HDFS + Hive Metastore)、计算层(Spark + Flink + YARN)、消息层(Kafka + ZooKeeper)、接入与调度层。每层之间通过内部网络通信,端口映射到宿主机方便调试。
3.2 版本选择与兼容性
版本选型是个坑。我踩过最大的坑是 Flink 1.14 和 Hadoop 3.3 的 Shaded Guava 冲突,折腾了两天。后来我总结了一套经过验证的组合:
| 组件 | 版本 | 说明 |
|---|---|---|
| Hadoop | 3.3.6 | 支持 EC 纠删码,适合 Lakehouse 存储 |
| Spark | 3.5.1 | 内置 Iceberg 支持,Hive 兼容性好 |
| Flink | 1.19.0 | 最新稳定版,CDC 和 Paimon 集成最佳 |
| Kafka | 3.7.0 | KRaft 模式可选,但这里用 ZooKeeper 模式更稳 |
| ZooKeeper | 3.9.1 | Kafka 和 HBase 共用 |
| Hive Metastore | 3.1.3 | 独立部署,后端用 PostgreSQL |
我的建议:别追最新版。比如 Flink 1.20 刚出时我试过,和 Paimon 0.8 有序列化兼容问题。选经过社区验证的组合,省心。
3.3 目录结构与配置文件
我习惯把每个组件的配置分开,再用一个根 docker-compose.yml 聚合。这样改某个组件时不会影响其他。
lakehouse-docker/
├── docker-compose.yml # 主编排文件
├── .env # 环境变量
├── config/
│ ├── hadoop/
│ │ ├── core-site.xml
│ │ ├── hdfs-site.xml
│ │ └── yarn-site.xml
│ ├── spark/
│ │ └── spark-defaults.conf
│ ├── flink/
│ │ └── flink-conf.yaml
│ └── kafka/
│ └── server.properties
├── data/ # 持久化数据目录
│ ├── hdfs/
│ ├── kafka/
│ └── postgres/
└── scripts/
├── init-hive.sh # 初始化 Hive Metastore
└── create-topics.sh # 创建 Kafka Topic
嗯,这里要注意:data/ 目录要提前创建好,否则 Docker 会以 root 权限创建,后面 HDFS DataNode 写权限会出问题。我曾经因为这个排查了半天。
3.4 核心 Docker Compose 配置
直接上干货。这是 docker-compose.yml 的核心片段,我删掉了重复的服务定义,保留了关键配置。
version: '3.8'
services:
# ========== 存储层 ==========
namenode:
image: bde2020/hadoop-namenode:3.3.6
hostname: namenode
environment:
- CLUSTER_NAME=lakehouse
- CORE_CONF_fs_defaultFS=hdfs://namenode:8020
ports:
- "9870:9870" # HDFS Web UI
volumes:
- ./data/hdfs/namenode:/hadoop/dfs/name
networks:
- lakehouse-net
datanode1:
image: bde2020/hadoop-datanode:3.3.6
hostname: datanode1
environment:
- CORE_CONF_fs_defaultFS=hdfs://namenode:8020
volumes:
- ./data/hdfs/datanode1:/hadoop/dfs/data
depends_on:
- namenode
networks:
- lakehouse-net
# ========== 计算层 ==========
spark-master:
image: bitnami/spark:3.5.1
hostname: spark-master
environment:
- SPARK_MODE=master
- SPARK_MASTER_HOST=spark-master
ports:
- "8080:8080" # Spark Web UI
- "7077:7077" # Spark Master RPC
networks:
- lakehouse-net
flink-jobmanager:
image: flink:1.19.0-scala_2.12
hostname: flink-jobmanager
ports:
- "8081:8081" # Flink Web UI
command: jobmanager
environment:
- JOB_MANAGER_RPC_ADDRESS=flink-jobmanager
networks:
- lakehouse-net
# ========== 消息层 ==========
zookeeper1:
image: confluentinc/cp-zookeeper:7.6.0
hostname: zookeeper1
environment:
- ZOOKEEPER_SERVER_ID=1
- ZOOKEEPER_CLIENT_PORT=2181
volumes:
- ./data/zookeeper/1:/var/lib/zookeeper/data
networks:
- lakehouse-net
kafka1:
image: confluentinc/cp-kafka:7.6.0
hostname: kafka1
depends_on:
- zookeeper1
environment:
- KAFKA_BROKER_ID=1
- KAFKA_ZOOKEEPER_CONNECT=zookeeper1:2181
- KAFKA_ADVERTISED_LISTENERS=PLAINTEXT://localhost:9092
ports:
- "9092:9092"
volumes:
- ./data/kafka/1:/var/lib/kafka/data
networks:
- lakehouse-net
networks:
lakehouse-net:
driver: bridge
避坑指南:Kafka 的 ADVERTISED_LISTENERS 一定要配成宿主机地址或 localhost,否则客户端连不上。我曾经配成容器名,结果 Flink 消费时一直报连接超时。
3.5 启动与验证
配置写好了,怎么启动?我一般分三步走:
- 拉取镜像:
docker compose pull,提前下好所有镜像,避免启动时超时。 - 启动集群:
docker compose up -d,后台运行。 - 检查状态:
docker compose ps,确保所有服务都是 Up 状态。
启动后,访问这些地址验证:
| 组件 | Web UI 地址 | 验证方法 |
|---|---|---|
| HDFS | http://localhost:9870 | 查看 DataNode 是否存活 |
| Spark | http://localhost:8080 | 查看 Worker 是否注册 |
| Flink | http://localhost:8081 | 查看 TaskManager 数量 |
| Kafka | 无 UI | docker exec kafka1 kafka-topics --list --bootstrap-server localhost:9092 |
小技巧:我习惯在 .env 文件里定义版本号,这样升级时只改一个文件。比如 FLINK_VERSION=1.19.0,然后在 docker-compose.yml 里引用 ${FLINK_VERSION}。
3.6 资源限制与调优
本地开发环境资源有限,不加限制的话,Flink 和 Spark 会抢光你的内存。我建议在 docker-compose.yml 里加上资源限制:
services:
flink-jobmanager:
deploy:
resources:
limits:
memory: 2g
cpus: '1.0'
environment:
- JOB_MANAGER_MEMORY=1024m
flink-taskmanager:
deploy:
resources:
limits:
memory: 4g
cpus: '2.0'
environment:
- TASK_MANAGER_MEMORY=2048m
spark-worker:
deploy:
resources:
limits:
memory: 4g
cpus: '2.0'
environment:
- SPARK_WORKER_MEMORY=2g
- SPARK_WORKER_CORES=2
为什么这么配?Flink JobManager 主要做调度,1GB 足够;TaskManager 要跑算子,给 2GB。Spark Worker 同理。你想想看,要是笔记本只有 16GB 内存,不限制的话,三个 Kafka Broker 就能吃掉 6GB。
3.7 网络与端口规划
端口冲突是家常便饭。我整理了一张端口映射表,方便你对照:
| 端口 | 组件 | 说明 |
|---|---|---|
| 9870 | HDFS NameNode | Web UI |
| 9864 | HDFS DataNode | 数据传输 |
| 8088 | YARN ResourceManager | Web UI |
| 8080 | Spark Master | Web UI |
| 8081 | Flink JobManager | Web UI |
| 9092 | Kafka Broker 1 | 客户端连接 |
| 9093 | Kafka Broker 2 | 客户端连接 |
| 2181 | ZooKeeper | 客户端连接 |
注意:如果你本地已经跑了 Kafka 或 ZooKeeper,端口会冲突。我建议要么停掉本地服务,要么改映射端口,比如 Kafka 映射到 19092。
3.8 持久化与数据安全
容器重启后数据会丢,这肯定不行。所以我把所有重要数据都挂载到宿主机目录:
- HDFS 数据:挂载到
./data/hdfs/,NameNode 和 DataNode 分开目录。 - Kafka 数据:挂载到
./data/kafka/,每个 Broker 独立目录。 - PostgreSQL 数据:挂载到
./data/postgres/,存 Hive Metastore 的元数据。 - ZooKeeper 数据:挂载到
./data/zookeeper/,避免选举状态丢失。
另外,我建议把 data/ 目录加到 .gitignore 里,否则 git 会追踪这些二进制文件,导致仓库臃肿。
3.9 常见问题与排查
环境搭建总会遇到问题。我列几个高频坑:
问题 1:HDFS DataNode 启动失败
原因:DataNode 的 dfs.datanode.data.dir 权限不对。容器内 UID 是 1000,但宿主机目录是 root 所有。
解决:sudo chown -R 1000:1000 ./data/hdfs/datanode1
问题 2:Flink 连不上 Kafka
原因:Kafka 的 ADVERTISED_LISTENERS 配成了容器名,Flink 在容器外无法解析。
解决:改成 PLAINTEXT://localhost:9092,或者用宿主机 IP。
问题 3:Spark 提交任务报 ClassNotFoundException
原因:Spark 和 Hadoop 的 Guava 版本冲突。
解决:在 spark-defaults.conf 里加 spark.driver.userClassPathFirst=true。
嗯,这些问题我基本都遇到过。尤其是 Guava 冲突,几乎每个版本升级都要折腾一次。后来我干脆把 Spark 的 spark.driver.userClassPathFirst 和 spark.executor.userClassPathFirst 都设成 true,省心。
3.10 一键启动脚本
最后,我写了个启动脚本,把初始化工作自动化:
#!/bin/bash
# start-lakehouse.sh
echo "🚀 启动 Lakehouse 集群..."
# 1. 创建数据目录
mkdir -p data/{hdfs/{namenode,datanode1,datanode2},kafka/{1,2,3},postgres,zookeeper/{1,2,3}}
# 2. 设置权限
sudo chown -R 1000:1000 data/hdfs data/kafka data/postgres data/zookeeper
# 3. 启动所有服务
docker compose up -d
# 4. 等待服务就绪
echo "⏳ 等待服务就绪..."
sleep 30
# 5. 初始化 Hive Metastore
docker exec -it hive-metastore /opt/hive/bin/schematool -dbType postgres -initSchema
# 6. 创建 Kafka Topic
docker exec kafka1 kafka-topics --create \
--topic lakehouse-orders \
--partitions 3 \
--replication-factor 2 \
--bootstrap-server localhost:9092
echo "✅ 集群启动完成!"
echo "HDFS: http://localhost:9870"
echo "Spark: http://localhost:8080"
echo "Flink: http://localhost:8081"
把这个脚本放到项目根目录,以后启动集群就一行命令:./start-lakehouse.sh。干净利落。
我的习惯:我还会写一个 stop-lakehouse.sh,里面执行 docker compose down -v。注意 -v 会删除 volume,如果你不想丢数据,就别加。