3、环境搭建:Docker与Docker Compose安装、Kafka单机/集群部署、Zookeeper配置、网络与端口规划

好,咱们正式开始动手了。

这一章,说白了就是先把「厨房」搭好。你想想看,后面所有的数据回放逻辑、Kafka 的读写、消息的流转,全都要跑在咱们今天搭建的环境上。如果地基没打牢,后面出问题你都不知道是代码的锅还是环境的锅——我踩过这种坑,一次就够了。

3.1 为什么选 Docker?

我个人习惯,所有中间件优先用 Docker 部署。原因很简单:隔离、可复现、快速销毁。

你想想看,Kafka 依赖 Zookeeper,Zookeeper 又依赖 Java 环境。如果直接在宿主机上装,版本冲突、路径污染、卸载不干净……这些破事我当年全经历过。用 Docker,一个 docker-compose up -d 就全搞定了。出了问题,docker-compose down 一键清理,干干净净。

核心原则: 所有中间件容器化,宿主机只保留代码和配置文件。

3.2 Docker 与 Docker Compose 安装

3.2.1 安装 Docker

嗯,这里要注意。不同操作系统安装方式略有不同,但核心思路一致。

Ubuntu / Debian 系:

# 更新包索引
sudo apt-get update

# 安装依赖
sudo apt-get install ca-certificates curl gnupg lsb-release

# 添加 Docker 官方 GPG 密钥
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# 设置稳定版仓库
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# 安装 Docker Engine
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io

# 验证安装
sudo docker run hello-world

CentOS / RHEL 系:

sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install docker-ce docker-ce-cli containerd.io
sudo systemctl start docker
sudo systemctl enable docker
小技巧: 安装完后记得把当前用户加入 docker 组,避免每次敲 sudo:sudo usermod -aG docker $USER。然后退出重新登录生效。

3.2.2 安装 Docker Compose

Docker Compose 是用来定义和运行多容器 Docker 应用的工具。说白了,就是用一个 YAML 文件把 Kafka、Zookeeper 这些容器的启动参数全写在一起,一键启动。

# 下载最新稳定版
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 赋予执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

我曾经在某个项目里,因为 Docker Compose 版本太低,导致 depends_on 条件不生效,Kafka 在 Zookeeper 还没完全启动时就尝试连接,结果一直报错。所以建议你装最新版,省心。

3.3 Zookeeper 配置

Kafka 依赖 Zookeeper 来管理集群元数据、选举 Controller。虽然新版本 Kafka 正在尝试脱离 ZK,但现阶段咱们还是老老实实用。

3.3.1 单机 Zookeeper

最简单的配置,直接跑一个容器:

version: '3.8'
services:
  zookeeper:
    image: confluentinc/cp-zookeeper:7.4.0
    container_name: zookeeper
    environment:
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
    ports:
      - "2181:2181"

这里 ZOOKEEPER_CLIENT_PORT 是客户端连接端口,ZOOKEEPER_TICK_TIME 是心跳间隔(毫秒)。默认 2000 就够用。

3.3.2 集群 Zookeeper(3 节点)

生产环境建议至少 3 个 ZK 节点,形成奇数集群。为什么是奇数?因为 ZK 选举需要超过半数节点存活,3 节点允许挂 1 个,4 节点也只允许挂 1 个(因为需要 3 票),多一个节点纯属浪费资源。

version: '3.8'
services:
  zk1:
    image: confluentinc/cp-zookeeper:7.4.0
    container_name: zk1
    environment:
      ZOOKEEPER_SERVER_ID: 1
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
      ZOOKEEPER_INIT_LIMIT: 5
      ZOOKEEPER_SYNC_LIMIT: 2
      ZOOKEEPER_SERVERS: "zk1:2888:3888;zk2:2888:3888;zk3:2888:3888"
    ports:
      - "2181:2181"

  zk2:
    image: confluentinc/cp-zookeeper:7.4.0
    container_name: zk2
    environment:
      ZOOKEEPER_SERVER_ID: 2
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
      ZOOKEEPER_INIT_LIMIT: 5
      ZOOKEEPER_SYNC_LIMIT: 2
      ZOOKEEPER_SERVERS: "zk1:2888:3888;zk2:2888:3888;zk3:2888:3888"
    ports:
      - "2182:2181"

  zk3:
    image: confluentinc/cp-zookeeper:7.4.0
    container_name: zk3
    environment:
      ZOOKEEPER_SERVER_ID: 3
      ZOOKEEPER_CLIENT_PORT: 2181
      ZOOKEEPER_TICK_TIME: 2000
      ZOOKEEPER_INIT_LIMIT: 5
      ZOOKEEPER_SYNC_LIMIT: 2
      ZOOKEEPER_SERVERS: "zk1:2888:3888;zk2:2888:3888;zk3:2888:3888"
    ports:
      - "2183:2181"
注意: 宿主机端口不能冲突。这里 zk1 用 2181,zk2 用 2182,zk3 用 2183。容器内部端口统一是 2181,通过映射区分。

3.4 Kafka 单机 / 集群部署

3.4.1 单机 Kafka

单机版适合开发测试。配置如下:

version: '3.8'
services:
  kafka:
    image: confluentinc/cp-kafka:7.4.0
    container_name: kafka
    depends_on:
      - zookeeper
    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
    ports:
      - "9092:9092"

这里 KAFKA_ADVERTISED_LISTENERS 是关键。它告诉客户端「你应该用这个地址连接我」。如果你在本地开发,写 localhost:9092 没问题。但如果你在远程服务器或者容器间通信,就得写成宿主机 IP 或者容器名。

我曾经犯过一个低级错误:在服务器上部署了 Kafka,客户端连不上,折腾了半天才发现是 ADVERTISED_LISTENERS 写成了 localhost,客户端从外部访问当然连不上。改成公网 IP 就好了。

3.4.2 集群 Kafka(3 节点)

生产环境至少 3 个 Broker,配合 3 个 ZK 节点。配置如下:

version: '3.8'
services:
  kafka1:
    image: confluentinc/cp-kafka:7.4.0
    container_name: kafka1
    depends_on:
      - zk1
      - zk2
      - zk3
    environment:
      KAFKA_BROKER_ID: 1
      KAFKA_ZOOKEEPER_CONNECT: 'zk1:2181,zk2:2181,zk3:2181'
      KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://localhost:9092'
      KAFKA_LISTENERS: 'PLAINTEXT://0.0.0.0:9092'
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
    ports:
      - "9092:9092"

  kafka2:
    image: confluentinc/cp-kafka:7.4.0
    container_name: kafka2
    depends_on:
      - zk1
      - zk2
      - zk3
    environment:
      KAFKA_BROKER_ID: 2
      KAFKA_ZOOKEEPER_CONNECT: 'zk1:2181,zk2:2181,zk3:2181'
      KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://localhost:9093'
      KAFKA_LISTENERS: 'PLAINTEXT://0.0.0.0:9093'
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
    ports:
      - "9093:9093"

  kafka3:
    image: confluentinc/cp-kafka:7.4.0
    container_name: kafka3
    depends_on:
      - zk1
      - zk2
      - zk3
    environment:
      KAFKA_BROKER_ID: 3
      KAFKA_ZOOKEEPER_CONNECT: 'zk1:2181,zk2:2181,zk3:2181'
      KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://localhost:9094'
      KAFKA_LISTENERS: 'PLAINTEXT://0.0.0.0:9094'
      KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 3
    ports:
      - "9094:9094"
关键点: 每个 Broker 的 KAFKA_BROKER_ID 必须唯一。KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR 设置为 3,确保 offset 信息有副本,不会因为单点故障丢失。

3.5 网络与端口规划

这一节容易被忽略,但恰恰是出问题最多的地方。我见过太多人因为端口冲突、网络不通排查半天。

3.5.1 端口规划表

服务 容器内部端口 宿主机映射端口 用途
Zookeeper 1 2181 2181 客户端连接
Zookeeper 2 2181 2182 客户端连接
Zookeeper 3 2181 2183 客户端连接
Zookeeper 集群通信 2888 不映射 Leader 选举
Zookeeper 集群通信 3888 不映射 数据同步
Kafka 1 9092 9092 生产者/消费者连接
Kafka 2 9093 9093 生产者/消费者连接
Kafka 3 9094 9094 生产者/消费者连接

3.5.2 网络规划

Docker Compose 默认会创建一个 bridge 网络,所有容器可以通过服务名互相访问。但如果你需要跨宿主机通信(比如 Kafka 集群分布在多台机器上),就得用 overlay 网络或者直接使用宿主机网络模式。

我个人建议:开发环境用 bridge 网络,生产环境用宿主机网络模式。宿主机网络模式性能更好,延迟更低,但端口管理要更小心。

# 使用宿主机网络模式的配置示例
services:
  kafka1:
    network_mode: "host"
    environment:
      KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://192.168.1.10:9092'  # 写实际宿主机 IP
避坑指南: 使用宿主机网络模式时,ADVERTISED_LISTENERS 必须写宿主机 IP,不能写 localhost。否则其他机器上的客户端连不上。

3.6 整体架构图

下面这张图展示了咱们搭建的完整环境:3 个 ZK 节点 + 3 个 Kafka Broker,通过 Docker 容器运行在同一台宿主机上。

数据回放系统 - 环境架构图 宿主机 (Host Machine) Docker Engine Zookeeper 集群 ZK1 端口: 2181 ZK2 端口: 2182 ZK3 端口: 2183 Kafka 集群 Broker 1 端口: 9092 Broker 2 端口: 9093 Broker 3 端口: 9094 Zookeeper 节点 Kafka Broker 依赖关系

3.7 启动与验证

所有配置写好后,启动就一行命令:

docker-compose up -d

验证是否启动成功:

# 查看所有容器状态
docker-compose ps

# 查看 Kafka 是否正常
docker exec -it kafka1 kafka-topics --bootstrap-server localhost:9092 --list

# 创建一个测试主题
docker exec -it kafka1 kafka-topics --bootstrap-server localhost:9092 --create --topic test-topic --partitions 3 --replication-factor 3

# 查看主题详情
docker exec -it kafka1 kafka-topics --bootstrap-server localhost:9092 --describe --topic test-topic

如果看到类似 Leader: 1, Replicas: 1,2,3, Isr: 1,2,3 的输出,说明集群搭建成功,三个 Broker 都在正常工作。

小贴士: 如果启动后 Kafka 连不上 ZK,先检查 ZK 是否完全启动。可以用 docker logs zk1 查看日志,看到 binding to port 0.0.0.0/0.0.0.0:2181 就说明 ZK 就绪了。

好了,环境搭完了。后面咱们就可以开始写代码,真正玩转数据回放了。

可可精品资料库