docker - 使用 kaniko 缓存运行 docker build 时,npm run build 未缓存
问题描述
我正在尝试为 React 应用程序( github repo)加速我的 Google Cloud Build 。因此,我开始按照官方 Cloud Build 文档中的建议使用 Kaniko Cache 。
看来npm install
我的构建过程的一部分现在确实被缓存了。但是,我希望npm run build
在源文件没有更改时也会缓存它。
我的 Dockerfile:
# Base image has ubuntu, curl, git, openjdk, node & firebase-tools installed
FROM gcr.io/team-timesheets/builder as BUILDER
## Install dependencies for functions first
WORKDIR /functions
COPY functions/package*.json ./
RUN npm ci
## Install app dependencies next
WORKDIR /
COPY package*.json ./
RUN npm ci
# Copy all app source files
COPY . .
# THIS SEEMS TO BE NEVER CACHED, EVEN WHEN SOURCE FILES HAVENT CHANGED
RUN npm run build:refs \
&& npm run build:production
ARG VCS_COMMIT_ID
ARG VCS_BRANCH_NAME
ARG VCS_PULL_REQUEST
ARG CI_BUILD_ID
ARG CODECOV_TOKEN
ENV VCS_COMMIT_ID=$VCS_COMMIT_ID
ENV VCS_BRANCH_NAME=$VCS_BRANCH_NAME
ENV VCS_PULL_REQUEST=$VCS_PULL_REQUEST
ENV CI_BUILD_ID=$CI_BUILD_ID
ENV CODECOV_TOKEN=$CODECOV_TOKEN
RUN npm run test:cloudbuild \
&& if [ "$CODECOV_TOKEN" != "" ]; \
then curl -s https://codecov.io/bash | bash -s - -X gcov -X coveragepy -X fix -s coverage; \
fi
WORKDIR /functions
RUN npm run build
WORKDIR /
ARG FIREBASE_PROJECT_ID
ARG FIREBASE_TOKEN
RUN if [ "$FIREBASE_TOKEN" != "" ]; \
then firebase deploy --project $FIREBASE_PROJECT_ID --token $FIREBASE_TOKEN; \
fi
构建输出:
BUILD
Pulling image: gcr.io/kaniko-project/executor:latest
latest: Pulling from kaniko-project/executor
Digest: sha256:b9eec410fa32cd77cdb7685c70f86a96debb8b087e77e63d7fe37eaadb178709
Status: Downloaded newer image for gcr.io/kaniko-project/executor:latest
gcr.io/kaniko-project/executor:latest
INFO[0000] Resolved base name gcr.io/team-timesheets/builder to builder
INFO[0000] Using dockerignore file: /workspace/.dockerignore
INFO[0000] Retrieving image manifest gcr.io/team-timesheets/builder
INFO[0000] Retrieving image gcr.io/team-timesheets/builder
INFO[0000] Retrieving image manifest gcr.io/team-timesheets/builder
INFO[0000] Retrieving image gcr.io/team-timesheets/builder
INFO[0000] Built cross stage deps: map[]
INFO[0000] Retrieving image manifest gcr.io/team-timesheets/builder
INFO[0000] Retrieving image gcr.io/team-timesheets/builder
INFO[0000] Retrieving image manifest gcr.io/team-timesheets/builder
INFO[0000] Retrieving image gcr.io/team-timesheets/builder
INFO[0001] Executing 0 build triggers
INFO[0001] Resolving srcs [functions/package*.json]...
INFO[0001] Checking for cached layer gcr.io/team-timesheets/app/cache:9307850446a7754b17d62c95be0c1580672377c1231ae34b1e16fc284d43833a...
INFO[0001] Using caching version of cmd: RUN npm ci
INFO[0001] Resolving srcs [package*.json]...
INFO[0001] Checking for cached layer gcr.io/team-timesheets/app/cache:7ca523b620323d7fb89afdd0784f1169c915edb933e1d6df493f446547c30e74...
INFO[0001] Using caching version of cmd: RUN npm ci
INFO[0001] Checking for cached layer gcr.io/team-timesheets/app/cache:1fd7153f10fb5ed1de3032f00b9fb904195d4de9dec77b5bae1a3cb0409e4530...
INFO[0001] No cached layer found for cmd RUN npm run build:refs && npm run build:production
INFO[0001] Unpacking rootfs as cmd COPY functions/package*.json ./ requires it.
INFO[0026] WORKDIR /functions
INFO[0026] cmd: workdir
INFO[0026] Changed working directory to /functions
INFO[0026] Creating directory /functions
INFO[0026] Taking snapshot of files...
INFO[0026] Resolving srcs [functions/package*.json]...
INFO[0026] COPY functions/package*.json ./
INFO[0026] Resolving srcs [functions/package*.json]...
INFO[0026] Taking snapshot of files...
INFO[0026] RUN npm ci
INFO[0026] Found cached layer, extracting to filesystem
INFO[0029] WORKDIR /
INFO[0029] cmd: workdir
INFO[0029] Changed working directory to /
INFO[0029] No files changed in this command, skipping snapshotting.
INFO[0029] Resolving srcs [package*.json]...
INFO[0029] COPY package*.json ./
INFO[0029] Resolving srcs [package*.json]...
INFO[0029] Taking snapshot of files...
INFO[0029] RUN npm ci
INFO[0029] Found cached layer, extracting to filesystem
INFO[0042] COPY . .
INFO[0043] Taking snapshot of files...
INFO[0043] RUN npm run build:refs && npm run build:production
INFO[0043] Taking snapshot of full filesystem...
INFO[0061] cmd: /bin/sh
INFO[0061] args: [-c npm run build:refs && npm run build:production]
INFO[0061] Running: [/bin/sh -c npm run build:refs && npm run build:production]
> thdk-timesheets-app@1.2.16 build:refs /
> tsc -p common
> thdk-timesheets-app@1.2.16 build:production /
> webpack --env=prod
Hash: e33e0aec56687788a186
Version: webpack 4.43.0
Time: 81408ms
Built at: 12/04/2020 6:57:57 AM
....
现在,由于缓存系统的开销,似乎甚至没有速度优势。
我对 Dockerfiles 比较陌生,所以希望我只是在这里遗漏了一个简单的行。
解决方案
简短的回答:缓存失效很难。
在RUN
Dockerfile 的一部分中,可以运行任何命令。一般来说,docker(当使用本地缓存时)或 Kaniko 现在已经决定是否可以缓存此步骤。这通常通过检查输出是否具有确定性来确定 - 换句话说:如果再次运行相同的命令,它是否会产生与以前相同的文件更改(相对于最后一个图像)?
现在,这种简单的视图不足以确定可缓存的命令,因为任何命令都可能具有不影响本地文件系统的副作用——例如,网络流量。如果您运行 acurl -XPOST https://notify.example.com/build/XYZ
将成功或失败的构建发布到某个通知 API,则不应缓存它。也许您的命令正在为管理员用户生成一个随机密码并将其保存到外部数据库 - 这一步也不应该被缓存。
另一方面,npm run build
由于缩小器和捆绑器的工作方式,完全可重现的仍然可能导致两个不同的捆绑包 - 例如,缩小和丑化的构建具有不同的短变量名称。尽管生成的构建在语义上是相同的,但它们不是字节级别的——因此尽管可以缓存此步骤,但docker 或 kaniko 无法识别它。
区分可缓存和不可缓存的行为基本上是不可能的,因此您会在缓存中一次又一次地遇到误报或误报形式的问题行为。
当我在构建管道时咨询客户时,我通常将 Dockerfile 拆分为多个阶段,或者如果 docker 决定某个步骤错误,则将 cache-miss-or-hit-logic 放入脚本中。
当您拆分 Dockerfile 时,您有一个基础映像(其中包含所有依赖项和其他准备步骤)并将自定义可缓存部分拆分为它自己的 Dockerfile - 然后后者引用前一个基础映像。这通常意味着,您必须有某种形式的模板(例如,FROM ${BASE_IMAGE}
在开始时有一个,然后通过envsubst
或更复杂的系统(如 helm)呈现)。
如果这不适合您的用例,您可以选择自己在脚本中实现逻辑。要找出哪些文件发生了变化,您可以使用git diff --name-only HEAD HEAD~1
. 通过将此与更多逻辑相结合,您可以自定义脚本行为以仅在特定文件集更改时执行某些逻辑:
#!/usr/bin/env bash
# only rebuild, if something changed in 'app/'
if [[ ! -z "$(git diff --name-only HEAD HEAD~1 | grep -e '^(app/|package.*)')" ]]; then
npm run build:ref
curl -XPOST https://notify.api/deploy/$(git rev-parse --short HEAD)
// ... further steps ...
fi
您可以轻松将此逻辑扩展到您的确切需求并自己完全控制缓存逻辑 - 但您应该只对 docker 或 kaniko 涉及误报或误报的步骤执行此操作,因为所有后续步骤都不会被缓存对于不确定的行为。
推荐阅读
- c - 使用 fseek 在 C 中重写文件
- spring - 如何有选择地使一个类成为应该在 Spring (Spring Boot 2.x) 中运行的代码
- c++ - C ++将引用传递给同一类中的函数
- node.js - ExpressJS 中间件方法使变量在其他(模块)文件中可用
- c++ - C++如何创建一个不允许模板参数的类模板
- java - Random.nextInt() 返回相同的数字
- windows - 为什么顶级窗口的 GetParent(hwnd) 和 (HWND)::GetWindow(hwnd, GW_OWNER) 会给出不同的结果?
- python - 发现具有特定标准的排列三元组中的可能组合
- javascript - 带有自己的路由文件的 Vue.js + MPA
- java - 范围问题 - 额外添加意图并从 2 个不同的功能开始活动