Docker 拉取失败排查记录
创建日期:2026-09-16 | 最近更新:2026-09-16
环境:Ubuntu(kernel 6.8.0-138)· Docker 29.5.3 · mihomo-party(Clash Party) 主机:LAN
192.168.1.50,网关192.168.1.1,ufw 运行中 排查日期:2026-09-15
一、现象
$ docker pull qdrant/qdrant:latest
Error response from daemon: failed to resolve reference "docker.io/qdrant/qdrant:latest":
failed to do request: Head "https://registry-1.docker.io/v2/qdrant/qdrant/manifests/latest":
dial tcp [2a03:2880:f107:83:face:b00c:0:25de]:443: i/o timeout
关键线索:报错里的地址 2a03:2880::/32 是 Facebook 的 IPv6 段。Docker Hub 不可能解析到这里 —— 这是典型的 DNS 投毒,不是 IPv6 配置问题。
二、根因
三层问题叠加,缺一不可。前两层解决后症状会变化,很容易误判为"没修好"。
第 1 层:dockerd 不继承 shell 代理
dockerd 由 systemd 以 root 启动,systemd 给服务的是一份干净环境,不读 ~/.zshrc,也不继承终端里 export 的变量。所以 shell 里 curl 能走代理,docker pull 却不行。
这不是权限问题。 用 root 跑
docker pull一样失败,因为 root 的 shell 环境同样不会传给 daemon。Mac 上能用是因为 Docker Desktop 是 GUI 程序,会主动读系统代理并注入到 VM 里的 daemon。
修复:/etc/systemd/system/docker.service.d/http-proxy.conf
[Service]
Environment="HTTP_PROXY=http://127.0.0.1:7890"
Environment="HTTPS_PROXY=http://127.0.0.1:7890"
Environment="NO_PROXY=localhost,127.0.0.1,::1,192.168.0.0/16,10.0.0.0/8,172.16.0.0/12"
sudo systemctl daemon-reload
sudo systemctl restart docker
两个坑:
ALL_PROXY无效 —— Go 的http.ProxyFromEnvironment只读HTTP_PROXY/HTTPS_PROXY/NO_PROXY,ALL_PROXY被直接忽略。- 协议必须写
http://,不能写socks://——socks://是 SOCKS4 语义,DNS 在本地解析,照样拿到投毒地址;http://走 HTTP CONNECT,把域名原样交给代理远端解析,才能绕过污染。非要用 SOCKS 得写socks5h://(带h= remote DNS)。
第 2 层:明文 UDP DNS 被投毒
| DNS 服务器 | registry-1.docker.io | www.google.com |
|---|---|---|
223.5.5.5(阿里,明文 UDP) | 31.13.83.2 ← Facebook 段 | 31.13.92.37 ← Facebook 段 |
119.29.29.29(DNSPod,明文 UDP) | 199.96.58.177 ← Twitter 段 | 185.45.5.35 ← 投毒 |
doh.pub(DoH / HTTPS) | ✅ 正确 AWS 地址 | ✅ 正确 |
dns.alidns.com(DoH) | ❌ 无应答 | ❌ 无应答 |
明文 53 端口的应答被中间人伪造。DoH 是干净的,doh.pub 可用,dns.alidns.com 实测完全无应答。
投毒返回的假地址是随机的 —— 有时落在国内 IP 段,有时落在国外段。这一点是第 3 层的直接诱因。
第 3 层:GEOIP,CN 放大危害(真正的坑)
订阅的兜底规则是:
- GEOIP,CN,DIRECT # 命中即直连
- MATCH,悠兔 # 兜底走代理
因为投毒地址随机,路由结果也跟着随机:
| 投毒返回 | 匹配规则 | 结果 |
|---|---|---|
国内 IP(如 118.193.202.219) | GeoIP/cn | ✗ 直连垃圾地址 → 超时 |
国外 IP(如 199.96.61.1) | Match | ✅ 走代理,域名交给节点远端解析 → 成功 |
表现为「同一个 docker pull 三次里成功一次」,极难复现和定位。真实日志:
# 失败
[TCP] dial DIRECT (match GeoIP/cn) --> registry-1.docker.io:443
error: connect failed: dial tcp 118.193.202.219:443: i/o timeout
# 成功
[TCP] --> registry-1.docker.io:443 match Match using 悠兔[专线2.5x-香港1]
三、排查过程中被证伪的假设
记录下来,避免下次重走:
| 假设 | 证伪方式 |
|---|---|
| 「IPv6 解析问题」 | 是 DNS 投毒;IPv4 同样是假地址(118.193.202.219) |
| 「用户权限问题导致走不了系统代理」 | 是 systemd 环境继承问题;root 同样失败,权限只影响「改配置要 sudo」 |
| 「切全局模式就能绕过」 | ① GLOBAL 组默认选中 DIRECT,等于没开代理 ② DNS 仍被投毒,daemon 依然解析出假地址 |
「default-nameserver: tls://223.5.5.5 的 853 端口不通」 | 实测 853 端口可达,且 mihomo 能正确解析自己的 DoH 服务器域名 |
「/etc/hosts 能修好」 | 无效。dockerd 走 HTTP CONNECT 时把域名交给代理,本地 hosts 根本不参与解析 |
四、最终解决方案
在 mihomo-party 的覆写(Override)里加域名规则,让 docker 流量按域名匹配,不再依赖解析结果:
配置位置:~/.config/mihomo-party/override/<id>.yaml
+rules:
- DOMAIN-SUFFIX,docker.io,悠兔
- DOMAIN-SUFFIX,docker.com,悠兔
三种写法只有 +rules: 是对的:
| 写法 | 行为 | 是否可用 |
|---|---|---|
rules: | 覆盖整份规则表(893 条全没) | ❌ 代理直接废掉 |
+rules: | 前置插入,优先级高于 GEOIP,CN | ✅ |
rules+: | 追加到末尾,排在 GEOIP,CN 之后 | ❌ 等于没加 |
覆盖到的域名:
registry-1.docker.io—— manifestauth.docker.io—— 拉取 tokenproduction.cloudfront.docker.com—— layer blob(注意是 cloudfront 不是 cloudflare)
验证
运行时配置中规则位置正确(必须在 GEOIP,CN 之前):
697: - DOMAIN-SUFFIX,docker.io,悠兔
698: - DOMAIN-SUFFIX,docker.com,悠兔
894: - GEOIP,CN,DIRECT
日志应显示 DomainSuffix 匹配而非 GeoIP:
[TCP] --> registry-1.docker.io:443 match DomainSuffix(docker.io) using 悠兔[专线2.5x-香港3]
[TCP] --> auth.docker.io:443 match DomainSuffix(docker.io) using 悠兔[专线2.5x-香港3]
五、遗留问题
DNS 污染本身没有解决。 域名规则只让 docker 免疫,其他被墙域名若没有显式规则,仍会受同样的随机性影响。
根治方案是让 mihomo 的解析走可信路径:
dns:
nameserver:
- "https://doh.pub/dns-query"
- "https://1.1.1.1/dns-query#悠兔" # 经代理解析,绕开污染
nameserver-policy:
"+.docker.io": ["https://1.1.1.1/dns-query#悠兔"]
"+.docker.com": ["https://1.1.1.1/dns-query#悠兔"]
六、可复用的排查手法
-
先看报错里的 IP 属于谁。
face:b00c是 Facebook 的特征串,31.13.x/69.63.x/199.96.x/104.244.42.x也都是投毒常用的段。地址明显不对 → 直接怀疑 DNS 污染,别在应用层绕圈。 -
用 DoH 对照验证解析。 一条命令区分「真解析失败」和「被投毒」:
curl -s -H 'accept: application/dns-json' \"https://doh.pub/dns-query?name=<域名>&type=A" -
看代理内核的分流日志,别看应用报错。 应用层只能看到
EOF/timeout/TLS error,真正的路由决策在 mihomo 日志里(~/.config/mihomo-party/logs/core-*.log)。 -
mihomo-party 的控制器在 Unix socket 上,不在 TCP 端口。
config.yaml里external-controller是空的,实际由内核参数指定:/opt/clash-party/resources/sidecar/mihomo -d .../work -ext-ctl-unix /tmp/mihomo-party-<uid>-<pid>.sockSOCK=$(ls /tmp/mihomo-party-*.sock | head -1)curl -s --unix-socket "$SOCK" http://localhost/configs # 看模式curl -s --unix-socket "$SOCK" http://localhost/proxies/GLOBAL # 看代理组选中项::: warning
zsh 不对未加引号的变量做分词,别把
--unix-socket和 URL 塞进同一个变量,会报option ... is unknown。:::
-
"时好时坏" 几乎总是随机性来源。 DNS 投毒返回随机假地址、负载均衡、多 DNS 竞速都会造成这种模式。定位到随机源,问题就解了。
-
Docker 的端口发布绕过 ufw。
-p会在 iptables 的DOCKER链插规则,优先级在 ufw 之前,ufw deny无效。暴露服务的唯一可靠防线是应用自身的认证。
附:排查中用到的一次性操作
临时切换 mihomo 模式(测试用,非持久):
SOCK=$(ls /tmp/mihomo-party-*.sock | head -1)
curl -s -X PATCH --unix-socket "$SOCK" -H 'Content-Type: application/json' \
-d '{"mode":"global"}' http://localhost/configs
改 GLOBAL 组的选中节点(默认是 DIRECT,全局模式下等于没代理):
curl -s -X PUT --unix-socket "$SOCK" -H 'Content-Type: application/json' \
-d '{"name":"专线2.5x-香港1"}' http://localhost/proxies/GLOBAL