【GitHub Actions】S3にキャッシュするアクションをリリースしました

GitHub Action - actions-s3-cache

はじめに

GitHub Actionsで、キャッシュをS3に保存するアクションactions-s3-cacheをリリースしました。

github.com

GitHub ActionsにおいてCI/CDを行う際にパッケージインストールやビルドを実行する場面があるかと思いますが、それらの結果をキャッシュするためのアクションとなります。

目次

公式のキャッシュ機能との比較

安定性

GitHub Actions公式のキャッシュ機能actions/cacheは、現在以下の問題を抱えているようです。

  • Pull Requestでコケた時にRe-run jobsするとactions/cacheアクションが正常に動作しない
  • actions/cacheアクションは時折キャッシュの取得に失敗することがある

GitHub Actionsの知見ご紹介 - Masteries

この点に関して、今回公開したアクションは安定しているのではと思います。

ジョブ異常終了時の挙動

公式のキャッシュは以下の仕様となっています。

  • 使用するステップを記述すると、そこがキャッシュをリストアするステップとなるが、キャッシュの保存に関しては、actions/cacheのステップが存在するジョブの最後にPost + actions/cacheというステップ名で実行される
  • ジョブが正常終了しなかった場合は、キャッシュを保存するステップは実行されない

つまり、

# 略
    steps:
      # 略
      - name: cache composer
        uses: actions/cache@v1.1.2
        with:
          path: vendor
          key: composer-v1-${{ hashFiles('composer.lock') }}
      - run: composer install -n –prefer-dist
      - run: vendor/bin/phpunit # テスト

のように、1ジョブ内で

  1. 公式のキャッシュを使用
  2. パッケージのインストール
  3. テストの実行

を実行する場合、初回のテストが通るまでインストールしたパッケージがキャッシュされることはありません。

actions-s3-cacheでは、後続のステップでエラーが発生してジョブが異常終了した時でも、キャッシュの保存を行います。

  • キャッシュを探す
  • 無ければパッケージをインストールする or ビルドする
  • キャッシュを保存する

という一連の流れを、アクション使用の1ステップ内で実行しているためです。

キャッシュヒット率

公式のキャッシュはGitHubリポジトリごとにキャッシュの保存容量が5GBあるそうです。

しかし、実際に使っていると5GBをまだ超過していなさそうなのに、キャッシュミスが発生するケースが見受けられました。

私の職場のとあるリポジトリについて、AさんがActionsでテストをしてキャッシュが保存された後、同じに日にBさんがテストをしたら、キャッシュのキーは同じなのにキャッシュミスになることがありました。

actions-s3-cacheではキャッシュのキーが合えば確実にキャッシュヒットする、はずです。

複数のディレクトリ・ファイルのキャッシュ可否

公式のキャッシュは、キャッシュ対象としてひとつのディレクトリしか指定できません。

また、ファイル名を指定してキャッシュすることができません。

利用シーンは限られるかもしれませんが、actions-s3-cacheでは複数のディレクトリとファイルの組み合わせをまとめてキャッシュ可能です。

使い方

例として、以下のように記述するとnpm ciによって作成されたnode_modulesをS3にキャッシュし、次回以降はS3からリストアします。

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: shonansurvivors/actions-s3-cache@v1.0.1
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          AWS_DEFAULT_REGION: ap-northeast-1
        with:
          s3-bucket: your-s3-bucket-name # 必須
          cache-key: npm-v1-${{ hashFiles('laravel/package-lock.json') }} # 必須 ('.zip' は記述不要)
          paths: node_modules # 必須(キャッシュするディレクトリやファイル。複数ある場合はスペース区切りで記述) 
          command: npm ci # 必須(インストールやビルドのコマンドを記述)
          zip-option: -ryq # 任意 (デフォルト: -ryq)
          unzip-option: -n # 任意 (デフォルト: -n)
          working-directory: laravel # 任意 (デフォルト: ./)

事前準備としては、

  • S3バケットを作成する
  • S3バケットを読み書き可能なポリシーを持ったIAMユーザーを作成する
  • GitHubリポジトリのsettings画面で上記IAMユーザーのアクセスキーIDとのキーをsecretsに設定する

が必要となります。キャッシュですので、S3バケットにはライフサイクルポリシーを付けて一定日数でオブジェクトが削除されるようにすると良いかと思います。

以下はIAMのポリシーの例です。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:GetObject",
                "s3:ListBucket",
            ],
            "Resource": [
                "arn:aws:s3:::your-s3-bucket-name",
                "arn:aws:s3:::your-s3-bucket-name/*"
            ]
        }
    ]
}

なお、アクションの種類には、DockerアクションとJavaScriptアクションが存在しますが、actions-s3-cacheは後者のJavaScriptアクションです。

ですので、GitHubが提供するVM環境で直接実行されます。

最後に

職場では今回のアクションの原型となる、S3キャッシュのためのシェルを作って使っているのですが、GitHubリポジトリごとにシェルをコピペしなければなりませんでした。

今回アクションとしてOSS化したことで共通的に使い回せるようになりました。

この記事を読んでいただいた方にも、このアクションを使っていただけたら幸いです。