简单理解 Docker 网络
简单理解 Docker 网络
概述
在现代微服务开发中,我们经常使用 Docker Compose 将多个容器编排成一个完整的应用系统。然而,在处理不同容器间的连接以及端口监听时,开发者常会遇到“明明端口映射了却连不上”或“容器之间无法通信”的诡异问题。
要彻底解决这些问题,我们需要厘清三个层面的网络上下文:宿主机、Docker Compose 网络以及容器内部。
容器内的网络隔离与“localhost”陷阱
首先,我们需要建立一个认知:每个 Docker 容器本质上都是一个独立的网络命名空间。
这就好比每个容器都是一台独立的小型服务器,拥有自己独立的协议栈、路由表、IP 地址和网络接口(如 eth0 和 lo)。
一个常见的错误场景是: 1. 在容器内启动了一个 Web 服务,配置它监听 localhost:8080(即 127.0.0.1:8080)。 2. 在 docker-compose.yml 中,将容器端口映射到宿主机:8080:8080。 3. 结果:在宿主机访问 http://localhost:8080 时,连接被拒绝。
为什么会这样?
Docker 的端口映射(Port Mapping)机制,本质上是将宿主机端口的流量转发到容器的 eth0 网卡接口上。如果应用仅监听容器内的 127.0.0.1(本地回环接口),它就只会处理来自容器内部本身的流量,而完全忽略从 eth0 进来的外部请求。
网络接口与监听地址
为了理解这个问题,我们回顾一下操作系统处理网络通信的抽象模型:
graph TD
A[应用程序] --> B[端口 Port]
B --> C[IP 地址]
C --> D[网络接口 Network Interface]
D --> E[物理/虚拟链路]
操作系统中的 TCP/IP 通信必须依赖网络接口(Network Interface)。这些接口各有其 IP,语义完全不同:
- lo (Loopback Interface): 对应的 IP 是
127.0.0.1(localhost)。这是一个纯逻辑的虚拟接口,数据包直接在内核中“自发自收”,不经过任何物理设备。 - eth0 (Ethernet Interface): 对应容器的虚拟网卡,通常拥有一个 Docker 子网 IP(例如
172.18.0.x)。这是容器与外界(包括宿主机和其他容器)通信的桥梁。
因此,服务监听地址(Bind Address)的选择决定了谁能访问它:
- 监听
127.0.0.1:8080:- 仅限本机访问。只有容器内部的进程可以通过
localhost访问该服务。 - 宿主机转发过来的流量(来自
eth0)会被内核丢弃或拒绝。
- 仅限本机访问。只有容器内部的进程可以通过
- 监听
192.168.x.x:8080(特定网卡 IP):- 仅限该网卡所在的网络访问。
- 监听
0.0.0.0:8080(最佳实践):- 绑定所有接口。这意味着服务同时监听
lo、eth0等所有可用接口。 - 无论是容器内部访问,还是宿主机转发进来的外部流量,都能被正确处理。
- 绑定所有接口。这意味着服务同时监听
所以结论就是,在 Docker 容器化环境中,如果服务需要被外界(宿主机、其他容器)访问,配置中的监听地址(Listen Address / Bind Address)必须设置为 0.0.0.0。
Docker Compose 网络与服务发现
当我们从单个容器扩展到使用 Docker Compose 编排多个服务(例如 App + Database + Gateway)时,情况又有所不同。我们不仅需要关注端口映射,还需要关注容器间的互联。
当执行 docker compose up 时,Docker 会执行以下网络操作: 1. 创建一个默认的桥接网络(Bridge Network):这相当于在逻辑上创建了一个虚拟交换机。 2. 分配子网:为这个网络分配一个 IP 段(如 172.20.0.0/16),并设置网关(Gateway)。 3. 连接容器:将每个服务容器都连接到这个虚拟交换机上,并分配独立的 IP。
基于 DNS 的服务发现
在 Docker Compose 网络中,我们不需要(也不应该)硬编码 IP 地址。Docker 内置了 DNS 服务器,实现了基于服务名的服务发现。
在 docker-compose.yml 中定义的 services 名称,会自动成为该网络中的主机名(Hostname)。
示例 docker-compose.yml:
1 | version: '3.8' |
在这个例子中: * 互通原理:console 容器可以通过域名 redpanda 解析到 redpanda-1 容器的内部 IP。 * 端口访问:console 访问 redpanda:9092 时,流量直接在 Docker 子网内传输,不经过宿主机的端口映射。 * 前提条件:redpanda 容器内的进程必须在 0.0.0.0:9092 上监听,才能接收来自 console(也就是来自 eth0)的连接请求。
总结:全链路网络架构图
为了更直观地理解流量是如何流转的,我们可以将网络架构分层可视化:
1 | [ 外部互联网 / 用户 ] |
关键点总结:
- 对外暴露:如果容器需要被宿主机外部访问,必须做端口映射,且容器内进程需监听
0.0.0.0。 - 内部互联:在 Docker Compose 中,服务之间通过服务名(如
redpanda)互相访问,流量走 Docker 内部子网,不依赖宿主机端口映射。 - 监听原则:无论哪种情况,容器内的服务若想接收“非本机”流量(包括来自网关或其他容器的流量),必须监听
0.0.0.0,而不是localhost。