4. 环境搭建:Docker Compose 搭建 Flink + Kafka + Hudi 开发环境

说实话,做流批一体最头疼的往往不是写代码,而是搭环境。我见过太多团队在环境上折腾一周,最后发现是版本不兼容。今天我们就用 Docker Compose 一把梭,把 Flink、Kafka、Hudi 这三件套一次性搞定。

4.1 为什么选 Docker Compose?

你可能会问:为啥不用直接装?嗯,我个人的习惯是——开发环境能容器化就容器化。原因很简单:

  • 隔离性:每个组件各跑各的容器,互不干扰
  • 一致性:团队所有人都用同一套配置,不会出现「我本地能跑」的尴尬
  • 快速重置:搞坏了?docker-compose down && up 一键重生

我在项目中遇到过好几次,因为本地装了不同版本的 Kafka,导致生产者和消费者序列化对不上。自从用了 Docker Compose,这种问题再也没出现过。

4.2 整体架构图

先看一张图,了解我们今天要搭什么:

Docker Compose 开发环境架构 Flink JobManager 端口: 8081 Flink TaskManager Slot: 2 Kafka Broker 端口: 9092 ZooKeeper 端口: 2181 Hudi HDFS / S3 MinIO (S3兼容) 端口: 9000 数据流说明 1. Flink 从 Kafka 消费实时数据 2. Flink 将处理结果写入 Hudi 表 3. Hudi 表存储在 MinIO (S3兼容对象存储) 4. 离线回填时,Flink 读取 Hudi 表历史数据 5. 回填结果再次写入 Hudi,完成数据修正

4.3 目录结构

我个人喜欢把配置文件和代码分开。下面是推荐的项目结构:

flink-kafka-hudi-env/
├── docker-compose.yml          # 主编排文件
├── flink/
│   ├── Dockerfile              # Flink 自定义镜像
│   └── conf/
│       ├── flink-conf.yaml     # Flink 配置
│       └── sql-client.yaml     # SQL 客户端配置
├── kafka/
│   └── server.properties       # Kafka 配置(可选)
├── hudi/
│   └── hudi-default.conf       # Hudi 配置
└── data/                       # 持久化数据目录
    ├── kafka/
    ├── zookeeper/
    └── minio/

4.4 docker-compose.yml 详解

这是核心文件。我直接贴出完整配置,然后逐段解释:

version: '3.8'

services:
  # ========== ZooKeeper ==========
  zookeeper:
    image: confluentinc/cp-zookeeper:7.4.0
    container_name: zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    ports:
      - "2181:2181"
    volumes:
      - ./data/zookeeper:/var/lib/zookeeper/data
    networks:
      - stream-net

  # ========== Kafka ==========
  kafka:
    image: confluentinc/cp-kafka:7.4.0
    container_name: kafka
    depends_on:
      - zookeeper
    ports:
      - "9092:9092"
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
      KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
      KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1
      KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1
    volumes:
      - ./data/kafka:/var/lib/kafka/data
    networks:
      - stream-net

  # ========== Flink JobManager ==========
  flink-jobmanager:
    image: flink:1.17.1-scala_2.12
    container_name: flink-jobmanager
    ports:
      - "8081:8081"
    command: jobmanager
    environment:
      - |
        FLINK_PROPERTIES=
        jobmanager.rpc.address: flink-jobmanager
        jobmanager.memory.process.size: 2048m
        taskmanager.memory.process.size: 2048m
    volumes:
      - ./flink/conf:/opt/flink/conf
      - ./hudi/hudi-flink-bundle.jar:/opt/flink/lib/hudi-flink-bundle.jar
    networks:
      - stream-net

  # ========== Flink TaskManager ==========
  flink-taskmanager:
    image: flink:1.17.1-scala_2.12
    container_name: flink-taskmanager
    depends_on:
      - flink-jobmanager
    command: taskmanager
    environment:
      - |
        FLINK_PROPERTIES=
        jobmanager.rpc.address: flink-jobmanager
        taskmanager.numberOfTaskSlots: 2
        taskmanager.memory.process.size: 2048m
    volumes:
      - ./flink/conf:/opt/flink/conf
      - ./hudi/hudi-flink-bundle.jar:/opt/flink/lib/hudi-flink-bundle.jar
    networks:
      - stream-net

  # ========== MinIO (S3兼容对象存储) ==========
  minio:
    image: minio/minio:latest
    container_name: minio
    ports:
      - "9000:9000"
      - "9001:9001"
    environment:
      MINIO_ROOT_USER: admin
      MINIO_ROOT_PASSWORD: password
    command: server /data --console-address ":9001"
    volumes:
      - ./data/minio:/data
    networks:
      - stream-net

networks:
  stream-net:
    driver: bridge

4.5 关键配置解读

这段配置里,有几个地方我特别想强调一下:

Kafka 的 ADVERTISED_LISTENERS

这个参数坑过我好几次。简单说,它告诉客户端「你应该连哪个地址」。如果你在容器内用 kafka:9092,在宿主机用 localhost:9092。我这里的配置是给宿主机用的,如果你要在其他容器里访问,记得改成服务名。

Flink 的 Hudi JAR 挂载

注意看 volumes 里那一行:

- ./hudi/hudi-flink-bundle.jar:/opt/flink/lib/hudi-flink-bundle.jar

这是把 Hudi 的依赖直接塞进 Flink 的 lib 目录。为什么这么做?因为 Flink 官方镜像默认不带 Hudi,你不挂载的话,跑 SQL 时会报 ClassNotFoundException。我曾经因为这个排查了半小时,后来发现是 jar 包版本不对——记住,Hudi 0.13+ 才支持 Flink 1.17。

MinIO 的端口映射

9000 是 S3 API 端口,9001 是管理控制台。开发时你可以打开 http://localhost:9001 看看数据有没有写进去。

4.6 启动与验证

配置写好了,我们来启动它:

# 启动所有服务
docker-compose up -d

# 查看日志
docker-compose logs -f

# 检查各服务状态
docker-compose ps

启动后,验证一下各个组件是否正常:

组件 验证方式 预期结果
ZooKeeper echo ruok | nc localhost 2181 返回 imok
Kafka docker exec kafka kafka-topics --list --bootstrap-server localhost:9092 返回空列表(无报错)
Flink 浏览器访问 http://localhost:8081 看到 Flink Web UI
MinIO 浏览器访问 http://localhost:9001 看到登录页面
小技巧: 我习惯在启动后先创建一个测试 topic:docker exec kafka kafka-topics --create --topic test --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1。这样能快速确认 Kafka 是否正常工作。

4.7 避坑指南

我曾经踩过的坑:
  • 端口冲突:本地如果已经装了 Kafka 或 Flink,记得先停掉,或者改端口映射
  • 内存不足:Flink JobManager 和 TaskManager 各占 2G,加上 Kafka 和 MinIO,建议给 Docker 至少分配 8G 内存
  • 网络问题:所有服务必须在同一个 network 里,否则连不上
  • Hudi 版本:Flink 1.17 必须用 Hudi 0.13.0+,低版本会报序列化错误

4.8 验证 Hudi 集成

最后一步,确认 Flink 能正常读写 Hudi。启动 Flink SQL Client:

docker exec -it flink-jobmanager ./bin/sql-client.sh

然后执行:

CREATE TABLE hudi_test (
  id INT,
  name STRING,
  ts TIMESTAMP(3),
  PRIMARY KEY (id) NOT ENFORCED
) WITH (
  'connector' = 'hudi',
  'path' = 's3a://hudi-bucket/test_table',
  'table.type' = 'COPY_ON_WRITE',
  'hoodie.bucket.index.num.buckets' = '4',
  's3a.endpoint' = 'http://minio:9000',
  's3a.access.key' = 'admin',
  's3a.secret.key' = 'password'
);

INSERT INTO hudi_test VALUES (1, 'hello', NOW());

SELECT * FROM hudi_test;

如果能看到数据返回,恭喜你,环境搭建成功了!

核心要点回顾:

  • Docker Compose 让环境搭建变得可重复、可分享
  • Kafka 的 ADVERTISED_LISTENERS 是常见坑点
  • Hudi JAR 必须挂载到 Flink lib 目录
  • MinIO 提供 S3 兼容存储,方便本地开发
  • 所有服务必须在同一个 Docker network 内