Docker 学习:多容器网络互通------从 host 模式到自定义 bridge 网络
前几天我成功用 docker-compose 启动了导航服务和任务管理两个容器,但它们用的是 network_mode: host,容器直接"裸奔"在宿主机网络上。今天的目标是搞清楚正规的跨容器网络通信到底是怎么工作的。
一、为什么不能用 host 模式?
network_mode: host 的意思是容器和宿主机共享同一个网络栈,相当于容器没有自己的网络"身份证",直接用的宿主机的。
这种模式在本地做测试确实方便,ROS 2 的 DDS 多播也能直接互相发现。但问题也很明显:
- 端口冲突:两个容器不能同时监听同一个端口(比如都想用 8080,就会打架)。
- 安全问题:容器可以直接访问宿主机的所有网络接口,隔离性几乎为零。
- 不可迁移:到了生产环境或者要上 Kubernetes,host 模式就完全不适用了。
所以,正经的工程化项目必须用自定义网络。
二、核心机制:虚拟网络 + 内置 DNS
Docker Compose 在启动一组服务时,会在背后悄悄做两件关键事情:
1. 创建虚拟网络,给每个容器分配独立 IP
当你在 compose 文件里声明了 networks:
yaml
networks:
robot_net:
driver: bridge
Compose 会创建一个叫 robot_net 的虚拟网络,并划定一个 IP 地址池(比如 172.20.0.0/16)。每个加入这个网络的容器,都会被自动分配一个私有的 IP 地址。
robot_net (自定义 bridge 网络)
├── nav_service → 172.20.0.2
└── task_service → 172.20.0.3
这个分配是全自动的,不需要你手动指定。你可以随时查看:
bash
docker-compose exec nav_service hostname -I
# 输出:172.20.0.2
2. 启动内置 DNS 服务,把服务名翻译成 IP
光有 IP 还不够,你得知道对方的具体 IP 才能通信。Docker 为此提供了一个内置的 DNS 服务器 (地址是 127.0.0.11)。
容器启动时,DNS 服务器会自动注册一条记录:"服务名 → 容器的 IP 地址" 。所以,同一个网络里的容器,不需要知道对方的 IP,直接用服务名就能找到对方。
三、ping task_service 的完整流程
我进入 nav_service 容器,执行 ping task_service,整个过程是这样的:
ping命令需要解析task_service的 IP。- 它去问系统配置的 DNS 服务器(
/etc/resolv.conf里指向的127.0.0.11)。 - 内置 DNS 查了一下自己的注册表,发现
task_service此刻对应的 IP 是172.20.0.3,然后返回这个地址。 ping拿着172.20.0.3,通过虚拟网桥把数据包直接发给了目标容器。
整个过程不需要知道对方的 IP,只需要记住服务名。IP 的分配和翻译全由 Docker 自动完成。
四、动手实操:改造 compose 文件
我把之前的 docker-compose.yml 升级为自定义网络版本。核心改动就三处:
- 删除
network_mode: host - 添加 顶层的
networks声明 - 给每个 service 添加
networks: - robot_net
yaml
version: '3.8'
networks:
robot_net:
driver: bridge
services:
nav_service:
image: my-ros-dev:1.0
container_name: nav_container
hostname: nav_host
networks:
- robot_net
# ... 其他配置不变 ...
task_service:
image: my-ros-dev:1.0
container_name: task_container
hostname: task_host
networks:
- robot_net
depends_on:
- nav_service
# ... 其他配置不变 ...
启动后,进入 nav_service 容器验证网络互通:
bash
docker-compose exec nav_service bash
ping task_service -c 3
输出:
PING task_service (172.20.0.3) 56(84) bytes of data.
64 bytes from task_container.robot_net (172.20.0.3): icmp_seq=1 ttl=64 time=0.123 ms
不写 IP,不写 hostname,直接用服务名,ping 通了。
五、延伸:服务与容器的关系
这里很容易混淆一个概念:服务(Service) 和 容器(Container) 到底是什么关系?
| 场景 | 服务数 | 容器数 | 关系 |
|---|---|---|---|
| 默认启动 | 1 个 nav_service | 1 个容器 | 一对一 |
| 水平扩展 | 1 个 nav_service | N 个容器 | 一对多 |
- 默认情况:每个 service 只创建一个容器,是一对一关系。
- 水平扩展 :
docker-compose up -d --scale nav_service=3可以让一个服务同时跑 3 个容器实例。Docker DNS 会自动做轮询负载均衡。 docker-compose exec 服务名 bash:进入的是该服务对应的容器。如果服务有多个实例,默认进入第一个。
服务是"逻辑分组",容器是"物理实例"。
六、跨主机能用这种方式吗?
不能。 Docker Compose 的网络和 DNS 只作用于单台宿主机。如果你需要跨主机通信,需要更高级的编排工具:
| 工具 | 适用场景 | 复杂度 |
|---|---|---|
| Docker Swarm | 小型集群,原生支持 Compose 文件 | 低 |
| Kubernetes(K8s) | 大规模生产集群 | 高 |
对于 ROS 2 的跨主机通信,还可以通过配置 DDS 的发现协议来手动指定对方 IP,但这个不在今天的学习范围内。
七、核心收获
- host 模式 = 容器裸奔在宿主机网络上,不安全,不推荐生产使用。
- 自定义 bridge 网络 = 每个容器有独立私有 IP,安全可控。
- Compose 内置 DNS = 同一网络的容器直接用服务名通信,不需要记 IP。
- 服务 vs 容器 = 服务是逻辑分组,容器是物理实例,默认一对一,可以水平扩展一对多。
- 跨主机通信 = Compose 不支持,需要 Swarm 或 K8s。
用一句话总结今天的核心:
Docker 给每个容器自动配 IP,再用 DNS 把服务名翻译成 IP。你只需要记服务名,完全不用管 IP 是多少。
这个机制就是微服务架构中服务发现的最简实现,理解了它,后面学 Kubernetes 的 Service 概念会非常顺畅。