Docker 网络结构完整说明

taki Lv1

📋 目录

  1. 核心概念

  2. 网络架构

  3. 容器网络通信

  4. 实战示例

  5. 常见问题

  6. 诊断命令


核心概念

1. Docker网络的三大组件

① 虚拟网卡对 (veth pair)

概念: 一对虚拟网卡,像一根虚拟网线连接容器和宿主机

1
2
3
4
容器内部          |          宿主机
|
eth0 ←═══════════════════→ vethxxxxxx
172.18.0.5 | (连接到网桥)

特点:

  • 一端在容器内(通常叫 eth0)

  • 另一端在宿主机(名字类似 veth1a2b3c4)

  • 数据从一端进,从另一端出

查看命令:

1
2
3
4
5
# 在宿主机查看
ip link show | grep veth

# 在容器内查看
docker exec 容器名 ip addr show eth0

② 网桥 (Bridge)

概念: 虚拟的网络交换机,连接多个容器

1
2
3
4
5
6
7
┌─────────── 网桥 (br-abc123) ───────────┐
│ │
│ veth1234 ←→ veth5678 ←→ veth9abc │
│ │ │ │ │
└──────┼───────────┼───────────┼─────────┘
│ │ │
[容器A] [容器B] [容器C]

作用:

  • 转发数据包(类似物理交换机)

  • 维护MAC地址表

  • 隔离不同网络的容器

查看命令:

1
2
3
4
5
# 查看所有网桥
brctl show

# 或使用Docker命令
docker network ls

③ 网络命名空间 (Network Namespace)

概念: 每个容器有独立的网络栈

1
2
3
4
5
6
容器A命名空间        容器B命名空间
┌────────────┐ ┌────────────┐
127.0.0.1 │ │ 127.0.0.1 │ ← 各自独立
│ eth0 │ │ eth0 │
│ 路由表 │ │ 路由表 │
└────────────┘ └────────────┘

隔离内容:

  • IP地址

  • 路由表

  • 防火墙规则

  • 网络接口


网络架构

Docker网络类型

1. bridge(默认网络)

1
2
3
docker network ls
# NETWORK ID NAME DRIVER
# abc123 bridge bridge ← Docker自带的默认网络

特点:

  • 所有 docker run 创建的容器默认加入

  • 网桥名称:docker0

  • 网段:通常 172.17.0.0/16

  • 容器间可以通过IP互访,但不能用容器名


2. 自定义网络(推荐)

1
2
3
4
5
# 手动创建
docker network create my_network

# docker-compose自动创建
docker-compose up # 自动创建 项目名_default 网络

特点:

  • 网桥名称:br-xxxxxxxx(随机ID)

  • 可以自定义网段

  • 容器间可以用容器名互访(内置DNS)

  • 更好的隔离性


完整网络架构图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
┌──────────────────────────────────────────────────────────────────┐
│ 宿主机 │
│ │
│ 物理网卡: ens20 (192.169.1.121) │
│ │ │
│ │ (外部流量) │
│ ↓ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ iptables / NAT 规则 │ │
│ │ 端口映射: -p 5001:5001172.18.0.8:5001 │ │
│ └────────────────┬───────────────────────────────────┘ │
│ ↓ │
│ ╔═══════════════════════════════════════════════════╗ │
│ ║ docker0 网桥 (默认bridge) ║ │
│ ║ 172.17.0.0/16 ║ │
│ ║ ║ │
│ ║ veth001f34c ←→ [GWAgent容器] ║ │
│ ║ ↑ 172.17.0.2 ║ │
│ ║ 宿主机侧 容器内 eth0 ║ │
│ ╚═══════════════════════════════════════════════════╝ │
│ │
│ ╔═══════════════════════════════════════════════════╗ │
│ ║ br-43ebdd777e58 (docker_default) ║ │
│ ║ 172.18.0.0/16 ║ │
│ ║ ║ │
│ ║ veth510a281 ←→ [Dify-API] 172.18.0.8 ║ │
│ ║ veth0d9257b ←→ [Dify-DB] 172.18.0.9 ║ │
│ ║ vethae6b5f8 ←→ [Dify-Redis] 172.18.0.10 ║ │
│ ║ ... (更多容器) ║ │
│ ╚═══════════════════════════════════════════════════╝ │
│ │
│ ╔═══════════════════════════════════════════════════╗ │
│ ║ br-2d6e1e6b461e (ssrf_proxy_network) ║ │
│ ║ 172.19.0.0/16 ║ │
│ ║ ║ │
│ ║ veth236b89c ←→ [Proxy容器] ║ │
│ ║ vethf1c8fce ←→ [Dify-API的eth1] ← 多网卡! ║ │
│ ╚═══════════════════════════════════════════════════╝ │
└──────────────────────────────────────────────────────────────────┘

容器网络通信

场景1:同一网络内的容器通信(推荐)

1
2
3
4
5
6
7
8
9
10
┌─────────── docker_default 网络 ───────────┐
│ │
│ [GWAgent] 172.18.0.5
│ │ │
│ │ curl [http://dify-api:5001](http://dify-api:5001/) │
│ ↓ │
│ 通过网桥转发 │
│ ↓ │
│ [Dify-API] 172.18.0.8:5001
└────────────────────────────────────────────┘

流量路径:

1
2
3
4
5
6
7
8
9
GWAgent容器 (eth0: 172.18.0.5)

数据包发送到网桥

网桥查MAC地址表

转发到目标容器的veth

Dify容器 (eth0: 172.18.0.8)

访问方式:

1
2
3
4
5
6
7
8
# ✅ 推荐:使用容器名(自动DNS解析)
curl [http://dify-api:5001/v1/workflows/run](http://dify-api:5001/v1/workflows/run)

# ✅ 也可以:使用容器IP
curl [http://172.18.0.8:5001/v1/workflows/run](http://172.18.0.8:5001/v1/workflows/run)

# ❌ 错误:使用[localhost](http://localhost/)
curl [http://localhost:5001](http://localhost:5001/) # 只会访问自己!

特点:

  • ⚡ 速度最快(~1-2ms延迟)

  • 🔒 内部隔离,外部无法直接访问

  • 🚫 不经过宿主机网络协议栈

  • 🚫 不经过NAT转换


场景2:跨网络容器通信(需要手动连接)

1
2
3
4
5
6
┌─── bridge网络 ───┐         ┌─── docker_default ───┐
│ │ │ │
│ [GWAgent] │ ✗ 不通 │ [Dify-API] │
172.17.0.2 │ ←─────→ │ 172.18.0.8
│ │ │ │
└───────────────────┘ └───────────────────────┘

解决方案:把容器加入目标网络

1
2
3
4
5
6
7
8
9
10
# 查看Dify所在的网络
docker inspect dify-api | grep -A 10 Networks

# 假设输出显示在 docker_default 网络
docker network connect docker_default gwagent

# 现在GWAgent有两个网卡了
docker exec gwagent ip addr
# eth0: 172.17.0.2 (bridge网络)
# eth1: 172.18.0.5 (docker_default网络) ← 新增

连接后的架构:

1
2
3
4
5
6
7
8
9
┌─── bridge网络 ───┐         ┌─── docker_default ───┐
│ │ │ │
│ [GWAgent] │ │ [GWAgent] ← 同一容器
eth0: 172.17.0.2 │ │ eth1: 172.18.0.5
│ │ │ │ │
└───────────────────┘ │ ↓ 可以访问 │
│ [Dify-API] │
172.18.0.8
└───────────────────────┘

场景3:宿主机访问容器

方式A:直接访问容器IP

1
2
# 在宿主机上
curl [http://172.18.0.8:5001](http://172.18.0.8:5001/)

流量路径:

1
2
3
4
5
6
7
8
9
宿主机

路由表判断: 172.18.0.8 在 br-43ebdd777e58

发送到网桥

网桥转发到目标veth

容器

特点:

  • ✅ 简单直接

  • ⚠️ 容器重启后IP可能变化

  • 🔒 只能在宿主机本地访问


方式B:通过端口映射(对外暴露)

1
docker run -p 5001:5001 dify-api

流量路径:

1
2
3
4
5
6
7
8
9
10
11
外部客户端 (192.169.1.100)

访问: [http://192.169.1.121:5001](http://192.169.1.121:5001/)

宿主机物理网卡 (ens20)

iptables NAT规则: 5001172.18.0.8:5001

网桥转发

容器 (172.18.0.8:5001)

特点:

  • 🌍 外部可访问

  • 🐌 经过NAT,稍慢(~5-10ms)

  • 🔓 需要考虑安全性


场景4:容器访问外部网络

1
2
3
4
5
6
7
8
9
10
11
12
容器 (172.18.0.8)

请求: [http://www.baidu.com](http://www.baidu.com/)

网桥的默认网关 (172.18.0.1)

iptables NAT (源地址转换)
172.18.0.8192.169.1.121

宿主机物理网卡 (ens20)

外部互联网

关键配置:

1
2
3
4
5
# 查看NAT规则
iptables -t nat -L -n

# 输出会包含类似规则:
# MASQUERADE all -- 172.18.0.0/16 0.0.0.0/0

实战示例

示例1:将GWAgent加入Dify网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 查看Dify容器的网络
docker inspect dify-api | grep -A 10 '"Networks"'

# 输出示例:
# "Networks": {
# "docker_default": {
# "IPAddress": "172.18.0.8",
# ...
# }
# }

# 2. 将GWAgent加入同一网络
docker network connect docker_default gwagent

# 3. 验证网络连接
docker exec gwagent ip addr
# 应该看到两个网卡:eth0 和 eth1

# 4. 测试连通性
docker exec gwagent curl [http://dify-api:5001/v1/health](http://dify-api:5001/v1/health)

示例2:创建自定义网络并运行容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 创建自定义网络
docker network create \
--driver bridge \
--subnet 172.20.0.0/16 \
--gateway 172.20.0.1 \
my_custom_network

# 2. 运行容器并指定网络
docker run -d \
--name app1 \
--network my_custom_network \
--ip 172.20.0.10 \
my_image

docker run -d \
--name app2 \
--network my_custom_network \
--ip 172.20.0.11 \
my_image

# 3. 容器间可以直接用名称访问
docker exec app1 ping app2 # ✅ 可以ping通
docker exec app1 curl [http://app2:8080](http://app2:8080/) # ✅ 可以访问

示例3:使用docker-compose管理网络

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# docker-compose.yml
version: '3.8'

services:
gwagent:
image: gwagent:latest
container_name: gwagent
networks:
- default # 自动加入 项目名_default 网络

dify-api:
image: dify-api:latest
container_name: dify-api
networks:
- default
- ssrf_proxy # 同时连接两个网络

dify-db:
image: postgres:15
networks:
- default

networks:
default:
driver: bridge
ipam:
config:
- subnet: 172.18.0.0/16

ssrf_proxy:
driver: bridge
ipam:
config:
- subnet: 172.19.0.0/16

启动后的效果:

1
2
3
4
5
6
7
8
9
docker-compose up -d

# gwagent 可以直接访问 dify-api
docker exec gwagent curl [http://dify-api:5000](http://dify-api:5000/)

# dify-api 有两个网卡
docker exec dify-api ip addr
# eth0: 172.18.0.x (default网络)
# eth1: 172.19.0.x (ssrf_proxy网络)

常见问题

Q1: 为什么 curl ``[localhost:5001](http://localhost:5001/) 访问不到其他容器?

答: [localhost](http://localhost/) / 127.0.0.1 永远指向容器自己

1
2
3
4
5
6
7
8
9
10
11
12
┌─────────────────────────────────────────┐
│ 容器内部 │
│ │
127.0.0.1 ──→ 只能访问本容器 │
172.18.0.8 ──→ 这才是对外的IP
│ │
│ curl [http://localhost:5001](http://localhost:5001/) │
│ → 访问本容器的5001端口 │
│ │
│ curl [http://other-container:5001](http://other-container:5001/) │
│ → 访问其他容器 ✅ │
└─────────────────────────────────────────┘

Q2: 容器IP会变吗?

答: 会!容器重启后IP可能改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 第一次启动
docker run --name app nginx
docker inspect app | grep IPAddress
# "IPAddress": "172.17.0.2"

# 重启
docker restart app
docker inspect app | grep IPAddress
# "IPAddress": "172.17.0.2" ← 可能不变

# 但如果删除重建
docker rm -f app
docker run --name app nginx
docker inspect app | grep IPAddress
# "IPAddress": "172.17.0.5" ← IP变了!

解决方案:

  • ✅ 使用容器名(Docker DNS自动更新)

  • ✅ 使用docker-compose指定固定IP

  • ❌ 不要在代码里写死IP


Q3: 同一网络可以用IP访问吗?

答: 可以,但不推荐

1
2
3
4
5
# ✅ 可以用IP
curl [http://172.18.0.8:5001](http://172.18.0.8:5001/)

# ✅✅ 更推荐用容器名
curl [http://dify-api:5001](http://dify-api:5001/)

原因:

  • 容器名稳定,IP可能变

  • 容器名更易读

  • Docker DNS自动解析


Q4: 如何判断两个容器是否在同一网络?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 方法1:查看网络成员
docker network inspect docker_default

# 输出:
# "Containers": {
# "abc123": {
# "Name": "gwagent",
# ...
# },
# "def456": {
# "Name": "dify-api",
# ...
# }
# }

# 方法2:查看容器的网络
docker inspect gwagent | grep -A 10 Networks
docker inspect dify-api | grep -A 10 Networks

# 比较输出的网络名称是否相同

Q5: 容器能访问宿主机服务吗?

答: 可以,使用特殊的主机名

Linux (Docker 20.10+)

1
2
3
4
5
# 在容器内访问宿主机的服务
curl [http://host.docker.internal:8080](http://host.docker.internal:8080/)

# 或使用网关IP
curl [http://172.18.0.1:8080](http://172.18.0.1:8080/) # 172.18.0.1 是网桥的网关

查看网关IP

1
2
docker network inspect docker_default | grep Gateway
# "Gateway": "172.18.0.1"

诊断命令

1. 查看网络列表

1
2
3
4
5
6
7
8
docker network ls

# 输出:
# NETWORK ID NAME DRIVER
# abc123 bridge bridge
# def456 docker_default bridge
# ghi789 host host
# jkl012 none null

2. 查看网络详情

1
2
3
4
5
6
docker network inspect docker_default

# 重要信息:
# - Subnet: 网段
# - Gateway: 网关
# - Containers: 连接的容器列表

3. 查看容器的网络配置

1
2
3
4
5
6
7
# 方法1:使用docker inspect
docker inspect 容器名 | grep -A 20 NetworkSettings

# 方法2:进入容器查看
docker exec 容器名 ip addr
docker exec 容器名 ip route
docker exec 容器名 cat /etc/resolv.conf # DNS配置

4. 查看宿主机的网桥和veth

1
2
3
4
5
6
7
8
9
10
# 查看所有网桥
brctl show
# 或
ip link show type bridge

# 查看网桥的详细信息
ip addr show br-43ebdd777e58

# 查看所有veth pair
ip link show | grep veth

5. 测试容器间连通性

1
2
3
4
5
6
7
8
# 从容器A ping容器B
docker exec 容器A ping 容器B

# 从容器A curl容器B
docker exec 容器A curl [http://容器B:端口](http://xn--b-y59a66n/:端口)

# 查看容器的路由表
docker exec 容器名 ip route

6. 查看网络流量(抓包)

1
2
3
4
5
6
7
8
# 在网桥上抓包
tcpdump -i br-43ebdd777e58 -nn

# 在veth上抓包
tcpdump -i veth510a281 -nn

# 过滤特定端口
tcpdump -i br-43ebdd777e58 port 5001 -nn

7. 查看iptables规则

1
2
3
4
5
6
7
8
# 查看NAT规则
iptables -t nat -L -n -v

# 查看FORWARD规则
iptables -L FORWARD -n -v

# 查看Docker自动添加的规则
iptables -t nat -L DOCKER -n

8. 排查网络问题的完整流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 确认容器在哪个网络
docker inspect 容器名 | grep -A 10 Networks

# 2. 确认网络的网段和网关
docker network inspect 网络名

# 3. 在容器内测试DNS解析
docker exec 容器名 nslookup 目标容器名
docker exec 容器名 cat /etc/resolv.conf

# 4. 测试网络连通性
docker exec 容器名 ping 目标容器名
docker exec 容器名 curl [http://目标容器名:端口](http://xn--eqrr8cw6g8xo88r/:端口)

# 5. 查看容器的网络接口
docker exec 容器名 ip addr
docker exec 容器名 ip route

# 6. 检查防火墙规则
iptables -L -n | grep 容器IP

最佳实践

✅ DO(推荐)

  1. 使用自定义网络

  2. 使用容器名访问

  3. 使用docker-compose管理网络

  4. 只暴露必要的端口


❌ DON’T(避免)

  1. 不要使用默认bridge网络(容器无法用名称互访)

  2. 不要在代码里写死IP地址(IP会变)

  3. 不要使用 [localhost](http://localhost/) 访问其他容器

  4. **不要暴露不必要的端口到 ****0.0.0.0**


总结

核心要点

  1. 每个容器都有独立的网络命名空间

  2. veth pair 连接容器和宿主机

  3. 网桥负责转发同一网络的流量

  4. 同一网络的容器可以用容器名互访

  5. 不同网络的容器默认隔离

  6. 127.0.0.1** 永远只访问容器自己**

通信方式对比


文档生成时间: 2025-12-23

适用版本: Docker 20.10+

Comments