
很多人用Docker用了一两年docker run -p 8080:80一把梭容器起来能访问就行。但一旦碰到容器互通、跨主机通信、性能调优这些场景就开始抓瞎了——为什么我的容器ping不通另一个容器为什么换了host模式性能就上去了overlay到底怎么跨主机的macvlan和bridge有啥本质区别Docker一共有5种网络模式bridge、host、none、overlay、macvlan。每种背后的实现原理完全不同适用场景也大相径庭。今天把这5种掰开了揉碎了讲清楚。Bridge模式默认的也是最容易被误解的你不指定任何网络参数启动容器用的就是bridge模式。Docker安装完之后会在宿主机上创建一个叫docker0的虚拟网桥默认网段是172.17.0.0/16。底层发生了什么每启动一个容器Docker会创建一对veth pair虚拟以太网设备对就像一根虚拟网线的两头一头插进容器的网络命名空间里变成容器的eth0另一头挂在宿主机的docker0网桥上给容器分配一个172.17.0.x的IP在宿主机的iptables里加一条NAT规则把外部流量转发给容器你可以自己验证一下# 启动一个容器dockerrun-d--nametestnginx# 在宿主机上看veth设备iplinkshowtypeveth# 你会看到类似 veth3a8f9b2if7 这样的设备# 看docker0网桥上挂了哪些设备brctl show docker0# bridge name bridge id STP enabled interfaces# docker0 8000.024216a5b5e5 no veth3a8f9b2# 看iptables的NAT规则iptables-tnat-L-n|grep172.17# MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0# DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80默认bridge的坑Docker自带的那个bridge就是docker0不支持容器间DNS发现。你用容器名是ping不通其他容器的只能用IP。这也是为什么Docker Compose会自动创建一个自定义bridge网络——自定义bridge才支持通过容器名互相解析。# 默认bridge - 容器名不通dockerrun-d--nameweb nginxdockerrun-it--rmalpinepingweb# ping: bad address web ← 不行# 自定义bridge - 容器名可达dockernetwork create mynetdockerrun-d--nameweb--networkmynet nginxdockerrun-it--rm--networkmynet alpinepingweb# PING web (172.18.0.2): 56 data bytes# 64 bytes from 172.18.0.2: seq0 ttl64 time0.089 ms ← 通了bridge模式适合什么场景单机上跑多个容器容器之间需要互通但又想和宿主机保持网络隔离。绝大多数开发环境和中小型生产部署用bridge就够了。性能代价所有流量都要经过NAT和iptables规则有一层转发开销。实测大概会带来2-5%的吞吐量损失和微秒级的延迟增加。对大多数Web应用来说感知不到但如果你跑的是高频交易或者万兆网卡打满的场景这个开销就不可忽略了。Host模式直接把容器的网络墙拆了Host模式非常暴力——容器不再创建自己的网络命名空间直接共享宿主机的网络栈。容器里的进程看到的网络接口、IP地址、路由表和你在宿主机上看到的一模一样。dockerrun-d--networkhost--namenginx-host nginx# 容器里看网络dockerexecnginx-hostipaddr# 你会看到宿主机的eth0、lo等所有接口# 宿主机上直接就能看到nginx监听的80端口ss-tlnp|grep:80# LISTEN 0 511 0.0.0.0:80 users:((nginx,pid12345,fd6))注意到没不需要-p 80:80了。因为容器和宿主机共享端口空间nginx在容器里监听80宿主机上80端口就直接被占了。这也意味着一个严重限制你不能在同一台宿主机上用host模式启动两个都监听80端口的容器——端口会冲突。host模式的性能优势没有了veth pair、没有了bridge转发、没有了iptables NAT规则网络性能和裸机完全一致。有人做过测试在高吞吐场景下host模式比bridge快15-20%。对于那些需要极致网络性能的应用——比如做GPU推理服务的模型并行通信、高性能数据库、流媒体转码——host模式是个实打实的选择。# 一个典型的高性能场景Redis用host模式避免NAT开销dockerrun-d--networkhost--nameredis redis:7\--bind0.0.0.0--port6379--maxmemory4gb# 对比bridge模式下的Redis延迟# host模式avg latency ~0.08ms# bridge模式avg latency ~0.12ms# 50%的延迟差距对缓存服务来说是能感知到的host模式适合什么场景对网络延迟极度敏感的应用数据库、缓存、消息队列需要监听大量端口或动态端口的服务比如FTP被动模式性能测试/压测场景需要排除网络虚拟化的干扰容器只是个打包工具不需要网络隔离的场景host模式的代价完全丧失网络隔离。容器里的进程能看到宿主机的所有网络流量能绑定任意端口。从安全角度讲这基本等于没有网络层面的容器隔离。None模式彻底断网None模式就是字面意思——容器里除了一个lo回环接口之外什么都没有。没有eth0没有IP地址没有路由没有任何对外通信的能力。dockerrun-it--networknone alpinesh# 容器里只有lo/# ip addr1: lo:LOOPBACK,UP,LOWER_UPmtu65536inet127.0.0.1/8 scopehostlo /# ping 8.8.8.8PING8.8.8.8(8.8.8.8):56data bytes ping: sendmsg: Network is unreachable为什么会有人用这个几个场景安全隔离某些处理敏感数据的容器比如加密运算、密钥生成你根本不希望它有任何网络能力以防万一被攻破后横向移动。自定义网络你想完全手动控制容器的网络配置——自己创建网络命名空间、自己配veth pair、自己设路由。一些CNI插件比如Kubernetes里的Calico、Cilium就是先用none模式创建容器然后再由插件接管网络配置。纯计算任务容器只是跑一个CPU密集型的批处理任务输入输出都通过挂载的volume传递根本不需要网络。# 安全场景密钥生成容器完全隔离网络dockerrun--networknone-v/secure/output:/output\my-keygen-image generate--output/output/key.pem实话说生产环境里直接用none的不多。但如果你在做安全合规相关的东西审计人员看到你的敏感容器用的是--network none会比较放心。Overlay模式跨主机通信的唯一官方解前面说的bridge、host、none都是单机上的事。当你有多台Docker宿主机需要让不同机器上的容器互相通信时overlay网络就登场了。Overlay的原理是VXLAN封装——把容器的二层帧封在一个UDP包里通过宿主机的物理网络传输到另一台宿主机再解封给目标容器。对容器来说它觉得自己和远程的容器在同一个二层网络里。容器A (Host1) 容器B (Host2) eth0: 10.0.0.2 eth0: 10.0.0.3 | | [VXLAN封装] [VXLAN解封] | | Host1 eth0: 192.168.1.10 ---UDP--- Host2 eth0: 192.168.1.11要用overlay网络你需要Docker Swarm模式或者手动配置一个key-value store如Consul/etcd来做服务发现# 初始化Swarm在manager节点dockerswarm init --advertise-addr192.168.1.10# worker节点加入dockerswarmjoin--tokentoken192.168.1.10:2377# 创建overlay网络dockernetwork create-doverlay--attachablemy-overlay# 在任意节点上启动容器并加入overlay网络dockerrun-d--nameweb--networkmy-overlay nginxdockerrun-d--nameapi--networkmy-overlay my-api-image# web和api可以通过容器名互通即使它们在不同的物理机上dockerexecwebpingapi# PING api (10.0.1.3): 56 data bytes# 64 bytes from 10.0.1.3: seq0 ttl64 time0.543 msOverlay的性能开销VXLAN封装/解封有成本。每个包要多封50字节的头VXLAN 8字节 UDP 8字节 IP 20字节 Ethernet 14字节而且有一次额外的内核态封包/解包操作。实测吞吐量比物理网络直连大概低10-15%延迟多几百微秒。Overlay适合什么场景Docker Swarm集群里的服务间通信跨数据中心的容器互联多租户环境下的网络隔离每个租户一个overlay网络Overlay不适合什么场景对延迟极度敏感的服务那点封装开销可能受不了单机部署杀鸡用牛刀需要精确控制MTU的场景VXLAN封装会吃掉50字节如果你的物理网络MTU是1500overlay的有效MTU就只有1450顺带一提如果你用Kubernetes而不是Docker SwarmK8s的CNI插件Calico、Cilium、Flannel也是用类似的思路实现跨节点通信的只不过有些用的是VXLAN有些用的是IP-in-IP有些用的是WireGuard有些直接用BGP做路由。原理殊途同归。Macvlan模式让容器假装是一台物理机Macvlan是最真实的一种网络模式。它给容器分配一个独立的MAC地址让容器直接出现在你的物理网络上和宿主机处于同一个二层网段。对上游交换机来说这个容器就是网络上的一台独立设备。# 创建macvlan网络# 假设宿主机的物理网卡是eth0网段是192.168.1.0/24网关192.168.1.1dockernetwork create-dmacvlan\--subnet192.168.1.0/24\--gateway192.168.1.1\-oparenteth0\my-macvlan# 启动容器直接分配物理网段的IPdockerrun-d--networkmy-macvlan--ip192.168.1.100--nameweb nginx# 同一个局域网里的其他机器可以直接访问 192.168.1.100:80# 不需要端口映射不需要NAT从网络拓扑上看是这样的物理交换机 |-- 宿主机 eth0: 192.168.1.10 (MAC: aa:bb:cc:dd:ee:01) |-- 容器 web: 192.168.1.100 (MAC: 02:42:ac:11:00:02) ← 独立MAC |-- 其他物理设备: 192.168.1.xxxMacvlan的一个大坑容器和宿主机之间默认是不通的。没错你的容器虽然和宿主机在同一个网段但因为Linux内核的macvlan实现机制宿主机的物理网卡不会接收发给macvlan子接口的流量。解决办法是在宿主机上也创建一个macvlan接口来通信# 宿主机上创建一个macvlan接口用来和容器通信iplinkaddmac0linketh0typemacvlan mode bridgeipaddradd192.168.1.200/24 dev mac0iplinksetmac0 up# 现在宿主机可以通过192.168.1.200这个地址和容器互通了ping192.168.1.100# 从mac0接口出去可以到达容器Macvlan适合什么场景容器需要在物理网络上有自己的IP比如对接老旧的硬件设备、SNMP监控系统需要绕过NAT的场景某些协议对NAT不友好比如SIP、某些工业协议家庭实验室/HomeLab里给容器分配局域网IP智能家居、NAS服务需要VLAN隔离的企业环境# VLAN场景容器直接接入指定VLAN# 宿主机eth0上已经配了VLAN 100的子接口 eth0.100dockernetwork create-dmacvlan\--subnet10.100.0.0/24\--gateway10.100.0.1\-oparenteth0.100\vlan100-netdockerrun-d--networkvlan100-net--ip10.100.0.50 my-app# 这个容器现在在VLAN 100里有自己的IPMacvlan vs Ipvlan顺便提一嘴ipvlan。macvlan给每个容器分配独立的MAC地址ipvlan则是所有容器共享宿主机的MAC地址只在IP层做隔离。当你的交换机有MAC地址数量限制某些云环境或老旧交换机或者你需要运行几百个容器时ipvlan比macvlan更合适——因为不会触发交换机的MAC地址表溢出。# ipvlan L2模式 - 共享MAC独立IPdockernetwork create-dipvlan\--subnet192.168.1.0/24\--gateway192.168.1.1\-oparenteth0\-oipvlan_model2\my-ipvlan一张表搞清楚怎么选维度BridgeHostNoneOverlayMacvlan隔离性✅ 好❌ 无✅ 完全✅ 好✅ 中等性能中等最好N/A较低好跨主机❌❌❌✅❌ (需L3)端口映射需要不需要N/A需要不需要容器互通同bridge可通走lo不通同overlay可通同网段可通DNS发现自定义bridge支持不支持不支持支持不支持复杂度低最低最低高中等典型场景开发/中小生产高性能服务安全隔离集群/多主机接入物理网络实战中的组合用法真实的生产环境里这几种模式经常混着用。举个例子一个典型的微服务部署# docker-compose.yml - 混合网络模式的实际例子version:3.8services:# Nginx反向代理 - 用host模式直接监听80/443性能最好nginx:image:nginx:latestnetwork_mode:hostvolumes:-./nginx.conf:/etc/nginx/nginx.conf# 应用服务 - 用自定义bridge容器间通过名字互通api:image:my-api:latestnetworks:-backendworker:image:my-worker:latestnetworks:-backend# Redis缓存 - 也在bridge网络里但如果对延迟敏感也可以用hostredis:image:redis:7networks:-backend# 如果延迟敏感换成:# network_mode: host# 密钥管理容器 - none模式彻底隔离vault-init:image:vault-init:latestnetwork_mode:nonevolumes:-secrets:/secretsnetworks:backend:driver:bridgeipam:config:-subnet:172.20.0.0/16volumes:secrets:还有一个容易被忽略的点Docker Compose默认就会创建自定义bridge网络所以你在compose里定义的服务天然支持容器名DNS解析——这比手动docker run然后指望默认bridge要好使得多。排障思路网络问题是Docker里最让人头疼的。分享几个排障的常用命令和思路# 1. 先确认容器用的什么网络模式dockerinspectcontainer--format{{.HostConfig.NetworkMode}}# 2. 看容器的IP和网络配置dockerinspectcontainer--format{{json .NetworkSettings.Networks}}|jq.# 3. 看宿主机上的网桥和veth设备brctl showiplinkshowtypeveth# 4. 看iptables规则是否正确bridge模式下端口映射靠这个iptables-tnat-L-n-v|grepcontainer_ip# 5. 进容器里排查dockerexec-itcontainersh# 然后: ip addr / ip route / ping / curl / nslookup# 6. 抓包 - 在宿主机的veth接口上抓容器的流量tcpdump-iveth3a8f9b2-nn# 7. overlay网络排障 - 检查VXLAN隧道ip-dlinkshowtypevxlan最常见的几个坑容器ping不通外网 → 检查宿主机的ip_forward是否开启sysctl net.ipv4.ip_forward端口映射不生效 → 检查iptables的DOCKER链有没有被其他防火墙规则覆盖overlay网络容器间不通 → 检查宿主机之间的UDP 4789端口VXLAN是否放通macvlan容器ping不通宿主机 → 正常现象需要额外创建macvlan接口前面讲过选型就一句话单机开发测试→ bridge默认的就行自定义bridge更好极致性能→ host牺牲隔离换性能安全隔离 / 纯计算→ none多主机集群→ overlay接入物理网络 / 绕NAT→ macvlan不用想太复杂。90%的场景用自定义bridge就够了剩下10%根据你的具体需求性能、安全、跨主机来选对应的模式。