前言

你有没有想过这个问题:

你的手机和电脑连的是同一个 WiFi,它们之间可以互相访问。但你的电脑上同时跑着十几个 Docker 容器,它们是怎么"认识"彼此的?为什么容器 A 能 ping 通容器 B,却不能直接被外网访问?

答案就藏在 Linux 网络 namespace虚拟网桥里。


一、先从生活类比说起

你家里有一台路由器,手机、电脑、平板都连在上面。路由器给每个设备分配了一个局域网 IP(比如 192.168.1.x),设备之间可以互相通信,但对外网来说,它们都躲在路由器后面,外网只能看到路由器的公网 IP。

Docker 的 bridge 网络做的事情完全一样:

  • Docker 在宿主机上创建一个虚拟路由器(docker0 网桥)
  • 每个容器分配一个虚拟局域网 IP(172.17.0.x
  • 容器之间通过 docker0 互相通信
  • 容器访问外网时,由宿主机做 NAT 转发,就像路由器帮你"出去"一样

二、容器网络隔离的底层:Linux namespace

在聊三种网络模式之前,必须先理解一个概念——Linux namespace

namespace 是 Linux 内核提供的一种隔离机制,Docker 就是靠它实现"每个容器以为自己是独立的系统"这个效果的。一共有 6 种 namespace:

namespace 隔离的内容
net 网络设备、IP、端口、路由表
pid 进程 ID,容器内看不到宿主机进程
mnt 文件系统挂载点
uts 主机名和域名
ipc 进程间通信
user 用户和用户组 ID

网络 namespace(net ns)是本文的核心。

每个容器启动时,Docker 都会给它创建一个独立的 net namespace,相当于给容器一张"独立的网卡"。容器内部看到的 eth0 是虚拟的,和宿主机的物理网卡完全隔离。

验证容器网络隔离:

首先查看当前运行中的容器:

bash

docker ps

找到两个运行中的容器名后(没有容器可以自己创建几个,文章下面创建自定义 bridge 网络部分也有容器创建的指令),分别进入查看网络接口:

bash

# 将 <容器名A> 和 <容器名B> 替换为 docker ps 查询出来的实际容器名
docker exec -it <容器名A> ip addr
docker exec -it <容器名B> ip addr

你会看到每个容器内只有两个网卡:

lo(loopback 回环网卡):固定 IP 127.0.0.1,只用于容器自己和自己通信。比如程序里写 localhost 访问自身服务就走这里,数据不经过任何网络直接回到自己。

eth0(容器虚拟网卡):Docker 分配的局域网 IP,比如 172.23.0.17。对比两个容器的 eth0 IP 会发现各不相同——这就是 net namespace 隔离的直观体现,每个容器都有自己独立的网络视图。eth0@if46 里的 if46 是 veth pair 在宿主机一端的编号,两端绑定,数据从这头进那头出。

⚠️ Windows 用户注意:Docker Desktop 运行在 WSL2 虚拟机内,docker0 网桥存在于 WSL2 里而非 Windows 层,所以 ipconfig 看不到它。这是正常现象,不影响容器网络正常工作。

三、bridge 模式(默认)

原理:虚拟网桥 + veth pair

bridge 是 Docker 默认的网络模式。它的底层实现依赖两个关键组件:

① docker0 网桥

Docker 启动时会在宿主机上创建一个名为 docker0 的虚拟网桥,默认 IP 是 172.17.0.1。所有使用 bridge 模式的容器都连接到这个网桥上,就像把网线插进同一台交换机。

② veth pair(虚拟网卡对)

每启动一个容器,Docker 就创建一对虚拟网卡(veth pair)——一端放进容器里叫 eth0,另一端留在宿主机上连接到 docker0 网桥。

数据从容器 eth0 发出 → 经过 veth 对端 → 到达 docker0 网桥 → 转发给目标容器。

容器 A (172.17.0.2)          容器 B (172.17.0.3)
    eth0                          eth0
     |                             |
   veth0a ---- docker0 网桥 ---- veth0b
                   |
              宿主机 eth0(物理网卡)
                   |
                 外网

容器访问外网怎么走?

靠 iptables 的 NAT 规则。容器发出的包到达 docker0 后,iptables 把源 IP 从 172.17.0.x 替换成宿主机的公网 IP,再发出去。响应包回来时再反向替换。这就是为什么外网看不到容器 IP,只能看到宿主机 IP。

什么是 iptables?

iptables 是 Linux 内核内置的防火墙/数据包处理工具,可以对经过网络的数据包做拦截、修改、转发、丢弃等操作。Docker 启动时会自动往 iptables 里写一批规则,你不需要手动配置,但理解它能帮你搞清楚数据包的流向。

什么是 NAT?

NAT(Network Address Translation,网络地址转换)就是把数据包里的 IP 地址替换掉。分两种:

  • SNAT(源地址转换):把发出去的包的源 IP 替换成宿主机 IP,让外网以为是宿主机在发请求,容器 IP 对外不可见。
  • DNAT(目标地址转换):把进来的包的目标 IP 替换成容器 IP,这就是 -p 8080:80 端口映射的底层实现——外网访问宿主机 8080,iptables 把目标地址改成容器的 172.17.0.x:80,转发进去。

所以 -p 端口映射的底层就是 iptables DNAT,不是什么魔法。

常用命令:

bash

# 查看所有网络
docker network ls

# 创建自定义 bridge 网络
docker network create my-network
# 删除网络
docker network rm my-network
# 删除所有未使用的网络
docker network prune

# 运行容器并加入指定网络(-d 后台运行)
docker run -d --network my-network --name app nginx
# 停止并删除容器
docker stop app && docker rm app

# 将已有容器加入网络
docker network connect my-network <容器名>
# 将容器从网络中移除(容器还在,只是退出这个网络)
docker network disconnect my-network <容器名>

# 查看网络详情(能看到哪些容器在里面)
docker network inspect my-network

实战:用自定义网络控制容器通信

下面用一个例子说明如何通过网络实现容器隔离——同一网络内的容器可以互相访问,不同网络的容器完全隔离:

bash

# 第一步:创建两个自定义网络
docker network create net-a
docker network create net-b

# 第二步:启动三个容器,container1 和 container2 在 net-a,container3 在 net-b
docker run -d --name container1 --network net-a nginx
docker run -d --name container2 --network net-a nginx
docker run -d --name container3 --network net-b nginx

# 第三步:进入 container1,测试通信
docker exec -it container1 curl http://container2   # 能通,同在 net-a
docker exec -it container1 curl http://container3   # 不能通,不同网络

# 清理
docker stop container1 container2 container3
docker rm container1 container2 container3
docker network rm net-a net-b

这个例子说明了三件事:

  • 自定义网络内支持容器名 DNS 解析,直接用名字访问不需要知道 IP
  • 不同网络之间默认完全隔离,无法互相访问
  • 网络隔离是天然的安全边界——比如数据库容器只加入后端服务的网络,前端容器根本访问不到数据库

💡 实际开发建议:不要用默认的 docker0,自己创建 bridge 网络。自定义 bridge 网络支持容器名 DNS 解析,这也是 Docker Compose 里 DB_HOST=mysql 能生效的原因。默认 docker0 不支持这个特性。


四、host 模式

原理:跳过 namespace,直接共享宿主机网络栈

host 模式下,容器不会创建独立的 net namespace,而是直接使用宿主机的网络接口:

bash

docker run -d --network host nginx

这时容器内的网络接口和宿主机完全一样,容器监听的端口就是宿主机的端口,不需要 -p 端口映射。

适合什么场景?

  • 对网络性能要求极高的场景(省去了 NAT 转发开销)
  • 需要监听宿主机所有网络接口的工具类容器

缺点:

  • 没有网络隔离,容器可以看到宿主机所有网络接口
  • 端口冲突风险高,两个容器不能监听同一个端口
  • 仅支持 Linux,Windows/Mac 上的 Docker Desktop 底层是虚拟机,host 模式共享的是虚拟机的网络栈而非真正的宿主机网络,效果和预期不同

五、overlay 模式

原理:跨主机容器网络,VXLAN 封装

bridge 和 host 都是单机模式,overlay 是用来解决多台宿主机上的容器互相通信的问题。

场景:你有三台服务器,每台上跑着几个容器,怎么让不同机器上的容器像在同一个局域网里一样通信?

overlay 的做法是在物理网络之上构建一层虚拟网络,用 VXLAN 协议把容器的数据包封装进 UDP 包,通过物理网络传输,到达目标机器后再解封装交给目标容器。对容器来说,感觉像直接通信,底层的跨机器传输是透明的。

bash

机器 A                          机器 B
容器1 (10.0.0.2)               容器2 (10.0.0.3)
    |                               |
  VXLAN 封装                    VXLAN 解封装
    |                               |
  eth0 ——— 物理网络 ————————— eth0

什么时候用?

  • Docker Swarm 集群
  • Kubernetes 的 CNI 网络插件(Flannel、Calico 底层都用了类似思路)

overlay 的详细原理会在后面 K8s 网络那篇深入讲,这里先有个印象。


六、三种模式对比

bridge host overlay
网络隔离
跨主机通信 不支持 不支持 支持
性能 中(有 NAT 开销) 高(无 NAT) 中低(有封装开销)
端口映射 需要 -p 不需要 不需要
适用场景 日常开发、单机部署 高性能工具容器 多机集群
DNS 服务名解析 自定义网络支持 不支持 支持

七、结合 Docker Compose 看网络

回到我们上一篇的项目,docker-compose.yaml 里所有服务都在同一个 Compose 项目下,Docker 会自动给它们创建一个自定义 bridge 网络(名字是 项目名_default)。

这就是为什么:

yaml

environment:
  - DB_HOST=mysql      # 直接写服务名就能找到 mysql 容器
  - REDIS_HOST=redis   # 同理

服务名就是 DNS 名,Compose 的自定义 bridge 网络内置了 DNS 解析,容器之间用服务名通信,Docker 自动帮你解析成对应的容器 IP。


总结

概念 作用
net namespace 给每个容器独立的网络视图
veth pair 连接容器 net namespace 和宿主机网桥的虚拟网线
docker0 / 自定义 bridge 容器间通信的虚拟交换机
iptables SNAT 容器访问外网时替换源 IP
iptables DNAT -p 端口映射的底层实现
overlay / VXLAN 跨主机容器网络的封装协议

下一篇:Docker Volume 数据持久化——容器删了数据为什么还在,挂载原理是什么 🚀


📌 觉得有帮助点个赞!有问题欢迎评论区交流 👇

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐