用docker buildx build镜像时最大的一个问题,就是拉镜像时速度超慢,20M的镜像要1个多小时,为什么这么慢?

217 阅读3分钟

出现 docker buildx build 拉取基础镜像比传统 docker build 明显“卡”的情况,90 % 不是 BuildKit 本身变慢,而是默认路径被改变后,把镜像拉到了另一张网卡 / 另一颗 CPU / 另一个守护进程,结果过去配置好的加速器、代理、镜像仓库缓存瞬间失效。常见根因与对应加速办法如下:

  1. 驱动换了,加速器没同步
    buildx 默认会起一个新的 BuildKit 容器(docker-container 驱动),它里面走自己的 /etc/buildkit/buildkitd.toml,不会继承宿主机 /etc/docker/daemon.json 里的 "registry-mirrors"
    解决:建 builder 时就把加速器写进去
    docker buildx create \
      --name cn \
      --driver docker-container \
      --driver-opt image=dockerpracticesig/buildkit:master \
      --config - <<EOF
    [registry."docker.io"]
      mirrors = ["https://hub-mirror.c.163.com","https://mirror.ccs.tencentyun.com"]
    EOF
    docker buildx use cn
    

注意,我用的也算是这条路径上的办法,我是在容器上改的config toml文件,结果是下载可以,不会出现30秒的timeout,但是超慢,慢到20M的镜像要1个多小时。真难受

image.png 图中1400秒下了7M多,合每秒5KB, 并且它可能会在1小时后挂住,这速度让人抓狂。

  1. 多平台 = 多架构拉取
    只要加 --platform linux/amd64,linux/arm64 等参数,BuildKit 会把每个架构的 manifest + 配置 + blob 全部拉一遍,体积成倍增加,国内网络下时间自然翻倍。
    解决:

    • 只构建本机架构,去掉 --platform
    • 或者先把外网机 docker pull --platform=xxxdocker save 回来,再 docker load 到 buildx 所在机,BuildKit 会优先用本地镜像。
  2. 懒拉取(lazy-pull)失效
    BuildKit ≥ 0.9 支持“先只拉 manifest,真正用到 blob 时再拉”,但如果你把输出改成 --output type=oci,dest=xxx.tar--output type=local…,它会一次性拉完所有层,于是“看起来”比 docker build 慢。
    解决:CI 场景可以先 --output type=image,name=reg.example.com/app:cache,push=true 推到内网仓库,再另做一次 docker pull 导出。

  3. 代理没透传
    公司环境常用 http_proxy/https_proxy。BuildKit 容器里默认读不到宿主机代理变量,需要显式注入:

    docker buildx create \
      --name proxy \
      --driver docker-container \
      --driver-opt env.http_proxy=http://proxy.xxx:7890 \
      --driver-opt env.https_proxy=http://proxy.xxx:7890
    docker buildx use proxy
    
  4. 国内云厂商间歇性阻断 DockerHub
    2024 年下半年开始,大批国内镜像站间歇屏蔽 DockerHub,导致无论 docker pull 还是 buildx 都会卡在 “retrying in 5s”。
    解决:

    • 在能出海的节点先 docker pull / buildx imagetools create 把基础镜像转存到阿里云 ACR / 腾讯云 TCR;
    • 在 Dockerfile 里把 FROM node:20-alpine 换成 FROM reg.example.com/library/node:20-alpine
  5. 缓存未命中,重复拉取
    如果每次 CI 都起一个新的 builder 实例,旧的缓存卷被销毁,BuildKit 只能重新拉取所有层。
    解决:

    • 固定一个 builder 并挂载持久化目录
      --driver-opt env.BUILDKIT_STEP_LOG_MAX_SIZE=10485760 \
      --driver-opt env.BUILDKIT_STEP_LOG_MAX_SPEED=10485760
    • 或者把缓存推到远端仓库
      --cache-to type=registry,ref=reg.example.com/app:cache,mode=max
      --cache-from type=registry,ref=reg.example.com/app:cache
  6. 网络本身 MTU / DNS 问题
    BuildKit 容器默认桥接网络 MTU 1500,在 PPPoE、VPN 场景下容易被拆包,导致 TLS 重传。
    解决:

    docker buildx create \
      --name mtu \
      --driver docker-container \
      --driver-opt network=host        # 直接用宿主机网络
    

结论
docker build 只是跟你本机守护进程打交道,所有“加速器、代理、镜像缓存”继承得顺其自然;而 docker buildx 默认把活交给另一个容器里的 BuildKit,于是过去“调好的网络”瞬间失效,看起来就像“拉镜像突然变慢”。把 builder 的网络、镜像加速、代理、缓存四项一次性对齐,拉取速度就能回到甚至超过原来的水平。