Docker Registry Push 超时排查全记录:从网络栈到残留 veth 的真相

📅 2026/7/9 21:38:50 👁️ 阅读次数 📝 编程学习
Docker Registry Push 超时排查全记录:从网络栈到残留 veth 的真相

摘要:
在私有化部署 Docker Registry 时,遇到宿主机 curl 容器映射端口超时的诡异问题。经历 iptables、rp_filter、bindv6only、抓包等深入排查后,最终发现是 Docker 卸载残留的 veth 接口扰乱了内核包转发路径。本文完整记录排错过程与根因,供同行参考。


一、问题现象

在宿主机(192.168.0.146)上使用docker run -d -p 5555:5000 registry:3启动官方 Registry 容器后,发现:

  • 容器内wget 127.0.0.1:5000/v2/正常返回200{}

  • 宿主机执行curl 127.0.0.1:5555/v2/超时Connection timed out

  • 宿主机执行curl 172.17.0.2:5000/v2/同样超时

  • 但宿主机ping 172.17.0.2却能通,且延迟极低。

二、环境信息

  • OS: CentOS Stream 8 / 9

  • Docker: 24.x

  • Registry 镜像:registry:3

  • 启动命令:docker run -d --name ags-registry -p 5555:5000 -v /data/...:/var/lib/registry registry:3

三、第一阶段:排查 iptables 与 Docker 代理

  1. 检查端口监听
    ss -tlnp | grep 5555显示docker-proxy正在监听所有网络接口的 5555 端口,状态正常。

  2. 检查 NAT 规则
    iptables -t nat -L -v中发现DNAT规则将访问宿主机 5555 端口的数据包转发到172.17.0.2:5000,看起来没有问题。

  3. 验证 docker-proxy 是否工作
    curl 127.0.0.1:5555192.168.0.146:5555都超时,说明流量进入了代理但未从容器返回。

  4. 排查 filter 表
    FORWARD链默认ACCEPTDOCKER链中有明确允许到容器 5000 端口的规则,未被拦截。

初步结论:流量成功送达容器,但应用层未响应。

四、第二阶段:怀疑内核网络参数

  1. rp_filter 反向路径过滤
    rp_filter为 1,可能导致容器回包被丢弃。临时关闭rp_filter后问题依旧,排除此项。

  2. bridge-nf-call-iptables
    该参数为 1,但关闭后依然超时,且 ping 通 TCP 不通,排除。

  3. conntrack 表
    连接跟踪表占用极低,无table full日志,排除。

五、第三阶段:深入应用层与 IPv6 陷阱

  1. 确认容器内服务正常
    docker exec进容器用wget 127.0.0.1:5000/v2/成功,证明 Registry 进程正常。

  2. 跨容器测试
    docker run --rm --network bridge busybox wget http://172.17.0.2:5000/v2/同样超时!这说明问题不是宿主机独有,而是任何非容器本身的访问都失败。

  3. 检查 Registry 监听地址
    日志中出现listening on [::]:5000,表明 Registry 默认绑定到了 IPv6 通配符地址。虽然容器内net.ipv6.bindv6only=0,但实际测试发现,来自桥接网络的 IPv4 流量虽然能完成 TCP 握手(抓包可见),但 HTTP 响应包永远不会发出。

  4. 使用 Host 网络模式验证
    改用--network host并设置REGISTRY_HTTP_ADDR=0.0.0.0:5555后,curl 127.0.0.1:5555/v2/立刻成功。由此基本确认:Bridge 网络下 IPv4 到 IPv6 监听套接字的映射存在缺陷,导致应用层无声丢弃连接。

六、转折点:发现残留 veth 接口

尽管 Host 模式解决了问题,但我们注意到ip neigh show中仍有旧容器的 IP172.17.0.2和 MAC 地址02:42:ac:11:00:02。进一步检查/sys/class/net/docker0/brif/发现残留的 veth 接口vethfa82f9f,且没有对应的容器进程。

该残留 veth 曾在之前的 Docker 卸载/重装中未被清理,其 MAC 地址恰好与新创建的 Registry 容器的 IP 相同。当新容器连接到 docker0 时,内核 ARP 缓存可能将流量错误地导向这个僵尸 veth,导致正常数据包被丢弃,而 ICMP 响应由内核直接处理不受影响,从而解释了“ping 通 TCP 不通”的现象。

七、最终修复

  1. 清理残留 veth 接口

    bash

    ip link del vethfa82f9f ip neigh flush dev docker0
  2. 彻底重启 Docker(推荐)或重新创建容器

    bash

    systemctl restart docker
  3. 强制 Registry 监听 IPv4 地址
    重新创建容器时添加环境变量:

    bash

    docker run -d --name ags-registry \ -p 5555:5000 \ -e REGISTRY_HTTP_ADDR=0.0.0.0:5000 \ registry:3
  4. 验证

    bash

    curl -v http://127.0.0.1:5555/v2/ # 200 OK docker push hub.ags.local:5555/myimage # 成功

八、总结

现象真正原因
ping 通但 TCP 超时残留 veth 导致非 ICMP 流量被导向无效端点
容器内访问正常容器内走 lo 接口,不受 veth 干扰
Host 网络下正常绕过了 docker0 桥接,避免残留接口影响
Registry 日志显式 IPv6双栈绑定在纯净网络下无问题,但与残留 veth 共存时触发内核 bug

经验教训

  • 卸载 Docker 时应使用yum remove并手动清理/var/lib/docker/run/docker,必要时重启。

  • 遇到类似“网络半通”故障时,检查桥接接口下的 veth 残留往往比反复调整 iptables 更高效。

  • 容器化服务应显式绑定0.0.0.0而非依赖通配符,避免 IPv6 兼容性踩坑