
说实话容器网络这块最容易让人产生误解。我刚接触Docker那会儿觉得容器网络无非就是docker run -p 8080:80把端口露出去容器之间能 ping 通就完事了。等到真要在多台机器上跑一套完整服务才发现同一个镜像换个网络模式行为完全不一样有的容器名能互相解析有的只能靠 IP有的天然支持跨宿主机通信有的离开本机就老死不相往来。这篇针对 Docker 网络部分做一个相对完整的拆解从底层模型讲到跨主机的 overlay再补一组排错思路。不管你是刚入门还是已经能熟练跑单机容器应该都能找到点实用的东西。1. 先想清楚一件事你需要的到底是哪种网络模式面对一长串网络模式第一个动作不是记参数而是把每个模式的边界画清楚。Docker 官方文档列了 bridge、host、none、overlay、macvlan、ipvlan 六种但实际使用中绝大多数场景只会用到前四种后两种更像特殊武器。1.1 单机场景下的三件套bridge、host、nonebridge 是 Docker 默认模式也是新手最先接触的。它的核心思路是用一个虚拟网桥默认叫 docker0把容器连接起来每个容器拥有独立的网络命名空间和独立 IP。这个模式的好处是隔离性够好容器之间可以通过 IP 通信配合端口映射又能对外提供服务。缺点是默认网桥下没有内嵌 DNS容器之间的服务发现能力很弱后面我会单独讲自定义网桥怎么补上这个短板。host 模式就直接多了容器不创建自己的网络命名空间直接借用宿主机的网络栈。带来的结果是网络性能几乎零损耗端口也天然暴露在宿主机上。代价是没有隔离容器里绑个端口宿主机上就多一个端口很容易冲突。这种模式适合对延迟极度敏感、并且你知道自己在干什么的场景比如跑一些网络性能基准测试、抓包工具等。none 模式更好理解容器只有一个 loopback 接口没有实际网络能力。它不是没用而是把网络配置的主动权完全交给使用方。某些跑批处理任务的容器根本不需要对外通信或者你想自己写一套插件化网络方案用 none 模式反而更干净。1.2 面向多主机与物理网络overlay、macvlan、ipvlanoverlay 是跨主机场景的主角。它不直接操作物理网络而是在宿主机之间建立一层隧道容器 IP 在隧道内传递。你不需要关心容器跑在哪台机器上只要它加入了同一个 overlay 网络彼此就能通信。这个模式配合 Docker Swarm 或 Kubernetes 使用非常顺手多节点间的服务自动发现、负载均衡都建立在它之上。macvlan 做的事情比较特殊它让容器直接拥有一个宿主机所在物理网络的 MAC 地址和 IP 地址相当于把容器当成物理网络里的一台独立设备。好处是容器可以被局域网直接访问无需做端口映射坏处是很多交换机开启了 MAC 地址学习限制macvlan 容器一多交换机的 MAC 表会被撑爆。所以 macvlan 更适合少量固定 IP 的特殊场景比如让某个容器直连物理网络里的数据库设备。ipvlan 和 macvlan 类似但所有容器共享宿主机网卡的 MAC 地址只通过 IP 区分。它在这块场景里的使用率不算高一般情况我建议优先考虑 macvlan除非你明确遇到 MAC 地址冲突或交换机限制的问题。整个选型逻辑可以压成一张表网络模式工作机制是否跨主机IP分配与隔离典型场景性能与安全取舍bridge宿主机内虚拟网桥否每个容器独立 IP隔离性好单机多容器、端口映射有 NAT 损耗但隔离和安全最好host共享宿主机网络栈否无独立 IP直接用宿主机端口性能敏感、抓包、排查宿主机网络性能最好完全没有网络隔离none仅 loopback否无网络接口离线任务、自定义网络无网络最安全overlay宿主机间 VXLAN 隧道是每个容器有虚拟 IP跨节点可见Swarm/K8s 集群、服务间通信有封装开销具备较好隔离和加密能力macvlan容器直连物理网络视物理网络容器获得物理网络 IP特殊设备直连、局域网广播场景低损耗但受交换机限制安全边界弱ipvlan共享宿主机 MAC用 IP 区分视物理网络无独立 MAC独立 IP受 MAC 表限制的物理网络场景低损耗兼容性依赖驱动支持选型这件事我给的建议很直接单机 80% 的情况用自定义 bridge 而不是默认 bridge多机生产环境老老实实上 overlay只有遇到容器要像物理设备一样被直接访问这种硬需求才去碰 macvlan。2. bridge 网络底层拆解veth pair、docker0 与 iptables 的三角关系很多人用 bridge 模式用了很久却说不清数据到底怎么从容器里出去的。这也不怪你Docker 把太多细节藏起来了。但如果你不想在网络出问题时两眼一抹黑这部分值得耐心看一遍。2.1 一个数据包从容器到外网要经过哪些环节假设宿主机是一台 Linux 机器容器 A 的 IP 是 172.17.0.2现在它要访问外网的 8.8.8.8。数据包的旅程大致是这样容器 A 查自己的路由表发现目标 IP 不在本机网段内默认走 eth0 送出。容器 A 的 eth0 其实是一个 veth 虚拟网卡的一端另一端连在宿主机上的 docker0 网桥上。这个 veth pair 就像一根虚拟网线一头插容器一头插交换机。数据包到达 docker0 后宿主机内核查看主路由表发现默认路由指向物理网卡于是包被转发到物理网络。如果这个包从容器发出且目标地址是外网docker0 会通过一条 MASQUERADE 规则做源地址转换把容器 IP 换成宿主机的物理 IP之后再真正送出去。你可以用iptables -t nat -nvL POSTROUTING看看这条规则会看到链里有一条指向172.17.0.0/16的 MASQUERADE。这就是容器能出外网的根本原因。没有这条规则容器即使配置了 IP出了宿主机也没法回包。如果你在自己电脑上的 Linux 环境里跟着验证把容器启动起来后在宿主机执行bridge link能看到容器 eth0 和 veth 接口是一对一对出现的。这种容器网卡-虚拟网线-网桥的结构是整个单机容器网络的基石。2.2 端口映射不是简单参数DNAT 规则浅析docker run -p 8080:80这行命令很多人理解成Docker 帮忙把流量导进去。本质上它是在 iptables 的 NAT 表里加了条 DNAT 规则把宿主机 8080 端口收到的 TCP 流量目标地址改写为容器的 80 端口。你可以执行iptables -t nat -vnL DOCKER看一眼会发现类似下面的规则Chain DOCKER (2 references) target prot opt source destination DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80这和你手动在 Linux 上用 iptables 做端口转发没有本质区别只是 Docker 把它自动化了。理解这一点对排错很有用如果端口映射不生效别只盯着容器内部服务先查这条 DNAT 规则还在不在是不是被外部防火墙或你自己写的手动规则覆盖了。这里有个容易忽略的细节Docker 每次重启容器IP 都可能变化DNAT 规则里的目标地址也会跟着更新。如果你手动往 iptables 里塞过映射规则容器一重建就失效。所以在生产环境里我强烈建议配合自定义网络并给容器固定 IP规则的可控性会高很多。2.3 为什么生产环境我推荐自定义 bridge域名解析与服务发现Docker 默认的 bridge 网络其实功能很弱最让人难受的一点是容器之间不能用容器名互相解析只能靠 IP。默认 bridge 上跑多个服务时你只能通过环境变量传入对方 IP或者用外部服务发现组件这非常反直觉。自定义 bridge 解决的就是这个痛点。创建方法很简单docker network create -d bridge \ --subnet 10.10.0.0/24 \ --gateway 10.10.0.1 \ myapp-net然后启动容器时带上--network myapp-net。只要是同一个自定义 bridge 网络里的容器Docker 内置 DNS 会自动把容器名解析成对应 IP。你可以在容器里直接ping app-server而不是去查它分到的 IP 是多少。这个能力在工作负载不变、容器频繁重建的场景下价值非常大因为你不用再把服务地址硬编码到配置文件里了。配套使用 docker compose 时网络定义会更直观。一个典型的配置大概长这样services: app: image: your-app-image networks: - frontend db: image: your-db-image networks: - frontend networks: frontend: driver: bridge这样app服务代码里只要把数据库地址写成db:5432就能连通不用关心数据库容器被调度到哪个 IP、重建过多少次。这里有个坑不同自定义网络之间的容器默认是不能直接互通的。如果你有跨网络通信的需求需要额外做网关路由比如让一个容器同时挂两个网络做转发或者提前规划清楚哪些服务需要放在同一个网络里避免后期为网络连通性打补丁。3. overlay 网络为什么能跨主机互通VXLAN 数据面与 gossip 控制面到多宿主机这一步bridge 就不够用了。你不可能在一台宿主机上创建一个网桥让另一台机器上的容器也连进来。overlay 网络解决的就是这个问题而理解它关键在于弄清楚控制面和数据面这两个概念。3.1 数据面与控制面分离VXLAN 隧道和 gossip 协议先抛结论overlay 网络里的容器 IP 是虚拟的这些虚拟 IP 不会出现在物理网络上。容器 A 和容器 B 之间通信时数据包要经历一次封装和解封的过程。数据面用的是 VXLAN 技术。容器 A 发出的原始数据包会被宿主机内核封装成外层 UDP 包外层源 IP 是宿主机 A 的物理 IP外层目的 IP 是宿主机 B 的物理 IP目的 UDP 端口默认是 4789。内层的容器 IP 和端口仍然保留。这就像你有一个快递要寄给隔壁城市的朋友但快递单上写的是中转站的地址由中转站再转发到最终收件人。外层的封装就是中转站的标签。控制面解决的是宿主机之间怎么知道对方存在、网络配置如何同步。Docker 这里用的是 gossip 协议节点之间通过 UDP 端口 7946 互相交换集群状态。它不需要所有节点两两直连只要每个节点知道一部分节点信息状态就能通过聊天逐步传播到整个集群。这个设计和 Redis Cluster 的 gossip 机制思路类似优点是节点数量多时也不会产生集中式的通信瓶颈缺点是状态传播有轻微延迟。每个 overlay 网络在宿主机上都会创建一个独立的 network namespace里面包含一个虚拟网桥和一个 VXLAN 隧道接口。你可以把它理解成每台宿主机都有一台虚拟交换机多台虚拟交换机之间用 VXLAN 隧道连接最终拼出一张横跨多台物理机的二层网络。3.2 从零搭建一个跨主机的 overlay 网络overlay 网络的创建前提是节点已经加入了 Docker Swarm 集群或者你在 Kubernetes 环境里由 CNI 插件替你完成。我以 Swarm 为例说明整个操作链路# 第一台机器做 manager 节点 docker swarm init --advertise-addr 192.168.1.10 # 其他机器加入集群 docker swarm join --token token 192.168.1.10:2377 # 创建 overlay 网络并把非 service 的普通容器也允许挂进来 docker network create -d overlay --attachable my-overlay创建完网络后在这个网络里启动两个容器即便它们分散在两台宿主机上也能用容器名直接互通。Swarm 内置的服务发现会自动完成解析不需要你配置额外的 DNS 或注册中心。有一个参数特别值得注意--attachable。如果不加这个 overlay 网络只能被 Swarm service 使用普通docker run容器是挂不进去的。我当初就在这里卡过一次创建完 overlay 后用普通容器测试连通结果一直报网络找不到后来才想起这个参数。默认创建的 overlay 网络控制面是加密的但数据面不加密。如果你的业务对数据安全敏感比如跨机房传输账务数据之类的创建时建议带上--encryptedDocker 会在 VXLAN 之上启用 IPsec 加密。代价是会增加些 CPU 开销但多了一层保障值得。3.3 overlay 网络的验证手段与性能损耗创建完网络不要急着说通了。我习惯用几个命令做完整验证# 在管理器上查看网络详情确认节点和容器都挂上来了 docker network inspect my-overlay # 在容器 A 里 ping 容器 B 的容器名 docker exec -it a-container ping b-container # 在宿主机 A 上抓包确认 VXLAN 封装 tcpdump -i eth0 udp port 4789 -n如果 ping 通了但抓包看不到 UDP 4789 流量也别慌可能是因为两个容器恰好被调度到了同一台宿主机。同机通信时Docker 会直接走本地网络栈不走隧道。这个行为容易让初学 overlay 的人产生困惑以为网络没打通。性能方面overlay 相比 bridge 多了一次 VXLAN 封装和解封装会带来一定延迟增量。如果只是普通业务接口调用体感不明显但对延迟极其敏感的服务比如高频交易或者实时音视频建议评估是否应该把这类服务直接放在宿主机网络或者通过 macvlan 直连物理网络。我见过不少团队把数据库放进 overlay结果性能测试时发现延迟不可接受最后又退回 host 模式的例子。选型时需要想清楚你要的是统一网络架构的便利还是极致的延迟表现。另外跨主机的 overlay 对防火墙策略有硬性要求节点之间的 TCP 7946 端口gossip 控制面、UDP 7946 端口gossip 数据面和 UDP 4789 端口VXLAN 数据面必须放通。很多跨主机失联的问题最后排查下来都是安全组或防火墙没有开这三个端口。4. 网络排错手记从ping 不通到端口映射失效的完整排查链路网络问题最折磨人的地方在于表面现象一样根因可能天差地别。同一个ping 不通可能是容器网络配置问题也可能是宿主机防火墙拦了还可能是跨主机隧道没建起来。下面这些排查路径是我在实际项目里反复用过很多次的按顺序走基本能覆盖大多数情况。4.1 容器之间 ping 不通第一现场的三层判断容器 A 和容器 B 都在同一台宿主机上但 A ping 不通 B。我一般按三个层次排查先确认两个容器是不是在同一个网络里。执行docker inspect 容器名 | grep -i network或者直接看docker network inspect。不在同一网络不通是正常的别在错误的方向上浪费时间。确认 IP 是否正常分配。进入容器执行ip addr如果 eth0 没有 IP多半是网络创建时出了问题重建容器或网络往往能解决。检查宿主机路由和 IP 转发。如果网段配置了容器却不通宿主机执行sysctl net.ipv4.ip_forward看结果是不是1。Linux 默认可能没开 IP 转发Docker 一般会帮你打开但某些精简系统或手工配置过内核参数的机器上这个值可能被改回去导致网桥转发失败。还有一种隐蔽场景容器能 ping 通 IP但解析不了容器名。这说明网络不是内嵌 DNS 的默认 bridge而是自定义配置问题。此时优先检查容器是否在同一自定义网络以及 Docker 内嵌 DNS 的地址 127.0.0.11 是否被覆盖掉了。如果你在容器的/etc/resolv.conf里手动指定了外部 DNSDocker 的内嵌 DNS 解析链路会被跳过服务名解析自然失效。4.2 端口映射失效规则被冲掉是最常见的坑最典型的场景是容器启动了docker ps里也显示0.0.0.0:8080-80/tcp但浏览器访问宿主机 IP 的 8080 端口就是不通。我的排查链路是这样# 第一步确认容器内服务确实在监听 80 docker exec -it 容器名 ss -tunlp | grep 80 # 第二步在宿主机上验证 DNAT 是否落到容器 IP iptables -t nat -nL DOCKER # 第三步确认宿主机防火墙状态 firewall-cmd --list-all很多情况下服务没问题DNAT 规则也没问题问题出在宿主机的 firewalld 或 ufw 默认拒绝了外部流量。Docker 在操纵自己的 DOCKER 链时不会替你把 firewalld 里的富规则也写进去。你需要在防火墙里显式放行对应端口。另一个常见坑是你手动往 iptables 的 FORWARD 链里加过一些安全过滤规则把来自外部网卡的转发流量直接 DROP 了。Docker 默认会维护DOCKER-USER链专门用于放你自定义的规则但如果你把规则加到 FORWARD 链且顺序不对就可能误伤容器端口映射。建议凡是需要自定义阻断的策略统一放到DOCKER-USER链里这个链是 Docker 设计的用户自定义入口即使 Docker 重启你的规则也不会被覆盖。4.3 跨主机失联隧道接口和路由表的联合排查overlay 网络下跨宿主机的容器之间通信突然失败我会优先做三道检查检查宿主机之间本身能不能通。直接用ping和nc -vz 对方IP 4789验证。如果物理网络都不通后面全是白搭。检查三个关键端口是否可访问TCP 7946、UDP 7946、UDP 4789。有些云安全组默认不放行 4789而 VXLAN 封装放不出来容器通信就表现为时通时不通甚至完全不通。在宿主机上用tcpdump -i eth0 udp port 4789抓包。如果只有单侧有流量说明隧道通信已经建立但回包被防火墙或安全策略吃掉如果根本没有封装包说明网络配置没传过来重新确认节点是否都在 Swarm 集群里。这里有个容易忽略的细节MTU。如果宿主机物理网络的 MTU 小于 1500而 overlay 默认按 1450 计算一旦链路里某个中间设备强制把 MTU 调低VXLAN 封装后的包就可能被丢弃。遇到小包通、大包不通的现象优先检查 MTU。比如容器里能 ping 通但rsync或scp大文件卡住多半就是这个原因。4.4 排错工具清单工具不在多关键是每个工具解决哪类问题要心里有数工具用途典型命令docker network查看网络列表与详情docker network ls、docker network inspectip addr / ip route检查容器和宿主机 IP、路由表ip addr、ip routebridge link查看 veth pair 与网桥的挂载关系bridge linkiptables查看 NAT 和过滤规则iptables -t nat -vnL、iptables -nL FORWARDtcpdump抓包定位数据是否到达预期节点tcpdump -i eth0 port 8080 -nnc测试 TCP/UDP 端口连通性nc -vz IP 端口ss查看容器内或宿主机上的监听端口ss -tunlpsysctl检查内核转发等网络参数sysctl net.ipv4.ip_forward这些工具的合理组合能处理 90% 以上的容器网络问题。我从实践中得到的体会是不要迷信某一个命令的输出而是要在宿主机和容器两端分别取样对比差异。比如容器里ip route有默认路由宿主机上却没有对应转发规则那问题多半就在宿主机网络栈如果两边路由都正常就去抓包看数据到底卡在哪一跳。最后分享一个我一直在用的习惯拿到任何网络异常先在纸上画一条数据路径标出容器网卡、veth、网桥、宿主机的 iptables、物理网卡、外层隧道这几个必经点然后用 tcpdump 在关键节点抓包一步步缩小范围。Docker 把很多细节藏起来了但只要记住各种网络模式本质上都是内核里网络命名空间、网桥和路由规则的组合排错思路就不会乱。