4、环境准备:Docker Compose搭建Kafka + MinIO + Spark + Iceberg环境

说实话,做数据管道最头疼的事,就是搭环境。

我记得刚接触Iceberg那会儿,光装依赖就折腾了两天。后来我学乖了——能用Docker Compose解决的,绝不动手装。这一节,我就带你用一套编排文件,把Kafka、MinIO、Spark和Iceberg全部跑起来。

本章目标:你将在本地启动一个完整的实时湖仓实验环境。所有组件容器化,一键部署,用完即毁。

4.1 整体架构

先看看我们要搭什么。说白了就是四层:

  • 存储层:MinIO(兼容S3的对象存储),Iceberg的表文件就放这里
  • 消息层:Kafka + Zookeeper,负责流数据的摄入
  • 计算层:Spark,负责读写Iceberg表
  • 元数据层:Iceberg的Hive Metastore(用PostgreSQL做后端),管理表结构

我画了张图,你看一眼就明白了:

Docker Compose 实验环境架构 Kafka Producer (模拟数据源) Kafka Broker (消息队列) Spark (计算引擎) Hive Metastore (元数据管理) MinIO (S3对象存储) PostgreSQL (Metastore后端) 数据流方向 Producer → Kafka → Spark → MinIO (Iceberg表文件) 元数据流 Spark ↔ Metastore (表结构/分区信息)

我的建议:如果你机器内存小于8GB,可以把Spark的executor内存调小一点。我曾在4GB的笔记本上跑过,把spark.executor.memory改成1g也能用,就是慢点。

4.2 编写docker-compose.yml

好,直接上代码。这是我反复打磨过的一个版本,每个组件的版本都经过验证:

version: '3.8'

services:
  # ---------- 存储层 ----------
  minio:
    image: minio/minio:RELEASE.2023-09-04T19-57-37Z
    container_name: ice-minio
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: password
    command: server /data --console-address ":9001"
    volumes:
      - minio-data:/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ---------- 元数据层 ----------
  postgres:
    image: postgres:13
    container_name: ice-postgres
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: metastore
      POSTGRES_USER: hive
      POSTGRES_PASSWORD: hive
    volumes:
      - pg-data:/var/lib/postgresql/data

  hive-metastore:
    image: apache/hive:4.0.0
    container_name: ice-hive-metastore
    ports:
      - "9083:9083"
    environment:
      SERVICE_NAME: metastore
      DB_DRIVER: postgres
      DB_HOST: postgres
      DB_PORT: 5432
      DB_DATABASE: metastore
      DB_USER: hive
      DB_PASSWORD: hive
    depends_on:
      postgres:
        condition: service_started

  # ---------- 消息层 ----------
  zookeeper:
    image: confluentinc/cp-zookeeper:7.5.0
    container_name: ice-zk
    ports:
      - "2181:2181"
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000

  kafka:
    image: confluentinc/cp-kafka:7.5.0
    container_name: ice-kafka
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
    depends_on:
      - zookeeper

  # ---------- 计算层 ----------
  spark:
    image: bitnami/spark:3.5.0
    container_name: ice-spark
    ports:
      - "8080:8080"
      - "7077:7077"
    environment:
      SPARK_MODE: master
      SPARK_MASTER_URL: spark://spark:7077
      SPARK_RPC_AUTHENTICATION_ENABLED: "no"
      SPARK_RPC_ENCRYPTION_ENABLED: "no"
      SPARK_LOCAL_STORAGE_ENCRYPTION_ENABLED: "no"
    volumes:
      - ./iceberg-spark-runtime-3.5_2.12-1.4.3.jar:/opt/bitnami/spark/jars/iceberg-spark-runtime.jar
      - ./aws-java-sdk-bundle-1.12.262.jar:/opt/bitnami/spark/jars/aws-bundle.jar
      - ./hadoop-aws-3.3.4.jar:/opt/bitnami/spark/jars/hadoop-aws.jar
    depends_on:
      minio:
        condition: service_healthy
      hive-metastore:
        condition: service_started

volumes:
  minio-data:
  pg-data:

注意:Spark镜像里默认没有Iceberg和AWS的依赖包。你需要手动下载三个jar包放到当前目录下。我曾经忘了放aws-bundle.jar,结果Spark连MinIO一直报403,排查了半天才发现是S3客户端没加载进去。

4.3 下载依赖JAR包

这三个jar包缺一不可。我列个表,你直接去Maven仓库搜:

JAR包名称 版本 作用
iceberg-spark-runtime-3.5_2.12-1.4.3.jar 1.4.3 Iceberg与Spark的集成运行时
aws-java-sdk-bundle-1.12.262.jar 1.12.262 MinIO兼容S3的Java SDK
hadoop-aws-3.3.4.jar 3.3.4 Hadoop与AWS S3的连接桥

下载命令示例(以Linux/Mac为例):

wget https://repo1.maven.org/maven2/org/apache/iceberg/iceberg-spark-runtime-3.5_2.12/1.4.3/iceberg-spark-runtime-3.5_2.12-1.4.3.jar

wget https://repo1.maven.org/maven2/com/amazonaws/aws-java-sdk-bundle/1.12.262/aws-java-sdk-bundle-1.12.262.jar

wget https://repo1.maven.org/maven2/org/apache/hadoop/hadoop-aws/3.3.4/hadoop-aws-3.3.4.jar

避坑指南:版本一定要对齐。我曾经试过用hadoop-aws 3.4.0配Spark 3.5.0,结果启动时直接ClassNotFoundException。老老实实按我上面列的组合来,都是实测过的。

4.4 启动与验证

所有文件就位后,执行一条命令:

docker-compose up -d

等个一两分钟,检查所有容器是否正常运行:

docker ps --format "table {{.Names}}\t{{.Status}}"

你应该看到类似这样的输出:

NAMES              STATUS
ice-minio          Up 2 minutes (healthy)
ice-postgres       Up 2 minutes
ice-hive-metastore Up 2 minutes
ice-zk             Up 2 minutes
ice-kafka          Up 2 minutes
ice-spark          Up 2 minutes

然后验证一下关键组件:

  • MinIO控制台:浏览器打开 http://localhost:9001,用 admin/password 登录
  • Kafka可用性:创建一个测试topic docker exec ice-kafka kafka-topics --create --topic test --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1
  • Spark UI:浏览器打开 http://localhost:8080

4.5 配置Spark连接Iceberg

环境跑起来之后,还需要告诉Spark怎么找到Iceberg和MinIO。我习惯在启动Spark-shell时直接传配置:

docker exec -it ice-spark spark-shell \
  --conf spark.sql.catalog.demo=org.apache.iceberg.spark.SparkCatalog \
  --conf spark.sql.catalog.demo.type=hadoop \
  --conf spark.sql.catalog.demo.warehouse=s3a://iceberg-warehouse/ \
  --conf spark.sql.catalog.demo.hadoop.fs.s3a.endpoint=http://minio:9000 \
  --conf spark.sql.catalog.demo.hadoop.fs.s3a.access.key=admin \
  --conf spark.sql.catalog.demo.hadoop.fs.s3a.secret.key=password \
  --conf spark.sql.catalog.demo.hadoop.fs.s3a.path.style.access=true

关键点:s3a://iceberg-warehouse/ 这个桶需要提前在MinIO里创建。登录MinIO控制台,新建一个叫 iceberg-warehouse 的bucket。不然Spark写数据时会报桶不存在的错误。

进到Spark-shell后,跑一条简单的SQL验证:

spark.sql("CREATE DATABASE IF NOT EXISTS demo.db")
spark.sql("USE demo.db")
spark.sql("CREATE TABLE IF NOT EXISTS test_table (id INT, name STRING) USING iceberg")
spark.sql("INSERT INTO test_table VALUES (1, 'hello'), (2, 'world')")
spark.sql("SELECT * FROM test_table").show()

如果能看到两行数据,恭喜你——环境搭好了。

我曾经踩过的坑:第一次跑INSERT时卡了很久,最后超时了。后来发现是MinIO的桶没创建,Spark一直在重试。所以记得先建桶,再跑SQL。

4.6 清理环境

实验结束后,想重置环境很简单:

docker-compose down -v

-v 参数会删除volume,下次启动时就是全新的环境。我个人习惯每次实验前都跑一遍这个命令,避免残留数据干扰。