如何缓存 GitHub 操作中的步骤?

How do I cache steps in GitHub actions?

假设我有一个包含 2 个步骤的 GitHub 操作工作流。

  1. 下载并编译我的应用程序的依赖项。
  2. 编译并测试我的应用程序

我的依赖项很少更改,并且可以安全地缓存编译的依赖项,直到我下次更改指定其版本的锁定文件。

是否有一种方法可以保存第一步的结果,以便在以后的工作流程中可以跳过该步骤?

My dependencies rarely change and the compiled dependencies can be safely cached until I next change the lock-file that specifies their versions. Is a way to save the result of the first step so that in future workflow can skip over that step?

第一步是:

Download and compile my application's dependencies.

GitHub 操作本身不会为您执行此操作。 我能给您的唯一建议是您遵守 Docker 最佳实践,以便以确保如果 Actions 确实使用了 docker 缓存,您的图像可以被重新使用而不是重建。参见:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#leverage-build-cache

When building an image, Docker steps through the instructions in your Dockerfile, executing each in the order specified. As each instruction is examined, Docker looks for an existing image in its cache that it can reuse, rather than creating a new (duplicate) image.

这也意味着 GitHub 操作 can/will 的底层系统利用了 Docker 缓存。

不过编译之类的东西,Docker是不会用到缓存机制的,所以如果你急需这个,建议你好好想想。另一种方法是从工件商店(Nexus、NPM、MavenCentral)下载 compiled/processed 文件以跳过该步骤。您必须权衡收益与在此基础上添加到构建中的复杂性。

现在通过 cache action. It works across both jobs and workflows within a repository. See also: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/caching-dependencies-to-speed-up-workflows.

原生支持缓存

考虑 following example:

name: GitHub Actions Workflow with NPM cache

on: [push]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    - name: Cache NPM dependencies
      uses: actions/cache@v1
      with:
        path: ~/.npm
        key: ${{ runner.OS }}-npm-cache-${{ hashFiles('**/package-lock.json') }}
        restore-keys: |
          ${{ runner.OS }}-npm-cache-

    - name: Install NPM dependencies
      run: npm install

其中cache动作的pathkey参数用于标识缓存。

可选的 restore-keys 用于可能回退到部分匹配(即,如果 package-lock.json 更改,将使用以前的缓存)。

使用一些 id 作为键的前缀(本例中的 npm-cache)在使用 restore-keys 回退并且有多个不同的缓存(例如,对于 JS 包和系统包)时很有用.否则,一个缓存可能回退到另一个不相关的缓存。同样,OS 前缀在使用矩阵构建时很有用,因此不同系统的缓存不会混淆。

您还可以使用 @actions/cache 构建自己的可重用缓存逻辑,例如:


旧答案:

当前无法使用本机缓存,expected to be implemented by mid-November 2019

您可以使用工件 (1, 2) to move directories between jobs (within 1 workflow) as proposed on the GH Community board. This, however, doesn't work across workflows.

如果您在工作流程中使用 Docker 作为 @peterevans answered, GitHub now supports caching through the cache 操作,但它有其局限性。

因此,您可能会发现 this action 绕过 GitHub 的操作限制很有用。

Disclaimer: I created the action to support cache before GitHub did it officially, and I still use it because of its simplicity and flexibility.

现在 原生支持使用:https://help.github.com/en/actions/automating-your-workflow-with-github-actions/caching-dependencies-to-speed-up-workflows

这是通过使用新的缓存操作实现的:https://github.com/actions/cache

我总结一下这两个选项:

  1. 缓存
  2. Docker

缓存

您可以在工作流程中添加一个命令来缓存目录。当到达该步骤时,它将检查您指定的目录是否以前保存过。如果是这样,它会抓住它。如果没有,它不会。然后在进一步的步骤中,您编写检查以查看缓存数据是否存在。例如,假设您正在编译一些很大且变化不大的依赖项。您可以在工作流程的开头添加一个缓存步骤,然后在目录内容不存在的情况下添加一个构建目录内容的步骤。您第一次 运行 它不会找到文件,但随后它会找到并且您的工作流程会 运行 更快。

在幕后,GitHub 正在将您目录的 zip 文件上传到 github 自己的 AWS 存储。他们会清除超过一周或达到 2GB 限制的所有内容。

这种技术的一些缺点是它只保存目录。因此,如果您安装到 /usr/bin,则必须缓存它!那会很尴尬。您应该改为安装到 $home/.local 并使用 echo set-env 将其添加到您的路径中。

Docker

Docker 有点复杂,这意味着您现在必须拥有一个 dockerhub 帐户并管理两件事。但它更强大。您将保存整台计算机,而不是仅仅保存一个目录!您要做的是制作一个 Docker 文件,其中将包含您的所有依赖项,例如 apt-get 和 python pip 行,甚至是长编译。然后您将构建 docker 图像并将其发布在 dockerhub 上。最后,您将在新 docker 图像上将测试设置为 运行,而不是例如 ubuntu-latest。从现在开始,它不再安装依赖项,而是只下载图像。

您可以通过将该 Docker 文件存储在与项目相同的 GitHub 存储库中,然后编写一个包含下载最新 docker 图像的步骤的作业来进一步自动化,如有必要,只需重新构建更改后的步骤,然后上传到 dockerhub。然后是“需要”那个并使用图像的工作。这样,您的工作流程将在需要时更新 docker 图像并使用它。

缺点是您的 deps 将在一个文件中,Docker 文件和工作流程中的测试,因此它们不会全部放在一起。另外,如果下载镜像的时间多于构建依赖的时间,这是一个糟糕的选择。


我认为每一个都有优点和缺点。缓存只适用于非常简单的东西,比如编译成 .local。如果您需要更广泛的东西,Docker 是最强大的。

cache 操作只能缓存文件夹的内容。所以如果有这样的文件夹,你可能会通过缓存来赢得一些时间。

例如,如果您使用一些虚构的 package-installer(例如 Python 的 pipvirtualenv,或 NodeJS 的 npm,或任何东西否则将其文件放入文件夹中),您可以通过这样做来赢得一些时间:

    - uses: actions/cache@v2
      id: cache-packages  # give it a name for checking the cache hit-or-not
      with:
        path: ./packages/  # what we cache: the folder
        key: ${{ runner.os }}-packages-${{ hashFiles('**/packages*.txt') }}
        restore-keys: |
          ${{ runner.os }}-packages-
    - run: package-installer packages.txt
      if: steps.cache-packages.outputs.cache-hit != 'true'

所以这里重要的是:

  1. 我们给这一步起个名字,cache-packages
  2. 后面我们用这个名字进行条件执行:if,steps.cache-packages.outputs.cache-hit != 'true'
  3. 为缓存操作指定要缓存的文件夹的路径:./packages/
  4. 缓存键:取决于输入文件的哈希值。也就是说,如果任何 packages.txt 文件更改,缓存将被重建。
  5. 第二步,安装包,只有在没有缓存的情况下才会运行

对于virtualenv的用户:如果你需要激活一些shell环境,你必须在每一步都这样做。像这样:

- run: . ./environment/activate && command