1. 为什么网络是容器化应用最容易翻车的地方先从一个非常典型的场景说起。某团队开发了一个微服务Demo,容器里跑着前端、后端、数据库三个进程,本地开发时一切正常。结果一到部署环境,前端服务就是连不上后端接口,后端日志里全是连接超时。排查了大半天,最后发现是跨主机容器通信的网络配置没搞定。这种问题几乎每个深入用过Docker的人都会遇到,而且它不像CPU、内存那样看一眼监控就能定位,网络问题往往牵一发动全身。很多人一开始学Docker,注意力全放在镜像构建、容器编排这些看得见摸得着的东西上,对网络这块往往是"能用就行"的心态。默认的bridge网络下,容器之间靠IP互通,重启一次IP变了,配置跟着失效;容器和宿主机之间的端口映射规则没搞清楚,服务明明在跑却从外面访问不到;多个容器服务部署在同一台机器上,端口冲突导致后面的服务起不来。这些坑我都踩过,而且踩完之后发现,根源不是命令记错了,而是没把Docker网络的工作原理彻底想明白。这篇文章打算从底层机制讲起,逐步拆解Docker的几类网络模式,从单机到跨主机,从原理到排错,把我实际操作中积累的经验和踩过的坑一起放出来。读完之后,你应该能建立起一套完整的Docker网络知识框架,遇到问题知道从哪里排查,而不是靠瞎试命令碰运气。内容既有原理层面的解释,也有可以直接复用的配置步骤和诊断技巧,适合那些已经会基本的Docker命令、但对网络理解还比较模糊的人。2. 先搞懂三件底层的事:命名空间、veth pair、网桥2.1 网络命名空间:容器网络隔离的基石要理解Docker网络,绕不开Linux网络命名空间(Network Namespace)这个概念。它本质上是一种隔离机制,每个命名空间拥有自己独立的网络协议栈,包括自己的网卡、路由表、iptables规则、socket等。你可以把网络命名空间想成一台完全独立的虚拟路由器,不同命名空间之间的网络设备互不可见,数据包不会串门。Docker创建容器时,默认会为每个容器创建一个独立的网络命名空间。这就意味着容器内看到的eth0、lo这些网卡,宿主机上是看不到的。反过来,宿主机的物理网卡在容器里面也是不存在的。这种隔离机制保证了容器之间的网络环境互不干扰,也正因如此,容器内的进程不可能直接通过宿主机的物理网卡收发数据。在Linux上,ip netns list命令可以查看当前系统中有哪些网络命名空间,但注意,Docker创建的网络命名空间通常不会直接显示在这个列表里,原因在于Docker没有把命名空间挂载到/var/run/netns目录下。要查看Docker容器的命名空间,可以通过docker inspect拿到进程PID,然后到/proc/<pid>/ns/net查看。不过对于日常使用来说,你只需要知道"容器有独立网络栈"这件事,排查问题时能理解现象的本质就够了。2.2 veth pair:连接两个命名空间的虚拟网线隔离是做到了,但容器总得跟外界通信吧。这时候就轮到veth pair登场了。veth pair可以理解为一根虚拟网线,它一头连着容器的网络命名空间,另一头连着宿主机上的网络命名空间。数据从一头进去,必然从另一头出来,数据包在这根"网线"上没有任何加工,纯粹转发。Docker在创建容器时,会为容器创建一对veth设备,一端放进容器里,变成容器内的eth0,另一端挂在宿主机创建的网桥上,名字通常是vethxxx这种随机字符串。在宿主机上执行ip link show,你会看到一堆veth开头的虚拟网卡,每个都对应一个正在运行的容器。docker network inspect bridge命令也能看到"Containers"下面每个容器对应的veth接口名称。理解veth pair的意义在于:当你看到容器的网络流量时,要清楚它实际的传输路径是"容器内eth0 → veth pair → 宿主机网桥 → 进一步转发",明确这一条链路之后,排查问题就能按路径逐个节点检测。2.3 Linux Bridge:容器间通信的虚拟交换机网桥(Bridge)是Docker默认网络模式的核心组件。Linux网桥工作在二层,行为类似物理交换机,它根据MAC地址表决定把数据帧从哪个端口转发出去。Docker会在宿主机上创建一个叫做docker0的网桥,默认IP段通常是172.17.0.0/16。所有默认bridge网络的容器,veth设备的宿主机端都插在docker0这个虚拟交换机上。当一个容器访问另一个容器时,数据包从源容器的eth0出发,通过veth到达docker0网桥,网桥根据目的MAC地址,把数据帧转发到目标容器对应的veth端口上。整个过程不经过宿主机的物理网卡,属于纯二层的内部交换。这也是为什么同一台机器上默认bridge网络的容器之间可以直接通过IP通信,而且速度快、延迟低,毕竟不经过外面的物理链路。但要注意一个关键点:docker0网桥的IP地址172.17.0.1通常是容器的默认网关。容器内的流量如果要访问外部网络(比如访问互联网),数据包会先发给docker0这个网关,再由宿主机进行三层的路由和NAT转发。这引出了下一节要讲的iptables规则和端口映射逻辑。3. 默认网络模式逐个拆解:bridge、host、none、container怎么选3.1 bridge模式:默认但并不能无脑用bridge模式是Docker默认的网络模式。使用docker run启动容器时不指定--network参数,容器就会加入名为bridge的默认网络。通过docker network ls可以看到一个名为bridge的默认网络,它是Docker安装时自动创建的。bridge模式的特点是:容器间通过IP互访,容器可以访问外网,外网默认无法直接访问容器内部服务,必须通过端口映射(-p参数)来暴露。它的核心逻辑我前面已经说过:veth pair + docker0网桥 + NAT转发。端口映射这块值得多说几句。docker run -p 8080:80 nginx这条命令的意思是:把宿主机的8080端口流量转发到容器的80端口。这个转发动作,Docker是通过iptables的DNAT规则来实现的。数据包到达宿主机时,如果目标端口是8080,DNAT规则会把目标地址改成容器的IP,目标端口改成80,然后数据包走路由进入docker0网桥,最终送到容器。这里有一个非常容易踩的坑:如果你在宿主机上用iptables -L -n查看规则,会发现Docker在iptables的链里插入了一大串规则。如果你自己写了firewalld或ufw的规则,很容易和Docker的规则产生冲突,导致端口映射失效或者外部流量被错误拦截。我遇到过不止一次,明明docker ps显示端口映射正确,但外部就是访问不到服务,排查到最后是系统防火墙规则和Docker的iptables规则打架了。建议在配置了Docker的宿主机上,要么统一用iptables管理防火墙规则,要么非常小心地配置规则避免覆盖Docker链。3.2 host模式:性能最好,隔离最弱host模式就简单粗暴了:容器不创建自己的网络命名空间,直接共享宿主机的网络栈。容器内的进程就像跑在宿主机上的进程一样,直接用宿主机的IP和端口。docker run --network host启动的容器,不需要-p端口映射,因为容器内的端口本来就在宿主机的端口空间里。host模式最大的优势是性能。没有NAT转换,没有veth pair转发,数据包直达物理网卡,延迟最低,吞吐最高。对于对网络性能极其敏感的应用(比如高性能计算、实时流处理),host模式是常见选择。但代价也很明显:端口不能冲突。两个使用host模式的容器,如果监听同一个端口,必然有一个起不来。另外,容器内的服务对宿主机网络没有任何隔离,安全性也弱一些。实际项目中,host模式通常用在那些不想引入额外网络层开销、且端口不冲突的组件上。但如果你想利用Docker的端口隔离能力,host模式就不太合适。3.3 none模式和container模式:特殊场景下的选择none模式是最简单的,容器只有lo回环接口,没有任何网络连接,完全隔离。这个模式很少直接用于生产服务,但有个典型的用途:运行一些纯计算任务或者安全要求极高的隔离任务,不需要容器访问外部网络。另外一个常见的用法是作为基础网络模式,然后在容器的网络命名空间里手动配置复杂的网络,相当于把网络的自由度完全交给使用者。container模式则比较特殊,它不创建新的网络命名空间,而是和另一个容器共享网络命名空间。这个模式最能发挥价值的地方是网络代理类容器。比如你要给某个容器抓包调试,可以启动一个安装了tcpdump工具的容器,用--network container:目标容器名连接到目标容器的网络命名空间,这样这个调试容器和目标容器共享同一个网络栈,直接就能抓到目标容器的网络流量,而不用在目标容器里费劲装调试工具。3.4 四种模式的选择对照网络模式隔离强度性能开销端口映射典型用途bridge高中需要 -p 映射单机多容器隔离部署host低极低不需要高性能服务、端口冲突可接受none极高无无纯计算任务、自建网络栈container特殊低共享目标容器调试抓包、代理容器选型思路一般是这样的:单机上跑多个服务且互相需要隔离,用bridge;追求性能且端口规划清晰,用host;纯粹要隔离不希望有任何网络通路,用none;需要一个带网络工具链的侧车容器去调试另一个容器,用container模式。理解了每种模式的取舍,选型就不会再靠猜了。4. 自定义网络与内置DNS:多容器协作的正确姿势4.1 为什么默认bridge网络不推荐用于容器互访很多初学者做多容器部署时,直接全部丢进默认bridge网络里,然后靠docker inspect查IP来互相访问。这样做最大的问题在于:容器重建或重启后,IP地址可能会变,写死在配置里的IP就失效了。另外,默认bridge网络里,容器之间只有通过IP访问这一条路,没法用服务名解析到IP,配置管理变得非常脆弱。正确做法是使用自定义网络。docker network create mynet就能创建一个自定义bridge网络。自定义网络的容器之间不仅仅可以靠IP互通,更重要的是可以靠容器名直接互访。Docker内置了一个DNS服务,会自动把容器名解析成对应的IP地址。比如你在mynet网络里启动一个容器名为db的MySQL,另一个应用容器只要在代码里配置连接地址为db:3306,就能正确解析到db容器的IP,不管它重启后IP怎么变都没关系。4.2 如何创建并使用自定义网络创建和使用自定义网络的完整流程如下:创建网络:docker network create --driver bridge --subnet 172.20.0.0/16 --gateway 172.20.0.1 appnet,其中--subnet和--gateway可以手动指定网段,不指定的话Docker会自己分配,但指定网段对后续防火墙规则和网络规划更可控。启动容器时指定网络:docker run -d --name web --network appnet nginx。已创建的容器加入网络:docker network connect appnet 容器名。多个容器使用同一个自定义网络后,就可以直接用容器名互访了。关于自定义网络有几点要特别注意。第一,自定义网络的容器可以动态加入或退出,但默认bridge网络不支持这种动态操作。第二,不同自定义网络之间的容器默认是不能互访的,如果要跨网络通信,需要额外的路由配置或让容器同时加入多个网络。第三,自定义网络和默认bridge的网络在DNS解析行为上有区别,默认bridge网络之前在较老版本里也支持通过--link参数实现容器名互访,但--link是官方明确不推荐使用的旧机制,新项目应该一律使用自定义网络加内置DNS。4.3 跨网络通信的几种思路实际项目中,经常出现"这个容器在前端网络,那个容器在后端网络,怎么让它们通信"的问题。我常用的方案有三种:让需要通信的容器同时加入多个网络。一个容器可以属于多个网络,这样它既能在前端网络里提供服务,又能在后端网络里访问数据库。操作就是docker network connect多执行几次。把需要互相访问的服务放在同一个自定义网络里,网络规划时明确各服务的所属网络,减少跨网络需求。在更复杂的场景下,用专门的边缘路由器容器(比如基于Linux路由功能自建)来做不同网段的转发。这个方案灵活性最高,但配置门槛也高,一般生产环境用得少。4.4 DNS解析的一个实战细节我在实际调试中遇到过这样的问题:容器里能ping通另一个容器的IP,但是在应用里配置了容器名解析却始终失败。最后定位到原因,是我自定义网络配置之后,Docker的内置DNS服务(监听127.0.0.11)在某些基础镜像里没有正确配置resolv.conf。容器里执行cat /etc/resolv.conf能看到nameserver 127.0.0.11,但如果镜像里自己的DNS配置覆盖了它,容器名解析就会失效。解决方法是:启动容器时显式指定Docker的DNS服务,或者检查镜像的/etc/resolv.conf不要被构建阶段残留的配置干扰。这种问题非常隐蔽,因为检查网络连通性时你通常先ping IP,不会马上想到是DNS解析的问题。但只要你用上了自定义网络的服务名互访,就要有意识地检查容器内DNS配置。5. Overlay网络与多主机通信:跨节点的容器编排基础5.1 单机之外的核心问题单机上无论怎么玩bridge网络,容器间通信都不涉及跨节点。但现实生产中,容器会分布在多台宿主机上。这时候,不同宿主机上的容器如何通信,就成了一个绕不开的问题。最简单的做法是把宿主机的物理IP透传给容器,用host模式,但这样会带来端口分配和跨节点的网络规划难题。更好的方案是使用Overlay网络。Overlay网络是一个逻辑上的虚拟二层网络,它叠加在物理网络之上。每个容器属于同一个Overlay网络,就好像它们接入了同一个虚拟交换机,无论它们的宿主机在哪里,容器间的IP通信都像是在同网段内完成的。底层的数据封装和解封装由Docker自动处理,对容器透明。Docker的Overlay网络实现,最初是依赖VXLAN技术来封装二层帧的。VXLAN是一种在UDP之上封装二层数据帧的隧道协议,它可以把一个二层广播域扩展到三层网络之上。容器A发出一个二层的帧,Docker在宿主机A上把整个帧封装进UDP包,目标地址是宿主机B的IP,宿主机B收到后解封装,把原始帧交给宿主机B上的对应容器。这样对容器来说,它感知不到对端的宿主机在哪,只知道对端IP在同一个子网里,通信就和在同一台机器上一样顺畅。5.2 Docker Swarm与Overlay网络的配合要使用Overlay网络,你首先需要把多台宿主机组成一个集群。Docker原生的集群方案是Swarm模式。初始化Swarm后,可以通过docker network create -d overlay myoverlay来创建一个跨主机的Overlay网络。凡是加入这个网络的容器,无论它们跑在哪个Swarm节点上,都能用Overlay网络内的IP直接通信。实际操作中,创建Overlay网络要确保满足几个前提条件:各节点之间需要打开VXLAN使用的UDP端口。默认情况下,VXLAN使用的端口是4789,Swarm还需要2377端口用于集群管理通信,以及7946端口用于节点间Gossip协议通信。防火墙和安全组规则必须允许这些端口的通信,很多Overlay网络启动后容器跨主机ping不通,排查下来都是端口没放行。Swarm节点的时钟要尽量保持一致,证书和token通信依赖时间校验,时间偏差过大会导致节点间握手失败。5.3 Overlay网络的性能考量Overlay网络方便归方便,性能开销是实实在在的。每一次跨主机通信,数据包都要经过"容器 → veth → 宿主机网桥 → VXLAN封装 → 物理网卡 → 对端物理网卡 → VXLAN解封装 → 对端网桥 → 对端veth → 目标容器"这么长的路径。相比同主机内部的bridge通信,延迟和CPU开销都会高不少。我在压测中观察到,Overlay网络下的小包吞吐大约是同主机bridge网络的一半左右,延迟也会增加几十微秒级别。对于普通的Web服务来说,这个开销通常可以接受,但是对延迟敏感型服务(比如高频交易、实时音视频传输),就需要认真评估了。这种场景下,可以考虑用host模式配合服务发现,或者使用支持SR-IOV和DPDK的CNI插件绕开Overlay开销,但那是另一个技术深度的话题了。5.4 手动验证Overlay网络是否正常踩过几次Overlay网络的坑之后,我总结了几个手动验证步骤:在任意Swarm节点上执行docker network ls,确认overlay网络已经创建成功。用docker network inspect myoverlay查看网络详情,确认子网和网关都符合预期。在两个不同节点上分别启动一个基于该网络的临时测试容器(比如docker run -it --rm --network myoverlay alpine sh),互相ping对方的容器名或IP。如果ping不通,在宿主机上抓包检查UDP端口4789是否有VXLAN流量。如果抓不到,大概率是防火墙或安全组拦截了VXLAN端口。这个链路排查是排错的基础思路,往下看我会展开讲更多实战排错的方法。6. 备好三把排查尺子:实用诊断方法和踩坑经验6.1 用 docker inspect 看清网络真相网络问题排查的第一站永远是docker inspect。很多问题其实不用进容器里折腾,直接在宿主机上就能看出端倪。docker inspect 容器名输出里有一个重要的"NetworkSettings"字段,里面会包含容器的IP地址、MAC地址、网关、所属网络列表。再配合docker inspect -f '{{json .NetworkSettings.Networks}}' 容器名可以只看网络相关的信息,输出是JSON格式,挺好解析的。排查端口映射失效问题时,我一般先检查这里。比如外部访问不到容器里的8080端口,先用docker ps看映射关系是否正常,再用docker port 容器名查看具体的端口绑定情况。如果端口映射显示正确,接着用curl在宿主机上测试http://127.0.0.1:8080看响应是否正常。这一步如果正常,说明容器和端口映射都没问题,问题出在外部网络或防火墙;如果不正常,问题可能在容器内的服务本身。6.2 进容器内部对照路由和DNS容器内网络问题,最常用的三个命令是ip addr、ip route、cat /etc/resolv.conf。ip addr查看容器内的IP地址分配是否正确,确认是否拿到了预期的网段IP。ip route查看默认路由是否指向网关(通常是docker0或自定义网络的网关)。cat /etc/resolv.conf查看DNS配置是否正确指向Docker内置DNS 127.0.0.11。举个实际例子:有一次容器内死活访问不了外网,ping 8.8.8.8有回包但ping www.example.com报错。一看/etc/resolv.conf,发现里面写的是宿主机上的一个DNS服务器地址,而不是127.0.0.11,说明容器的DNS配置被镜像构建或启动参数覆盖了,重新启动容器并显式指定--dns参数后解决。6.3 宿主机上用 tcpdump 抓包定位问题当容器内部的诊断走到死胡同时,就该上tcpdump了。抓包是网络排错里最朴实也最有效的手段,能看到数据包的真实走向。比较有用的几个抓包方式:在宿主机上抓特定容器的veth接口:tcpdump -i vethxxx port 80。抓docker0网桥上的流量:tcpdump -i docker0 -n port 80,注意加-n参数避免IP反查DNS浪费时间。抓物理网卡的流量:tcpdump -i eth0 -n port 4789,这个通常用于验证VXLAN流量是否到达宿主机。有一次排查跨主机容器通信失败,容器A ping容器B始终没有回包。我开始怀疑Overlay网络的问题,然后到宿主机B上抓包,发现VXLAN包确实到了,但宿主机B上没有把包解封装转发到目标容器。后来定位到是宿主机B上的Docker服务在重启后,Overlay网络没被正确恢复,需要重新部署服务才解决。没有tcpdump的话,这个问题的排查周期会长很多。6.4 iptables规则冲突:最隐蔽的坑Docker本身的网络依赖iptables很重,但宿主机上如果还有其他防火墙管理工具,冲突几乎是必然的。我遇到过的情况包括:firewalld和Docker同时存在时,firewalld的重载操作会把Docker的iptables链规则清掉,导致所有端口映射失效。自定义的FORWARD链DROP规则会直接阻断容器间转发流量,导致容器间ping不通但容器自身启动正常。手动添加的DNAT规则放在Docker规则之前,导致端口转发被错误匹配。排查iptables冲突,先执行iptables -L FORWARD -n -v看看默认策略是不是DROP,再执行iptables -t nat -L -n查一下Docker生成的DNAT规则是否还在。如果规则确实丢失了,最简单的恢复方法是重启Docker服务,让它重新生成规则。6.5 长期稳定的网络方案建议排错经验积累多了之后,我一般这样规划一套长期稳定的Docker网络方案:单机多容器服务,一律建自定义bridge网络,通过容器名互访,不要用默认bridge的IP互访方式。跨主机场景,优先考虑Swarm模式加Overlay网络,除非性能指标明确不达标。对外暴露的服务统一通过-p映射端口,映射前先规划好端口使用清单,避免不同容器抢端口。宿主机上防火墙和Docker的iptables规则要保持一致的管理方式,如果用了firewalld,踩过几次坑之后就果断统一到iptables直接管理。所有涉及网络的生产变更,操作前先拍快照或者记录当前docker network ls和iptables -t nat -L -n的输出,出问题好回滚。7. 一次多节点部署的完整复盘:从翻车到稳定最后分享一个比较完整的实战案例。有次我帮一个团队部署一套包含前端、后端、Redis、MySQL的分布式应用,一共三台宿主机,每台跑若干个容器。第一回合,我按单机思路把每个节点上的容器都放默认bridge网络,服务之间用IP地址互访。结果自然是灾难:MySQL容器重启了,IP变了,后端配置文件里的IP连接失败;三个节点上的前端服务都要映射宿主机的8000端口,互相冲突。这次翻车说明,多节点场景下如果没有统一的网络规划,Docker默认行为就是会狠狠教育你。第二回合,我改用自定义网络加上Swarm。具体步骤是:三台宿主机上分别执行docker swarm init和docker swarm join组成集群,然后创建一个overlay网络,把前端、后端、数据库等容器全部加入到这个overlay网络中。容器启动后,应用配置里直接写mysql:3306、redis:6379,不再依赖IP。重启容器、迁移容器到其他节点,服务发现都正常。踩过的一个小坑是跨节点的容器在Overlay网络内不能互相ping通,但服务访问正常。这个现象其实是IPVS模式下负载均衡的正常表现,服务端口可达,ICMP可能被SDN策略拦截,不一定代表网络故障。我当时差点误判,多亏了先检查服务端口再下结论的习惯。最终运行稳定之后,我重新梳理了一下这套方案的要点:Overlay网络的子网规划要避开和物理网络冲突的网段;所有敏感端口只通过Swarm的服务端口暴露,不会直接映射到宿主机;每个节点上的防火墙放行UDP 4789、TCP 2377和UDP 7946。这套配置跑了几个月没有因为网络问题出过故障。8. 最后再讲一句实在话Docker网络这块内容,说明书式的知识点翻来覆去就那么多,但真正拉开差距的是你能不能在看不懂报错、定位不到断连点时快速缩小问题范围。我的体会是:先把命名空间、veth、网桥、NAT这些底层机制吃透,再上手测试各种网络模式之间的差异,最后在真实项目里反复踩坑、记录、修正。不要怕网络出问题,最怕的是出了问题不知道为什么。把这篇文章里的排错思路留在脑子里,下次遇到"容器连不上""端口访问不到""跨主机不通"这些问题,你至少知道该往哪个方向开枪了。
1. 为什么网络是容器化应用最容易翻车的地方先从一个非常典型的场景说起。某团队开发了一个微服务Demo,容器里跑着前端、后端、数据库三个进程,本地开发时一切正常。结果一到部署环境,前端服务就是连不上后端接口,后端日志里全是连接超时。排查了大半天,最后发现是跨主机容器通信的网络配置没搞定。这种问题几乎每个深入用过Docker的人都会遇到,而且它不像CPU、内存那样看一眼监控就能定位,网络问题往往牵一发动全身。很多人一开始学Docker,注意力全放在镜像构建、容器编排这些看得见摸得着的东西上,对网络这块往往是"能用就行"的心态。默认的bridge网络下,容器之间靠IP互通,重启一次IP变了,配置跟着失效;容器和宿主机之间的端口映射规则没搞清楚,服务明明在跑却从外面访问不到;多个容器服务部署在同一台机器上,端口冲突导致后面的服务起不来。这些坑我都踩过,而且踩完之后发现,根源不是命令记错了,而是没把Docker网络的工作原理彻底想明白。这篇文章打算从底层机制讲起,逐步拆解Docker的几类网络模式,从单机到跨主机,从原理到排错,把我实际操作中积累的经验和踩过的坑一起放出来。读完之后,你应该能建立起一套完整的Docker网络知识框架,遇到问题知道从哪里排查,而不是靠瞎试命令碰运气。内容既有原理层面的解释,也有可以直接复用的配置步骤和诊断技巧,适合那些已经会基本的Docker命令、但对网络理解还比较模糊的人。2. 先搞懂三件底层的事:命名空间、veth pair、网桥2.1 网络命名空间:容器网络隔离的基石要理解Docker网络,绕不开Linux网络命名空间(Network Namespace)这个概念。它本质上是一种隔离机制,每个命名空间拥有自己独立的网络协议栈,包括自己的网卡、路由表、iptables规则、socket等。你可以把网络命名空间想成一台完全独立的虚拟路由器,不同命名空间之间的网络设备互不可见,数据包不会串门。Docker创建容器时,默认会为每个容器创建一个独立的网络命名空间。这就意味着容器内看到的eth0、lo这些网卡,宿主机上是看不到的。反过来,宿主机的物理网卡在容器里面也是不存在的。这种隔离机制保证了容器之间的网络环境互不干扰,也正因如此,容器内的进程不可能直接通过宿主机的物理网卡收发数据。在Linux上,ip netns list命令可以查看当前系统中有哪些网络命名空间,但注意,Docker创建的网络命名空间通常不会直接显示在这个列表里,原因在于Docker没有把命名空间挂载到/var/run/netns目录下。要查看Docker容器的命名空间,可以通过docker inspect拿到进程PID,然后到/proc/<pid>/ns/net查看。不过对于日常使用来说,你只需要知道"容器有独立网络栈"这件事,排查问题时能理解现象的本质就够了。2.2 veth pair:连接两个命名空间的虚拟网线隔离是做到了,但容器总得跟外界通信吧。这时候就轮到veth pair登场了。veth pair可以理解为一根虚拟网线,它一头连着容器的网络命名空间,另一头连着宿主机上的网络命名空间。数据从一头进去,必然从另一头出来,数据包在这根"网线"上没有任何加工,纯粹转发。Docker在创建容器时,会为容器创建一对veth设备,一端放进容器里,变成容器内的eth0,另一端挂在宿主机创建的网桥上,名字通常是vethxxx这种随机字符串。在宿主机上执行ip link show,你会看到一堆veth开头的虚拟网卡,每个都对应一个正在运行的容器。docker network inspect bridge命令也能看到"Containers"下面每个容器对应的veth接口名称。理解veth pair的意义在于:当你看到容器的网络流量时,要清楚它实际的传输路径是"容器内eth0 → veth pair → 宿主机网桥 → 进一步转发",明确这一条链路之后,排查问题就能按路径逐个节点检测。2.3 Linux Bridge:容器间通信的虚拟交换机网桥(Bridge)是Docker默认网络模式的核心组件。Linux网桥工作在二层,行为类似物理交换机,它根据MAC地址表决定把数据帧从哪个端口转发出去。Docker会在宿主机上创建一个叫做docker0的网桥,默认IP段通常是172.17.0.0/16。所有默认bridge网络的容器,veth设备的宿主机端都插在docker0这个虚拟交换机上。当一个容器访问另一个容器时,数据包从源容器的eth0出发,通过veth到达docker0网桥,网桥根据目的MAC地址,把数据帧转发到目标容器对应的veth端口上。整个过程不经过宿主机的物理网卡,属于纯二层的内部交换。这也是为什么同一台机器上默认bridge网络的容器之间可以直接通过IP通信,而且速度快、延迟低,毕竟不经过外面的物理链路。但要注意一个关键点:docker0网桥的IP地址172.17.0.1通常是容器的默认网关。容器内的流量如果要访问外部网络(比如访问互联网),数据包会先发给docker0这个网关,再由宿主机进行三层的路由和NAT转发。这引出了下一节要讲的iptables规则和端口映射逻辑。3. 默认网络模式逐个拆解:bridge、host、none、container怎么选3.1 bridge模式:默认但并不能无脑用bridge模式是Docker默认的网络模式。使用docker run启动容器时不指定--network参数,容器就会加入名为bridge的默认网络。通过docker network ls可以看到一个名为bridge的默认网络,它是Docker安装时自动创建的。bridge模式的特点是:容器间通过IP互访,容器可以访问外网,外网默认无法直接访问容器内部服务,必须通过端口映射(-p参数)来暴露。它的核心逻辑我前面已经说过:veth pair + docker0网桥 + NAT转发。端口映射这块值得多说几句。docker run -p 8080:80 nginx这条命令的意思是:把宿主机的8080端口流量转发到容器的80端口。这个转发动作,Docker是通过iptables的DNAT规则来实现的。数据包到达宿主机时,如果目标端口是8080,DNAT规则会把目标地址改成容器的IP,目标端口改成80,然后数据包走路由进入docker0网桥,最终送到容器。这里有一个非常容易踩的坑:如果你在宿主机上用iptables -L -n查看规则,会发现Docker在iptables的链里插入了一大串规则。如果你自己写了firewalld或ufw的规则,很容易和Docker的规则产生冲突,导致端口映射失效或者外部流量被错误拦截。我遇到过不止一次,明明docker ps显示端口映射正确,但外部就是访问不到服务,排查到最后是系统防火墙规则和Docker的iptables规则打架了。建议在配置了Docker的宿主机上,要么统一用iptables管理防火墙规则,要么非常小心地配置规则避免覆盖Docker链。3.2 host模式:性能最好,隔离最弱host模式就简单粗暴了:容器不创建自己的网络命名空间,直接共享宿主机的网络栈。容器内的进程就像跑在宿主机上的进程一样,直接用宿主机的IP和端口。docker run --network host启动的容器,不需要-p端口映射,因为容器内的端口本来就在宿主机的端口空间里。host模式最大的优势是性能。没有NAT转换,没有veth pair转发,数据包直达物理网卡,延迟最低,吞吐最高。对于对网络性能极其敏感的应用(比如高性能计算、实时流处理),host模式是常见选择。但代价也很明显:端口不能冲突。两个使用host模式的容器,如果监听同一个端口,必然有一个起不来。另外,容器内的服务对宿主机网络没有任何隔离,安全性也弱一些。实际项目中,host模式通常用在那些不想引入额外网络层开销、且端口不冲突的组件上。但如果你想利用Docker的端口隔离能力,host模式就不太合适。3.3 none模式和container模式:特殊场景下的选择none模式是最简单的,容器只有lo回环接口,没有任何网络连接,完全隔离。这个模式很少直接用于生产服务,但有个典型的用途:运行一些纯计算任务或者安全要求极高的隔离任务,不需要容器访问外部网络。另外一个常见的用法是作为基础网络模式,然后在容器的网络命名空间里手动配置复杂的网络,相当于把网络的自由度完全交给使用者。container模式则比较特殊,它不创建新的网络命名空间,而是和另一个容器共享网络命名空间。这个模式最能发挥价值的地方是网络代理类容器。比如你要给某个容器抓包调试,可以启动一个安装了tcpdump工具的容器,用--network container:目标容器名连接到目标容器的网络命名空间,这样这个调试容器和目标容器共享同一个网络栈,直接就能抓到目标容器的网络流量,而不用在目标容器里费劲装调试工具。3.4 四种模式的选择对照网络模式隔离强度性能开销端口映射典型用途bridge高中需要 -p 映射单机多容器隔离部署host低极低不需要高性能服务、端口冲突可接受none极高无无纯计算任务、自建网络栈container特殊低共享目标容器调试抓包、代理容器选型思路一般是这样的:单机上跑多个服务且互相需要隔离,用bridge;追求性能且端口规划清晰,用host;纯粹要隔离不希望有任何网络通路,用none;需要一个带网络工具链的侧车容器去调试另一个容器,用container模式。理解了每种模式的取舍,选型就不会再靠猜了。4. 自定义网络与内置DNS:多容器协作的正确姿势4.1 为什么默认bridge网络不推荐用于容器互访很多初学者做多容器部署时,直接全部丢进默认bridge网络里,然后靠docker inspect查IP来互相访问。这样做最大的问题在于:容器重建或重启后,IP地址可能会变,写死在配置里的IP就失效了。另外,默认bridge网络里,容器之间只有通过IP访问这一条路,没法用服务名解析到IP,配置管理变得非常脆弱。正确做法是使用自定义网络。docker network create mynet就能创建一个自定义bridge网络。自定义网络的容器之间不仅仅可以靠IP互通,更重要的是可以靠容器名直接互访。Docker内置了一个DNS服务,会自动把容器名解析成对应的IP地址。比如你在mynet网络里启动一个容器名为db的MySQL,另一个应用容器只要在代码里配置连接地址为db:3306,就能正确解析到db容器的IP,不管它重启后IP怎么变都没关系。4.2 如何创建并使用自定义网络创建和使用自定义网络的完整流程如下:创建网络:docker network create --driver bridge --subnet 172.20.0.0/16 --gateway 172.20.0.1 appnet,其中--subnet和--gateway可以手动指定网段,不指定的话Docker会自己分配,但指定网段对后续防火墙规则和网络规划更可控。启动容器时指定网络:docker run -d --name web --network appnet nginx。已创建的容器加入网络:docker network connect appnet 容器名。多个容器使用同一个自定义网络后,就可以直接用容器名互访了。关于自定义网络有几点要特别注意。第一,自定义网络的容器可以动态加入或退出,但默认bridge网络不支持这种动态操作。第二,不同自定义网络之间的容器默认是不能互访的,如果要跨网络通信,需要额外的路由配置或让容器同时加入多个网络。第三,自定义网络和默认bridge的网络在DNS解析行为上有区别,默认bridge网络之前在较老版本里也支持通过--link参数实现容器名互访,但--link是官方明确不推荐使用的旧机制,新项目应该一律使用自定义网络加内置DNS。4.3 跨网络通信的几种思路实际项目中,经常出现"这个容器在前端网络,那个容器在后端网络,怎么让它们通信"的问题。我常用的方案有三种:让需要通信的容器同时加入多个网络。一个容器可以属于多个网络,这样它既能在前端网络里提供服务,又能在后端网络里访问数据库。操作就是docker network connect多执行几次。把需要互相访问的服务放在同一个自定义网络里,网络规划时明确各服务的所属网络,减少跨网络需求。在更复杂的场景下,用专门的边缘路由器容器(比如基于Linux路由功能自建)来做不同网段的转发。这个方案灵活性最高,但配置门槛也高,一般生产环境用得少。4.4 DNS解析的一个实战细节我在实际调试中遇到过这样的问题:容器里能ping通另一个容器的IP,但是在应用里配置了容器名解析却始终失败。最后定位到原因,是我自定义网络配置之后,Docker的内置DNS服务(监听127.0.0.11)在某些基础镜像里没有正确配置resolv.conf。容器里执行cat /etc/resolv.conf能看到nameserver 127.0.0.11,但如果镜像里自己的DNS配置覆盖了它,容器名解析就会失效。解决方法是:启动容器时显式指定Docker的DNS服务,或者检查镜像的/etc/resolv.conf不要被构建阶段残留的配置干扰。这种问题非常隐蔽,因为检查网络连通性时你通常先ping IP,不会马上想到是DNS解析的问题。但只要你用上了自定义网络的服务名互访,就要有意识地检查容器内DNS配置。5. Overlay网络与多主机通信:跨节点的容器编排基础5.1 单机之外的核心问题单机上无论怎么玩bridge网络,容器间通信都不涉及跨节点。但现实生产中,容器会分布在多台宿主机上。这时候,不同宿主机上的容器如何通信,就成了一个绕不开的问题。最简单的做法是把宿主机的物理IP透传给容器,用host模式,但这样会带来端口分配和跨节点的网络规划难题。更好的方案是使用Overlay网络。Overlay网络是一个逻辑上的虚拟二层网络,它叠加在物理网络之上。每个容器属于同一个Overlay网络,就好像它们接入了同一个虚拟交换机,无论它们的宿主机在哪里,容器间的IP通信都像是在同网段内完成的。底层的数据封装和解封装由Docker自动处理,对容器透明。Docker的Overlay网络实现,最初是依赖VXLAN技术来封装二层帧的。VXLAN是一种在UDP之上封装二层数据帧的隧道协议,它可以把一个二层广播域扩展到三层网络之上。容器A发出一个二层的帧,Docker在宿主机A上把整个帧封装进UDP包,目标地址是宿主机B的IP,宿主机B收到后解封装,把原始帧交给宿主机B上的对应容器。这样对容器来说,它感知不到对端的宿主机在哪,只知道对端IP在同一个子网里,通信就和在同一台机器上一样顺畅。5.2 Docker Swarm与Overlay网络的配合要使用Overlay网络,你首先需要把多台宿主机组成一个集群。Docker原生的集群方案是Swarm模式。初始化Swarm后,可以通过docker network create -d overlay myoverlay来创建一个跨主机的Overlay网络。凡是加入这个网络的容器,无论它们跑在哪个Swarm节点上,都能用Overlay网络内的IP直接通信。实际操作中,创建Overlay网络要确保满足几个前提条件:各节点之间需要打开VXLAN使用的UDP端口。默认情况下,VXLAN使用的端口是4789,Swarm还需要2377端口用于集群管理通信,以及7946端口用于节点间Gossip协议通信。防火墙和安全组规则必须允许这些端口的通信,很多Overlay网络启动后容器跨主机ping不通,排查下来都是端口没放行。Swarm节点的时钟要尽量保持一致,证书和token通信依赖时间校验,时间偏差过大会导致节点间握手失败。5.3 Overlay网络的性能考量Overlay网络方便归方便,性能开销是实实在在的。每一次跨主机通信,数据包都要经过"容器 → veth → 宿主机网桥 → VXLAN封装 → 物理网卡 → 对端物理网卡 → VXLAN解封装 → 对端网桥 → 对端veth → 目标容器"这么长的路径。相比同主机内部的bridge通信,延迟和CPU开销都会高不少。我在压测中观察到,Overlay网络下的小包吞吐大约是同主机bridge网络的一半左右,延迟也会增加几十微秒级别。对于普通的Web服务来说,这个开销通常可以接受,但是对延迟敏感型服务(比如高频交易、实时音视频传输),就需要认真评估了。这种场景下,可以考虑用host模式配合服务发现,或者使用支持SR-IOV和DPDK的CNI插件绕开Overlay开销,但那是另一个技术深度的话题了。5.4 手动验证Overlay网络是否正常踩过几次Overlay网络的坑之后,我总结了几个手动验证步骤:在任意Swarm节点上执行docker network ls,确认overlay网络已经创建成功。用docker network inspect myoverlay查看网络详情,确认子网和网关都符合预期。在两个不同节点上分别启动一个基于该网络的临时测试容器(比如docker run -it --rm --network myoverlay alpine sh),互相ping对方的容器名或IP。如果ping不通,在宿主机上抓包检查UDP端口4789是否有VXLAN流量。如果抓不到,大概率是防火墙或安全组拦截了VXLAN端口。这个链路排查是排错的基础思路,往下看我会展开讲更多实战排错的方法。6. 备好三把排查尺子:实用诊断方法和踩坑经验6.1 用 docker inspect 看清网络真相网络问题排查的第一站永远是docker inspect。很多问题其实不用进容器里折腾,直接在宿主机上就能看出端倪。docker inspect 容器名输出里有一个重要的"NetworkSettings"字段,里面会包含容器的IP地址、MAC地址、网关、所属网络列表。再配合docker inspect -f '{{json .NetworkSettings.Networks}}' 容器名可以只看网络相关的信息,输出是JSON格式,挺好解析的。排查端口映射失效问题时,我一般先检查这里。比如外部访问不到容器里的8080端口,先用docker ps看映射关系是否正常,再用docker port 容器名查看具体的端口绑定情况。如果端口映射显示正确,接着用curl在宿主机上测试http://127.0.0.1:8080看响应是否正常。这一步如果正常,说明容器和端口映射都没问题,问题出在外部网络或防火墙;如果不正常,问题可能在容器内的服务本身。6.2 进容器内部对照路由和DNS容器内网络问题,最常用的三个命令是ip addr、ip route、cat /etc/resolv.conf。ip addr查看容器内的IP地址分配是否正确,确认是否拿到了预期的网段IP。ip route查看默认路由是否指向网关(通常是docker0或自定义网络的网关)。cat /etc/resolv.conf查看DNS配置是否正确指向Docker内置DNS 127.0.0.11。举个实际例子:有一次容器内死活访问不了外网,ping 8.8.8.8有回包但ping www.example.com报错。一看/etc/resolv.conf,发现里面写的是宿主机上的一个DNS服务器地址,而不是127.0.0.11,说明容器的DNS配置被镜像构建或启动参数覆盖了,重新启动容器并显式指定--dns参数后解决。6.3 宿主机上用 tcpdump 抓包定位问题当容器内部的诊断走到死胡同时,就该上tcpdump了。抓包是网络排错里最朴实也最有效的手段,能看到数据包的真实走向。比较有用的几个抓包方式:在宿主机上抓特定容器的veth接口:tcpdump -i vethxxx port 80。抓docker0网桥上的流量:tcpdump -i docker0 -n port 80,注意加-n参数避免IP反查DNS浪费时间。抓物理网卡的流量:tcpdump -i eth0 -n port 4789,这个通常用于验证VXLAN流量是否到达宿主机。有一次排查跨主机容器通信失败,容器A ping容器B始终没有回包。我开始怀疑Overlay网络的问题,然后到宿主机B上抓包,发现VXLAN包确实到了,但宿主机B上没有把包解封装转发到目标容器。后来定位到是宿主机B上的Docker服务在重启后,Overlay网络没被正确恢复,需要重新部署服务才解决。没有tcpdump的话,这个问题的排查周期会长很多。6.4 iptables规则冲突:最隐蔽的坑Docker本身的网络依赖iptables很重,但宿主机上如果还有其他防火墙管理工具,冲突几乎是必然的。我遇到过的情况包括:firewalld和Docker同时存在时,firewalld的重载操作会把Docker的iptables链规则清掉,导致所有端口映射失效。自定义的FORWARD链DROP规则会直接阻断容器间转发流量,导致容器间ping不通但容器自身启动正常。手动添加的DNAT规则放在Docker规则之前,导致端口转发被错误匹配。排查iptables冲突,先执行iptables -L FORWARD -n -v看看默认策略是不是DROP,再执行iptables -t nat -L -n查一下Docker生成的DNAT规则是否还在。如果规则确实丢失了,最简单的恢复方法是重启Docker服务,让它重新生成规则。6.5 长期稳定的网络方案建议排错经验积累多了之后,我一般这样规划一套长期稳定的Docker网络方案:单机多容器服务,一律建自定义bridge网络,通过容器名互访,不要用默认bridge的IP互访方式。跨主机场景,优先考虑Swarm模式加Overlay网络,除非性能指标明确不达标。对外暴露的服务统一通过-p映射端口,映射前先规划好端口使用清单,避免不同容器抢端口。宿主机上防火墙和Docker的iptables规则要保持一致的管理方式,如果用了firewalld,踩过几次坑之后就果断统一到iptables直接管理。所有涉及网络的生产变更,操作前先拍快照或者记录当前docker network ls和iptables -t nat -L -n的输出,出问题好回滚。7. 一次多节点部署的完整复盘:从翻车到稳定最后分享一个比较完整的实战案例。有次我帮一个团队部署一套包含前端、后端、Redis、MySQL的分布式应用,一共三台宿主机,每台跑若干个容器。第一回合,我按单机思路把每个节点上的容器都放默认bridge网络,服务之间用IP地址互访。结果自然是灾难:MySQL容器重启了,IP变了,后端配置文件里的IP连接失败;三个节点上的前端服务都要映射宿主机的8000端口,互相冲突。这次翻车说明,多节点场景下如果没有统一的网络规划,Docker默认行为就是会狠狠教育你。第二回合,我改用自定义网络加上Swarm。具体步骤是:三台宿主机上分别执行docker swarm init和docker swarm join组成集群,然后创建一个overlay网络,把前端、后端、数据库等容器全部加入到这个overlay网络中。容器启动后,应用配置里直接写mysql:3306、redis:6379,不再依赖IP。重启容器、迁移容器到其他节点,服务发现都正常。踩过的一个小坑是跨节点的容器在Overlay网络内不能互相ping通,但服务访问正常。这个现象其实是IPVS模式下负载均衡的正常表现,服务端口可达,ICMP可能被SDN策略拦截,不一定代表网络故障。我当时差点误判,多亏了先检查服务端口再下结论的习惯。最终运行稳定之后,我重新梳理了一下这套方案的要点:Overlay网络的子网规划要避开和物理网络冲突的网段;所有敏感端口只通过Swarm的服务端口暴露,不会直接映射到宿主机;每个节点上的防火墙放行UDP 4789、TCP 2377和UDP 7946。这套配置跑了几个月没有因为网络问题出过故障。8. 最后再讲一句实在话Docker网络这块内容,说明书式的知识点翻来覆去就那么多,但真正拉开差距的是你能不能在看不懂报错、定位不到断连点时快速缩小问题范围。我的体会是:先把命名空间、veth、网桥、NAT这些底层机制吃透,再上手测试各种网络模式之间的差异,最后在真实项目里反复踩坑、记录、修正。不要怕网络出问题,最怕的是出了问题不知道为什么。把这篇文章里的排错思路留在脑子里,下次遇到"容器连不上""端口访问不到""跨主机不通"这些问题,你至少知道该往哪个方向开枪了。