4. 环境搭建与基础配置:Docker Compose 搭建开发环境
说实话,很多人在搭建实时数据服务目录时,第一步就卡住了。不是技术多难,而是环境太乱。Java 版本冲突、Python 虚拟环境打架、端口被占用……我见过太多团队花了两天时间,最后发现是本地装了个旧版 Zookeeper 在捣乱。
所以这一章,我带你用 Docker Compose 一把梭。把开发环境做成「可复现的基建」,换台机器也能秒级拉起。
4.1 网络规划:别让容器「失联」
先想清楚一件事:我们的服务目录需要跟哪些组件通信?
- 元数据存储:MySQL / PostgreSQL,存服务定义和版本历史
- 实时消息通道:Kafka,接收服务注册和变更事件
- 缓存层:Redis,扛查询压力
- 服务发现:Consul / Etcd,做健康检查和动态路由
- 应用本身:Spring Boot 应用,暴露 REST 和 WebSocket 接口
这么多容器,如果各自用默认网络,IP 乱飞,你连调试都找不到门。我个人的习惯是:创建一个独立的 bridge 网络,所有服务都挂上去。
网络规划要点
- 使用自定义网络,不要用默认的 bridge
- 服务名即主机名,容器间通过服务名通信
- 预留一个管理网络段(如 172.20.0.0/16),避免跟宿主机冲突
为什么强调自定义网络?因为默认 bridge 网络不支持容器间的 DNS 解析。你想想看,如果 Kafka 的地址写死了 IP,容器重启后 IP 变了,整个链路就断了。用服务名通信,Docker 会自动做 DNS 解析,这才是真正的「声明式配置」。
4.2 端口映射:内外有别
端口映射的原则很简单:内部用标准端口,外部按需暴露。
举个例子,Kafka 内部监听 9092,但宿主机上可能已经跑了一个 Kafka 实例。这时候把外部端口映射成 9093,既不影响内部通信,也不跟宿主机冲突。
| 服务 | 内部端口 | 外部映射 | 说明 |
|---|---|---|---|
| MySQL | 3306 | 3307 | 避免跟本地 MySQL 冲突 |
| Redis | 6379 | 6380 | 同上 |
| Kafka | 9092 | 9093 | 外部客户端连接用 |
| 应用服务 | 8080 | 8080 | 直接暴露给前端 |
| Consul | 8500 | 8500 | 管理界面 |
小技巧:Kafka 的 KAFKA_ADVERTISED_LISTENERS 要配置成宿主机地址,否则外部客户端连不上。我曾经在这上面踩过坑,容器内部通信正常,但本地代码死活连不上 Kafka,折腾了半天才发现是 advertised 地址没改。
4.3 基础镜像准备:选对「地基」
镜像选型直接影响构建速度和运行稳定性。我的建议是:优先用官方镜像,版本锁定到小版本。
为什么?官方镜像经过大量验证,安全补丁也及时。锁定小版本是为了避免「昨天还能跑,今天拉了个新镜像就挂了」的尴尬。
4.3.1 应用镜像
我们的服务目录基于 Spring Boot 3.x,JDK 选 17 LTS。基础镜像我推荐用 eclipse-temurin:17-jre-alpine,体积小、安全更新快。
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY target/service-catalog-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
4.3.2 中间件镜像
中间件全部用官方镜像,版本如下:
- MySQL:
mysql:8.0.32— 注意 8.0 以上默认使用 caching_sha2_password,客户端驱动要兼容 - Redis:
redis:7.0-alpine— 轻量,够用 - Kafka:
confluentinc/cp-kafka:7.4.0— Confluent 版带 Kraft 模式,可以不用 Zookeeper - Consul:
consul:1.16.2— 服务发现和 KV 存储
注意:Kafka 从 3.x 开始支持 Kraft 模式,不再强依赖 Zookeeper。如果你是新项目,直接用 Kraft 模式,少维护一个组件。我去年帮客户迁移时,从 ZK 模式切到 Kraft,运维复杂度降了一半。
4.4 Docker Compose 编排:把一切串起来
好了,网络、端口、镜像都定好了,接下来用 Docker Compose 把它们编排起来。下面是我实际项目中用的精简版配置:
version: '3.8'
networks:
catalog-net:
driver: bridge
ipam:
config:
- subnet: 172.20.0.0/16
services:
mysql:
image: mysql:8.0.32
container_name: catalog-mysql
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: service_catalog
ports:
- "3307:3306"
networks:
- catalog-net
volumes:
- mysql-data:/var/lib/mysql
redis:
image: redis:7.0-alpine
container_name: catalog-redis
ports:
- "6380:6379"
networks:
- catalog-net
kafka:
image: confluentinc/cp-kafka:7.4.0
container_name: catalog-kafka
environment:
CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk"
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: broker,controller
KAFKA_CONTROLLER_QUORUM_VOTERS: "1@localhost:9093"
KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9093
KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_INTER_BROKER_LISTENER_NAME: PLAINTEXT
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
ports:
- "9093:9092"
networks:
- catalog-net
consul:
image: consul:1.16.2
container_name: catalog-consul
command: agent -dev -client=0.0.0.0
ports:
- "8500:8500"
networks:
- catalog-net
app:
build: .
container_name: catalog-app
depends_on:
- mysql
- redis
- kafka
- consul
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/service_catalog
SPRING_REDIS_HOST: redis
SPRING_KAFKA_BOOTSTRAP_SERVERS: kafka:9092
SPRING_CLOUD_CONSUL_HOST: consul
ports:
- "8080:8080"
networks:
- catalog-net
volumes:
mysql-data:
这里有几个细节要注意:
- depends_on 只保证容器启动顺序,不保证服务就绪。建议在应用启动脚本里加健康检查等待逻辑
- volumes 挂载持久化数据,避免容器重启后数据丢失
- 环境变量 里用服务名(如
mysql)而不是 IP,Docker DNS 会自动解析
4.5 启动与验证
配置写好了,怎么验证它是对的?我一般分三步走:
- 启动所有服务:
docker compose up -d,观察日志有没有报错 - 检查网络连通性:
docker exec catalog-app ping mysql,确认 DNS 解析正常 - 验证业务端口:用 curl 或浏览器访问
http://localhost:8080/actuator/health,看返回是否 200
避坑指南:我曾经在 Kafka 启动时遇到 TimeoutException,原因是 KAFKA_ADVERTISED_LISTENERS 配成了容器内部 IP。外部客户端(比如你的本地应用)连不上。记住:外部访问用宿主机 IP,内部通信用服务名。
4.6 整体架构图
下面这张图展示了我们搭建的开发环境全貌。每个组件都在自定义网络里,通过服务名通信,端口按需映射到宿主机。
4.7 小结
这一章我们做了三件事:规划了网络和端口、选定了基础镜像、用 Docker Compose 把整个环境串了起来。说白了,就是给后面的开发工作搭了个「可复现的台子」。
你可能会问:为什么不用 Kubernetes?嗯,开发环境用 K8s 太重了,Docker Compose 足够。等上了生产,再考虑迁移到 K8s 也不迟。