docker pull 卡在 Pulling fs layer、报 net/http: TLS handshake timeout、或者抛出 unauthorized: authentication required——镜像拉取失败是运维日常里出现频率最高的 Docker 故障之一。它可能来自网络、DNS、仓库地址、认证凭据、磁盘空间甚至时间同步,表面上都是「拉不下来」,处理思路却完全不同。
很多新手遇到拉取失败就习惯性换镜像源,结果有时能通有时不通,问题反复出现。其实只要按「网络层 → 传输层 → 仓库认证 → 本地存储」这个顺序逐层排除,绝大多数问题能在几分钟内定位。
本文会先讲清 Docker 拉取镜像的完整流程,再给出一套分层的排查命令,最后覆盖私有仓库认证、代理配置、磁盘与 inode 不足等常见坑位,并提供可复制的修复步骤和回滚方式。
一、先理解 docker pull 做了什么
一次 docker pull 大致分四步:
1. 解析镜像名,得到仓库地址(Registry)、命名空间、仓库名和标签;
2. 向 Registry 发起请求,获取镜像 Manifest(清单);
3. 按 Manifest 中的 digest 逐层下载 Layer(层),每层都做校验;
4. 解压写入本地存储目录(默认 /var/lib/docker)。
任何一步失败都会中断。把这四步和错误信息对应起来,定位就快:
- 第 1、2 步失败 → 多半是 DNS、网络、仓库地址问题;
- 认证相关报错 → 是凭据问题;
- 下载中途失败 → 传输、代理或 MTU 问题;
- 写盘失败 → 磁盘空间或 inode 不足。
常见报错与阶段对照:
| 报错关键字 | 所处阶段 | 大概率原因 |
|---|---|---|
| no such host | 解析仓库地址 | DNS 失败 |
| TLS handshake timeout | 建立连接 | 网络受阻/代理未配 |
| connection refused | 建立连接 | 仓库端口不可达 |
| unauthorized | 认证 | 未登录或凭据过期 |
| denied: requested access | 权限 | 账号无该仓库权限 |
| no space left on device | 写盘 | 磁盘/inode 满 |
| EOF / unexpected EOF | 下载层 | 传输中断/MTU |
二、第一步:确认网络与 DNS
先确保基础网络没问题:
# 解析仓库域名
getent hosts registry-1.docker.io
# 测试端口连通性(443)
curl -v --connect-timeout 5 https://registry-1.docker.io/v2/
# 若返回 401 且带 WWW-Authenticate 头,说明网络通、只是需要认证,属正常
如果 getent hosts 失败,是 DNS 问题,参考容器 DNS 排查思路处理宿主机解析。若解析正常但连不上 443,检查出口防火墙、代理和 NAT。
在宿主机层面也可以直接用容器验证网络:
docker run --rm curlimages/curl -sv --connect-timeout 5 https://registry-1.docker.io/v2/
三、配置代理(企业内网最常见)
如果宿主机走 HTTP 代理出网,Docker 守护进程本身并不会自动读取 shell 的 http_proxy,必须单独配置 systemd 环境变量。
针对 docker.service 创建 drop-in:
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/http-proxy.conf >/dev/null <<'EOF'
[Service]
Environment="HTTP_PROXY=http://proxy.example.com:3128"
Environment="HTTPS_PROXY=http://proxy.example.com:3128"
Environment="NO_PROXY=localhost,127.0.0.1,10.0.0.0/8,192.168.0.0/16,.internal"
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
验证环境变量是否生效:
sudo systemctl show docker --property=Environment
风险提示:重启 Docker 会中断运行中的容器,请安排在维护窗口;NO_PROXY 一定要包含内网网段和私有仓库域名,否则内网拉取会被错误地走代理。回滚方式是删除该 drop-in 文件后 daemon-reload 并重启。
同时,容器内的程序若也要走代理,需在 ~/.docker/config.json 中配置:
{
"proxies": {
"default": {
"httpProxy": "http://proxy.example.com:3128",
"httpsProxy": "http://proxy.example.com:3128",
"noProxy": "*.internal,10.0.0.0/8"
}
}
}
四、私有仓库认证
访问企业私有仓库(如 Harbor、云厂商的容器镜像服务)通常需要先登录:
docker login registry.internal.example.com -u <用户名>
# 查看已登录的凭据(凭据经过 base64,属于敏感信息,注意权限)
cat ~/.docker/config.json
认证类报错的处理:
- unauthorized: authentication required:没登录或 token 过期,重新 docker login 即可;
- denied: requested access to the resource is denied:账号没有该仓库的 pull 权限,找管理员授权或换账号;
- 登录成功但 pull 仍报 401:检查机器时间是否准确,token 有效期受时间影响。
时间偏移是很隐蔽的原因,用下面的命令确认:
timedatectl status
# 若时间偏差较大,开启 NTP 同步
sudo timedatectl set-ntp true
另外,配合 Kubernetes 时凭据放在 imagePullSecrets 里:
kubectl create secret docker-registry regcred \
--docker-server=registry.internal.example.com \
--docker-username=<用户名> \
--docker-password='<密码或token>' \
--docker-email=ops@example.com
在 Deployment 中引用:
spec:
template:
spec:
imagePullSecrets:
- name: regcred
五、磁盘与 inode:容易被忽视的写盘失败
镜像层解压会占用大量空间。一次拉取失败如果报 no space left on device,先看空间:
df -h /var/lib/docker
df -i /var/lib/docker
df -h 看容量,df -i 看 inode。有时候容量还有富余,但 inode 用尽,同样报这个错。清理时优先删无用镜像和停止的容器:
# 清理未使用的镜像、容器、网络(谨慎,会删掉未使用的资源)
docker system prune
# 只清理悬空镜像
docker image prune
# 查看占用
docker system df -v
风险提示:docker system prune -a 会删除所有未被容器引用的镜像,包括你手动拉取的常用镜像,生产环境执行前务必确认现有容器不依赖这些镜像。回滚手段有限——被删的镜像只能重新拉取,因此建议先 docker images 导出清单留档,必要时用 docker save 备份关键镜像。
如果确实磁盘紧张,可迁移数据目录到更大的盘。编辑 /etc/docker/daemon.json:
{
"data-root": "/data/docker"
}
迁移步骤(需先停 Docker):
sudo systemctl stop docker
sudo rsync -aHAX --info=progress2 /var/lib/docker/ /data/docker/
sudo systemctl start docker
docker info | grep "Docker Root Dir"
风险提示:迁移期间不要有其他进程写 /var/lib/docker,rsync 完成后先保留原目录一段时间再删除,确认无误后再清理,避免数据丢失。
六、镜像加速与仓库地址
在国内拉取公共镜像慢或超时,可以配置镜像加速器(Registry Mirror)。编辑 /etc/docker/daemon.json:
{
"registry-mirrors": [
"https://<你的加速地址>"
]
}
配置后重启:
sudo systemctl daemon-reload
sudo systemctl restart docker
docker info | grep -A3 "Registry Mirrors"
需要说明的是,加速器地址由各服务商提供且可能随时间调整,请以官方最新说明为准,不要长期依赖来历不明的第三方地址。对于私有仓库,直接使用其完整域名拉取即可,如 docker pull registry.internal.example.com/team/app:1.0。
另外,insecure-registries 用于自签名证书的私有仓库,配置后允许走 HTTP:
{
"insecure-registries": ["registry.internal.example.com:5000"]
}
风险提示:允许明文 HTTP 传输意味着凭据和镜像可能被窃听,仅在受信任的内网使用,长期应部署正规 TLS 证书。
七、修改仓库地址后的验证与回滚
改完 daemon.json 后统一做一遍验证:
# 校验 JSON 语法,避免写错导致 daemon 起不来
python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -E "Registry|Root Dir|Proxy"
docker pull alpine:3.19
如果 Docker 起不来,通常是 daemon.json JSON 语法错误。此时查看日志:
journalctl -u docker --no-pager -n 50
回滚就是把 daemon.json 恢复成修改前的内容(建议改动前先 cp /etc/docker/daemon.json{,.bak}),再重启。
八、为什么拉取失败常常「时好时坏」
很多人反馈:同一个镜像,早上拉不下来,下午重试就好了;或者一台机器能拉,另一台就不行。这种「时好时坏」最容易让人误判成 Docker 的 bug,其实背后往往是几个可控因素的叠加。
一是镜像层的并发下载。Docker 默认会同时开多个连接下载不同的层,层数越多、并发越高,对出口带宽和代理连接数的压力就越大。当代理的最大连接数被打满,或出口带宽在小文件突发时被打满,就会出现部分层下载超时,表现为整体拉取失败。可以通过守护进程参数收敛并发:
{
"max-concurrent-downloads": 3,
"max-download-attempts": 5
}
max-concurrent-downloads 控制同时下载的层数,网络条件一般的内网把它降到 3 到 5 往往更稳;max-download-attempts 控制单层重试次数,在网络抖动频繁的环境适当提高。
二是大层与小层的差异。镜像里动辄几百 MB 的层(比如把一堆依赖打包进一个层)对链路稳定性要求高,一个小丢包就可能触发重传甚至超时;而小的配置层几乎不受影响。这也是为什么「有的镜像能拉、有的不能」——不一定是仓库不一样,可能是镜像层构成不一样。构建镜像时把不常变的大依赖放到前面的层、让层尺寸更均匀,能间接提升拉取稳定性。
三是仓库侧的限流与 CDN 回源。公共镜像仓库对匿名拉取通常有限流策略,高峰时段会出现 429 之类的限流响应;而使用加速器时,如果加速器缓存未命中需要回源,回源链路慢也会让整体变慢甚至失败。这类问题靠重试确实能缓解,但长期看应该通过私有仓库或镜像预热来规避。
理解这些因素后,遇到「时好时坏」的拉取失败,先别急着改配置,可以按「降低并发 → 检查代理连接数 → 检查是否限流(看返回码)→ 考虑预热镜像」的顺序处理,通常能稳定下来。
常见问题
Q1:docker pull 报 TLS handshake timeout,重启 Docker 就好了,过一会又出现。
A:说明网络链路不稳定或代理连接数受限。检查代理最大连接数、出口带宽,考虑给 daemon.json 配置更合理的 max-concurrent-downloads,降低并发。
Q2:宿主机 curl 能访问仓库,但 docker pull 不行。
A:Docker 守护进程不继承 shell 代理,需按第三节给 docker.service 配置 systemd 环境变量。
Q3:unauthorized 和 denied 有什么区别?
A:unauthorized 是没通过认证(没登录/凭据过期),denied 是通过了认证但没有该资源的访问权限,前者重新登录,后者要找管理员授权。
Q4:Kubernetes 里 Pod 报 ImagePullBackOff,和 Docker pull 失败一样排查吗?
A:思路一致,但要先确认是节点级问题还是 Pod 级问题。用 kubectl describe pod 看 Events,常见原因是 imagePullSecrets 缺失或节点无法访问仓库。
Q5:no space left on device,但 df -h 显示还有空间?
A:检查 inode,用 df -i。也可能是被删除但未释放的文件占用,用 lsof | grep deleted 排查。
总结
- 按「网络 → 传输 → 认证 → 存储」四层顺序排查,把报错关键字对应到具体阶段,效率最高。
- Docker 守护进程不继承 shell 代理,企业内网务必给
docker.service配 systemd 代理环境变量,并正确设置NO_PROXY。 - 私有仓库认准三件事:登录状态、账号权限、机器时间同步。
no space left on device要先分容量还是 inode,清理前用docker system df -v看清占用,谨慎使用system prune -a。- 任何
daemon.json改动前先备份,改完用python3 -m json.tool校验语法,出了问题看journalctl -u docker并快速回滚。
企业QQ咨询




