☰
Docker网络详解:从bridge到overlay,容器互访与端口映射实战
2026/10/10 16:54:32 网站建设 项目流程

容器化部署早已是后端开发和运维的基本功,但很多人玩转 Docker 镜像、容器、数据卷之后,一碰到网络就抓瞎。服务起不来、容器间互相访问超时、端口明明映射了却死活连不上——这些问题十有八九都出在网络配置上。今天这篇就来完整梳理 Docker 的 network 网络管理,从最常用的 bridge 模式到跨主机通信的 overlay,把原理讲透,把命令讲明白,再把我自己踩过的坑一并交代清楚。

这篇内容适合三类人看:刚把 Docker 装好、准备上手跑中间件的新手;被“端口映射不生效”“容器间不通”反复折磨的排障者;以及想把跨主机容器组网彻底搞清楚、准备在生产环境落地的运维和开发。内容围绕 Docker 网络的核心模型、单机通信、跨主机通信和问题排查展开,全程不涉及平台专属概念,可以直接对照操作。

1. Docker 网络模型全景拆解

1.1 从网络命名空间说起

要理解 Docker 网络,绕不开 Linux 的网络命名空间(Network Namespace)。简单打个比方,Linux 系统是一个大厂房,厂房里有若干独立的小隔间,每个隔间有自己的一套网络设备、路由表、防火墙规则和 DNS 配置,跟其他隔间相互隔离。Docker 就是靠这个机制,让每个容器拥有独立的网络环境。容器里面的 eth0、lo 接口,都是建立在各自命名空间里的,容器以为自己在独立机器上跑,实际上它跟宿主机共享同一个内核。

Docker 的每个容器默认会分配一个虚拟网卡 eth0,这是一个 veth 接口对(veth pair)的一端。veth 接口对像一根虚拟网线,一头插在容器里,另一头插在宿主机或网桥上,数据包从一头进去就从另一头出来。容器内的 eth0 通常配一个 IP,这个 IP 只在 Docker 网络内部可见,宿主机网段看不到它。这就是为什么你在宿主机上ping容器 IP 往往能通,但容器 IP 不能当作真实业务地址对外提供服务。

理解这层逻辑之后,很多现象就能解释了。比如容器里运行的服务监听在 3306 端口,你在宿主机上curl 127.0.0.1:3306连不上,这很正常——服务其实监听在容器命名空间内的 eth0 上,宿主机要访问它,必须通过端口映射或者依附到 host 网络才行。网络命名空间用ip netns命令也能操作,Docker 只是把它封装成更上层的用户体验。

1.2 Docker 内置的四种网络模式

Docker 安装完成之后,系统里默认存在四个网络:bridge、host、none、container。用docker network ls可以看到它们。

网络模式底层机制隔离性端口映射典型场景
bridgeLinux 网桥 + veth 对容器间隔离但可互通支持(iptables DNAT)单机多容器部署
host共享宿主机网络栈无隔离不支持(天然全端口)性能敏感、高端口利用率
none仅回环接口完全隔离不支持跑批次任务、安全隔离
container复用指定容器网络栈与目标容器共享依赖目标容器调试、sidecar 容器

实际项目里,bridge 模式占了九成以上的使用场景。host 模式性能好,但端口管理混乱,常常两个容器争抢同一个端口;none 模式适合完全不需要网络的场景,比如单独跑一个离线计算任务;container 模式在调试和某些特殊架构里很有用,比如让一个网络抓包容器跟目标容器共享网络栈,直接抓它的流量。

需要特别注意,默认的 bridge 网络(名字就叫 bridge)虽然也是桥接模式,但它不支持基于容器名的 DNS 解析。换句话说,你在默认 bridge 网络里用容器名去访问另一个容器,会解析失败。这是多少新手踩过的坑:docker run不指定网络时默认在 bridge 里,容器之间靠 IP 能通,靠名字不通。后面章节我会专门说怎么解决这个问题。

1.3 Docker 网络相关的核心命令行工具

日常管理网络不需要记住太多命令,但下面这几个必须烂熟于心。我按使用频率排个序:

  • docker network ls:列出所有网络。注意观察 DRIVER 和 SCOPE 两列,scope 为 local 表示单机网络,swarm 表示跨节点网络。
  • docker network inspect <网络名>:查看一个网络的详细信息,包括网段、网关、已连接的容器。排障时这是第一个该执行的命令。
  • docker network create:自定义创建网络。可以指定驱动、子网、网关、IP 范围等参数,后面实战部分我会演示。
  • docker network connect/disconnect:动态地将一个运行中的容器挂到某个网络,或从某个网络摘下来。
  • docker run --network <网络名>:启动容器时指定所用网络。

还有两个容易忽略但很实用的参数:docker run --network host表示使用 host 模式;docker run -p的端口映射实际上也是网络配置的一部分,它依赖 iptables 规则动态维护。

2. 从零上手:bridge 网络的端口映射与容器互通实战

2.1 端口映射背后的 iptables 逻辑

先说说端口映射。很多人以为-p 3306:3306就是把宿主机的 3306 和容器的 3306 打通了,原理确实是端口转发,但实现方式是靠 iptables 的 DNAT 规则。这条规则的链路大致是:宿主机收到目标端口为 3306 的数据包,先经过 PREROUTING 链,命中 Docker 写入的 DNAT 规则后,把目标地址改写成容器 IP,再把数据包转发进 docker0 网桥,最后到达容器内的 eth0。

之所以要搞清楚这段逻辑,是因为排障时经常要检查 iptables。如果你在宿主机上跑了 firewalld、ufw 或者其他防火墙管理工具,它们的规则可能跟 Docker 的规则冲突。最常见的是 Docker 安装后,因为 iptables 被清空或者 FORWARD 链默认策略被改成 DROP,导致容器网络直接不可用。遇到这种情况,先看一眼/etc/sysctl.conf里的net.ipv4.ip_forward是否开启,再检查 iptables 的 FORWARD 链策略,基本能定位问题。

实际操作里,-p有两种简写:-p 8080:80是宿主机 8080 映射到容器 80;-P是随机映射容器所有暴露的端口到宿主机高位端口。后者不常用,但如果你只是临时起一个容器看看效果,用-P配合docker ps查看PORTS列会比较省事。

2.2 搭建一个可互通的多容器环境

假设现在要跑一个常见的 Web 服务加数据库的场景:一个 Nginx 容器,一个 MySQL 容器,二者要能相互通信。最容易上手的方案是创建一个自定义 bridge 网络,然后把两个容器都挂上去。这里直接给命令:

先创建网络,指定网段:

docker network create --driver bridge --subnet=172.20.0.0/16 --gateway=172.20.0.1 myapp_net

--subnet指定子网,--gateway指定网关。如果你不确定网段怎么规划,也可以不加这两个参数,让 Docker 自动分配,但为了后面固定容器 IP 和服务配置,我强烈建议手动指定。生产环境里容器 IP 经常要写进配置文件、白名单或 Consul 之类的注册中心,如果每次重建容器 IP 都变,后面的麻烦会非常多。

接着启动 MySQL,并把它挂到 myapp_net 上:

docker run -d --name mysql8 --network myapp_net \ -e MYSQL_ROOT_PASSWORD=root123 \ -p 3306:3306 \ mysql:8.0

再启动 Nginx 容器:

docker run -d --name nginx --network myapp_net -p 80:80 nginx:latest

两个容器都挂在同一个自定义网络里后,Nginx 里可以直接用mysql8:3306访问 MySQL 的服务地址。这一点在默认 bridge 网络里做不到,但在自定义 bridge 网络里,Docker 内置的 DNS 解析会自动把容器名解析成对应的容器 IP。我在这上面吃过亏:最开始用默认网络启动 MySQL,Java 后端容器里配置的数据库地址写的是jdbc:mysql://mysql8:3306/...,结果一直报 UnknownHostException。后来改成自定义网络就通了,各位一定要记住这个区别。

2.3 动态添加与移除网络连接

有些场景下,容器启动时没有规划好网络,后面又想临时加一个网络。不用重启容器,用docker network connect即可:

docker network connect myapp_net nginx

执行之后,Nginx 容器里会多出一个额外的网络接口。这个特性在灰度发布、跨网络临时访问、故障切换这些场景里很好用。比如某个服务原本在 A 网络里,需要临时访问 B 网络里的一个组件库,动态 connect 过去就行,不用重启进程。docker network disconnect则是反过来,把容器从一个网络上摘除。

动态连接网络时要注意,容器内如果程序绑定的是固定 IP 地址,切换网络后程序可能不会自动适配新的 IP,需要重启容器内的服务。如果用的是 hostname 或域名访问,一般不受影响。

3. 跨主机通信:overlay 网络的原理与部署实操

3.1 overlay 网络解决什么问题

单机场景下 bridge 网络够用了,但一旦到了多台物理机的集群,问题就来了:容器 A 跑在机器 1 上,容器 B 跑在机器 2 上,它们要怎么通信?两个选择:一是把容器端口映射到宿主机,通过宿主机 IP 加端口互访;二是用 overlay 网络,让跨机器的容器像在同一台机器上一样互通。

第一种方案简单,但端口管理混乱,服务发现也要自己做。第二种方案是 Docker Swarm 和 Kubernetes 这类容器编排系统的核心方案,overlay 网络本质上是一个 VXLAN 隧道网络。每个节点上跑一个负责封包解包的程序,容器发出的数据包被封装成 VXLAN 报文,通过宿主机物理网络传到目标节点,再解包还原,交给目标容器。整个过程对容器透明,容器以为对面就在同一个二层网络里。

3.2 部署一个最小可用的 overlay 网络

要使用 overlay 网络,Docker 需要运行在 Swarm 模式下。先初始化 Swarm:

docker swarm init

这条命令执行后,当前节点会成为 Swarm manager。如果有多台机器,其他节点用下面这条命令加入集群:

docker swarm join --token <token> <manager-ip>:2377

docker swarm init执行成功后会输出 join 命令和 token,保存好。加入集群之后,创建一个 overlay 网络:

docker network create --driver overlay --attachable --subnet=10.10.0.0/16 myoverlay_net

--attachable这个参数很关键,它允许非 Swarm 服务直接以docker run方式使用这个 overlay 网络。不加这个参数的话,只有通过docker service create创建的服务才能用它。

然后在两个节点上分别启动容器:

docker run -d --name app1 --network myoverlay_net --ip 10.10.0.10 nginx docker run -d --name app2 --network myoverlay_net --ip 10.10.0.11 nginx

注意,这里我在两个不同节点上执行了同样的创建命令,但容器 IP 可以在同一个网段内,且互不冲突。这就是 overlay 网络的意义:Docker 负责跨节点的 IP 分配和路由,用户不需要关心容器到底跑在哪台机器上。

3.3 overlay 排障的几个常见注意点

跨主机网络最容易出的问题,不是容器配置错了,而是宿主机之间的底层网络不通。VXLAN 通信默认使用 UDP 4789 端口,如果宿主机之间有防火墙拦截 UDP 4789,容器之间永远 ping 不通,但docker network ls显示网络又是正常的。排查时先确认两件事:宿主机之间能否互相 ping 通;UDP 4789 端口是否放行。

还有一点,overlay 网络里的ping并不总是能通。因为很多基础镜像里没有包含 ICMP 相关的工具,或者容器里的防火墙规则限制了流量。但 TCP 服务正常。所以排障时别迷信 ping,试试用实际业务端口测试连通性,比如用nc -zv <目标IP> <端口>。

4. 容器网络故障排查与避坑指南

4.1 “网络不通”的标准化排查流程

遇到容器网络不通,我建议按下面的顺序来排查,而不是东试一下西试一下:

第一步,看网络拓扑是否正常。执行docker network inspect <网络名>,查看每个容器的 IP 是否都在预期的网段里,容器列表是否完整。如果容器不在列表里,说明它没挂到这个网络上。

第二步,确认容器内的网络配置。进入容器,查看/etc/hosts和/etc/resolv.conf。Docker 会用内置 DNS 自动维护容器的域名解析,如果这两个文件被手动改坏了,名字解析就会出现问题。

第三步,检查端口映射是否生效。在宿主机上执行iptables -t nat -L -n | grep <端口>,确认有对应的 DNAT 规则。如果你的端口映射在启动时没生效,看看是不是宿主机端口被其他进程占用了。:8080已经被某进程监听,Docker 会直接报错,但有时候端口是通的但进程没监听,这时候要进容器看服务状态。

第四步,测链路。在容器里测试到网关的连通性(一般是 172.17.0.1 或 172.20.0.1),再到宿主机测试到容器的连通性,逐段缩小范围。整个链路分段排查,比盲目重启容器高效得多。

4.2 常见问题速查与避坑经验

现象常见原因快速处理
容器之间按容器名访问不通容器在默认 bridge 网络改用自定义 bridge 网络
宿主机无法访问容器服务没有用-p映射端口加-p映射或改用 host 网络
端口映射后外部访问仍然超时防火墙拦了宿主机的入口流量放行宿主机对应端口
Docker 启动后宿主机网络中断iptables FORWARD 链策略被改恢复 FORWARD 策略或重启 Docker
overlay 网络跨节点不通宿主机 UDP 4789 不通检查和放行 VXLAN 端口
容器内可以上网但 DNS 失败容器 DNS 配置异常检查/etc/resolv.conf和 daemon 配置

这里提一个我自己的独家经验:自定义 bridge 网络的子网规划,千万别跟宿主机所在局域网网段重合。如果你的局域网是 192.168.1.0/24,那 Docker 网络就尽量别用 192.168.1.0/24。因为 Docker 在宿主机上会创建网桥并配一个网关 IP,如果这个 IP 跟局域网网关或其他设备 IP 相同,路由会变得混乱,现象就是宿主机上某些外网地址访问异常,但排查半天又找不到原因。我建议一般用 172.16.0.0/16、10.10.0.0/16 这些在家庭和企业局域网里比较不常用的网段。

4.3 与 Docker Desktop 相关的常见网络问题

不少 Windows 和 macOS 用户用 Docker Desktop 练手,它的底层网络实现跟 Linux 上直接用 Docker Engine 略有差异。Docker Desktop 在 Windows 上依赖 Hyper-V 或 WSL 2,网络栈实际运行在一个轻量级虚拟机里,主机的端口映射要经过虚拟机的转发。有时候你启动容器时-p 8080:80映射成功,但浏览器访问 localhost:8080 不通,原因多半是 WSL 2 和 Docker Desktop 之间的端口转发异常,重启 Docker Desktop 或执行netstat -ano检查端口监听状态,就能定位。

Docker Desktop 里还有一个容易被忽视的流量转发问题:如果容器内需要访问宿主机上的服务,Linux 里可以用172.17.0.1访问宿主机,但 Docker Desktop 里这个地址对应的可能是虚拟机的内部 IP。这种情况下,建议从宿主机环境变量传入 IP,或者让容器连接到 macvlan 网络直接成为局域网里的独立主机。

5. 自定义网络规划与多网络容器架构

5.1 什么时候需要规划多网络

项目小的时候,所有容器挂同一个 network 里没问题。但到了中大型项目,比如开发环境、测试环境、生产环境共享同一套 Docker 宿主机时,把所有容器放在一个大平面网络里,安全隐患很明显。比如一个支付服务和一个开发用的调试工具放同一个网络,一旦出了安全问题,横向攻击面会非常大。

更好的做法是按业务域隔离:数据库集群放一个网络,业务服务放一个网络,中间只保留必需的访问通道。这样即使某个容器被攻破,也拿不到数据库的网络访问权限,风险可控。Docker 支持一个容器同时挂多个网络,所以“网关服务同时访问业务网和数据网”这种架构完全可行。

5.2 自定义网络时的参数选择思路

创建网络时常用的参数就几个,但每个都要有依据:

  • --driver:桥接用 bridge,跨主机用 overlay。
  • --subnet:确定容器网段。建议避开宿主机现有网段,也避开所有业务网段,避免路由冲突。
  • --gateway:指定网关 IP。一般取子网内的第一个地址,比如 172.20.0.1。
  • --ip-range:限制容器自动分配 IP 的范围。如果某些 IP 要预留给固定服务,用这个参数控制分配范围配合--ip参数,效果很好。
  • --attachable:overlay 网络里启用,允许普通容器使用。

还有一个常用参数是-o com.docker.network.bridge.name,它用来指定桥接网卡的名字。默认的网桥名是 docker0 加一串哈希,看起来不直观,改成br-myapp这种格式之后,用ip a排查网络时一眼就能认出是哪个网络。

5.3 多网络容器的实际配置步骤

下面演示一个网关容器挂双网络的场景。先创建两个网络:

docker network create --driver bridge --subnet=172.21.0.0/24 --gateway=172.21.0.1 front_net docker network create --driver bridge --subnet=172.22.0.0/24 --gateway=172.22.0.1 back_net

启动两个业务容器,分别挂到两个网络:

docker run -d --name web --network front_net nginx docker run -d --name db --network back_net mysql:8.0

这时 web 和 db 之间如果不经过特殊路由是不能互通的,因为它们在隔离的网络里。接着创建一个网关容器,把它同时连接到两个网络:

docker run -d --name gateway --network front_net busybox sleep 3600 docker network connect back_net gateway

现在 gateway 能同时访问 front_net 和 back_net 里的容器,而 web 和 db 依然互相隔离。这就是典型的多网络架构,网上那些“不同 Docker 网络之间如何互访”的提问,正确解答就是通过这样一个网关容器中转。

还要补充一点:如果一个容器同时连接多个网络,网络间默认是互通的,除非你在 docker daemon 或者 iptables 层面做额外限制。如果需要严格隔离,比如两个网络之间有敏感数据,必须通过docker network disconnect把不需要的通路断掉,或者用防火墙规则显式禁止网络间互访。

6. 网络规划与生产落地的几条硬经验

6.1 固定 IP 还是容器名解析

容器名字解析机制已经非常好用,新项目直接采用自定义 bridge 网络加容器名互访即可。但有些老系统在配置里写死了对端 IP,这种时候建议给你依赖的容器固定 IP。如何在自定义网络里固定 IP?启动时这样写:

docker run -d --name mysql8 --network myapp_net --ip 172.20.0.10 mysql:8.0

这里有个前提条件:自定义网络创建时必须预留了这个 IP 在可分配范围内。如果用--subnet指定了整个网段但没用--ip-range限制,--ip随便在网段里挑一个没被占用的地址就行。如果容器重建频率高,固定 IP 能减少下游系统修改配置文件的工作量。

但固定 IP 也不是银弹。容器重建后会重新分配网络接口,如果短时间内大量容器重建,可能会因为 Docker 的 IP 分配不及时而冲突。更推荐的方式是:业务代码里用域名环境变量做配置,底层用 Docker 内置 DNS 解析,这样容器重启、漂移都不影响调用方。

6.2 与防火墙共存的策略

装了 Docker 之后,最好别再去动整个 iptables 的 FORWARD 链默认策略。Ubuntu 上常见的ufw服务如果在 Docker 之后启动,可能会把 FORWARD 策略改为 DROP,导致所有容器都无法访问外网。此时解决方案不是关闭 ufw,而是把 Docker 需要的转发规则放行。最简单安全的方式是把 Docker 的 daemon 配置保持不变,用ufw allow放行需要的端口,然后重启防火墙服务。

容器里访问外网的流量要经过宿主机的 NAT 转发,如果宿主机开了 firewalld,需要确认 masquerade(伪装)规则存在,通常在 Docker 安装时会自动添加,但有些人手动清过 iptables,会造成这个规则丢失。此时手动重建一条即可:

iptables -t nat -A POSTROUTING -s 172.20.0.0/16 -j MASQUERADE

注意这里的网段要替换成你容器网络实际用的子网。

6.3 清理网络资源的正确姿势

长时间运行的 Docker 宿主机上,网络对象会积累很多废弃配置。docker network prune会清理所有未被容器使用的网络,但执行前一定要看一眼,有些网络里没有容器但配置了重要的自定义子网,删了重建成本也不小,尤其是 overlay 网络,关联的服务还需要重新配置。生产环境建议先docker network ls列出所有网络,再用docker network inspect确认无容器引用,做好记录再清理。

遇到容器删不掉、网络删不掉这类问题,多半是因为有容器还连着。先删容器再删网络,执行顺序反了就会报错。还有一种特殊情况:手动创建了 bridge 网桥和 iptables 规则,网络对象删除了但底层设备还在,用ip link show查看并手动清理设备即可。

7. 实操总结:5 分钟搭一套健壮的容器网络架构

最后把我平时的标准操作串起来,形成一个可复用的流程,照着做就能快速搭出一套健壮的容器网络环境。

第一步,规划网段。用独立的 172.20.0.0/16 或者 10.10.0.0/16 段,避免和局域网冲突。

第二步,创建自定义网络:

docker network create --driver bridge --subnet=172.20.0.0/16 --gateway=172.20.0.1 base_net

第三步,启动业务容器时统一用--network base_net。需要对外提供服务就加-p映射,内部服务之间直接容器名互访,不需要额外配置。

第四步,数据库、缓存等基础组件如果需要在多个网络之间共享,单独起一个网关容器,或者用docker network connect动态加入目标网络。

第五步,定期巡检:docker network ls看待清理的残留网络;docker network inspect检查容器连接关系;iptables -t nat -L -n确认端口映射规则完整。

我实际操作中最深的一个体会是:Docker 网络出问题时,百分之七十的情况下根因不在网段和容器配置,而在宿主机防火墙规则和路由表。所以排查前先冷静下来检查 iptables 和 ip_forward,往往比反复重启容器高效得多。还有一次,整个环境的容器 IP 全变了,原因是创建网络时没有指定--subnet,Docker 在重启后重新分配了不同网段,连累了所有依赖固定 IP 的配置。从那以后,凡是需要长期使用的网络,我必定手动指定子网和网关,这个习惯一直保留到现在。

再分享一个小技巧:创建网络时顺手加上--label做标记,比如--label project=payment,网络一多之后用docker network ls --filter label=project=payment过滤,管理起来会轻松很多。别小看这个动作,几十个网络堆在一起的时候,有个清晰的标记体系能节省大量排查时间。

Docker 网络管理覆盖的内容其实很深,本文从单机 bridge、跨主机 overlay、故障排查、生产规划几个维度做了完整梳理,每个命令和参数都来自实际使用中的总结。希望这篇内容能帮你把 Docker 网络这块拼图补上,少走一些我走过的弯路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询