MIXI DEVELOPERS NOTE
🌊

大量にある GitHub Actions の Artifact を頑張って消す方法

2024/04/11に公開

問題の発生

普段頻繁に利用している GitHub のリポジトリで、ストレージが爆発していました。

3TB 近くも使っている…?

というわけで調査を開始。
確認したところ、 GitHub Actions でアップロードしている Artifact が 1 つにつき 400MB 近くも消費していることが判明しました。

そして同時に、この問題が発覚する前日ぐらいから、 No space left on device というエラーで GitHub Actions が停止することが発生。

System.IO.IOException: No space left on device : '/home/runner/runners/2.314.1/_diag/Worker_20240312-035245-utc.log'
   at System.IO.RandomAccess.WriteAtOffset(SafeFileHandle handle, ReadOnlySpan`1 buffer, Int64 fileOffset)
   at System.IO.Strategies.BufferedFileStreamStrategy.FlushWrite()
   at System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
   at System.Diagnostics.TextWriterTraceListener.Flush()
   at GitHub.Runner.Common.HostTraceListener.WriteHeader(String source, TraceEventType eventType, Int32 id)
   at GitHub.Runner.Common.HostTraceListener.TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, Int32 id, String message)
   at System.Diagnostics.TraceSource.TraceEvent(TraceEventType eventType, Int32 id, String message)
   at GitHub.Runner.Worker.Worker.RunAsync(String pipeIn, String pipeOut)
   at GitHub.Runner.Worker.Program.MainAsync(IHostContext context, String[] args)

で、いろいろ確認すると、 Artifact に本来含まなくて良いはずのファイルが入っており、これが数百 MB あったため、1つの Artifact が 400MB 近くになったようでした。
1回の GitHub Actions では 50 近くの Artifact をアップロードしていたので、単純計算してそれだけで 20GB にもなります。
また、アップロードしたファイルは後段の job でまとめてダウンロードして処理をしているのですが、 20GB もあるため、 No space left on device が発生しているようでした。
この GitHub Actions は、割とフランクに実行されていたのもあり、積もりに積もった結果 3 TB となってしまったのでした。

対応

Artifact に不必要なファイルが入らないように設定

当たり前ですが、まずは不要なファイルを Artifact に含めないようにします。
Artifact をアップロードする際に使われる upload-artifact という action は、アップロードする path を指定する際に、!で特定のパスを除外することができます。
https://github.com/actions/upload-artifact?tab=readme-ov-file#retention-period
今回はこれを指定して不要なファイルがアップロードされないようにしました。

Retention Period の設定

GitHub のリポジトリの Settings -> Actions -> General のところに、 Artifact and log retention の設定項目があり、ここでリポジトリ単位の Artifact や GitHub Actions の有効期限を設定することができます。

デフォルトの設定は 90 日なのですが、今回のリポジトリではそこまで長く保持する必要もないので、ここは 14 日に設定しました。

Artifact ごとに Retention Period を設定

upload-artifact の action では、アップロードするファイルごとにも Retention Period を設定することができます。
https://github.com/actions/upload-artifact?tab=readme-ov-file#retention-period
今回 Artifact にアップロードしていたファイルは、以前記事に書いた matrix で並列実行したファイルを後段で処理するためだけに上げていたものでした。
https://zenn.dev/mixi/articles/3e9bd4afa34097
後段で処理したあとは必要がなくなるので、これらは Retention Period を 1 日に設定することにしました。

古い Artifact を削除

上記で設定した Retention Period は、設定後にアップロードされるファイルにのみ適用されるため、アップロード済みの Artifact はそのままになります。
アップロード時から(デフォルト設定の) 90 日以上経てば消えてくれますが、その間はストレージに料金がかかるので、古いファイルは消す必要があります。

ところが、 GitHub Actions には Artifact をまとめて消す方法というのがなく、削除するには管理画面からポチポチ手動で消していくか、API 経由で消していくかの二択しかありません。
それはなかなかに辛いので、何か便利なものがないかと探すと、以下のような便利な GitHub Actions がありました。
https://github.com/c-hive/gha-remove-artifacts

これは一定期間より前の Artifact をまとめて消してくれる GitHub Actions です。
今回はファイルが多く削除に時間がかかってしまうことから、これをそのまま実行するのではなく、 act というローカルで GitHub Actions を実行できるツールを使って実行しました。
https://github.com/nektos/act

やり方としては、以下のような適当な GitHub Actions のファイルを作って、

name: Remove old artifacts

on:
  push:
    branches:
      - 'remove_artifacts'

jobs:
  remove-old-artifacts:
    runs-on: ubuntu-latest

    steps:
      - name: Remove old artifacts
        uses: c-hive/gha-remove-artifacts@v1
        with:
          age: '2 weeks'

以下のように act コマンドを実行すれば

act push --job remove-old-artifacts --env 'GITHUB_TOKEN=****'

もりもり Artifact を消してくれます。

 % act push --job remove-old-artifacts --env 'GITHUB_TOKEN=****'
INFO[0000] Using docker host 'unix:///var/run/docker.sock', and daemon socket 'unix:///var/run/docker.sock'
WARN  ⚠ You are using Apple M-series chip and you have not specified container architecture, you might encounter issues while running act. If so, try running it with '--container-architecture linux/amd64'. ⚠
[Remove old artifacts/remove-old-artifacts] 🚀  Start image=catthehacker/ubuntu:act-latest
INFO[0000] Parallel tasks (0) below minimum, setting to 1
[Remove old artifacts/remove-old-artifacts]   🐳  docker pull image=catthehacker/ubuntu:act-latest platform= username= forcePull=true
[Remove old artifacts/remove-old-artifacts] using DockerAuthConfig authentication for docker pull
INFO[0002] Parallel tasks (0) below minimum, setting to 1
[Remove old artifacts/remove-old-artifacts]   🐳  docker create image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[Remove old artifacts/remove-old-artifacts]   🐳  docker run image=catthehacker/ubuntu:act-latest platform= entrypoint=["tail" "-f" "/dev/null"] cmd=[] network="host"
[Remove old artifacts/remove-old-artifacts]   ☁  git clone 'https://github.com/c-hive/gha-remove-artifacts' # ref=v1
[Remove old artifacts/remove-old-artifacts] ⭐ Run Main Remove old artifacts
[Remove old artifacts/remove-old-artifacts]   🐳  docker cp src=/Users/****/.cache/act/c-hive-gha-remove-artifacts@v1/ dst=/var/run/act/actions/c-hive-gha-remove-artifacts@v1/
[Remove old artifacts/remove-old-artifacts]   🐳  docker exec cmd=[node /var/run/act/actions/c-hive-gha-remove-artifacts@v1/dist/index.js] user= workdir=
| Maximum artifact age: 2 weeks ( created before 2024-02-29T08:12:18+00:00 )
| Successfully removed artifact (id: ******, name: ******).
| Successfully removed artifact (id: ******, name: ******).
| Successfully removed artifact (id: ******, name: ******).
...

この action は、一応 GitHub の API の rate limit を加味して実行してくれているようで、大量のファイルがあっても、動かしっぱなしでしっかり消してくれました。
大体 1 〜 2 時間ぐらい放っておいたら全部消せたようでした。

その後

Organization の Settings -> Billing and plans -> 右上の Get usage report から使用状況の CSV がダウンロードできるんですが、ファイルを消した翌日からストレージが 565 MB になりました。

ヨシ!!

MIXI DEVELOPERS NOTE
MIXI DEVELOPERS NOTE

Discussion