☰
Docker网络模式与配置详解:从bridge到跨主机组网实践
2026/10/22 4:55:04 网站建设 项目流程

接手过几十个Docker部署环境之后,我越来越确认一件事:容器运维的故障案例里,网络相关的能占三四成。镜像拉不下来、端口映射不生效、两个容器互相访问不通、跨主机连不上……随便搜一个排查帖子,绕不开的就是docker network。标题里的“docker网络模式及配置”,看起来像是个基础话题,但真正把它弄懂的人,反而比能背一堆docker命令的人少得多。

这篇东西我按自己的实操经验来写,不按文档顺序讲,而是从“容器网络到底是怎么组织的”开始,把内置四种模式、自定义网络、跨主机组网、常见故障排查都过一遍。适合刚接触Docker不久、被各种网络概念绕晕的人,也适合已经用了一阵子、想系统补一补网络知识的开发者。看完之后你至少能回答三个问题:为什么要用自定义bridge网络、端口映射的本质是什么、跨主机联调选哪种方案。

1. 容器网络的三块基石:命名空间、veth 与网桥

1.1 网络命名空间:每个容器都有自己的独立网线

Docker容器之所以能拥有独立的IP、独立的路由表、独立的防火墙规则,靠的是Linux内核的Network Namespace机制。简单说,每个容器创建时,Docker都会给它分配一套全新的网络协议栈,这套协议栈和宿主机以及其他容器完全隔离。容器里的eth0、路由表、iptables规则,都是这份网络栈里的私有资产,外面看不到。

画个类比:宿主机是一栋写字楼,容器是楼里的一个个房间。网络命名空间就是房间自带的一套独立电话线路,房间内拨内线、拨外线都有自己的规则,互不干扰。Docker网络的所有模式,本质上都是在决定“这个房间的电话线路怎么接到楼外”“房间和房间之间怎么互通”。

查看容器命名空间的命令很简单,但很多人没注意过:

docker inspect --format '{{.State.Pid}}' <容器名> ls -la /proc/<pid>/ns/net

你会看到类似net:[4026531840]这样的编号,它就是网络命名空间的唯一标识。两个容器如果这个编号相同,说明它们共享同一套网络栈。

1.2 veth 网线:容器与网桥之间的透明通道

容器里有一个eth0,宿主机上对应一个veth开头的虚拟网卡,它们两头是一对。Linux里管这个叫veth pair,本质上就是一根虚拟网线,一头插进容器的网络命名空间,另一头插进宿主机上的网桥。数据包从容器出去,一路顺着这根网线走到网桥;外部数据包进来,也走这根网线进入容器。

这个设计看似简单,但它是整个容器网络能够成立的基础。没有veth的话,容器内部的协议栈和宿主机的协议栈没法直接打交道,毕竟两个网络命名空间是隔离的。veth pair的作用就是在两个隔离的网络栈之间搭一座桥,让数据包可以从一个房间里走到走廊上。

在宿主机执行ip link show,你会看到一堆vethxxxxxxx@ifN的虚拟网卡,后面那个@ifN数字,就表示veth另一端所连接的网络接口索引。每次启动一个连接到bridge网络的容器,宿主机就会增加一根这样的veth。

1.3 网桥:走廊里的虚拟交换机

bridge网络模式下,Docker会在宿主机上创建一个Linux网桥,默认叫docker0。所有容器发出的数据包,经由veth进入这个网桥后,网桥根据目标MAC地址决定是转发给另一个容器,还是往上送到宿主机协议栈,再通过NAT走向外网。

docker0就是那根走廊,veth是每个房间接出来的网线,网桥负责当中间的交换机。自定义bridge网络创建之后,你会看到宿主机上多了一个br-xxxxxxxxxxxx的网桥设备,那就是为你单独创建的一条全新“走廊”。

网桥本身是二层设备,但它要配合三层路由和防火墙规则才能完成完整通信。这也是为什么Docker会往iptables里塞一堆规则的原因——它不是在做危险操作,而是宿主机默认只处理本机数据包,跨命名空间的转发必须靠iptables的FORWARD链放行。

1.4 CNM 模型:理解Docker网络的官方词汇

Docker的libnetwork遵循CNM(Container Network Model),里面有三个核心概念:Sandbox、Endpoint、Network。对应到实际环境:

CNM术语实际对应物作用
Sandbox容器的网络命名空间隔离网络栈
Endpointveth pair的宿主机端容器接入网络的连接点
Network网桥、overlay网络等一组Endpoint的集合

很多人在创建自定义网络时搞不清楚--subnet、--gateway、--ip-range这几个参数的关系,其实只要想明白“Network决定走廊,Endpoint决定门牌”,思路就会清晰。subnet是这套走廊连着的网段,gateway是走廊的出口,ip-range是允许分配给每个房间的门牌区间。

2. 四种内置网络模式怎么选

2.1 bridge:默认选择,但默认配置不算好用

Docker默认的bridge网络是docker0网桥,容器创建后不指定--network,都会接到这个网络上。默认bridge适合单机、少量容器、简单互联的场景,比如本地开发环境里跑一个MySQL和一个后端应用,用IP互相访问。

但默认bridge有几个天生毛病:

  • 不支持基于容器名的自动DNS解析。容器可以通过--link参数拿到对方的hosts条目,但容器重建后IP变化,hosts里的IP不会自动更新,非常容易踩坑。
  • 无法在容器运行中动态接入网络。想调整网络拓扑,只能把容器停了重新创建。
  • 所有接入默认bridge的容器都处在一个大内网里,没有网络隔离能力。

这些毛病不是bug,而是Docker有意为之的简化。真正生产环境多服务部署,几乎没有人直接用默认bridge,而是用自定义bridge网络。

2.2 host:牺牲隔离换性能

--network host模式下,容器直接使用宿主机的网络命名空间,不创建自己的veth,也不分配独立的IP。容器内的进程监听端口,就相当于监听宿主机的端口,网络性能和宿主机原生进程几乎没有区别。

这种模式适合对网络延迟极其敏感的服务,例如某些实时通信中间件、压测工具。同时它也意味着放弃了隔离性——容器可以访问宿主机全部的网络接口和端口,宿主机上已有的端口占用会直接导致容器启动失败。-p端口映射参数在host模式下不生效,你再怎么指定端口,Docker也不会去折腾iptables。

我的看法:能用bridge解决的,尽量别用host。host模式省下的那点性能,对于绝大多数互联网服务来说完全感知不到,反而会引入端口冲突、安全边界模糊的问题。

2.3 none:最干净,也最不容易用

--network none模式下,容器里只有一个loopback接口,没有任何外部网络路径。适合离线计算、安全沙箱、一次性任务这类不需要对外通信的场景。

可我见到过不少人拿none模式做“手动组网”的起点——进到容器网络命名空间里自己配置veth、路由,等于抛弃Docker的网络管理,完全手工介入。这种用法不常见,但对某些定制化极强的网络方案(比如要对接特定的高性能网络设备)来说,反而比Docker自带的方案更灵活。

2.4 container:共享指定容器的网络命名空间

--network container:<其他容器名>会让新容器跳进目标容器的网络栈。两个容器在里面看到的eth0、路由、IP完全一样,可以通过127.0.0.1直接互相访问。

这个模式的典型使用场景是sidecar容器:主容器只负责业务逻辑,旁边挂一个日志采集、流量代理容器,两者共享网络,日志采集容器完全不需要自己绑端口,直接用localhost就能接主容器的流量。需要注意生命周期绑定问题:主容器停止或者重启,共享它网络的容器会跟着出问题,编排时要把这个依赖关系想清楚。

3. 自定义bridge网络的完整配置流程

3.1 为什么要自己建网络

Docker默认bridge网络最大的缺陷是DNS和隔离能力不足,而自定义bridge网络把这两个短板都补上了。在自定义网络里,容器可以通过容器名互相解析,Docker内置的DNS服务会动态维护名字和IP的对应关系,容器重建后有了新IP,其他容器依然能用旧名字访问它。这一点对微服务场景来说几乎是刚需。

自定义网络天然隔离:不同自定义网络之间的容器,默认完全无法通信。这就像同一栋楼里的不同公司,各占各的走廊,中间没有门。要做网络分区、多环境并存、前后端隔离,都得靠自定义网络来实现。

此外,自定义网络还有更好的iptables规则管理、支持动态连接/断开容器、支持--ip明确指定静态IP等特性。这些都是生产环境部署时很需要的。

3.2 创建网络时的关键参数

创建自定义bridge网络的命令如下:

docker network create \ --driver bridge \ --subnet 192.168.30.0/24 \ --ip-range 192.168.30.128/25 \ --gateway 192.168.30.1 \ --label env=production \ app-network

参数的意义:

参数含义说明
--driver bridge指定网络驱动单机场景就用bridge
--subnet网络网段建议选一个和宿主机网段不冲突的私有网段
--ip-range容器IP分配范围限制动态分配的IP在某个区间
--gateway网关地址通常是网段的第一个可用地址
--label网络标签方便后期识别管理

我特意在示例里把--subnet设为/24、--ip-range设为/25,目的是保留前半段网段给静态IP使用,后半段给动态分配。静态IP地址可以固定在192.168.30.10-192.168.30.20这些低地址段,动态分配的容器则分布在高地址段,互不干扰。有人可能要问,容器IP不是应该尽量靠DNS名访问、不依赖固定IP吗?话是这么说,但某些场景——比如接入外部防火墙白名单、对接老系统时,固定IP还是绕不开。预留一段静态IP空间,能省去后面很多沟通成本。

3.3 容器接入网络并分配静态IP

创建好网络之后,启动容器时通过--network指定网络名,通过--ip指定静态IP:

docker run -d --name web-server \ --network app-network \ --ip 192.168.30.10 \ -p 8080:80 \ nginx:alpine

想让容器接入多个网络也没问题,Docker允许一个容器同时连多个网络:

docker network connect app-network mysql-server docker network connect app-network --ip 192.168.30.11 web-server

通过docker network connect可以在容器运行中动态增加网络连接,这在调整服务拓扑时特别好用,完全不用重启容器。但要注意,给容器同时接入多个网络时,DNS解析行为依然由它所处的网络决定,多个网络之间如果存在重复目标,容器访问外部服务时就需要依赖路由优先级了。为减少混乱,一个容器尽量不要接超过两个网络。

3.4 网络隔离的实际验证

我每次配置新网络拓扑,都会先做一次连通性验证。用两个容器分别加入不同网络,测试默认隔离:

docker network create net-a docker network create net-b docker run -itd --name verify-a --network net-a alpine sleep 300 docker run -itd --name verify-b --network net-b alpine sleep 300 docker exec verify-a ping verify-b

结果是请求卡住不动,ping不到。再把verify-b连到net-a上:

docker network connect net-a verify-b docker exec verify-a ping verify-b

这时就能通了。这个验证过程经常被我用来模拟“两个服务本来隔离,后来需要互访”的情况,效果非常直观。

3.5 端口映射的底层逻辑

bridge网络下,-p 宿主机端口:容器端口的本质是两条iptables规则:一条DNAT规则,把到达宿主机某个端口的数据包目标地址改写成容器IP;另一条对应的SNAT/MASQUERADE规则,把容器返回数据包的源地址伪装回宿主机IP。

也就是说,端口映射并不是Docker在用户态转发网络流量,而是数据包进了宿主机协议栈后被防火墙规则重新写了目的地。明白了这一点,你就能理解为什么宿主机防火墙策略会影响Docker端口映射的生效——Docker自己有一条独立的DOCKER链,但数据包总是要从宿主机入站流程里走一遭。

Docker在iptables里维护了一张独立于系统防火墙的规则链。外部访问通过宿主机的物理网卡进来,如果系统防火墙(比如ufw/firewalld)设置了默认拒绝入站,理论上会把Docker映射的端口也挡在外面。可实际使用中,大家经常遇到“ufw deny了8080,但Docker -p 8080:80还是能从外面访问”的情况,原因就是Docker规则链的生效顺序比一般的INPUT策略更靠前,而且它主要在FORWARD链上操作。解决这个问题的正规姿势是修改DOCKER-USER链,而不是去碰系统防火墙默认策略:

iptables -I DOCKER-USER -i eth0 -s 192.168.1.0/24 -p tcp --dport 8080 -j ACCEPT iptables -I DOCKER-USER -i eth0 ! -s 192.168.1.0/24 -p tcp --dport 8080 -j DROP

这两条规则的意思是:只允许来自办公网段192.168.1.0/24的机器访问宿主机的8080端口,其他来源全部拒绝。加在DOCKER-USER链里之后,不会影响Docker自身规则,并且会优先于DOCKER链内的自动规则被匹配到。这是我在生产环境限制Docker端口访问时最常用的一套方法,比在容器外面再套一层防火墙靠谱得多。

4. 跨主机组网方案与选型对比

4.1 单机网络的天花板

brige网络模式只适用于单台宿主机。如果把Docker部署在多台机器上,每台机器都有自己独立的docker0网桥和独立的容器网段,A机器上的容器想访问B机器上的容器,靠bridge网络是做不到的。有人想通过“宿主机IP+端口映射”来互相访问,这样做在小规模场景下能跑,但服务多起来之后,端口管理、地址规划、故障排查都会变得一团糟。

跨主机组网的核心目标,是让所有节点上的容器“看起来在同一张内网里”,能像单机bridge网络那样直接用容器名或IP互相访问。

4.2 overlay网络:Swarm模式下的原生选择

overlay网络是Docker Swarm内置的跨主机网络方案,基于VXLAN隧道技术实现。数据包从A节点的容器发出,会被封装成UDP报文,通过宿主机物理网络转发到B节点,B节点再解封装还原。容器之间感受不到物理网络的距离,只觉得自己连在同一个大网桥上。

启用overlay前需要先初始化Swarm集群:

docker swarm init --advertise-addr <本机IP> docker network create -d overlay \ --subnet 192.168.50.0/24 \ --gateway 192.168.50.1 \ --attachable \ app-overlay

注意--attachable参数,它允许非Swarm服务的普通容器手动连接到这个overlay网络,方便测试。如果不用这个参数,只有Swarm服务能自动接入。

overlay网络最大的代价是VXLAN封装带来的性能损耗。每包数据都多一层UDP头、多一次加解密,对带宽密集型、低延迟敏感型的应用来说,损耗可能会达到两位数百分比。另外,节点之间的UDP 4789端口必须放行,这是VXLAN的默认监听端口,防火墙没放开的话,跨主机容器通信会莫名其妙地断。

4.3 macvlan:让容器像独立主机一样接入物理网络

macvlan驱动给每个容器分配一个和宿主机同网段的IP,以及一个独立的MAC地址。从交换机的角度看,容器就像一台直接插在物理网络里的独立主机,不需要任何隧道封装,网络延迟很接近裸机,吞吐量也高。

创建macvlan网络的命令:

docker network create -d macvlan \ --subnet=192.168.0.0/24 \ --gateway=192.168.0.1 \ --ip-range=192.168.0.128/25 \ -o parent=eth0 \ macnet

-o parent=eth0指定宿主机参与macvlan的物理网卡,容器会直接在这块网卡上创建虚拟接口。使用macvlan有几个限制你要提前知道:宿主机和同一个macvlan网络里的容器之间默认ping不通(因为macvlan子接口不响应来自宿主机的MAC地址),同一宿主机上的容器之间可以通信,但要通过外部交换机绕一圈回来反而可能失败。要让宿主机直接访问macvlan容器,需要额外创建一个macvlan子接口并手动配置IP。

macvlan适合那些确实需要直接暴露在物理局域网里的传统应用,比如要对接组播、广播协议的服务。但对大部分内部微服务来说,它带来的“直接暴露”不是优势而是风险,容器安全边界变得全靠物理网络隔离控制。

4.4 基于三层路由的组网方案

隧道方案之外,还有一类基于路由的跨主机方案。这类方案不给容器做封装,而是给每台宿主机规划一个唯一的容器网段,然后通过路由协议或手动静态路由,让所有节点知道“某个容器网段该走哪台宿主机”。数据包直接以原始三层包在物理网络上转发,效率和传统物理机网络几乎没有差别。

这种方案的优势在于数据路径清晰、没有隧道封装开销;劣势在于网络规划要求高,每台Node的容器网段必须全局唯一,路由器或节点上的路由表要保持一致。遇到需要更细粒度策略控制(比如跨主机的网络策略、加密要求)的场景,就得再叠加额外的路由规则和加密网关设施。

4.5 到底怎么选

方案是否封装性能损耗网络规划难度适用场景
overlayVXLAN封装中等低Swarm集群、跨主机服务发现
macvlan无低中需要直接暴露物理网络的传统应用
host+端口映射无低高少量容器、手动管理可接受
三层路由无低高大规模集群、已有较好网络基础设施

我个人的项目经验:如果只是十几台机器、二十几个服务的规模,用Swarm加overlay就够了,省心。如果容器集群规模已经发展到上百节点,且对性能要求极高,可以考虑路由方案,把容器当成“逻辑服务器”来规划地址。macvlan我只建议在特定场景使用,普通业务不要为了追求性能去用它,后面维护成本会偏高。

5. 网络故障排查:命令、思路与踩坑记录

5.1 快速信息收集

遇到容器网络问题,我习惯按下面的顺序收集信息,不要急于改配置:

docker network ls # 有哪些网络 docker network inspect <网络名> # 网络配置与已接入容器 docker inspect <容器名> # 容器的网络模式、IP、网关 ip addr && ip route # 宿主机网卡与路由表 iptables -t nat -S # NAT规则 iptables -S FORWARD # 转发链规则

很多人第一步就漏掉iptables -t nat -S。实际上,Docker端口映射不生效、容器出外网不通,八成问题都能在NAT规则里找到线索。规则里如果没有对应容器的DNAT条目,那就要想想是不是有系统防火墙、重启恢复工具或手动清空规则的操作把Docker的规则干掉了。

进入容器内部检查网络状态时,不一定要docker exec进入shell,你可以直接用nsenter进入容器的网络命名空间:

PID=$(docker inspect -f '{{.State.Pid}}' <容器名>) nsenter -t $PID -n ip addr nsenter -t $PID -n ip route

这样不用在容器里安装任何额外工具,就能看到容器自己的网络视图,做排查很方便。想在容器里抓包时,我通常也是先把tcpdump装到宿主机上,然后用nsenter跑抓包命令,避免往容器镜像里塞调试工具。

5.2 容器出外网超时

表现:容器可以ping通宿主机,但访问外网IP没反应。

排查步骤:

  1. 在容器内ping 114.114.114.114,确认是不是所有外网都不通。
  2. 在宿主机执行sysctl net.ipv4.ip_forward,确认IP转发已开启。Docker一般会自动开,但有些系统加固脚本会把它关掉。
  3. 查看MASQUERADE规则是否还在:
iptables -t nat -S | grep MASQUERADE

没有这条规则,容器出外网的数据包源地址还是容器IP,回来时路由不对,外网响应根本找不到源。这种情况常见于有人用防火墙管理工具覆盖了Docker规则,或者重启了系统但Docker服务没正确起来。

  1. 如果规则都在,再看是否受系统防火墙默认规则影响。某些云主机镜像会放一个默认拒绝所有转发的策略,Docker的规则在特定链上不会生效。

5.3 容器之间解析不到容器名

表现:自定义bridge网络里的容器,ping对方IP能通,ping对方容器名却提示无法解析。

排查步骤:

  1. 先确认两个容器确实接在同一个自定义网络里:docker network inspect <网络名>看终端列表。
  2. 进入容器看DNS配置:cat /etc/resolv.conf,正常情况下第一行会是nameserver 127.0.0.11,这是Docker内置DNS的监听地址。
  3. 如果resolv.conf里没有127.0.0.11,说明容器不是用自定义网络启动的,或Docker DNS功能被异常关闭。Docker的嵌入式DNS只对用户自定义网络生效,默认bridge和host模式下没有这个能力。

另外提一个常见坑:如果你在容器里自己改了/etc/resolv.conf,或者在容器启动时通过--dns指定了外部DNS,嵌入式DNS会被部分覆盖,容器名解析就可能失效。默认配置下不要乱动resolv.conf。

5.4 端口映射反复失效

表现:-p 8080:80启动容器成功,但从外部访问不通,或者Docker启动报端口绑定失败。

排查顺序:

  1. 宿主机上ss -lntp | grep 8080,看端口有没有被其他进程占用。docker-proxy会监听映射端口,如果启动报错,通常就是端口被占了。
  2. 确认iptables的DNAT规则:iptables -t nat -S | grep 8080,如果规则存在但访问依然不通,重点检查DOCKER-USER链。
  3. 确认是不是系统防火墙与Docker的规则链冲突。有些环境里firewalld会周期性地清空iptables自定义链,导致Docker规则被清掉,表现就是“之前还能访问,某次重启后突然不行了”。

解决方案很简单:

systemctl restart docker

Docker重启时会重建全部网络规则。如果系统防火墙一重启就清规则,可以把Docker服务设置为“在防火墙服务之后启动”,或者把Docker规则保存成持久化规则。但最推荐的方式是先搞清楚到底是谁在动iptables,不要头疼医头。据我所知,生产环境里因为firewalld和Docker叠加导致的诡异网络故障,多到可以单独写一本书。

5.5 别手欠清空iptables

最后必须强调一个血泪教训:千万、千万、不要在生产环境上直接执行iptables -F来“清理”防火墙规则。这条命令会清掉Docker的所有网络规则,包括NAT、转发链,后果是所有容器瞬间断网,外部也访问不了任何映射端口。

如果确实需要对Docker网络做精细控制,请使用DOCKER-USER链。这条链专门留给用户自定义规则,Docker重启和规则重建都不会动它。所有的入站流量在进入Docker链之前,会先经过DOCKER-USER链,所以你在这里写限制规则的优先级最高。我目前所有Docker端口访问控制,都收敛在这一条链里处理,系统防火墙保持默认不动,两边互不干扰。

最后再分享一个小技巧

我自己在排查Docker网络问题时,最值钱的一个习惯是:用docker network inspect配合jq把容器的IP列表拉出来对比。多节点环境里IP冲突、容器重复连接网络这种事儿,一眼就能看穿。

docker network inspect app-network \ | jq -r '.[0].Containers[] | "\(.Name) \(.IPv4Address)"'

这个输出会把网络上所有容器的名字和IP列成一行一条,比在控制台里翻docker inspect输出快得多。配合docker network connect动态调整拓扑,我经常能在几分钟内定位出到底是哪个容器接错了网络、占错了网段、或者跟哪个服务的IP重叠了。

这几年做容器化部署,我越来越觉得Docker网络不是“配好就完事”的静态配置,它在业务的每一次扩容缩容、服务迁移里都在动态变化。把netns、veth、iptables、DNS这几个底层概念吃透,比记住一堆docker network命令有用得多。

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

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

立即咨询