内网拉镜像太慢?crane 分片中转 + xray 代理双方案
摘要
家里的 Docker 主机(PVE 下的 LXC 容器 CT702)直连 Docker Hub 极慢,ghcr.io 甚至 DNS 都解析不出来,配了好几个国内镜像加速源还是拉不顺。这一篇把我实际走通的两套方案完整记录下来:方案一是用 crane 在本机把镜像拉成 tar 包,再经 VPS 分片中转、最后在目标机 docker load;方案二是给容器装上 xray 客户端走代理,让 Docker daemon 直接能连上官方 registry。两套都经过实测,前者救急、后者治本,附带 pct push 丢执行权限、no-proxy 漏网等几个坑。
目录
- 引言
- 一、症状盘点:家里拉镜像到底有多难
- 二、先理解:docker pull 到底在干什么
- 三、方案一:crane 拉包 + 分片中转 + docker load
- 四、拼包、校验与清理的完整流程
- 五、方案二:xray 客户端代理,一劳永逸
- 六、daemon.json 的完整写法与 no-proxy 规则
- 七、两套方案对比
- 八、踩坑记录
- 九、总结
引言
这套家庭数据中心的 Docker 主力是 CT702:一台 PVE 下的 Debian 13 LXC 容器,2 核 3G 内存,跑着 Docker Server 29.8.1,Stirling PDF、Uptime Kuma、Dashy 导航页都部署在它上面。机器本身没问题,问题出在网络:从家里直接拉 Docker Hub 镜像慢到怀疑人生,ghcr.io 更狠,连 DNS 都直接不可达。刚开始我以为配几个镜像加速源就能解决,现实给了我一记闷棍。
一、症状盘点:家里拉镜像到底有多难
先把症状列清楚,方便有同样处境的朋友对号入座。
| 现象 | 具体表现 |
|---|---|
| Docker Hub 直连 | 极慢,拉大镜像基本走不完 |
| ghcr.io | DNS 不可达,直接连不上 |
| 镜像加速源 | 配了 docker.1panel.live、docker.m.daocloud.io、docker.1ms.run,依然拉不顺 |
| 大文件传输 | 即使有办法把镜像包传进来,大文件直管道也容易中途断掉 |
结论是:指望"换个加速源"这条路,在我这条线路上是走不通的。得换个思路:要么把镜像包从别处搬进来,要么让整台机器的出站流量走代理。
二、先理解:docker pull 到底在干什么
要选对方案,得先知道 docker pull 背后发生了什么。Docker 拉镜像走的是 Registry HTTP API(v2),大致分三步:
- 拿 manifest:daemon 先向 registry 请求镜像的 manifest(一个 JSON),里面列出每一层(layer)的 digest 和大小;
- 并发拉 layer:每一层都是一个独立的 blob(gzip 压缩的 tar),daemon 会开多个并发连接去下载;
- 解压组装:layer 下完后逐层解压、挂载成镜像。
这个模型解释了所有症状:Docker Hub 直连慢,是因为到境外 registry 的每条 TCP 连接都慢,而并发拉 layer 会把"慢"放大——任何一层卡住,整个 pull 就卡住;ghcr.io DNS 不可达,是域名解析层就被掐了,连第一步都迈不出去;镜像加速源"配了还是拉不顺",是因为加速源本身也要回源到官方 registry,在这条线路上回源同样慢,缓存命中率再高也救不了冷门镜像。
所以真正的解法只有两类:绕开这条烂线路(把包从别处搬进来),或者给整条出站流量换条路(代理)。下面两套方案正好一类一个。
三、方案一:crane 拉包 + 分片中转 + docker load
思路
crane 是个轻量的镜像搬运工具(go-containerregistry 项目的命令行前端),不需要 Docker daemon 就能 pull/push 镜像。思路很简单:
- 在网络好的本机(或任何能正常访问 Docker Hub 的机器)用 crane 把镜像拉成 tar 包;
- 把 tar 包传到 VPS,再从 VPS 转进家里的 PVE/CT702;
- 在 CT702 上
docker load导入。
这条链路利用了已有的 VPS 中转通道,属于"人肉搬运",但胜在可靠、可控。
实战:Stirling PDF 约 1.1G 镜像
当时要部署 Stirling PDF,镜像约 1.1G。实测发现大文件直接走管道传输会中途断掉,最后定的策略是按 200M 分片。
# 1. 本机用 crane 拉包(不需要 Docker)
crane pull <镜像名>:<标签> stirling-pdf.tar
# 2. 按 200M 切片,避免大文件直传中途断掉
split -b 200M stirling-pdf.tar stirling-pdf.tar.part-
# 3. 分片传到 VPS(走你现有的 SSH 通道即可)
scp stirling-pdf.tar.part-* root@<VPS>:/tmp/
# 4. VPS 再转进家里(经 nps/SSH 通道到 PVE,再进 CT702)
# 5. 在 CT702 上拼起来并导入
cat stirling-pdf.tar.part-* > stirling-pdf.tar
docker load -i stirling-pdf.tar
注意事项
| 要点 | 说明 |
|---|---|
| 分片大小 | 200M 是实测下来稳妥的值,1.1G 的包切成 6 片 |
| 校验 | 拼包后建议核对 sha256,确认分片传输没丢字节 |
| 清理 | 导入成功后,临时镜像包和分片及时删除,别占着磁盘 |
| 记录 | 整个部署过程我记进了 NAS 运维日志,方便以后复查 |
这套方案的优点是零侵入:目标机器什么都不用改,纯搬运。缺点也很明显:每次拉新镜像都要手动走一遍,1.1G 的包切 6 片传两跳,费时费力。所以它更适合"救急",比如就部署这一个应用。
四、拼包、校验与清理的完整流程
分片传输最容易翻车的地方不在"切",而在"拼"和"验"。把完整流程写全:
# 在 CT702 上,先确认分片到齐(6 片,最后一片约 100M)
ls -lh stirling-pdf.tar.part-*
# 应看到 part-aa ~ part-af 共 6 个文件
# 拼包(注意通配符展开顺序即字母序,与 split 命名一致)
cat stirling-pdf.tar.part-* > stirling-pdf.tar
# 校验:对比本机源文件的 sha256
sha256sum stirling-pdf.tar
# 与本机 crane 拉包后记录的哈希比对,一致才继续
# 导入
docker load -i stirling-pdf.tar
# 输出 "Loaded image: xxx:yyy" 即成功
# 清理:分片和 tar 包全部删除
rm -f stirling-pdf.tar.part-* stirling-pdf.tar
几个细节:split 默认按字母序命名(aa、ab、ac…),cat 通配符展开也是字母序,两者天然对齐,不用操心顺序问题。sha256 一定要在拼包后、导入前做——分片传输丢字节是静默的,不校验就导入,起容器时才报 layer 损坏,排查成本翻倍。如果某次分片传输出错,不用全部重传,只重传坏的那一片再拼即可,这是分片相比整包传输的另一个好处:故障隔离。
五、方案二:xray 客户端代理,一劳永逸
Stirling PDF 部署完之后,用户拍板:与其每次人肉搬运,不如直接给 CT702 装个代理,让 Docker daemon 自己能连上官方 registry。这就是方案二,一次配置,长期受益。
部署步骤
VPS 上本来就有一个现成的 VLESS + Reality 节点(连 82.139.204.8:8443),直接复用,不用再搭一套。步骤如下:
- 拷贝 xray 二进制:从 VPS 的
/usr/local/bin/xray拷到 CT702(注意后面踩坑里说的权限问题); - 写 xray 客户端配置:VLESS + Reality,连 VPS 的 8443 节点(节点凭据只归档在 NAS 私密配置文件夹,文章和日志里都不写);
- 只监听本地:入站只绑
127.0.0.1:7890,不对外暴露,纯粹给本机 Docker 用; - systemd 开机自启:
xray-client.service设为 enabled + active,重启也不怕; - Docker daemon 配代理:在 CT702 的
/etc/docker/daemon.json里加 proxies,no-proxy 覆盖 localhost 和常用内网网段,避免内网流量也绕代理。
# 改完 daemon.json 后重载 Docker
systemctl reload docker
# 或
systemctl restart docker
实测验收
配置完不是"感觉能用了"就行,得拿数据说话:
| 测试项 | 结果 |
|---|---|
| 经代理访问 Docker Hub registry | 约 1.3 秒返回预期的 HTTP 401(401 说明连通了,匿名访问被拒是正常行为) |
docker pull hello-world |
约 9 秒成功 |
| 原有容器影响 | stirling-pdf、dashy、uptime-kuma 三个容器重载后保持运行且 healthy |
| 自启 | xray-client.service 已 enabled + active |
测试用的 hello-world 镜像拉完就删了,不留垃圾。no-proxy 里把内网网段加进去很关键,否则容器访问内网服务(比如 NAS)也会被塞进代理,要么变慢要么直接不通。
六、daemon.json 的完整写法与 no-proxy 规则
daemon.json 是方案二里最容易写错的一处,把完整写法和每条规则的含义一次说清:
{
"proxies": {
"http-proxy": "http://127.0.0.1:7890",
"https-proxy": "http://127.0.0.1:7890",
"no-proxy": "localhost,127.0.0.1,192.168.0.0/24"
}
}
| 字段 | 含义 |
|---|---|
http-proxy / https-proxy |
daemon 出站走本地 xray 的 7890 端口 |
no-proxy |
这些地址不走代理,直连 |
no-proxy 的三项一个都不能少:localhost 和 127.0.0.1 保证本机回环直连(很多容器健康检查走回环);192.168.0.0/24 是家里内网段,容器访问 NAS、PVE 面板、其他内网服务都靠它直连。漏了内网段的后果很典型:表面看 pull 镜像飞快,实则容器连 NAS 超时,排查时还以为是存储出了问题。
注意这是 daemon 级别的代理,只影响 daemon 自己拉镜像、建容器时的出站;容器内应用的出站要不要走代理,是另一回事(可以用容器的 http_proxy 环境变量单独控制)。两者别混为一谈。
七、两套方案对比
| 维度 | 方案一:crane 分片中转 | 方案二:xray 代理 |
|---|---|---|
| 原理 | 人肉搬运镜像包 | 让 daemon 直连官方 registry |
| 侵入性 | 零侵入,目标机不用改 | 要装 xray、改 daemon.json |
| 单次成本 | 高(拉包、切片、两跳传输、拼包、导入) | 配置一次,之后零成本 |
| 适用场景 | 救急、只部署一两个镜像 | 长期、频繁拉取更新 |
| 依赖 | 依赖 VPS 中转通道 | 依赖代理节点可用性 |
| 大镜像 | 200M 分片可解,但 1.1G 传两跳很费时间 | 直接 pull,体验接近正常网络 |
我的实际路径是:先用方案一救急把 Stirling PDF 跑起来,再用方案二治本。如果你家里网络连 Docker Hub 只是慢而不至于完全不通,也可以先试试方案二,一步到位。
八、踩坑记录
坑 1:pct push 会丢文件执行权限
xray 二进制是从 VPS 拷到 CT702 的,经 pct push 推进容器。实测发现 pct push 能保留文件内容,但可能丢执行位。xray 拷进去后直接运行会报权限不足,表现很像文件损坏,容易误判。
解法很简单,进容器里手动补上:
pct enter 702
chmod +x /usr/local/bin/xray
教训:经 pct push 进容器的可执行文件,事后都顺手 chmod +x 检查一遍,别浪费时间排查"文件坏了"。
坑 2:大文件直管道传输会断
1.1G 的镜像包直接走管道传,中途断掉的概率很高。改成 200M 分片后稳如老狗。分片虽土,但对不稳定链路就是最有效的办法。传完记得拼包校验、删临时文件。
坑 3:no-proxy 别漏内网段
只配 http-proxy/https-proxy 不配 no-proxy,容器访问内网地址也会走代理。家里 NAS、PVE 面板这些都在内网,一旦被塞进代理轻则变慢重则不通。localhost、127.0.0.1 和家里常用内网网段都要写进 no-proxy。
坑 4:代理节点挂了,docker pull 会跟着挂
方案二引入了一个新单点:xray 客户端和 VPS 上的代理节点。节点不可用时,daemon 的出站全走 127.0.0.1:7890 这个黑洞,pull 会超时而不是"自动回退直连"。所以 xray-client.service 的 enabled + active 状态要纳入日常检查,真出问题时,先把 daemon.json 的 proxies 注释掉 reload docker,就能恢复直连(慢但能用),再慢慢修代理。
方案选择的决策树
两套方案都通了,新人该怎么选?按这个决策树走:
家里能直连 Docker Hub 吗?
├── 能,只是慢 → 方案二(xray 代理),一次配置长期受益
├── 完全不能,但有 VPS 中转 → 方案一救急 + 方案二治本
│ └── 先用 crane 分片把急需的镜像搬进来,再配代理
└── 连 VPS 中转都没有 → 先解决中转通道,再谈拉镜像
另外,镜像加速源不是完全没用:对热门镜像(nginx、redis 这种),加速源的缓存命中率高,配上没坏处。只是别指望它解决冷门大镜像(Stirling PDF 这种 1.1G 的),该走代理走代理。
还有一个长期主义建议:把常用镜像的版本 pin 死。compose 里写 image: xxx:1.2.3 而不是 latest,升级时手动改版本号、手动 pull。这样做的好处:回滚有明确版本,半夜不会被上游的 breaking change 惊醒。镜像拉取策略和版本策略是一体的。
九、总结
家里 Docker 主机拉镜像困难,根因是出境线路质量差,换镜像加速源治标不治本。两套实测可行的方案:
- crane 分片中转:本机 crane 拉包,按 200M 切片经 VPS 转进家里,拼包校验后 docker load 导入。零侵入,适合救急。
- xray 客户端代理:复用 VPS 现有 VLESS + Reality 节点,CT702 本地 127.0.0.1:7890 只监听本机,daemon.json 配代理 + no-proxy 内网段,systemd 开机自启。一次配置长期受益,实测 registry 约 1.3 秒返回 401,pull hello-world 约 9 秒。
共同的坑:pct push 丢执行位记得 chmod +x;大文件传输用分片;no-proxy 别漏内网。凭据类信息(节点 UUID 等)统一归档在 NAS 私密配置文件夹,文不载值。
相关阅读:《PVE 里跑 Docker:LXC 容器 CT702 实战》、《自建图床 EasyImage 部署与公网化全记录》。