容器网络不通怎么调试?veth 对与网桥的完整排查路径

发布时间:2026/7/24 13:42:02
容器网络不通怎么调试?veth 对与网桥的完整排查路径 容器网络不通怎么调试veth 对与网桥的完整排查路径实验环境Ubuntu 24.04内核 6.8.0-106-genericDocker 29.1.3。本文所有改动均在 docker0 / veth / 自建 netns / iptables 的 DOCKER 相关链范围内且每种故障都做了恢复。容器跑起来了但curl不通——这是容器网络最高频的排障场景。网上很多文章贴一张「容器→veth→docker0→iptables→eth0」的图就结束了但图谁都会画真出问题你怎么定位断点在哪一段本文用一个 nginx 容器先验证数据路径再人为制造 3 种典型故障每一种都按「现象 → 抓包/命令定位 → 修复」完整走一遍最后给出一份从容器到宿主机出口的排查 checklist。0. 实验准备跑一个带端口映射的 nginxdockerrun-d--nameweb-p8080:80 nginx:alpinecurl-s-o/dev/null-wHTTP%{http_code}\nhttp://127.0.0.1:8080/HTTP200# 基线通重要前提Docker 主机必须开启 IP 转发。本文实测中发现若net.ipv4.ip_forward0容器完全无法访问外网egress 全丢而 ingress 经 DNAT 反而能通——这个坑我们放在第 6 节专门讲。生产 Docker 主机应确保其值为 1。1. 数据路径先验证别只信图容器 bridge 网络的完整数据路径以「外部客户端访问容器发布端口 8080」为例外部客户端 │ SYN dst宿主IP:8080 ▼ [eth0] 宿主机物理网卡 │ PREROUTING(nat 表 DOCKER 链) 做 DNAT: dst → 172.17.0.2:80 ▼ [docker0] Linux 网桥网关 172.17.0.1 │ 二层转发到对应端口 ▼ [vethXXX] 宿主机侧 veth网桥端口 │ veth pair 另一端 ▼ [eth0容器] 容器内的网卡 │ ▼ 容器进程(nginx :80)回程方向对称且在 POSTROUTING 链做 MASQUERADESNAT把源 172.17.0.2 改成宿主 IP 再出 eth0。1.1 找 veth 配对容器内 iflink ↔ 宿主机 ip link容器的eth0和宿主机的vethXXX是一对 veth pair。配对方法是容器内读eth0的iflink序号再在宿主机ip link里找相同 index 的接口。# 容器内 eth0 的 iflink$dockerexecwebcat/sys/class/net/eth0/iflink18# 宿主机 ip linkindex18 的就是它$ip-olinkshow|grep-E^18:18: vethc2a937dif2:...UP...master docker0... link-netnsid0# bridge link 确认它挂在 docker0 上$ bridgelink18: vethc2a937deth0:...UP...master docker0 state forwarding...容器网络信息来自nsenter进容器 netns$PID$(dockerinspect-f{{.State.Pid}}web)$ nsenter-t$PID-nip-oaddr show eth02: eth0 inet172.17.0.2/16... $ nsenter-t$PID-niproute show default via172.17.0.1 dev eth0172.17.0.0/16 dev eth0 proto kernel scopelinksrc172.17.0.2结论容器eth0(iflink18)↔宿主 vethc2a937d(index 18)master 是docker0容器 IP172.17.0.2网关172.17.0.1。1.2 抓包验证 DNATingress 方向宿主机curl http://127.0.0.1:8080同时在docker0和veth上抓包# 请求目标端口 80看 DNAT 后的目标地址00:23:01.019864 IP172.17.0.1.50072172.17.0.2.80: Flags[S],... 00:23:01.019871 IP172.17.0.1.50072172.17.0.2.80: Flags[S],...# veth 侧一致注意目的地址已经是172.17.0.2:80——这就是 DNAT 生效的铁证8080→80 且目的 IP 改写为容器 IP。1.3 抓包验证 MASQUERADEegress 方向从容器内ping 8.8.8.8三层同时抓# veth容器内出向源还是 172.17.0.2IP172.17.0.28.8.8.8: ICMPechorequest# docker0源还是 172.17.0.2IP172.17.0.28.8.8.8: ICMPechorequest# eth0SNAT 之后源变成宿主 IPIP192.168.0.2308.8.8.8: ICMPechorequest IP8.8.8.8192.168.0.230: ICMPechoreplyeth0 上源地址从172.17.0.2变成192.168.0.230这就是 POSTROUTING 链MASQUERADE的作用。对应的 iptables 规则$ iptables-tnat-LPOSTROUTING-nChain POSTROUTING(policy ACCEPT)target prot optsourcedestination MASQUERADE0--172.17.0.0/160.0.0.0/0 $ iptables-tnat-LDOCKER-nChain DOCKER(2references)target prot optsourcedestination DNAT6--0.0.0.0/00.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80踩坑提醒curl http://宿主自己的IP:8080这种「访问本机 IP」的流量内核会本地交付根本不会出现在eth0的抓包里它走的是 OUTPUT/PREROUTING 的 DNAT直接进 docker0。想看 eth0 上的 ingress必须由外部客户端发起。本文 egress 抓包已证明 eth0 在「出向」确实参与。2. tcpdump 分段抓包定位法核心思路在链路的每一段都抓对比哪一段「有包进、没包出」断点就在那里。抓包点命令能看到什么容器内 eth0docker exec web tcpdump -i eth0 ...容器内无 tcpdump 时改用nsenter容器收发的原始包宿主 vethtcpdump -i vethc2a937d ...进/出容器的包bridge 端口docker0tcpdump -i docker0 ...桥上的转发包DNAT 之后eth0tcpdump -i eth0 ...宿主对外收发包SNAT 之后实践中常抓veth和docker0就够了两者内容通常一致veth 是桥的端口若 docker0 有 SYN 但 veth 没有 → 桥到 veth 这一段断了如 veth down若 veth 有入向 SYN 但无回包 → 容器内部路由/进程问题。3. 故障①veth 端口被 down制造故障$iplinksetvethc2a937d down $iplinkshow vethc2a937d|grep-ostate [A-Z]*state DOWN $curl-s-m3-o/dev/null-wcurl%{http_code}\nhttp://127.0.0.1:8080/curl000# 连接超时/失败定位ip link show直接看到 veth 是DOWN。同时抓包docker0 和 veth都抓不到任何包——因为网桥发现唯一端口 down直接丢弃包根本不会「发出去」所以设备层抓不到。这是一个「断在二层链路状态」的典型不看ip link光抓包反而容易误判。修复$iplinksetvethc2a937d up $curl-s-m3-o/dev/null-wcurl%{http_code}\nhttp://127.0.0.1:8080/curl2004. 故障②容器默认路由被删场景有人或某个初始化脚本在容器 netns 里ip route del default容器从此「能 ping 网关但上不了外网」。制造故障用nsenter进容器 netns 操作不动宿主机$PID$(dockerinspect-f{{.State.Pid}}web)$ nsenter-t$PID-niproute del default $ nsenter-t$PID-niproute show172.17.0.0/16 dev eth0 proto kernel scopelinksrc172.17.0.2# 只剩直连路由# 容器内访问外网 - 失败$dockerexecwebping-c2-W28.8.8.8 PING8.8.8.8(8.8.8.8):56data bytes ping: sendto: Network unreachable# 但同一子网的网关仍可达关键细节$dockerexecwebping-c1-W2172.17.0.1 round-trip min/avg/max0.061/0.061/0.061 ms定位ip route show里没有defaultping 8.8.8.8报Network unreachable而ping 172.17.0.1网关在 /16 直连内正常。抓veth出向 ICMP 包数为0——因为根本没有路由可走包在容器 IP 栈就被丢弃没机会出 veth。为什么 ingress 从网关进来时没断前面 Blog 1 提过网关172.17.0.1属于容器直连的172.17.0.0/16不需要默认路由即可回包。所以「删默认路由」只影响跨子网/外网通信容器访问外部 API、以及外部客户端访问时容器需要回包到非同子网站点都会断。排查时务必分清「同子网」和「跨子网」。修复$ nsenter-t$PID-niprouteadddefault via172.17.0.1 $dockerexecwebping-c2-W28.8.8.8|tail-1round-trip min/avg/max235.840/236.272/236.705 ms# 恢复5. 故障③DOCKER 链 DNAT 规则被删端口映射失效场景端口映射突然失效通常是 iptables 规则被清理/重置或有人手动改了 DOCKER 链。前提要复现「删 DNAT 就断」必须关闭 userland-proxy默认开启时 docker-proxy 进程会在用户态兜底监听 8080删了 DNAT 也照样通。生产/K8s 通常设userland-proxy: false本文实验机已如此配置/etc/docker/daemon.json加userland-proxy:false后systemctl restart docker。制造故障$ iptables-tnat-SDOCKER|grep8080-ADOCKER-ptcp-mtcp--dport8080-jDNAT --to-destination172.17.0.2:80 $ iptables-tnat-DDOCKER-ptcp-mtcp--dport8080-jDNAT --to-destination172.17.0.2:80 $ iptables-tnat-SDOCKER|grep8080||echo(已无 8080 规则)$curl-s-m3-o/dev/null-wcurl(127.0.0.1)%{http_code}\nhttp://127.0.0.1:8080/ curl(127.0.0.1)000# Connection refused —— 没有任何进程/规则接这个 8080 了$curl-s-m3-o/dev/null-wcurl(192.168.0.230)%{http_code}\nhttp://192.168.0.230:8080/ curl(192.168.0.230)000定位iptables -t nat -L DOCKER -n里 8080 的 DNAT 规则已经消失。此时 SYN 到达宿主后无可转发的目标DNAT 没了本地 8080 也无监听直接Connection refused。在 eth0 抓包会看到 SYN 的目的地址始终是宿主 IP:8080、未被改写——印证了 NAT 阶段已经没有规则接手。修复把规则加回去或docker restart web让 Docker 自动重建$ iptables-tnat-ADOCKER-ptcp-mtcp--dport8080-jDNAT --to-destination172.17.0.2:80 $curl-s-m3-o/dev/null-wcurl(127.0.0.1)%{http_code}\nhttp://127.0.0.1:8080/ curl(127.0.0.1)200⚠️安全约束本文只删/加了与容器网段相关的 DOCKER 链 DNAT 规则并在实验后恢复严禁改动 PREROUTING/OUTPUT 的基础结构或宿主自身规则否则会失联。6. 隐藏故障net.ipv4.ip_forward0 让容器「半残」这是个容易被忽略的全局开关。本文做 Blog 1 实验时把它恢复成了 0结果容器出现诡异现象ingress 正常外部/本机访问容器发布端口 8080 → 走 DNAT → 容器能收到因为目的就是容器 IP属「输入转发」到 docker0。egress 全死容器ping 8.8.8.8/ 访问外网 → 包到 docker0 后需要转发到 eth0但ip_forward0禁止转发 → 包被丢外接全部超时。验证$cat/proc/sys/net/ipv4/ip_forward0# 容器 ping 8.8.8.8 - 100% lossveth/docker0 看得到出向包eth0 一个都没有$sysctl-wqnet.ipv4.ip_forward1# Docker 主机必须1# 容器立刻恢复外访排障时若发现「能进不能出」或「容器上不了网但端口映射正常」先cat /proc/sys/net/ipv4/ip_forward。7. 排查 checklist从容器到宿主出口一步步来容器内进程是否在监听docker exec c sh -c ss -ltnp | grep :80或netstat。没监听 → 应用没起来。容器内 IP/路由对不对nsenter -t $PID -n ip addr/ip route。确认有 IP、有default via 172.17.0.1。veth 链路状态宿主机ip link show vethXXX看是不是UPbridge link看是不是forwarding。DOWN →ip link set up故障①。容器能否同子网通信docker exec c ping 172.17.0.1。通 → 二层/网桥 OK不通 → veth 或 docker0 问题。容器能否跨子网/外网docker exec c ping 8.8.8.8。Network unreachable→ 缺默认路由故障②能出 ICMP 但业务不通 → 应用层。ip_forward 开了吗cat /proc/sys/net/ipv4/ip_forward应为 1故障⑥。端口映射DNAT在吗iptables -t nat -L DOCKER -n看有没有对应dpt:端口 to:容器IP:容器端口。没有 → 重插或docker restart故障③。注意 userland-proxy 是否兜底。抓包分段定位在veth/docker0/eth0分别tcpdump对比「哪段有进无出」。docker0 有 SYN 而 veth 无 → 桥到 veth 断veth 有入向 SYN 无回包 → 容器路由/进程问题eth0 有包则问题在外部。最后看 Docker 网络驱动/容器状态docker network inspect bridge、docker inspect c的 Networks 段确认 SandboxKey、IP、Gateway 正确。8. 小结veth 配对靠「容器内eth0的iflink↔ 宿主机ip link同 index」再bridge link确认挂在哪座桥上。数据路径容器eth0 → veth → docker0 → iptables NAT → eth0。DNAT 在 ingress 改写目的 IP:端口抓 docker0 可见172.17.0.2:80MASQUERADE 在 egress 改写源 IP抓 eth0 可见192.168.0.230。三种典型故障(①)veth down → 看ip link(②)缺默认路由 → 跨子网断、同子网不断看容器ip route(③)DNAT 规则丢失 → 端口映射失效看iptables -t nat -L DOCKER。别忘了ip_forward1这个隐藏前提以及「访问本机 IP 不走 eth0」的抓包陷阱。下一篇《容器网络延时与乱序包veth 的开销到底有多大》—— 我们用 ping / iperf3 量化 veth 引入的延迟与吞吐损耗并解释 softirq、RPS/RFS 与乱序包。