Кэширование пакетов 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

Я знаю, что Action'ы поддерживают кеширование определенного каталога (и уже есть несколько ответов на SO-вопросы и статьи по этому поводу), но я не уверен, где находятся все различные пакеты, установленные apt. Я предполагаю, что /bin/ или /usr/bin/ - не единственные каталоги, на которые влияет установка пакетов.

Есть ли элегантный способ кэширования установленных системных пакетов для будущих запусков рабочего процесса?


person natiiix    schedule 10.12.2019    source источник


Ответы (3)


Цель этого ответа - показать, как можно кэшировать с помощью действий github. Необязательно показывать, как кэшировать valgrind, что он показывает, но более того, чтобы показать, что не все может / должно быть кэшировано, и что необходимо учитывать компромиссы между кешированием и восстановлением кеша и переустановкой зависимости.


Для этого вы воспользуетесь действием actions/cache.

Добавьте его как шаг (до того, как вам понадобится valgrind):

- name: Cache valgrind
  uses: actions/cache@v2
  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 secret как вывод:

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

это позволит вам сделать кеш недействительным при выпуске новой версии, просто изменив значение секрета.

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 /tmp/valgrind/* /

Что в основном скопирует все файлы из кеша в корневой раздел.

В дополнение к описанному выше процессу у меня также есть пример кеширования valgrind путем его установки и компиляции из исходников. Размер кэша сейчас составляет около 63 МБ (в сжатом виде), и все еще необходимо установить libc отдельно, что противоречит цели.


Примечание. Другой ответ на этот вопрос предлагает то, что я мог бы считать более безопасным подходом к кешированию зависимостей, используя контейнер, который поставляется с предустановленными зависимостями. Самое приятное то, что вы можете использовать действия, чтобы поддерживать эти контейнеры в актуальном состоянии.

Использованная литература:

person smac89    schedule 11.12.2019
comment
О, я понимаю, это гениально. Я понятия не имел, что вы можете безопасно взять все установленные файлы и просто переместить их в другой каталог, ничего не сломав. Хотя я не уверен, что это работает. Я запустил рабочий процесс 3 раза и всегда иду Cache not found for input keys: ***.. Я добавил VALGRIND_VERSION секрет в Настройки ›Секреты, верно? - person natiiix; 11.12.2019
comment
Мне удалось получить попадание в кеш, но я получаю следующую ошибку от valgrind: --2906-- Reading syms from /lib/x86_64-linux-gnu/ld-2.27.so --2906-- Considering /lib/x86_64-linux-gnu/ld-2.27.so .. --2906-- .. CRC mismatch (computed 1b7c895e wanted 2943108a) --2906-- object doesn't have a symbol table - person natiiix; 11.12.2019
comment
@natiiix есть вероятность, что кэширование valgrind сделало так, что libc зависимость не устанавливается при извлечении кеша. Сейчас меня нет рядом с монитором, но я просмотрел вашу ошибку, и похоже, что это ошибка с valgrind. Вы также можете попробовать установить libc версии 6 и посмотреть, поможет ли это. Я обновлю ответ сегодня же - person smac89; 11.12.2019
comment
Да вроде так. Если я добавлю sudo apt-get install -y libc6-dbg, он будет работать нормально, но тогда я тоже там, где начал, потому что установка этого пакета занимает еще 30 секунд. - person natiiix; 11.12.2019
comment
@natiiix Кажется, что кеширование valgrind может потребовать больше работы, чем ожидалось, но, по крайней мере, это показывает, как кеширование может быть выполнено в ubuntu. Глядя на зависимости valgrind, есть как минимум 6 зависимостей, и я думаю, что все они, вероятно, должны быть кешированы, чтобы это работало. - person smac89; 11.12.2019
comment
Вот почему я разместил список пакетов, которые установлены вместе с valgrind в вопросе. Могу ли я использовать одну и ту же цепочку dpkg -L ... для кеширования их всех? Между прочим, установка libc6-dbg, как я полагаю, также устанавливает все другие зависимости. - person natiiix; 12.12.2019
comment
@natiiix Действительно, можете. Просто используйте цикл for. - person smac89; 12.12.2019
comment
@natiiix обратите внимание, что вам действительно не нужны все эти другие зависимости; если вы запустите apt-cache depends valgrind, вы увидите, что единственная реальная зависимость - это libc6-dbg, которая, в свою очередь, зависит от libc6, а остальные просто предлагаются или рекомендуются, но не требуются для запуска valgrind - person smac89; 12.12.2019
comment
@natiiix другой вариант - скомпилировать и установить valgrind с нуля. Аналогично тому, что делается здесь - person smac89; 12.12.2019
comment
Понятно. Спасибо за вашу помощь. Если бы вы могли отредактировать свой вопрос, чтобы упомянуть также о необходимости разрешения зависимостей, я с радостью его принял бы. Большинство людей не сразу читают комментарии, поэтому, если кто-нибудь когда-нибудь столкнется с вопросом, они, вероятно, также столкнутся с проблемой с зависимостями. - person natiiix; 12.12.2019

Вы можете создать образ докера с предустановленным valgrind и запустить на нем свой рабочий процесс.

Создайте Dockerfile примерно так:

FROM ubuntu

RUN apt-get install -y valgrind

Соберите его и отправьте в dockerhub:

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

Затем используйте в качестве рабочего процесса что-то вроде следующего:

name: C Workflow

on: [push, pull_request]

jobs:
  build:
    container: natiiix/valgrind

    steps:
    - uses: actions/checkout@v1

    - name: make
      run: make

    - name: valgrind
      run: valgrind -v --leak-check=full --show-leak-kinds=all ./bin

Совершенно непроверенный, но идею вы поняли.

person deivid    schedule 29.03.2020
comment
Это очень интересная идея, но она как бы подрывает весь принцип, позволяющий GitHub Actions кэшировать среду / артефакты для будущих запусков, и вместо этого требует некоторых дополнительных усилий с моей стороны. С другой стороны, после того, как это будет сделано, его, вероятно, можно будет довольно легко использовать повторно. - person natiiix; 30.03.2020
comment
Вам решать, что лучше всего подходит для вас или что требует наибольших усилий с вашей стороны ¯_ (ツ) _ / ¯ - person deivid; 31.03.2020
comment
Проголосовали против, потому что основная причина, по которой люди склонны использовать виртуальные машины, заключается в том, чтобы избежать проблем с докером внутри докера. - person Yoker; 01.10.2020
comment
Лично я считаю, что это наиболее разумный ответ, поскольку другие ответы, показывающие кеширование зависимости, вручную показывают, насколько это чревато. - person Code Novitiate; 23.01.2021
comment
Честно говоря, эта идея мне нравится больше, чем мой ответ. Вы даже можете создать отдельный рабочий процесс, который непрерывно создает контейнер и развертывает его на самом github. - person smac89; 18.04.2021

Обновлено: я создал действие GitHub, которое работает как это решение, меньше кода и лучше оптимизированы. Кэшировать все новое

Это решение аналогично наиболее проголосовавшему. Я попробовал предложенное решение, но оно не сработало для меня, потому что я устанавливал texlive-latex и pandoc, которые имеют много зависимостей и подзависимостей.

Я создал решение, которое должно помочь многим людям. Один случай - когда вы устанавливаете пару пакетов (apt install), другой - когда вы make программу, и это занимает некоторое время.

Решение:

  1. Step which has all the logic, it will cache.
    • Use find to create a list of all the files in the container.
    • Установите все пакеты или make программы, все, что вы хотите кэшировать.
    • Используйте find, чтобы создать список всех файлов в контейнере.
    • Используйте diff, чтобы получить новые созданные файлы.
    • Добавьте эти новые файлы в каталог кеша. Этот каталог будет автоматически сохранен с actions/cache@v2.
  2. Step which load the created cache.
    • Copy all the files from the cache directory to the main path /.
  3. Шаги, которые будут извлечены из кеша и другие шаги, которые вам нужны.

Когда это использовать?

  • Я не использовал кеш, установка пакетов занимала около 2 минут, чтобы завершить весь процесс.
  • With the cache, it takes 7~10 minutes to create it the first time.
    • Using the cache takes ~ 1 minute to finish all the process.
  • Это полезно только в том случае, если ваш основной процесс занимает много времени, также он удобен, если вы развертываете очень часто.

Реализация:

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 '[email protected]'
          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, имеет значение «истина».

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
person Israel Alberto RV    schedule 13.12.2020