GitHub AcitonsでRailsプロジェクトのrspec/rubocopを実行するような場合に、bundle install
したgemのキャッシュを行いたいのだが、Permission denied
エラーが出てうまくキャッシュが作れず、キャッシュキーが汚染1されることがあった。
Post job cleanup.
/bin/tar --posix --use-compress-program zstd -T0 -cf cache.tzst -P -C /home/runner/work/project/project --files-from manifest.txt
/bin/tar: ../../../../../tmp/cache/bundle/gems/puma-5.1.1/ext/puma_http11/.gem.20201227-1-wtkvl5: Cannot open: Permission denied
/bin/tar: ../../../../../tmp/cache/bundle/gems/mini_racer-0.3.1/ext/mini_racer_extension/.gem.
[中略]
/bin/tar: ../../../../../tmp/cache/bundle/gems/sassc-2.4.0/ext/.gem.20201227-1-1ompol5: Cannot open: Permission denied
/bin/tar: Exiting with failure status due to previous errors
Warning: Tar failed with error: The process '/bin/tar' failed with exit code 2
上記のエラーはローカルの /var/cache/bundle
ディレクトリを、コンテナの /bundle
にマウントした状態で BUNDLE_PATH=/bundle bundle install
を実行し、/var/cache/bundle
を Actions/Cache
でキャッシュさせる場合に発生した
docker-compose と workflow のyamlは下記のような感じ
version: '3.7'
services:
app:
...
volumes:
- ./:/app:cached
- /var/cache/bundle:/bundle
jobs:
...
rspec:
...
env:
GEMS_CACHE_DIR: /tmp/cache/bundle
steps:
...
- name: Cache bundle gems
id: cache-bundle-gems
uses: actions/cache@v2
with:
path: ${{env.GEMS_CACHE_DIR}}
key: ${{ runner.os }}-${{ matrix.ruby }}-${{ env.GEM_CACHE_TAG }}-${{ hashFiles('Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.ruby }}-${{ env.GEM_CACHE_TAG }}-
解決策 1 アクセス権限を書き換える
アクセス権限の問題なので、雑にアクセス権限を書き換えてしまう方法。
cache作成時のtarコマンドでsudoがつけられたらいいのだが、そういうオプションはないので、下記のようなアクションsteps
の最後に差し込む
- name: Fix permissions of bundle
id: fix-permissions-of-bundle
run: sudo chmod -R a+rwx ${{env.GEMS_CACHE_DIR}}
この方法で多分大丈夫。
ですが、アクセス権限書き換えることになるので、その辺りの動作確認は取れません(そこが問題になることなんてほぼないと思いますが…
解決策 2 コンテナ上でtarを作成してcacheさせる
アクセス権限の問題が発生するのは、コンテナ上で bundler install
しているのにローカルでtarを作成しているため。
コンテナ上で /bundle
のtarを作成し、そのtarを Actions/Cache
でキャッシュして貰えば問題ありません。
下記の設定では必要最低限の軽量コンテナのみを立ち上げて(今回は standalone
という名前のコンテナを使用している)、tarを作っています。
env:
GEMS_ARCHIVE_DIR: /tmp/cache/bundle-gem-archive
...
# $GEMS_ARCHIVE_DIR ディレクトリをキャッシュ
- name: Cache bundle gems
id: cache-bundle-gems
uses: actions/cache@v2
with:
path: ${{ env.GEMS_ARCHIVE_DIR }}
key: ${{ runner.os }}-${{ matrix.ruby }}-${{ env.GEM_CACHE_TAG }}-${{ hashFiles('Gemfile.lock') }}
restore-keys: |
${{ runner.os }}-${{ matrix.ruby }}-${{ env.GEM_CACHE_TAG }}-
# $GEMS_ARCHIVE_DIR をマウントした軽量コンテナ上で /archive/bundle.tar からファイルを抽出
- name: Unarchive bundle gems cache
id: unarchive-bundle-gems-cache
run: test -f ${{env.GEMS_ARCHIVE_DIR}}/bundle.tar
&& docker-compose run --rm -v ${{ env.GEMS_ARCHIVE_DIR }}:/archive:cached standalone
tar -xf /archive/bundle.tar -C /
|| echo "bundle gem cache does not exist."
...
# 軽量コンテナ上で $GEM_HOME の内容をtarで固め、/archive/bundle.tar の名前で保存
- name: Create bundle gems archive
id: create-bundle-gems-archive
if: steps.cache-bundle-gems.outputs.cache-hit != 'true'
run: docker-compose run --rm -v ${{ env.GEMS_ARCHIVE_DIR }}:/archive:cached
standalone tar -cf /archive/bundle.tar -C / bundle
この方法の場合、コンテナ上でtarを実行するので多少遅くなるようだが自分の環境の場合はほぼ誤差(数秒程度)しか変わらなかった(gemの量などにもよると思う)。