Docker 基础体系 · 第 8/70 篇。示例以现代 Docker Engine、BuildKit 与 Compose 规范为基础,并明确 Linux 容器边界。
Docker 网络完整指南:Bridge、端口映射、DNS、Overlay 和排障
Docker 网络的核心不是“给容器分配一个 IP”,而是把多个 Linux 网络组件组合起来:网络命名空间隔离进程的网络视图,虚拟以太网设备连接命名空间与宿主机,Linux bridge 提供二层转发,路由与 NAT 负责跨网段通信,Docker 内置 DNS 负责名称解析,端口发布则把宿主机地址映射到容器地址。
本文以现代 Docker Engine、BuildKit 和 Compose 规范为背景,重点讨论 Linux 容器。Docker Desktop 中的容器实际运行在 Linux VM 内,宿主机看到的网络路径可能多一层虚拟机转发;Rootless Docker 也可能使用 slirp4netns、pasta 等用户态网络实现,因此部分底层表现与 rootful Linux Engine 不同。
一、先建立网络模型:容器到底连接在哪里
1. 网络命名空间提供隔离
Linux 网络命名空间(network namespace,简称 netns)为进程提供独立的:
- 网络接口;
- IP 地址;
- 路由表;
- ARP/邻居表;
- 防火墙规则视图;
- 端口监听空间。
容器中的进程通常位于自己的网络命名空间内。因此,容器中监听 0.0.0.0:80,表示监听该容器命名空间内所有接口的 80 端口,而不是直接监听宿主机的 80 端口。
这解释了一个重要事实:
容器端口和宿主机端口是两个不同网络命名空间中的端口,除非使用 host 网络模式,否则它们不会天然相同。
2. veth pair 把容器接入宿主机
普通 Bridge 网络通常通过一对虚拟以太网设备(veth pair)连接容器与宿主机:
容器 netns 宿主机 netns
+----------------+ +----------------------+
| eth0 |<==== veth pair =>| vethXXXX |
| 172.20.0.2/16 | | |
| default route | | docker bridge |
+----------------+ | 172.20.0.1/16 |
+----------+-----------+
|
| 路由 / NAT / 防火墙
|
外部网络或宿主机
veth pair 的两端相连:一端放入容器命名空间,通常命名为 eth0;另一端留在宿主机,并接入某个 Linux bridge,例如 docker0 或用户自定义 bridge。
在容器内部执行:
ip addr
ip route
常见结果类似:
2: eth0@if123: <BROADCAST,MULTICAST,UP,LOWER_UP>
inet 172.20.0.2/16 scope global eth0
default via 172.20.0.1 dev eth0
172.20.0.0/16 dev eth0 proto kernel scope link src 172.20.0.2
这里:
172.20.0.2是容器在该网络中的地址;172.20.0.1通常是 bridge 在宿主机上的网关地址;eth0@if123中的if123是宿主机侧 veth 的接口索引提示;- 默认路由把非本地网段的流量交给 bridge 网关。
3. Bridge 是什么
Linux bridge 可以理解为一个虚拟二层交换机。它根据 MAC 地址学习转发表,并在接入端口之间转发以太网帧。
Docker 的 bridge 网络驱动通常完成以下工作:
- 创建或使用 Linux bridge;
- 为网络分配子网;
- 为容器创建 veth pair;
- 将宿主机侧 veth 接入 bridge;
- 将另一端放入容器命名空间;
- 设置容器 IP、默认路由和 DNS 配置;
- 配置跨网络或出站流量所需的路由与 NAT;
- 根据端口发布规则安装转发规则。
可以用命令观察网络对象:
docker network ls
docker network inspect bridge
用户自定义网络的输出中通常可以看到:
Driver: bridge;Subnet和Gateway;- 已连接容器的名称、IPv4 和 IPv6 地址;
- 端点配置。
docker network inspect 展示的是 Docker 的网络模型;它不一定直接显示所有内核级转发细节。要观察宿主机接口,还可以执行:
ip link show
ip addr show docker0
bridge link
bridge fdb show
不同发行版和 Docker 配置可能使用 iptables、nftables 或兼容层实现防火墙与 NAT。不要假设一定存在某一套规则格式。
二、Bridge 网络:默认网络和用户自定义网络不是一回事
1. 默认 bridge 网络
如果启动容器时没有指定 --network,传统 Docker Engine 通常会把容器接入名为 bridge 的默认网络:
docker run -d --name web nginx
查看容器地址:
docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
默认 bridge 支持容器通过 IP 通信,但不应把它当作现代应用编排网络使用。一个关键差异是:
默认
bridge网络不提供像用户自定义网络那样可靠、自然的容器名称服务发现;不要依赖默认网络中的容器名解析。
在默认网络上,历史上的 --link 可以提供有限的名称和环境变量注入能力,但它是旧机制,不适合作为新系统的服务发现方案。
2. 用户自定义 bridge 网络
创建用户自定义 bridge:
docker network create \
--driver bridge \
--subnet 172.30.0.0/16 \
app-net
启动两个容器:
docker run -d --name api --network app-net nginx
docker run --rm --network app-net busybox \
wget -qO- http://api
第二条命令能够使用 api 作为主机名,是因为用户自定义网络启用了 Docker 的内置 DNS 和基于容器名称的服务发现。
用户自定义 bridge 的价值不仅是名称解析,还包括:
- 可以按应用划分网络;
- 可以在运行时连接和断开容器;
- 网络隔离边界更清晰;
- 可以配置子网、网关、IP 范围和部分驱动选项;
- 不同应用的容器不会默认处于同一个二层广播域。
容器也可以同时连接多个网络:
docker network create frontend
docker network create backend
docker run -d --name api --network backend nginx
docker network connect frontend api
此时 api 拥有两个网络端点。它可以从两个网络收发流量,但这不等于宿主机上的两个 bridge 被直接桥接。Docker 仍然通过容器内的网络接口、路由和网络端点管理隔离边界。
3. 网络隔离的实际含义
假设有:
frontend: 172.31.0.0/16
backend: 172.32.0.0/16
web 连接 frontend
api 连接 frontend、backend
db 连接 backend
那么:
web可以访问api;api可以访问db;web默认不能直接解析或访问db,因为它们不在同一 Docker 网络;api作为多宿主容器,成为两个网络之间的应用层参与者,但 Docker 不会因为它连接两个网络,就自动把两个网络变成完全互通的二层网络。
这是一种常见的分层方式:前端服务只连接 frontend,数据库只连接 backend,API 同时连接两者。
三、端口映射:EXPOSE、-p 和 -P 完全不同
1. 容器监听不等于宿主机可访问
启动一个监听 80 端口的 Nginx:
docker run -d --name web nginx
此时 Nginx 监听的是容器网络命名空间内的 80 端口。宿主机执行:
curl http://127.0.0.1:80
通常不会访问到该容器,因为宿主机的 80 端口没有被发布。
容器之间可以通过容器 IP 或用户自定义网络中的服务名访问:
docker run --rm --network container:web curlimages/curl \
http://127.0.0.1:80
这里使用 --network container:web,测试容器与 web 共享同一个网络命名空间,所以 127.0.0.1 指向同一网络栈。若改为普通 --network bridge,127.0.0.1 就只指向测试容器自己。
2. EXPOSE 只是镜像元数据
Dockerfile 中的:
EXPOSE 8080
不会创建防火墙规则,也不会自动把宿主机端口映射到容器。它主要表达:
该镜像中的应用预计监听容器端口 8080。
可以用以下命令查看镜像声明:
docker image inspect IMAGE \
--format '{{json .Config.ExposedPorts}}'
真正发布端口需要在运行时指定:
docker run -d \
--name web \
-p 127.0.0.1:8080:80 \
nginx
这条规则的含义是:
宿主机 127.0.0.1:8080 -> 容器 80
访问方式:
curl http://127.0.0.1:8080
但宿主机其他网卡地址上的 8080 不会因为这条规则自动开放。
如果写成:
docker run -d --name web -p 8080:80 nginx
通常表示绑定宿主机所有适用地址的 8080 端口,包括外部网络可达的地址。是否真正能够从外部访问,还取决于宿主机路由、安全组、云防火墙和主机防火墙。
因此生产环境中,-p 8080:80 与 -p 127.0.0.1:8080:80 的暴露边界完全不同。
3. 端口映射的数据路径
以客户端访问宿主机 192.0.2.10:8080 为例,目标容器地址为 172.30.0.2:80:
客户端
|
| TCP 192.0.2.20:53000 -> 192.0.2.10:8080
v
宿主机接收网卡
|
| DNAT / 端口发布规则
| 目标变为 172.30.0.2:80
v
docker bridge
|
v
容器 eth0:80
返回包必须沿着连接跟踪状态返回。NAT 不是单向替换字符串,而是维护连接状态,使返回流量对双方仍表现为原始连接。
端口映射成立通常需要同时满足:
- 容器内应用确实监听目标端口;
- 应用监听的地址包含容器端点,例如
0.0.0.0或容器实际 IP; - 容器网络端点存在;
- 宿主机发布规则存在;
- 宿主机能够路由到容器网络;
- 主机防火墙、安全组和云网络没有阻断;
- 客户端访问的是正确的宿主机地址和端口。
只要其中一个条件不成立,就可能出现“容器运行正常但端口访问失败”。
4. 常见监听地址错误
应用只监听容器内的回环地址:
127.0.0.1:8080
即使发布了:
-p 8080:8080
Docker 也不能把外部连接转发到该应用,因为转发后的目标地址通常是容器 IP,而应用没有监听容器 IP。
容器中检查监听状态:
ss -lntp
期望看到类似:
LISTEN 0 128 0.0.0.0:8080 ...
如果看到:
LISTEN 0 128 127.0.0.1:8080 ...
应根据应用配置改为监听 0.0.0.0 或明确的容器接口地址。不要简单地把监听地址改成宿主机地址;容器通常并不知道宿主机的业务 IP。
5. Compose 中的端口写法
Compose 服务中的:
services:
web:
image: nginx
ports:
- "127.0.0.1:8080:80"
等价于发布宿主机回环地址的 8080 到容器 80。
而:
services:
web:
image: nginx
expose:
- "80"
只表达容器间使用的内部端口,不发布到宿主机。实际上,同一 Docker 网络中的容器可以访问服务监听的端口,即使没有写 expose;expose 更接近文档和元数据声明,而不是访问控制规则。
Compose 服务间访问通常写:
http://web:80
而不是:
http://localhost:8080
因为 localhost 在 api 容器内指向 api 自己。只有从宿主机访问时,才使用宿主机发布端口,例如 http://127.0.0.1:8080。
四、Docker DNS:容器名、服务名和外部域名
1. 容器内的 /etc/resolv.conf
容器通常拥有自己的 /etc/resolv.conf:
docker run --rm alpine cat /etc/resolv.conf
在用户自定义网络中,常见结果包含 Docker 内置 DNS 地址:
nameserver 127.0.0.11
options ndots:0
127.0.0.11 是容器网络命名空间内可访问的 Docker Embedded DNS。这里的 127.0.0.11 不是宿主机的 DNS 地址;它只在该容器的网络命名空间内有意义。
应用查询域名时,大致有两类路径:
查询容器名或服务名
-> Docker 内置 DNS
-> 返回同一 Docker 网络中的端点地址
查询公共域名
-> Docker 内置 DNS
-> 按宿主机或 Docker 配置转发给上游 DNS
-> 返回外部解析结果
可以区分测试:
docker run --rm --network app-net busybox nslookup api
docker run --rm --network app-net busybox nslookup example.com
第一条验证 Docker 网络内服务发现,第二条验证外部 DNS 转发。
2. 名称解析的作用域
容器名称解析不是全局 DNS。名称是否可解析取决于查询方和目标是否共享相应网络。
例如:
docker network create net-a
docker network create net-b
docker run -d --name api --network net-a nginx
docker run --rm --network net-a busybox nslookup api
docker run --rm --network net-b busybox nslookup api
在 net-a 中通常能解析 api;在 net-b 中通常不能。若把容器连接到两个网络,它在两个网络中可能拥有各自的网络别名和可解析性。
3. Compose 的服务名解析
Compose 默认会为项目创建网络,常见名称类似:
项目名_default
在同一 Compose 网络中,服务名通常可以作为 DNS 名称:
services:
api:
image: my-api
db:
image: postgres
api 应使用:
postgresql://db:5432/app
而不是写死 db 的容器 IP。因为容器重建、扩缩容或网络重新创建后,IP 可能变化,但服务发现名称保持稳定。
Compose 中的 container_name 不应作为常规服务发现基础。它会限制某些扩缩容场景,而且把应用身份绑定到固定容器名。优先使用服务名和网络别名。
可以显式配置别名:
services:
api:
image: my-api
networks:
app:
aliases:
- user-api
networks:
app:
此时同一网络内可以通过 api 或 user-api 访问该服务。
4. 多副本下的 DNS 行为
服务有多个端点时,解析结果可能包含多个地址,或者解析到一个虚拟 IP(VIP),具体取决于编排器和服务发现模式。应用必须把 DNS 结果视为可变化的:
- 不应永久缓存服务 IP;
- 不应假设一次解析只返回一个地址;
- 连接失败后应具备重试和重新解析能力;
- 长连接应处理后端实例被替换的情况。
在 Docker Swarm 中,服务可以使用 VIP 或 DNS round-robin(DNSRR)模式。VIP 模式由服务虚拟 IP 接收流量,再转发到任务;DNSRR 模式返回任务地址,由客户端自行选择。DNSRR 并不自动提供 HTTP 负载均衡,也不适合所有客户端。
5. DNS 失败和 TCP 失败要分开
以下错误属于不同层次:
lookup api: no such host
表示名称解析失败,可能是网络不一致、名称错误或内置 DNS 异常。
connection refused
表示已经到达目标地址,但目标端口没有接受连接,常见原因是应用未监听或监听地址错误。
i/o timeout
表示连接建立过程没有及时完成,可能是路由、防火墙、MTU、网络策略或目标服务阻塞。
排障时先执行:
getent hosts api
再执行:
nc -vz api 8080
最后才检查应用协议:
curl -v http://api:8080/health
这样可以把 DNS、TCP 和 HTTP 层的问题分离。
五、Overlay 网络:跨宿主机连接容器
1. Overlay 的使用前提
Bridge 网络主要连接同一台宿主机上的容器。Overlay 网络用于跨 Docker 节点连接容器,通常与 Docker Swarm 服务一起使用。
Overlay 的基本前提是:
- 节点加入同一个 Swarm;
- 节点之间的控制面和数据面端口可达;
- 使用兼容的 Docker Engine 网络能力;
- 网络被创建为 overlay;
- 需要让独立容器接入时,使用可附加(attachable)的 overlay 网络。
创建 Swarm:
docker swarm init --advertise-addr 192.0.2.10
创建可附加 overlay:
docker network create \
--driver overlay \
--attachable \
app-overlay
在 Swarm 中创建服务:
docker service create \
--name web \
--network app-overlay \
nginx
--attachable 允许普通 docker run 容器连接该 overlay;如果只供 Swarm 服务使用,则不一定需要它。
2. Overlay 的两层结构
Overlay 同时涉及控制面和数据面。
控制面负责:
- 节点成员关系;
- 网络对象和端点信息;
- 服务任务的发现;
- 跨节点网络状态同步。
数据面负责:
- 把一个节点上容器发出的报文送到另一个节点;
- 对原始容器网络报文进行封装;
- 在目标节点解封装并交给目标容器。
常见实现使用 VXLAN 等封装机制。原始包会被包在宿主机之间传输的外层 UDP 包中:
容器 A
原始包:172.40.0.3 -> 172.40.0.8
|
v
节点 1:封装为宿主机间数据包
|
| 物理网络 / 云网络
v
节点 2:解封装
|
v
容器 B
因此,Overlay 的 MTU 通常小于物理网卡的有效 MTU。外层封装增加了头部开销。如果路径不支持分片或 PMTU 发现异常,可能出现“小包正常、大包超时”的故障。
3. Swarm 常见端口
节点之间通常需要根据 Docker Swarm 配置放通:
2377/TCP:Swarm 管理通信;7946/TCP和7946/UDP:节点发现和网络成员通信;4789/UDP:Overlay VXLAN 数据面。
这些端口必须在节点间可达,尤其是在云安全组、主机防火墙和跨子网路由存在时。不要只测试节点之间的 SSH 或 ping;Overlay 数据面使用的端口和协议可能不同。
验证基础连通性:
nc -vz NODE_IP 2377
nc -vz NODE_IP 7946
UDP 不能用普通 TCP 的 nc -vz 充分验证,应结合防火墙规则、抓包和 Swarm 状态检查。命令选项还会因 nc 实现不同而变化。
4. Overlay 与端口发布不是一回事
服务接入 overlay 后,节点之间的服务通信可以使用服务名,但外部客户端访问服务仍需要端口发布:
docker service create \
--name web \
--network app-overlay \
--publish published=8080,target=80 \
nginx
Swarm 的发布模式还涉及 ingress routing mesh。默认情况下,访问任意合适的 Swarm 节点的发布端口,流量可能被路由到运行该服务任务的节点。
这与普通容器的:
docker run -p 8080:80 nginx
不同:
- 普通容器端口发布主要是该宿主机上的 DNAT;
- Swarm 服务发布还可能包含节点间转发;
- 故障排查必须确认访问的是哪个节点、服务是否有任务、ingress 网络是否正常。
Swarm 也支持 host 发布模式,使端口只在运行任务的节点上直接监听。具体配置应使用当前 Engine 的服务发布语法,并明确节点调度和端口冲突风险。
5. Overlay 加密的边界
Overlay 可以配置加密选项,使节点间数据面使用加密机制。加密只解决相应 Overlay 数据面的机密性和完整性问题,不等于:
- 容器应用协议自动安全;
- 宿主机已被保护;
- 节点管理流量全部按同一方式加密;
- 应用不再需要 TLS、认证和授权。
启用加密还会引入额外处理开销,必须通过实际流量验证性能和 MTU 行为。不能把“网络已加密”直接等同于“系统端到端安全”。
六、其他网络模式:理解边界才能正确选型
1. host 模式
docker run --rm --network host nginx
在 Linux Engine 上,容器与宿主机共享网络命名空间。此时:
- 容器不会拥有独立的容器 IP;
-p通常没有普通 bridge 模式下的意义;- 容器监听的端口就是宿主机端口;
- 多个容器可能发生端口冲突;
- 网络隔离明显减弱。
host 模式适合明确需要减少网络隔离或直接使用宿主机网络的场景,不应因为“访问方便”而默认使用。
Docker Desktop 的 host networking 依赖其具体版本和配置,不能直接将 Linux Engine 行为套用到 Desktop。
2. none 模式
docker run --rm -it --network none alpine
容器只有回环接口或极少的本地网络能力,不连接 Docker 提供的外部网络。它适合需要显式配置网络或测试网络隔离的场景。
3. container:<name> 模式
docker run -d --name app nginx
docker run --rm --network container:app alpine ip addr
第二个容器共享 app 的网络命名空间,因此共享:
- IP 地址;
- 网络接口;
- 路由;
- 端口空间。
这种模式常用于 sidecar、调试容器或为无法安装诊断工具的容器提供工具。但它不是“把两个容器接入同一 bridge”,而是让两个容器使用同一个网络栈。
七、Compose 网络的生命周期和工程边界
一个典型 Compose 配置:
services:
api:
image: nginx:latest
networks:
- frontend
- backend
ports:
- "127.0.0.1:8080:80"
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: example
networks:
- backend
networks:
frontend:
backend:
启动:
docker compose up -d
Compose 通常会:
- 创建项目网络;
- 创建服务容器;
- 将服务接入声明的网络;
- 创建服务名对应的 DNS 记录;
- 根据
ports安装宿主机端口发布规则。
此时:
- 宿主机访问
127.0.0.1:8080,到达api; api可以通过db:5432访问数据库;db不在 frontend 网络中,不能直接通过 Compose DNS 解析api;api同时连接两个网络,但数据库端口没有因为db使用 backend 就自动暴露到宿主机。
查看实际配置和实际网络:
docker compose config
docker compose ps
docker network ls
docker network inspect PROJECT_backend
docker compose config 用于确认变量替换、合并文件和最终配置;排障时不要只看原始 YAML,因为环境变量和多个 Compose 文件可能改变最终结果。
depends_on 不等于网络就绪
服务依赖关系主要影响启动顺序或健康检查等待逻辑,不会保证数据库已经接受连接,更不会修复 DNS、路由和端口问题。
例如,应用启动时应实现:
- DNS 失败重试;
- TCP 连接重试;
- 数据库协议级健康检查;
- 合理的超时;
- 连接池重建。
把 depends_on 当成“数据库已可用”的证明,会把启动时序问题误判成网络问题。
八、从“服务能否访问”推导检查条件
对于容器 A 访问容器 B 的 TCP 服务,可以把成功条件写成:
其中:
- :名称或目标地址可解析;
- :从 A 到 B 的路由和 Docker 网络路径存在;
- :B 内应用在目标地址和端口监听;
- :中间防火墙和网络策略允许连接;
- :协议层请求符合服务要求。
访问宿主机发布端口时,还要增加端口发布条件 :
其中:
- :客户端解析到正确的宿主机地址;
- :宿主机端口到容器端口的 NAT/发布规则存在。
这个分解直接指导排障顺序:
- 先确认名称解析;
- 再确认 TCP 路径;
- 再确认服务监听;
- 再确认防火墙和端口发布;
- 最后检查 HTTP、TLS、认证等应用协议。
如果一开始就修改 Docker daemon 配置或重建所有容器,通常会丢失故障现场,且无法确定是哪一个条件失败。
九、系统化排障流程
1. 先确认容器和网络状态
docker ps
docker inspect api
docker network ls
docker network inspect app-net
重点看:
- 容器是否实际运行;
- 容器是否退出后被重启;
- 容器是否连接到了预期网络;
- 容器获得的 IP 是否为空或变化;
- 网络是否被删除、重建或连接错误;
- Compose 项目是否使用了另一个项目名导致网络名不同。
查看容器的网络摘要:
docker inspect -f '{{json .NetworkSettings.Networks}}' api
不要只检查容器状态为 Up。Up 只表示主进程仍在运行,不表示应用已经监听端口,也不表示网络路径可用。
2. 在容器内部确认监听和路由
docker exec api ss -lntp
docker exec api ip addr
docker exec api ip route
如果镜像没有 ss、ip 或 nslookup,可以启动临时诊断容器加入同一网络:
docker run --rm -it --network app-net alpine sh
然后执行:
ip addr
ip route
getent hosts api
nc -vz api 8080
如果需要从目标容器的网络命名空间观察,而不是从另一个容器观察,可以在 Linux 宿主机上使用容器 PID:
docker inspect -f '{{.State.Pid}}' api
随后使用 nsenter:
PID=$(docker inspect -f '{{.State.Pid}}' api)
sudo nsenter -t "$PID" -n ss -lntp
这要求宿主机具备 nsenter,并且当前用户有足够权限。
3. 区分 DNS、连接和应用错误
在同一网络中运行:
getent hosts api
nc -vz api 8080
curl -v --connect-timeout 3 http://api:8080/health
结果解释:
| 现象 | 更可能的层次 |
|---|---|
Name or service not known |
名称、网络作用域、Docker DNS |
解析成功但 connection refused |
应用未监听、端口错误、监听地址错误 |
| 连接超时 | 路由、防火墙、MTU、服务阻塞或节点间网络 |
| TCP 成功但 HTTP 4xx/5xx | 应用路由、认证、配置或上游依赖 |
| 宿主机能访问,容器不能访问 | 容器路由、DNS、网络隔离或代理配置 |
| 容器能访问,外部不能访问 | 端口发布、绑定地址、主机防火墙或云安全组 |
测试容器内的 localhost 时必须特别谨慎。下面两个命令测试的不是同一对象:
docker exec api curl http://127.0.0.1:8080
docker run --rm --network app-net curlimages/curl \
http://api:8080
前者测试 api 自己的网络命名空间,后者测试另一个容器通过网络访问 api。
4. 检查端口发布
docker port web
docker ps --format 'table {{.Names}}\t{{.Ports}}'
ss -lntp
docker port web 能说明 Docker 记录的发布关系,例如:
80/tcp -> 127.0.0.1:8080
如果发布关系存在但访问失败,再检查:
- 应用是否监听容器端口;
- 发布是否绑定到了仅本机地址;
- 客户端是否访问了错误的宿主机 IP;
- 主机防火墙是否允许该端口;
- 云安全组是否允许入站;
- 是否有其他进程占用了宿主机端口。
不要仅凭 ss 看到宿主机监听端口就断定应用健康。某些端口发布路径可能由内核规则、用户态代理或不同 Engine 配置实现,具体监听表现具有实现差异。应结合端到端连接测试和 Docker 网络信息判断。
5. 检查防火墙和 Docker 规则
在使用 iptables 的系统上,可以查看:
sudo iptables -t nat -S
sudo iptables -S
在 nftables 系统上,可以查看:
sudo nft list ruleset
Docker 会安装与容器转发和端口发布相关的规则。不要直接清空或手工删除 Docker 维护的规则,否则可能同时破坏:
- 已有容器的出站 NAT;
- 端口发布;
- bridge 间隔离;
- Swarm ingress;
- Docker 重启时的状态一致性。
如果需要为宿主机统一增加规则,iptables 环境中通常应优先考虑 DOCKER-USER 链,而不是直接修改 Docker 自动生成的链。规则仍需结合当前发行版、防火墙管理器和 nftables 兼容模式验证。
同时检查 Docker daemon 配置:
docker info
关注:
- Rootless 或 rootful 模式;
- Swarm 是否启用;
- IPv6 是否启用;
- 使用的存储和网络环境;
- 安全配置和代理配置。
6. 用抓包确定报文停在哪里
Linux 宿主机上可以先抓 bridge:
sudo tcpdump -ni docker0 tcp port 80
如果是用户自定义 bridge,先通过:
docker network inspect app-net
确认对应 bridge 名称,再抓对应接口。
Overlay 排障时,除容器接口外,还要观察节点间的 UDP 数据面,例如:
sudo tcpdump -ni any udp port 4789
抓包结果可帮助区分:
- 客户端根本没有发包;
- 宿主机收到了包但没有进行预期转发;
- 报文到达 bridge 但没有进入容器;
- Overlay 外层包没有到达目标节点;
- 目标节点解封装后没有送达任务。
抓包会暴露请求数据、Cookie 或凭据,生产环境中应限制权限和保存范围。
十、典型故障路径和恢复方法
故障一:容器之间使用 localhost 连接
错误配置:
DATABASE_HOST=localhost
如果应用和数据库是两个容器,localhost 指向应用容器自身,不是数据库容器。
修复方式:
DATABASE_HOST=db
DATABASE_PORT=5432
前提是两个服务连接同一 Docker 网络,并且数据库实际监听容器网络地址。
验证:
getent hosts db
nc -vz db 5432
故障二:服务名无法解析
检查三件事:
docker network inspect app-net
docker inspect api
docker inspect db
确认 api 和 db 的网络端点是否位于同一个网络。常见原因包括:
- Compose 服务声明了不同网络;
- 手工
docker run接入了错误网络; - 使用了容器名而不是 Compose 服务名;
- 网络被删除后容器未重新创建;
- 查询发生在默认
bridge网络; - DNS 配置被应用或启动脚本覆盖。
修复后应重新测试:
docker exec api getent hosts db
仅重启应用进程通常不能修复“容器未连接正确网络”;必要时应使用正确网络重新创建容器。
故障三:发布端口后仍然 connection refused
按顺序执行:
docker port web
docker exec web ss -lntp
docker inspect web
curl -v http://127.0.0.1:8080
若 docker port 没有映射,重新创建容器并添加 -p。端口发布是容器创建时的重要配置,不能把 EXPOSE 当成动态发布。
若映射存在但容器内只监听 127.0.0.1,修改应用监听地址。
若容器内服务端口错误,例如应用监听 8080 却发布:
-p 8080:80
则应改为:
-p 8080:8080
宿主机端口和容器端口不要求相同,但目标端口必须匹配应用真实监听端口。
故障四:跨节点 Overlay 中小请求正常,大请求超时
这通常提示 MTU 或路径分片问题。原因链是:
- Overlay 为原始报文增加封装头;
- 有效负载空间变小;
- 某些路径禁止分片或丢弃 ICMP;
- 小报文仍能通过,大报文触发丢包;
- TCP 可能表现为连接建立后请求卡住或重传。
可以使用逐步增大的探测报文和抓包验证路径,但具体 ping 的 -M 语法因系统而异。修复方向包括:
- 统一节点间网络 MTU;
- 为 Overlay 配置合适的 MTU;
- 允许必要的 PMTU 发现报文;
- 检查云厂商网络和隧道链路;
- 不要在未验证的情况下盲目把 MTU 改成某个固定值。
MTU 是路径属性,不是“容器网络越小越安全”的开关。
故障五:Overlay 服务发布可用,但任务间通信失败
这说明 ingress 或发布路径可能正常,但 overlay 数据面或服务发现异常。应分别测试:
- 解析服务名;
- 访问服务 VIP 或任务地址;
- 查看服务任务分布;
- 检查节点间 7946 和 4789;
- 检查 overlay 网络状态。
命令示例:
docker service ls
docker service ps web
docker network inspect app-overlay
docker node ls
不要因为访问发布端口成功,就推断所有任务间网络都正常;发布路径和服务内部路径可能经过不同组件。
十一、网络安全边界:能连通不代表应当连通
1. 发布端口扩大了入口
不指定绑定地址:
-p 8080:80
通常会让宿主机外部地址也参与监听或转发。只供本机开发工具访问时,优先:
-p 127.0.0.1:8080:80
只供内部应用访问时,通常不需要发布到宿主机,直接把服务放到同一用户自定义网络即可。
2. Bridge 网络不是强安全边界
同一 bridge 中的容器能够直接进行网络通信。应用级认证、数据库授权和 TLS 仍然必要。Docker 网络隔离可以减少不必要的连通性,但不能替代:
- 身份认证;
- 最小权限;
- 主机访问控制;
- 密钥管理;
- 加密传输;
- 审计和监控。
在多租户或高风险环境中,还应评估宿主机权限、容器能力、网络策略和运行时隔离,而不能只依赖“不同 Docker 网络”。
3. internal 网络的含义
Compose 或 Docker 网络可以配置为内部网络,使其不直接连接外部网络路径。它适合数据库、缓存等不需要出站访问的组件,但具体行为仍需结合驱动和 Docker 版本验证。
“内部网络”不等于网络中的服务彼此隔离。相同内部网络中的容器仍可能互相访问,应用层访问控制仍然需要存在。
十二、IPv6、地址规划和网络配置边界
Docker 默认环境常以 IPv4 为主。启用 IPv6 需要同时考虑:
- Docker daemon 的 IPv6 配置;
- 网络创建时的 IPv6 子网;
- 宿主机和上游网络的 IPv6 路由;
- 防火墙规则;
- 应用是否监听 IPv6;
- DNS 是否返回 AAAA 记录。
不能因为容器拥有 IPv6 地址,就推断外部 IPv6 客户端一定能够访问。外部可达性仍取决于路由、发布规则和防火墙。
自定义子网时应避免与宿主机、VPN、云 VPC 或其他 Docker 网络重叠:
docker network create \
--subnet 172.30.0.0/16 \
app-net
如果宿主机已经通过 VPN 使用 172.30.0.0/16,容器发往该地址的流量可能被错误地认为是本地 Docker 网络流量。地址规划冲突会表现为间歇性、环境相关的网络故障,尤其难以从单个容器内部判断。
十三、生产环境中的取舍
使用用户自定义 Bridge 的场景
适合:
- 单机 Compose 应用;
- 明确划分 frontend、backend 等网络;
- 服务之间需要通过名称发现;
- 不需要跨宿主机通信。
使用 Overlay 的场景
适合:
- 多 Docker 节点;
- Swarm 服务需要跨节点通信;
- 需要服务级别的跨节点发现和调度;
- 能够管理节点间控制面、数据面和防火墙。
如果系统实际运行在 Kubernetes、云负载均衡或其他编排平台上,应使用平台提供的网络抽象,不要把 Docker Compose 的网络模型直接当成跨平台标准。
端口发布的原则
内部服务尽量通过 Docker 网络访问,不发布到宿主机:
services:
api:
networks: [backend]
db:
networks: [backend]
只有需要被宿主机、反向代理或外部客户端访问的入口才发布端口:
services:
gateway:
ports:
- "127.0.0.1:8080:80"
如果反向代理运行在另一台机器上,则不能绑定 127.0.0.1,因为远程代理无法访问宿主机回环地址;此时应绑定适当的宿主机地址,并通过防火墙限制来源。
十四、最终检查表
遇到 Docker 网络问题时,可以按以下顺序收敛范围:
# 1. 容器是否运行
docker ps -a
# 2. 容器连接了哪些网络
docker inspect CONTAINER
docker network inspect NETWORK
# 3. 目标名称是否能解析
docker exec SOURCE getent hosts TARGET
# 4. 目标端口是否能建立 TCP 连接
docker exec SOURCE nc -vz TARGET PORT
# 5. 目标应用是否监听正确地址和端口
docker exec TARGET ss -lntp
# 6. 宿主机发布关系是否存在
docker port TARGET
ss -lntp
# 7. 宿主机路由、防火墙和 Docker 规则
ip route
sudo nft list ruleset
# 或按系统使用 iptables 检查
# 8. 跨节点 Overlay 状态
docker node ls
docker service ps SERVICE
docker network inspect OVERLAY_NETWORK
# 9. 仍不确定时抓包
sudo tcpdump -ni any ...
核心判断可以归纳为三句话:
- 容器间访问优先使用同一用户自定义网络中的服务名,而不是
localhost或固定 IP。 EXPOSE不是端口发布;外部访问必须明确配置-p或 Composeports,并检查绑定地址。- Bridge 解决单机容器连接,Overlay 解决跨节点服务网络;两者都依赖 Linux 路由、NAT、防火墙、DNS 和实际监听状态。
理解这些数据流和状态边界后,Docker 网络故障就不再只是“重启容器试试看”,而可以被拆解为名称解析、网络端点、路由转发、端口发布、应用监听和协议响应等可验证的独立条件。
系列导航与关联阅读
- 系列入口:Docker 完整学习路线:从镜像与容器到安全、可观测和生产交付
- 上一篇:Docker 多架构构建:Buildx、QEMU、Manifest 与跨平台发布
- 下一篇:Docker 存储:Volume、Bind Mount、tmpfs、权限和备份恢复
- 延伸:Docker Compose 完整指南:服务、网络、卷、依赖、Profile 和生产边界
- 延伸:Docker 故障排查:启动失败、网络、磁盘、OOM、构建和 Daemon
官方资料
本文依据 Docker、OCI 与 CNCF 官方文档重新梳理;正文与生产检查清单由 WR BLOG 编写。

评论
0 条讨论