
如果你在国内使用 Docker,大概率遇到过这种场景:
docker pull nginx
然后屏幕一直停在:
Pulling from library/nginx
最后可能得到:
i/o timeout
或者:
context deadline exceeded
甚至:
TLS handshake timeout
很多人的解决方式非常直接:
找一个 Docker 国内镜像地址,复制到
daemon.json。
这样确实可能解决问题。
但如果从 Docker 的角度来看,这里面其实涉及 Docker Registry、Docker Hub、镜像分层、Registry Mirror、缓存代理等一整套机制。
今天我们从底层把它讲明白。
一、docker pull 到底做了什么?
先来看最普通的一条命令:
docker pull nginx
很多初学者可能理解成:
我的电脑
↓
下载 nginx
↓
完成
实际上没有这么简单。
Docker 首先需要解析:
nginx
到底代表哪个仓库。
因为没有显式指定 Registry,所以 Docker 默认认为它来自 Docker Hub。
完整理解可以近似成:
docker.io/library/nginx:latest
其中:
docker.io
代表 Registry。
library
代表 Docker 官方镜像默认命名空间。
nginx
是镜像仓库名称。
latest
是 Tag。
所以:
docker pull nginx
实际上相当于请求:
Docker Hub
↓
library/nginx
↓
latest
这也是为什么很多 Docker 镜像代理要求你这样写:
docker pull docker.1ms.run/library/nginx:latest
而不是:
docker pull docker.1ms.run/nginx:latest
因为完整路径实际上存在一个:
library
命名空间。
二、Docker 镜像并不是一个 ZIP 文件
理解 Docker 国内镜像之前,还要理解一个非常重要的概念:
Docker Image 是分层的。
例如:
docker pull nginx
你可能看到:
a1b2c3d4: Pull complete
b2c3d4e5: Pull complete
c3d4e5f6: Pull complete
d4e5f6g7: Pull complete
这些就是不同的 Layer。
可以简单理解成:
nginx:latest
│
├── Linux 基础层
├── 系统依赖
├── nginx 程序
├── 配置文件
└── 其他文件
Docker 首先获取镜像 Manifest,然后根据里面记录的信息下载对应 Layer。
因此一次:
docker pull nginx
背后实际上会发生很多网络请求。
如果 Registry 访问不稳定,就可能出现:
某一层下载成功
某一层下载失败
某一层一直 timeout
所以你经常会看到 Docker Pull 下载到一半卡住。
三、为什么 Docker 国内镜像这么重要?
Docker Hub 是 Docker 生态最重要的公共镜像仓库之一。
比如我们平时使用的:
docker pull nginx
docker pull mysql
docker pull redis
docker pull postgres
docker pull node
docker pull python
默认基本都会访问 Docker Hub。
问题就在这里。
对于部分国内网络环境来说,访问 Docker Hub 的网络链路可能出现延迟高、连接不稳定或者超时等情况。
于是:
服务器
│
│ 网络请求
↓
Docker Hub
这条链路出现问题之后:
docker pull
自然也会跟着失败。
而 Docker Registry Mirror 的思路其实很简单:
原来:
Docker Client
↓
Docker Hub
加入 Mirror:
Docker Client
↓
Registry Mirror
↓
Docker Hub
如果镜像代理已经缓存过 nginx:
Docker Client
↓
Registry Mirror
↓
直接返回缓存
这样就不一定每次都需要重新访问 Docker Hub。
Docker 官方同样提供了 Registry Mirror 和 pull-through cache 机制,可以通过 Docker daemon 的 registry-mirrors 配置镜像地址。
四、一个专门整理 Docker 国内镜像的开源项目
我自己整理了一个项目:
Rodert/DockerHub
GitHub:
https://github.com/Rodert/DockerHub
这个项目做的事情并不复杂。
核心就是:
持续整理目前还能使用的 Docker 工具下载地址和 Docker Hub 公共镜像地址。
截至目前,项目中整理了多个公共入口,例如:
docker.1panel.live
docker.1ms.run
dockerproxy.net
dockerproxy.link
docker.m.daocloud.io
docker.jiaxin.site
同时项目已经把失效、需要 Token、付费或者仅限特定内网的地址排除掉,并提供 Linux、Windows、macOS 的配置方法。
为什么我觉得这种项目有必要长期维护?
因为 Docker 镜像地址不是:
配置一次,永久有效。
公共代理可能随时发生:
停止服务
限流
更换域名
网络调整
上游变化
所以相比在一篇两年前的博客里找地址,我更希望维护一个持续更新的仓库。
五、最简单的方法:直接通过镜像地址 Pull
假设原来的命令是:
docker pull nginx
可以直接通过镜像代理:
docker pull docker.1ms.run/library/nginx:latest
或者:
docker pull dockerproxy.net/library/nginx:latest
例如 Redis:
docker pull docker.1ms.run/library/redis:latest
MySQL:
docker pull docker.1ms.run/library/mysql:8.4
Node:
docker pull docker.1ms.run/library/node:22
这里有一个非常容易踩坑的地方。
Docker 官方镜像通常需要:
library/
比如:
library/nginx
library/mysql
library/redis
但如果原来的镜像本身属于某个用户或者组织:
username/myapp
就应该使用:
docker pull docker.1ms.run/username/myapp:latest
而不是:
docker.1ms.run/library/username/myapp
六、更推荐的方式:配置 registry-mirrors
如果你每天都在使用 Docker,每次写:
docker.1ms.run/library/nginx
显然非常麻烦。
Docker 提供了更方便的方法:
registry-mirrors
Linux 创建:
sudo mkdir -p /etc/docker
编辑:
sudo vim /etc/docker/daemon.json
配置:
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://dockerproxy.net",
"https://dockerproxy.link"
]
}
然后:
sudo systemctl daemon-reload
重启 Docker:
sudo systemctl restart docker
最后测试:
docker pull nginx
如果能够正常下载:
latest: Pulling from library/nginx
...
Status: Downloaded newer image for nginx:latest
说明配置已经生效。
项目 README 目前也是采用这种方式给 Linux 用户提供配置示例。
七、怎么看镜像加速有没有生效?
执行:
docker info
然后寻找:
Registry Mirrors:
例如:
Registry Mirrors:
https://docker.1ms.run/
https://dockerproxy.net/
https://dockerproxy.link/
说明 Docker 已经读取到了配置。
也可以:
docker info | grep -A 10 "Registry Mirrors"
快速查看。
八、macOS 怎么配置?
如果使用的是 Docker Desktop,就不需要去修改:
/etc/docker/daemon.json
打开:
Docker Desktop
进入:
Settings
→ Docker Engine
里面通常会看到一段 JSON。
增加:
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://dockerproxy.net"
]
}
注意:
如果本来已经有配置:
{
"features": {
"containerd-snapshotter": true
}
}
不要整个覆盖掉。
应该合并:
{
"features": {
"containerd-snapshotter": true
},
"registry-mirrors": [
"https://docker.1ms.run",
"https://dockerproxy.net"
]
}
然后点击:
Apply & Restart
重新测试:
docker pull nginx
即可。
Windows Docker Desktop 的配置逻辑也类似。
九、为什么我配置了镜像还是拉不下来?
这也是最常见的问题。
不要看到:
docker pull timeout
就认定是镜像地址坏了。
Docker Pull 涉及很多环节。
可以按照下面的方法排查。
首先:
docker info
确认:
Registry Mirrors
有没有读取成功。
然后:
curl -I https://docker.1ms.run
看看网络能不能连接。
再测试一个最简单的镜像:
docker pull hello-world
或者:
docker pull alpine
如果:
docker pull alpine
可以成功,但某个特殊镜像失败,那么问题未必出在 Docker Mirror。
还可能是:
镜像不存在
Tag 不存在
镜像为 Private
平台架构不支持
代理暂未同步
上游 Docker Hub 异常
十、还有一个很容易误判的问题:429
有时候:
docker pull
失败并不是网络问题。
而是:
429 Too Many Requests
Docker Hub 本身存在 Pull Rate Limit。
目前 Docker 官方文档显示,未登录用户和 Docker Personal 用户存在以 6 小时为周期的 Pull 限制;例如未认证访问按照 IP 地址计算。
所以:
Timeout
和:
429
其实完全是两个问题。
如果看到:
You have reached your pull rate limit
首先应该考虑:
docker login
而不是继续疯狂更换镜像地址。
十一、生产环境不要只依赖公共镜像
如果只是:
个人开发
学习 Docker
测试项目
临时服务器
使用公共 Docker 镜像代理通常很方便。
但是如果是:
企业生产环境
几十台服务器
Kubernetes 集群
CI/CD
高频构建
我更建议搭建自己的 Registry Cache。
例如:
开发服务器
│
├─────────┐
│ │
↓ ↓
Kubernetes CI/CD
│ │
└────┬────┘
↓
Docker Registry Cache
↓
Docker Hub
这样第一次:
docker pull nginx
访问 Docker Hub。
第二台服务器再拉:
docker pull nginx
就可以直接命中缓存。
Docker 官方同样建议通过缓存和镜像机制减少重复 Pull。
十二、甚至可以自己搭一个 Docker Hub 缓存
Docker Registry 支持:
pull-through cache
例如配置:
proxy:
remoteurl: https://registry-1.docker.io
它的工作逻辑就是:
你的 Docker
↓
自己的 Registry
↓
缓存里有没有?
↙ ↘
有 没有
↓ ↓
返回 Docker Hub
↓
缓存
↓
返回
对于几十台服务器来说,这个方案比:
所有机器分别访问 Docker Hub
更加合理。
十三、Docker Compose 同样会使用镜像加速
很多人还有一个疑问:
如果我运行:
docker compose up -d
镜像加速还有效吗?
答案是:
有效。
比如:
services:
nginx:
image: nginx:latest
redis:
image: redis:7
mysql:
image: mysql:8.4
执行:
docker compose up -d
Docker Compose 最终仍然需要 Docker Engine 去拉:
nginx
redis
mysql
因此只要:
registry-mirrors
已经正确配置:
docker compose pull
同样会受益。
不需要改成:
image: docker.1ms.run/library/nginx
这种写法。
这也是我更推荐修改 Docker daemon 配置,而不是修改项目 docker-compose.yml 的原因。
十四、不要把公共镜像地址写死到业务代码
例如有人会这样写:
services:
nginx:
image: docker.1ms.run/library/nginx:latest
个人测试没有问题。
但正式项目最好仍然保持:
services:
nginx:
image: nginx:1.28
为什么?
因为今天:
docker.1ms.run
可用。
不代表一年以后一定还可用。
如果写死:
第三方域名
你的项目就产生了额外依赖。
更合理的做法是:
应用代码
↓
nginx:1.28
↓
Docker Engine
↓
registry-mirrors
让:
业务配置
和:
基础设施配置
分开。
十五、还有一个比镜像加速更重要的问题:镜像安全
公共 Docker Mirror 很方便。
但不要忘了:
它毕竟属于你的软件供应链。
尤其生产环境,不建议随便在网上找到一个:
xxx-docker-mirror.com
就加入服务器。
对于重要服务最好:
使用可信 Registry
+
固定镜像版本
+
必要时固定 Digest
比如不要一直:
docker pull nginx:latest
而是:
docker pull nginx:1.28
更加严格的生产环境甚至可以使用:
nginx@sha256:xxxxxx
这样你部署的就不再只是:
某个 Tag
而是明确的镜像内容版本。
十六、一个 Docker 项目真正应该解决什么问题?
很多人认为:
Docker 国内镜像项目
就是:
整理 10 个网址
我反而觉得不是。
真正有价值的是持续维护:
Docker 官方安装入口
+
当前可用 Docker Hub 镜像
+
Linux 配置方法
+
Docker Desktop 配置方法
+
失效地址清理
+
常见问题排查
这也是我维护:
https://github.com/Rodert/DockerHub
这个项目的原因。
Docker 镜像服务的状态会随着网络环境和服务商调整不断发生变化,所以这个项目更像一个:
Docker Hub 国内访问的“可用资源索引”。
项目当前收录的公共地址也明确提醒:
地址会随着网络环境变化,应该优先选择当前可用项并合理使用。
总结
最后把 Docker 国内镜像这件事情总结成一张流程图:
docker pull nginx
│
↓
Docker Engine
│
↓
检查 registry-mirrors
│
↓
Docker Mirror
│
┌──┴──┐
│ │
有缓存 无缓存
│ │
↓ ↓
返回 Docker Hub
│
↓
下载
│
↓
缓存
│
↓
返回
所以 Docker 镜像加速真正改变的不是:
Docker 镜像本身
而是:
Docker 获取镜像的网络路径。
对于普通开发者来说,可以直接使用持续维护的公共 Docker 镜像。
对于团队和企业来说,更进一步则应该考虑:
Registry Mirror
↓
Pull-through Cache
↓
Harbor / 私有 Registry
↓
内部镜像供应链
从:
docker pull nginx
这样一条简单命令出发,背后其实已经涉及到了完整的容器镜像分发体系。
如果你最近也遇到了 Docker Hub 拉取失败的问题,可以收藏:
https://github.com/Rodert/DockerHub
里面会持续整理 Docker 国内可用镜像和相关资源。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/qq_40374604/article/details/165287705




