GitHub Actionsでは依存パッケージやビルド結果などをうまくキャッシュすることで、テストやビルドの時間を短縮できます。 actions/setup-nodeやactions/setup-javaなどの各言語のオフィシャルアクションは各パッケージマネージャーのためのキャッシュ機構を提供していますし、actions/cacheを使って任意のファイルをキャッシュすることもできます。 これらは内部で@actions/cacheパッケージを使っており、キャッシュの機構はGitHub自身の機能と密に結びついています。 しかし、GitHub Actionsのキャッシュはリポジトリごとに10GBまでという制限があり、開発者の多いリポジトリではsetup-nodeのキャッシュだけでもすぐに上限に達してしまいます。 私の所属するチームのリポジトリはGitHub Enterprise Serverにホストされており、キャッシュの制限は25GBに緩和してもらっていますが (参考)、それでも一日に数十GB以上利用してしまう日もあり、効果的にキャッシュを利用できているとは言えません。
今回、GitHub ActionsでファイルをAmazon S3にキャッシュするアクションをフルスクラッチで作りました。 二週間前から作り始めてようやく形になってきたので、タグを打ってMarketplaceにも公開しました。
- uses: aws-actions/configure-aws-credentials@v4 with: aws-region: ${{ vars.S3_CACHE_AWS_REGION }} role-to-assume: ${{ vars.S3_CACHE_ASSUME_ROLE_ARN }} - uses: itchyny/s3-cache-action@v1 with: path: ~/.npm key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | npm-${{ runner.os }}- bucket-name: ${{ vars.S3_CACHE_BUCKET_NAME }} # AWSの認証情報を直に指定することも可能 # aws-region: ${{ vars.S3_CACHE_AWS_REGION }} # aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} # aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
基本的にactions/cache
とほぼ同じように使えますが、いくつかの違いがあります。
まず、ブランチによるスコープ分離がありません。
actions/cache
のこの制約 (参考) は安全だとは思いますが、私のチームでは同じキーのキャッシュが大量に作られることもあり、とても不便に感じています。
私のアクションにはブランチによるスコープは実装していません。
必要であればkeys
やrestore-keys
にブランチ名を含めると良いでしょう。
また、actions/cache
はWindowsとそれ以外のOSでキャッシュが混ざらないようになっていますが、同じことはkey
にrunner.os
を含めることで実現できるので、私のアクションでは実装していません。
そのためenableCrossOsArchive
というオプションはありません。
actions/cache
にはブランチのスコープとは別にバージョンという概念があります (参考)。
簡単に言うと、キャッシュのpath
が異なるキャッシュは別のバージョンとして扱われます。
これは重要な機能で、単純にkey
だけでキャッシュをマッチさせてしまうとpath
だけを変えた時に意図しないキャッシュをリストアしてしまいます。
キャッシュのバージョンがあるおかげで、path
に新しいディレクトリを追加したとしても新しいkey
を考えなくてもよくなっているのです。
私の作ったアクションでも、path
に基づいたハッシュをオブジェクトのキーに付与することで同じような挙動を実装しています。
actions/cache
のpath
が違えば別のキャッシュとして扱われるというこの挙動は、実装を追っていくと納得できる挙動でもあります。
このアクションは、tar
コマンドで--absolute-names
(-P
)オプションを使って絶対パスを含めたアーカイブにして保存しています。
展開時も同じオプションで展開するだけで、例えばpath
に相当する場所に移動するという処理はありません。
そのため、仮にpath
が一つ指定されているだけであったとしても、パスが違えば別のキャッシュとして扱われるのです。
actions/cache/save
で保存したファイルを別のパスにactions/cache/restore
できないのも、この実装によるものです。
今回、アクションを実装する前に既存のアクションが使えるかをかなり調査しましたが、自分のユースケースでまともに動きそうなアクションは一つも見つけられませんでした。
例えばS3にオブジェクトがたくさんある時にうまく動かなかったり、キャッシュのバージョンに相当する挙動を実装していなくてpath
を変えてもリストアしてしまったりしました。
また、actions/cache
をforkしていたり、S3にアクセスできない場合にfallbackする機能を実装していたりして、私が欲しい物に対して実装が大きすぎて実装を追うのもつらく、メンテナンスも厳しそうに感じました (弊社ではVerified creatorでない作者のアクションを導入するにはソースコードの精査が義務付けられています)。
actions/cache
の中でも特に重要な機能を抽出しつつ、キャッシュをS3に保存するだけのシンプルなアクションが欲しかったので、自分で作ることにしました。
actions/cache
はネイティブのtar
コマンドを実行していますが、s3-cache-action
はnode-tar
を使っています。
内部的にはnode-tar
のtar部分はJavaScriptで書かれていて、gzipにはNode.jsのzlib bindingを使っています。
この実装で十分に速度が出ているので、特に問題はないと思っています。
Brotliも検証しましたが、npmパッケージの保存を試したところ圧縮処理がとても遅くなり、キャッシュサイズもgzipと大差なかったのでやめました。
globパターンの展開はactions/cache
と同じく@actions/glob
を使っているので、ここの挙動の際はありません。
GitHub Actionsでのキャッシュにお困りの方は、ぜひ使ってみてください。 それでは、また。 github.com