使用缓存层优化 Azure DevOps Docker 流水线

6

我试图优化我的Azure DevOps流水线中的构建时间,但是我的Dockerfile中的npm install阶段无法缓存。为什么?

这是我的Dockerfile。我已经将复制package*.json文件和npm install分开成一个层,然后再复制其余的源代码,因为这是最佳实践,并且应该使npm install层在构建之间可缓存。

FROM node:12-alpine3.12 AS builder
WORKDIR /app
ARG VERSION

COPY package.json ./
COPY package-lock.json ./
RUN npm install

COPY . .
RUN npm run build

  ...
FROM node:12-alpine3.12
COPY --from=builder /dist .
  ...

这是我的构建流程。由于Azure每次都在干净的虚拟机上构建,我尝试拉取现有镜像以利用先前的构建缓存(参考:如何在Azure DevOps中启用Docker层缓存)。
- script: |
    registry=myregistry.azurecr.io
    image=${registry}/myApp:$(Build.SourceBranchName)

    # Pull in previously built builder image because cache
    docker pull ${image}-builder
    # Build the builder target
    docker build \
      --target builder \
      --cache-from ${image}-builder \
      -t ${image}-builder \
      --build-arg VERSION=$(Build.BuildNumber) \
      -f apps/myApp/Dockerfile .

    # Pull in previously built image because cache
    docker pull ${image}
    docker build \
      --cache-from ${image}-builder \
      --cache-from ${image} \
      -t ${image} \
      --build-arg VERSION=$(Build.BuildNumber) \
      -f apps/myApp/Dockerfile .

    docker push ${image}
    docker push ${image}-builder
  displayName: Build and push an image

正如你所看到的,我已经将 Dockerfile 中的每个阶段与管道中的各自阶段分开。一个用于构建“builder”阶段,另一个用于构建生成的镜像。每个阶段的 docker 镜像都被推送到我的容器注册表中。 在重新构建或 package.json 未更改的构建中,我期望 npm install 层会输出 ---> Using cache,但在运行“builder”阶段时它从未这样做。

Step 1/8 : FROM node:12-alpine3.12 AS builder
12-alpine3.12: Pulling from library/node
188c0c94c7c5: Already exists
c4e63f2c1114: Already exists
74bf6ceff101: Already exists
1f6472fc624b: Already exists
Digest: sha256:f2e453020045d7d93790777bc3ce2c992f097ce9a6d577d73490093df93b0702
Status: Downloaded newer image for node:12-alpine3.12
 ---> ccd680d0b809
Step 2/8 : WORKDIR /app
 ---> Using cache
 ---> 9f88e2fda996
Step 3/8 : ARG VERSION
 ---> Using cache
 ---> 707e936abbc5
Step 4/8 : COPY package.json ./
 ---> Using cache
 ---> 034785fd08a7
Step 5/8 : COPY package-lock.json ./
 ---> Using cache
 ---> ab778dbabb01
Step 6/8 : RUN npm install
 ---> Running in df1dc4b5bf91
    ...
Removing intermediate container df1dc4b5bf91
 ---> 4ee43e4f6095
Step 7/8 : COPY . .
 ---> 9ea6540727f2
Step 8/8 : RUN npm run build
 ---> Running in bd65f90191a5

请注意上面的Removing intermediate container df1dc4b5bf91。这可能与问题有关?尽管我尝试过docker build --rm=false,但在重新构建时仍然没有使用缓存层。 然而,在构建我的流水线的最后一个阶段时,它确实从缓存中运行:
Step 1/16 : FROM node:12-alpine3.12 AS builder
 ---> ccd680d0b809
Step 2/16 : WORKDIR /app
 ---> Using cache
 ---> 9f88e2fda996
Step 3/16 : ARG VERSION
 ---> Using cache
 ---> 707e936abbc5
Step 4/16 : COPY package.json ./
 ---> Using cache
 ---> 034785fd08a7
Step 5/16 : COPY package-lock.json ./
 ---> Using cache
 ---> ab778dbabb01
Step 6/16 : RUN npm install
 ---> Using cache
 ---> 4ee43e4f6095

我错过了什么?

1
这是您控制的构建节点还是公共节点?如果是公共节点,则可能每次获取的不是同一个 - 因此,它将不会缓存NPM包。 - Ben W
1
这是一个公共代理。这就是为什么我拉取现有的镜像,因为我知道每次都会获得干净的虚拟机。参考:https://dev59.com/r1IH5IYBdhLWcg3wh_On - Øystein Amundsen
1个回答

6

搞定了!

问题在于 Dockerfile 中的 ARG 关键字。它总是会改变,因此会创建一个无法缓存的层,从而更改下面其他层的哈希值。

根据 Docker 文档:https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact

ARG 是唯一可以在 Dockerfile 中出现在 FROM 之前的指令。

ARG VERSION

FROM node:12-alpine3.12 AS builder
WORKDIR /app

COPY package.json ./
COPY package-lock.json ./
RUN npm install

COPY . .
RUN npm run build

  ...
FROM node:12-alpine3.12
COPY --from=builder /dist .
RUN if [ "x$VERSION" = "x" ] ; then echo "VERSION not set" ; else echo "$VERSION" > ./assets/version.txt ; fi
  ...

通过在Dockerfile中首先放置ARG,构建上下文仍将接收它,但它不会成为一个层并破坏缓存。

我不太明白你的参数 VERSION 到底是如何使用的?从文档中看,任何在 FROM 之外声明的参数都不能在 FROM 内部使用,但它可以作为 FROM alpine:${VERSION} 的一部分在 FROM 语句中使用。 - jonas
@jonas: 我编辑了上面的解决方案,以展示我如何在Dockerfile中使用VERSION参数(脚本的最后一行)。ARG将成为构建范围内的环境变量。 - Øystein Amundsen
@ØysteinAmundsen 您确定这个有效吗?根据您提供的文档,如果要在阶段内使用ARG,则必须在FROM之后重复该命令。 - PeterE
@PeterE 在我的 Azure 容器上运行正常... ¯_(ツ)_/¯ - Øystein Amundsen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接