我有一个Azure Devops管道,用于构建将在不同的IoT-Edge设备上运行的docker映像。这些设备的互联网连接非常差,因此小的码头工人差异大小至关重要。
代码库由一个打字稿nodejs服务器(因此yarn build
(组成,它需要node_modules需要使用make gcc等进行构建。
当我在我的机器上本地运行 docker 构建过程时,docker 使用以前构建的中间层,因此使用docker history <image_id>
时的差异仅为 700kb。(实际代码库(。我在构建日志中看到yarn install
是从缓存中获取的,因此层哈希变得相同。
在 azure 中生成映像时,差异变为 90MB。(复制整个node_modules( 我从每个图像中提取了node_modules,并通过比较 SHA-1 和 SHA-256HashMyFiles.exe
比较了每个文件夹中所有文件的哈希值。
但是未压缩的tar哈希并不相同,参考这篇关于Docker层如何散列的文章:Docker如何计算每个层的哈希?它是确定性的吗?
因此,问题是,在 Azure 中构建映像时,如何避免为每个代码更改拉取整个node_modules。
我们讨论过的一种解决方案是使用我们所需的node_modules构建 docker 节点映像预安装。但这不是首选,在更改模块时需要额外的工作。
来自 Azure 中构建的两个相同代码库的 Docker 历史记录:1
PS C:tempcby> docker history a8f3453f4c1c
IMAGE CREATED CREATED BY SIZE COMMENT
a8f3453f4c1c 2 hours ago /bin/sh -c #(nop) CMD ["node" "./dist/index… 0B
<missing> 2 hours ago /bin/sh -c #(nop) ENV NODE_ENV=production 0B
<missing> 2 hours ago /bin/sh -c mkdir ./logs/ 0B
<missing> 2 hours ago /bin/sh -c yarn run build 3.34MB
<missing> 2 hours ago /bin/sh -c #(nop) COPY dir:31a5b4423ce7e6928… 323kB
<missing> 2 hours ago /bin/sh -c #(nop) COPY dir:a234dce19106582d9… 93.7MB
<missing> 2 hours ago /bin/sh -c #(nop) WORKDIR /app 0B
<missing> 2 hours ago /bin/sh -c apk add --no-cache udev 1.83MB
<missing> 2 days ago 70.2MB merge sha256:eef5dfda7c2565cba57f222376d551426487839af67cf659bb3bb4fa51ef688a to sha256:6d1ef012b5674ad8a127ecfa9b5e6f5178d171b90ee462846974177fd9bdd39f
<missing> 2 days ago /bin/sh -c rm -rf latest.tar.gz* /tmp/* … 0B
<missing> 2 days ago /bin/sh -c apk del curl gnupg 0B
<missing> 2 days ago /bin/sh -c curl -sfSL -O https://yarnpkg.com… 0B
<missing> 2 days ago /bin/sh -c for server in ipv4.pool.sks-keyse… 0B
<missing> 2 days ago /bin/sh -c /usr/lib/node_modules/npm/bin/npm… 0B
<missing> 2 days ago /bin/sh -c apk upgrade --no-cache -U && ap… 0B
<missing> 2 days ago /bin/sh -c #(nop) COPY file:fc6fb2d3d0d591f8… 0B
<missing> 2 days ago /bin/sh -c #(nop) COPY dir:3d23406cd5b322399… 0B
<missing> 2 days ago /bin/sh -c #(nop) COPY dir:857b32a43b41ef438… 0B
<missing> 3 days ago /bin/sh -c #(nop) COPY file:20cc2cc5b0ae7508… 0B
<missing> 10 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 10 months ago /bin/sh -c #(nop) ADD file:aa17928040e31624c… 4.21MB
阿拉伯数字
PS C:tempcby> docker history 2fc80525d55e
IMAGE CREATED CREATED BY SIZE COMMENT
2fc80525d55e 45 seconds ago /bin/sh -c #(nop) CMD ["node" "./dist/index… 0B
<missing> 46 seconds ago /bin/sh -c #(nop) ENV NODE_ENV=production 0B
<missing> 46 seconds ago /bin/sh -c mkdir ./logs/ 0B
<missing> 48 seconds ago /bin/sh -c yarn run build 3.34MB
<missing> 57 seconds ago /bin/sh -c #(nop) COPY dir:31a5b4423ce7e6928… 323kB
<missing> About a minute ago /bin/sh -c #(nop) COPY dir:a234dce19106582d9… 93.7MB
<missing> About a minute ago /bin/sh -c #(nop) WORKDIR /app 0B
<missing> About a minute ago /bin/sh -c apk add --no-cache udev 1.83MB
<missing> 2 days ago 70.2MB merge sha256:eef5dfda7c2565cba57f222376d551426487839af67cf659bb3bb4fa51ef688a to sha256:6d1ef012b5674ad8a127ecfa9b5e6f5178d171b90ee462846974177fd9bdd39f
<missing> 2 days ago /bin/sh -c rm -rf latest.tar.gz* /tmp/* … 0B
<missing> 2 days ago /bin/sh -c apk del curl gnupg 0B
<missing> 2 days ago /bin/sh -c curl -sfSL -O https://yarnpkg.com… 0B
<missing> 2 days ago /bin/sh -c for server in ipv4.pool.sks-keyse… 0B
<missing> 2 days ago /bin/sh -c /usr/lib/node_modules/npm/bin/npm… 0B
<missing> 2 days ago /bin/sh -c apk upgrade --no-cache -U && ap… 0B
<missing> 2 days ago /bin/sh -c #(nop) COPY file:fc6fb2d3d0d591f8… 0B
<missing> 2 days ago /bin/sh -c #(nop) COPY dir:3d23406cd5b322399… 0B
<missing> 2 days ago /bin/sh -c #(nop) COPY dir:857b32a43b41ef438… 0B
<missing> 3 days ago /bin/sh -c #(nop) COPY file:20cc2cc5b0ae7508… 0B
<missing> 10 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 10 months ago /bin/sh -c #(nop) ADD file:aa17928040e31624c… 4.21MB
我的 Dockerfile,我尝试了多个 docker文件,有和没有多阶段构建,结果相同。Azure 在下载映像时给出了很大的差异:
FROM mhart/alpine-node:10
RUN apk add --no-cache make gcc g++ python linux-headers udev
WORKDIR /app
# Install node modules first (avoids reinstalling for every source code change).
COPY package.json yarn.lock ./
RUN yarn install
FROM mhart/alpine-node:10
RUN apk add --no-cache udev
WORKDIR /app
COPY --from=0 /app/node_modules ./node_modules
COPY . .
RUN yarn run build
RUN mkdir ./logs/
ENV NODE_ENV production
CMD ["node", "./dist/index.js"]
.dockerignore
node_modules
/build
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/logs
/tests
/testlogs
/dist
/ota
.vscode
.git
编辑 + 临时解决方案
我们最终做了一个自托管的构建代理,它有点贵,但我们为每个操作获得了更快的构建时间和正确的缓存。最重要的是,我们的下载量要小得多。
我不确定为什么每次我们对每个操作运行构建时 docker 构建都会给出一个新的哈希。
如果哈希相同,则生成时间仍然很慢,因为 Azure 生成代理每次都从干净的计算机开始。
对于您正在做的事情,您实际上不需要 2 阶段构建。一个就够了。但是您的方法存在一些普遍问题。
在运行yarn build
之前,您只需要复制 package*.json,而不是上下文中的所有内容(请记住,您的本地上下文已经包含 node_modules 但您的远程服务器不包含(。
执行以下操作时:
COPY --from=0 /app/node_modules ./node_modules
COPY . .
您实际上使用上下文中的任何内容覆盖文件夹node_modules,因此基本上您之前的步骤毫无用处。
我建议你尝试这样的事情:
FROM mhart/alpine-node:10
RUN apk add --no-cache udev
WORKDIR /app
COPY package*.json ./
RUN yarn install
COPY index.js ./
# also copy here any other project files if any
RUN yarn run build
RUN mkdir ./logs/
ENV NODE_ENV production
CMD ["node", "./dist/index.js"]
Docker 将各个步骤的结果缓存在 Dockerfile 中。 这是相当全有或全无;如果上一步已缓存,并且您正在执行的步骤与之前执行的步骤相同,则docker build
将使用缓存的结果,但如果它不相同,则从此缓存中不会产生任何内容。
特别是在您的构建阶段,当您
COPY . ./
如果有任何文件更改,这将使缓存失效;然后,当您在下一行运行yarn install
时,它几乎总是会重复。 此时,您实际上只需要包元数据文件,因此您可以改为
COPY package.json yarn.lock ./
RUN yarn install
这不会在重建时重复。
如果图像大小是一个问题,您还可以yarn install --production
不要将devDependencies
安装到package.json
中。 在典型使用中,您可以在最终运行时映像中执行此操作,但在您的情况下,您需要一个 C 工具链来构建这些依赖项。 这意味着您的 Dockerfile 中有三个阶段:
- 基于 Node,加上一个 C 工具链,可以安装所有依赖项并运行
yarn build
- 基于 Node,外加一个 C 工具链,只能运行
yarn install --production
- 基于 Node,仅从第一阶段
COPY --from=...
构建的应用程序,从第二阶段node_modules
运行时,然后具有通常的EXPOSE
和CMD
元数据