在GitHub Actions工作流程中缓存APT软件包

时间:2019-12-10 14:48:08

标签: apt github-actions

我将以下Github Actions工作流程用于我的C项目。工作流在约40秒内完成,但其中一半以上的时间是通过安装valgrind软件包及其依赖项而花费的。

我相信缓存可以帮助我加快工作流程。我不介意再等几秒钟,但这似乎对GitHub的资源毫无意义的浪费。

name: C Workflow

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v1

    - name: make
      run: make

    - name: valgrind
      run: |
        sudo apt-get install -y valgrind
        valgrind -v --leak-check=full --show-leak-kinds=all ./bin

运行sudo apt-get install -y valgrind将安装以下软件包:

  • gdb
  • gdbserver
  • libbabeltrace1
  • libc6-dbg
  • libipt1
  • valgrind

我知道Actions支持特定目录的缓存(并且已经有一些关于此的已解答的SO问题和文章),但是我不确定apt安装的所有不同软件包的最终结果。我假设/bin//usr/bin/并不是受安装软件包影响的唯一目录。

是否存在一种优雅的方式来缓存已安装的系统软件包以供将来的工作流运行?

3 个答案:

答案 0 :(得分:10)

您可以创建一个预安装df<-apply(df,2,as.numeric) colMeans(df, na.rm = T) 的docker映像,并在该映像上运行您的工作流。

创建类似以下内容的valgrind

Dockerfile

构建并将其推送到dockerhub:

FROM ubuntu

RUN apt-get install -y valgrind

然后将以下内容用作您的工作流程:

docker build -t natiiix/valgrind .
docker push natiiix/valgrind

完全未经测试,但您明白了。

答案 1 :(得分:1)

此答案的目的是演示如何使用github操作完成缓存,而不一定要演示其确实显示的如何缓存valgrind,但是随着您的阅读,您将会发现意识到并非所有内容都需要缓存,因此需要考虑缓存和还原缓存与重新安装依赖项之间的权衡。


您可以使用actions/cache操作执行此操作。

将其添加为一个步骤(在需要使用valgrind之前):

- name: Cache valgrind
  uses: actions/cache@v1.0.3
  id: cache-valgrind
  with:
      path: "~/valgrind"
      key: ${{secrets.VALGRIND_VERSION}}

下一步应该尝试安装缓存的版本(如果有的话)或从存储库中安装:

- name: Install valgrind
  env:
    CACHE_HIT: ${{steps.cache-valgrind.outputs.cache-hit}}
    VALGRIND_VERSION: ${{secrets.VALGRIND_VERSION}}
  run: |
      if [[ "$CACHE_HIT" == 'true' ]]; then
        sudo cp --verbose --force --recursive ~/valgrind/* /
      else
        sudo apt-get install --yes valgrind="$VALGRIND_VERSION"
        mkdir -p ~/valgrind
        sudo dpkg -L valgrind | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/valgrind/
      fi

说明

VALGRIND_VERSION机密设置为以下内容的输出:

apt-cache policy valgrind | grep -oP '(?<=Candidate:\s)(.+)'

然后将您的安装命令更改为

apt-get install --yes valgrind=${{secrets.VALGRIND_VERSION}}.

这将使您仅通过更改密钥的值即可在发布新版本时使缓存无效。

dpkg -L valgrind用于列出使用sudo apt-get install valgrind时安装的所有文件。

我们现在可以执行的命令是将所有依赖项复制到我们的缓存文件夹中:

dpkg -L valgrind | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/valgrind/

此外

除了复制valgrind的所有组件之外,还可能需要复制依赖项(在这种情况下,例如libc),但是我不建议您继续学习这条路径是因为依赖关系链从那里开始增长。确切地说,要最终运行适合valgrind的环境,需要复制的依赖项如下:

  • libc6
  • libgcc1
  • gcc-8-base

要复制所有这些依赖项,可以使用与上面相同的语法:

for dep in libc6 libgcc1 gcc-8-base; do
    dpkg -L $dep | while IFS= read -r f; do if test -f $f; then echo $f; fi; done | xargs cp --parents --target-directory ~/valgrind/
done

在那种情况下,当首先安装valgrind所需的全部只是简单运行sudo apt-get install valgrind,并且您的目标是加快速度时,我看不到缓存的好处了。在构建过程中,那么您还必须考虑还原(下载和提取)此缓存所需的时间,而不是再次运行命令以安装valgrind。


要恢复缓存(假设它存储在/tmp/valgrind中),可以使用以下命令:

cp --force --recursive ~/valgrind/* /

基本上,这会将所有文件从缓存复制到根分区。

除了上述过程外,我还通过从源代码安装和编译它来制作example的“缓存valgrind”。现在,缓存的大小约为63MB(压缩),并且仍然需要单独安装libc,这无法达到目的。


参考文献:

答案 2 :(得分:1)

此解决方案类似于投票最多的解决方案。我尝试了建议的解决方案,但对我而言不起作用,因为我正在安装texlive-latexpandoc,它们具有许多依赖性和子依赖性。

我创建了一个可以帮助很多人的解决方案。一种情况是您安装了两个软件包(apt install),另一种解决方案是当您make一个程序时,需要花费一段时间。

解决方案:

  1. 具有所有逻辑的步骤,它将缓存。
    • 使用find创建容器中所有文件的列表。
    • 安装所有程序包或make程序,无论您要缓存什么。
    • 使用find创建容器中所有文件的列表。
    • 使用diff来获取新创建的文件。
    • 将这些新文件添加到缓存目录。该目录将自动存储在actions/cache@v2中。
  2. 加载创建的缓存的步骤。
    • 将所有文件从缓存目录复制到主路径/
  3. 可从缓存和所需的其他步骤中受益的步骤。

何时使用此功能?

  • 我没有使用缓存,软件包的安装大约需要2分钟才能完成所有过程。
  • 使用缓存,第一次创建它需要7到10分钟。
    • 使用缓存大约需要1分钟才能完成所有过程。
  • 仅当您的主流程要花费很多时间时,它才有用。如果您经常部署,则很方便。

实施:

release.yml

name: CI - Release books

on:
  release:
    types: [ released ]
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-18.04
    steps:
      - uses: actions/checkout@v2

      - uses: actions/cache@v2
        id: cache-packages
        with:
          path: ${{ runner.temp }}/cache-linux
          key: ${{ runner.os }}-cache-packages-v2.1

      - name: Install packages
        if: steps.cache-packages.outputs.cache-hit != 'true'
        env:
          SOURCE: ${{ runner.temp }}/cache-linux
        run: |
          set +xv
          echo "# --------------------------------------------------------"
          echo "# Action environment variables"
          echo "github.workspace: ${{ github.workspace }}"
          echo "runner.workspace: ${{ runner.workspace }}"
          echo "runner.os: ${{ runner.os }}"
          echo "runner.temp: ${{ runner.temp }}"
          echo "# --------------------------------------------------------"
          echo "# Where am I?"
          pwd
          echo "SOURCE: ${SOURCE}"
          ls -lha /
          sudo du -h -d 1 / 2> /dev/null || true
          echo "# --------------------------------------------------------"
          echo "# APT update"
          sudo apt update
          echo "# --------------------------------------------------------"
          echo "# Set up snapshot"
          mkdir -p "${{ runner.temp }}"/snapshots/
          echo "# --------------------------------------------------------"
          echo "# Install tools"
          sudo rm -f /var/lib/apt/lists/lock
          #sudo apt install -y vim bash-completion
          echo "# --------------------------------------------------------"
          echo "# Take first snapshot"
          sudo find / \
                -type f,l \
                -not \( -path "/sys*" -prune \) \
                -not \( -path "/proc*" -prune \) \
                -not \( -path "/mnt*" -prune \) \
                -not \( -path "/dev*" -prune \) \
                -not \( -path "/run*" -prune \) \
                -not \( -path "/etc/mtab*" -prune \) \
                -not \( -path "/var/cache/apt/archives*" -prune \) \
                -not \( -path "/tmp*" -prune \) \
                -not \( -path "/var/tmp*" -prune \) \
                -not \( -path "/var/backups*" \) \
                -not \( -path "/boot*" -prune \) \
                -not \( -path "/vmlinuz*" -prune \) \
                > "${{ runner.temp }}"/snapshots/snapshot_01.txt 2> /dev/null \
                || true
          echo "# --------------------------------------------------------"
          echo "# Install pandoc and dependencies"
          sudo apt install -y texlive-latex-extra wget
          wget -q https://github.com/jgm/pandoc/releases/download/2.11.2/pandoc-2.11.2-1-amd64.deb
          sudo dpkg -i pandoc-2.11.2-1-amd64.deb
          rm -f pandoc-2.11.2-1-amd64.deb
          echo "# --------------------------------------------------------"
          echo "# Take second snapshot"
          sudo find / \
                -type f,l \
                -not \( -path "/sys*" -prune \) \
                -not \( -path "/proc*" -prune \) \
                -not \( -path "/mnt*" -prune \) \
                -not \( -path "/dev*" -prune \) \
                -not \( -path "/run*" -prune \) \
                -not \( -path "/etc/mtab*" -prune \) \
                -not \( -path "/var/cache/apt/archives*" -prune \) \
                -not \( -path "/tmp*" -prune \) \
                -not \( -path "/var/tmp*" -prune \) \
                -not \( -path "/var/backups*" \) \
                -not \( -path "/boot*" -prune \) \
                -not \( -path "/vmlinuz*" -prune \) \
                > "${{ runner.temp }}"/snapshots/snapshot_02.txt 2> /dev/null \
                || true
          echo "# --------------------------------------------------------"
          echo "# Filter new files"
          diff -C 1 \
              --color=always \
              "${{ runner.temp }}"/snapshots/snapshot_01.txt \
              "${{ runner.temp }}"/snapshots/snapshot_02.txt \
              | grep -E "^\+" \
              | sed -E s/..// \
              > "${{ runner.temp }}"/snapshots/snapshot_new_files.txt
          < "${{ runner.temp }}"/snapshots/snapshot_new_files.txt wc -l
          ls -lha "${{ runner.temp }}"/snapshots/
          echo "# --------------------------------------------------------"
          echo "# Make cache directory"
          rm -fR "${SOURCE}"
          mkdir -p "${SOURCE}"
          while IFS= read -r LINE
          do
            sudo cp -a --parent "${LINE}" "${SOURCE}"
          done < "${{ runner.temp }}"/snapshots/snapshot_new_files.txt
          ls -lha "${SOURCE}"
          echo ""
          sudo du -sh "${SOURCE}" || true
          echo "# --------------------------------------------------------"

      - name: Copy cached packages
        if: steps.cache-packages.outputs.cache-hit == 'true'
        env:
          SOURCE: ${{ runner.temp }}/cache-linux
        run: |
          echo "# --------------------------------------------------------"
          echo "# Using Cached packages"
          ls -lha "${SOURCE}"
          sudo cp --force --recursive "${SOURCE}"/. /
          echo "# --------------------------------------------------------"

      - name: Generate release files and commit in GitHub
        run: |
          echo "# --------------------------------------------------------"
          echo "# Generating release files"
          git fetch --all
          git pull --rebase origin main
          git checkout main
          cd ./src/programming-from-the-ground-up
          ./make.sh
          cd ../../
          ls -lha release/
          git config --global user.name 'Israel Roldan'
          git config --global user.email 'israel.alberto.rv@gmail.com'
          git add .
          git status
          git commit -m "Automated Release."
          git push
          git status
          echo "# --------------------------------------------------------"

解释部分代码:

在这里,操作缓存指示一个key,它将被生成一次并在以后的执行中进行比较。 path是用于生成缓存压缩文件的文件所在的目录。

      - uses: actions/cache@v2
        id: cache-packages
        with:
          path: ${{ runner.temp }}/cache-linux
          key: ${{ runner.os }}-cache-packages-v2.1

如果有条件key退出cache-hit,则此条件搜索为'true'。

if: steps.cache-packages.outputs.cache-hit != 'true'
if: steps.cache-packages.outputs.cache-hit == 'true'

这不是很关键,但是当第一次执行du命令时,Linux会为所有文件建立索引(5〜8分钟),然后当我们使用find时,只需要大约50秒即可获取所有文件。您可以根据需要删除此行。

后缀为|| true的命令可防止2> /dev/null返回错误,否则该操作将停止,因为它将检测到您的脚本输出了错误。您将在脚本中看到几个这样的结论。

sudo du -h -d 1 / 2> /dev/null || true

这是神奇的部分,使用find生成实际文件列表,但不包括某些目录以优化高速缓存文件夹。它也将在安装和make程序之后执行。在下一个快照中,文件名应为不同的snapshot_02.txt

sudo find / \
      -type f,l \
      -not \( -path "/sys*" -prune \) \
      -not \( -path "/proc*" -prune \) \
      -not \( -path "/mnt*" -prune \) \
      -not \( -path "/dev*" -prune \) \
      -not \( -path "/run*" -prune \) \
      -not \( -path "/etc/mtab*" -prune \) \
      -not \( -path "/var/cache/apt/archives*" -prune \) \
      -not \( -path "/tmp*" -prune \) \
      -not \( -path "/var/tmp*" -prune \) \
      -not \( -path "/var/backups*" \) \
      -not \( -path "/boot*" -prune \) \
      -not \( -path "/vmlinuz*" -prune \) \
      > "${{ runner.temp }}"/snapshots/snapshot_01.txt 2> /dev/null \
      || true

安装一些软件包和pandoc

sudo apt install -y texlive-latex-extra wget
wget -q https://github.com/jgm/pandoc/releases/download/2.11.2/pandoc-2.11.2-1-amd64.deb
sudo dpkg -i pandoc-2.11.2-1-amd64.deb
rm -f pandoc-2.11.2-1-amd64.deb

生成添加了新文件的文本文件,这些文件也可以是符号文件。

diff -C 1 \
      "${{ runner.temp }}"/snapshots/snapshot_01.txt \
      "${{ runner.temp }}"/snapshots/snapshot_02.txt \
      | grep -E "^\+" \
      | sed -E s/..// \
      > "${{ runner.temp }}"/snapshots/snapshot_new_files.txt

最后,将所有文件作为归档文件复制到缓存目录中,以保留原始信息。

while IFS= read -r LINE
do
  sudo cp -a --parent "${LINE}" "${SOURCE}"
done < "${{ runner.temp }}"/snapshots/snapshot_new_files.txt

将所有缓存的文件复制到主路径/中的步骤。

      - name: Copy cached packages
        if: steps.cache-packages.outputs.cache-hit == 'true'
        env:
          SOURCE: ${{ runner.temp }}/cache-linux
        run: |
          echo "# --------------------------------------------------------"
          echo "# Using Cached packages"
          ls -lha "${SOURCE}"
          sudo cp --force --recursive "${SOURCE}"/. /
          echo "# --------------------------------------------------------"

此步骤是我使用缓存生成的已安装软件包的地方,./make.sh脚本使用pandoc进行了一些转换。如前所述,您可以创建其他步骤,这些步骤利用缓存的好处或不利用缓存的步骤。

      - name: Generate release files and commit in GitHub
        run: |
          echo "# --------------------------------------------------------"
          echo "# Generating release files"
          cd ./src/programming-from-the-ground-up
          ./make.sh