docker pull 卡在 pulling fs layer 十分钟不动、net/http: TLS handshake timeout、unexpected EOF——Docker Hub 直连的不稳定,叠加 2024 年年中公共加速器的批量关停,让「拉个镜像」成了国内开发环境的日常障碍。
出路不止一条,按见效速度从快到慢排成五条,各有适用面。开始之前先记住两个高频误区,后面反复用到:
registry-mirrors只对 Docker Hub(docker.io)生效——ghcr.io、quay.io、registry.k8s.io 的失败它救不了。- 拉镜像的是 dockerd 守护进程——终端里
export http_proxy对它完全无效。
出路一:镜像加速器(registry-mirrors)
最经典的路径:/etc/docker/daemon.json 写入 registry-mirrors 数组,systemctl restart docker。
排错顺序固定:先 docker info 看底部有没有列出 Registry Mirrors——大量「配了没用」其实是没生效:daemon.json 写错路径(Docker Desktop 要在 Settings → Docker Engine 里改,改宿主机文件无效)、JSON 语法错误导致整个文件被忽略、改完没重启服务。
生效了还拉不动,就是加速器本身死了。公共加速器的存活状态变化很快,别恋战,换下一个——可用地址以国内镜像源速查维护的清单为准,这类信息写死在博客里过几个月就是错的。
出路二:前缀替换(免配置,救所有仓库)
加速器是透明代理;前缀替换则是显式改名,把镜像服务的域名直接写进镜像名:
docker pull docker.m.daocloud.io/library/nginx:alpine
# 拉完改回原名,compose/K8s 清单不用动
docker tag docker.m.daocloud.io/library/nginx:alpine nginx:alpine
注意官方镜像要补 library/ 命名空间。这条路的独特价值有两个:无需 root 改配置(临时救急、无权限环境);能救 registry-mirrors 覆盖不了的仓库——同类服务通常成套提供 ghcr.m.daocloud.io、k8s.m.daocloud.io 等前缀。
代价是依赖具体的第三方服务。纪律是:前缀只出现在拉取命令里,Dockerfile 和 compose 落盘的仍写原始镜像名,用 tag 改名兜底——否则该服务一停服,全线构建跟着挂。
出路三:给守护进程配代理
有可用代理时的正解,但必须配到 daemon 上:
# /etc/systemd/system/docker.service.d/proxy.conf
[Service]
Environment="HTTPS_PROXY=http://<代理地址>:<端口>"
Environment="NO_PROXY=localhost,127.0.0.1,<内网registry域名>"
systemctl daemon-reload && systemctl restart docker,docker info 出现 HTTP Proxy 字段即生效。Docker Desktop 在 Settings → Resources → Proxies 里填。
两个次生坑:NO_PROXY 漏掉公司内网 registry,会把内网推拉流量也送进代理,全挂;daemon 代理只管 pull/push,docker build 过程中容器内的 apt/pip 请求不继承它——构建内的下载用 build-arg 传代理,或直接换国内镜像源。
出路四:云上生产环境——自建中转仓库
服务器在阿里云、腾讯云上时,别再依赖任何公共加速器:把需要的公共镜像同步进自己在该云的镜像仓库(ACR/TCR 都有免费个人版),生产机器同地域内网拉取。速度是内网级的,且彻底摆脱公网波动。
同步的三种姿势:本地拉了 tag 成仓库地址 push 上去;用镜像服务自带的海外构建/代理拉取能力;CI 里加一个 skopeo copy 的同步 job 批量搬运。
这条路的真正收益超出网络本身:镜像版本被自己的仓库固化,可审计、可回滚,部署不再依赖第三方存活状态——把「网络问题」升级成「供应链管理」,生产环境本来就该走到这一步。
出路五:K8s 集群——containerd 是另一套配置
从 Docker 迁到 K8s 的团队最常见的失配:现代 K8s 节点的运行时是 containerd,不读 /etc/docker/daemon.json,按 Docker 的方法配自然没用。
containerd(1.5+)的推荐姿势:/etc/containerd/config.toml 里启用 config_path(通常指向 /etc/containerd/certs.d),然后按仓库建目录放 hosts.toml(如 certs.d/docker.io/hosts.toml 写镜像端点),重启 containerd。验证用 crictl pull 或看 kubelet 事件——节点上可能根本没有 docker 命令。
节点多的集群更推荐结构性方案:统一走出路四的中转仓库;或集群内自建 pull-through 缓存(Harbor 代理项目、registry:2 的 proxy 模式),全集群只有缓存节点需要出网,公网流量也省了。换仓库地址时记得同步 imagePullSecrets。
选择建议
| 场景 | 首选 |
|---|---|
| 个人机器临时拉一个 | 前缀替换 |
| 个人/团队日常开发 | 加速器 + 前缀替换兜底 |
| 有稳定代理 | daemon 代理 |
| 云上生产 | 自建中转仓库 |
| K8s 集群 | containerd 配置 + 中转仓库/缓存 |
配完发现速度还是不理想,瓶颈可能根本不在 registry——代理与镜像的关系、DNS 层的干扰,见换了国内镜像还是慢和公共 DNS 的选择与污染诊断。拉下来的镜像要从 docker run 沉淀成可维护的编排文件,见docker run 与 compose 的参数对照。