国内服务器 Docker 构建提速:从 41 分钟到 4 分钟

首次构建等了 41 分钟没结束,Maven 中央仓只有 15 kB/s。本篇用内联镜像源、替换 apk 源、调分层顺序三招,把它压到 4 分钟。

一、症状:一个永远结束不了的构建

在国内服务器上第一次执行 docker build,等了 41 分钟,没看到结束:

[INFO] Downloading from central: https://repo.maven.apache.org/maven2/org/springframework/...
Progress (1): 128/512 kB
Progress (1): 256/512 kB
...

卡在下载依赖上,进度条一格一格挪。

实测了几个数字:

操作 耗时
mvn dependency:go-offline(Maven 中央仓库) 1714 秒(28.5 分钟)
apk add curl tzdata(Alpine 默认源) 345 秒(5.75 分钟)
下载速度 15–316 kB/s

两个瓶颈,两个原因,都要修。

二、瓶颈一:Maven 中央仓库

国内直连 repo.maven.apache.org 就是这个速度——不是网络故障,是线路问题,不会自己变好。

修复:在 Dockerfile 里内联阿里云镜像配置

# ---------------------------- 阶段 1:构建 -----------------------------------
FROM maven:3.9-eclipse-temurin-17 AS builder

WORKDIR /build

# 内联阿里云 Maven 镜像配置。
# 【关键】放在 COPY pom 之前,这样只要镜像地址不变,这一层就始终命中缓存。
RUN mkdir -p /root/.m2 && cat > /root/.m2/settings.xml << 'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0">
  <mirrors>
    <mirror>
      <id>aliyun-central</id>
      <name>Aliyun Maven Central Mirror</name>
      <url>https://maven.aliyun.com/repository/central</url>
      <mirrorOf>central</mirrorOf>
    </mirror>
    <mirror>
      <id>aliyun-public</id>
      <name>Aliyun Maven Public</name>
      <url>https://maven.aliyun.com/repository/public</url>
      <mirrorOf>*</mirrorOf>
    </mirror>
  </mirrors>
</settings>
EOF

COPY pom.xml .
COPY api-cloud-common/pom.xml           ./api-cloud-common/
COPY api-cloud-service-user/pom.xml     ./api-cloud-service-user/
# ...

为什么放两个 mirror

mirror id mirrorOf 作用
aliyun-central central 代理中央仓库
aliyun-public * 兜底:代理所有其他仓库(含第三方仓库、插件仓库)

只配 central 的话,项目里如果声明了其他 repository(很多 Spring 相关依赖会走 spring-milestones 等),那些请求还是会直连国外。

mirrorOf 的匹配按声明顺序生效,先匹配到的胜出。所以 central 要写在 * 前面。

关键是那一行的位置

RUN mkdir -p /root/.m2 && cat > /root/.m2/settings.xml << 'EOF'   # ← 先写 settings
...
COPY pom.xml .                                                    # ← 再拷 pom

Docker 的层缓存是从上到下的:任何一层内容变了,它以及它之后的所有层都会重建。

这两种写法的差别:

写法 改了 pom.xml 之后
settings 在 COPY pom.xml 之后 settings 层要重建 → 即使内容没变,也走一遍慢速下载
settings 在 COPY pom.xml 之前 settings 层命中缓存 → 直接复用,不重新下载

把这个顺序写对,是最省事的一次性优化。

三、瓶颈二:Alpine 的包管理源

第二阶段的基础镜像是 eclipse-temurin:17-jre-alpine,需要装 curl(healthcheck 依赖它,详见 C4 篇)和 tzdata

# ❌ 默认写法:直连 dl-cdn.alpinelinux.org
RUN apk add --no-cache curl tzdata

dl-cdn.alpinelinux.org 在国内同样很慢——345 秒装两个小包

修复:先换源再装

RUN sed -i 's|dl-cdn.alpinelinux.org|mirrors.aliyun.com|g' /etc/apk/repositories \
    && apk add --no-cache curl tzdata

换源后:3–10 秒(本项目实测整体降到约 170 秒,包含镜像层重建的开销)。

sed 替换而不是覆盖整个 /etc/apk/repositories 文件,是因为不同 Alpine 版本的源地址格式略有差异,替换域名(而非重写文件)兼容性更好。

顺带一提:mirrors.aliyun.com 的 Alpine 源路径结构与官方一致,所以只换域名就够。

四、修复效果

环节 修复前 修复后
Maven 依赖下载 1714 秒 aliyun-central
apk add curl tzdata 345 秒 ~3–10 秒
common 模块构建 —— 33 秒
整体构建 > 41 分钟未完成 约 4 分钟

验证是否真的走了镜像,看构建日志里有没有这个关键字:

Downloaded from aliyun-central: https://maven.aliyun.com/repository/central/...

看到 aliyun-central 才算生效。 如果日志里还是 repo.maven.apache.org,说明 settings.xml 没被 Maven 读到(路径写错、或 COPY 顺序错导致旧层被复用)。

五、顺带说一下分层设计

完整的 Dockerfile 分层顺序(本项目实际使用):

# ① 换源配置(几乎不变)
RUN mkdir -p /root/.m2 && cat > /root/.m2/settings.xml << 'EOF'
...
EOF

# ② 只拷 pom,不拷源码(pom 变化频率:低)
COPY pom.xml .
COPY api-cloud-common/pom.xml           ./api-cloud-common/
COPY api-cloud-service-user/pom.xml     ./api-cloud-service-user/
COPY api-cloud-service-business/pom.xml ./api-cloud-service-business/
COPY api-cloud-service-order/pom.xml    ./api-cloud-service-order/
COPY api-cloud-gateway/pom.xml          ./api-cloud-gateway/

# ③ 预拉依赖(最耗时,但只在 pom 变化时重建)
RUN mvn -B -q dependency:go-offline -DskipTests || true

# ④ 拷源码(变化频率:高)
COPY api-cloud-common/src       ./api-cloud-common/src
COPY api-cloud-service-user/src ./api-cloud-service-user/src

# ⑤ 构建(只在源码变化时重建)
RUN mvn -B clean package -DskipTests -pl api-cloud-service-user -am \
    && cp api-cloud-service-user/target/*.jar /build/app.jar

核心思想:按"变化频率从低到高"排列。 日常改 Java 代码时,只有第 ④⑤ 层重建,依赖层的缓存一直有效。

第 ③ 步的 || true 是有意加的:dependency:go-offline 有时会因为某些插件的元数据问题返回非 0,但它已经把该下的依赖都下完了。不能因为这步返回非 0 就让整个构建失败——后面真正的 package 会验证依赖完整性。

六、复盘

两条可以立刻用到自己项目里的优化

  1. 任何在国内服务器上跑 Docker 构建的项目,第一件事是配 Maven / npm / pip 的国内镜像源,并且放在 COPY 依赖清单之前,让它吃满缓存。
  2. Alpine 基础镜像装包前先 sed 换源dl-cdn.alpinelinux.org 在国内是 5 分钟量级,换阿里云后是秒级。

一条更通用的经验

构建慢不是"环境就是这样",而是有具体原因和具体量级的。先测量,再优化。

本项目之所以能定位到"两个独立瓶颈",是因为分别测了 dependency:go-offlineapk add 的耗时,而不是笼统地觉得"docker build 很慢"。如果只优化其中一个,构建时间也只能减半。

原创文章,作者:ECHO陈文,如若转载,请注明出处:https://www.luweipai.cn/ops/1789977406/

  • 0