カラーミーショップ ECブログリレー GitHub Actions

「GitHub Packagesの最新n件を残して他は消す」オリジナルのGitHub Actionsを作った話

カラーミーショップ ECブログリレー GitHub Actions

全国1億3000万人の GitHub Actions をご利用の皆様、いかがお過ごしでしょうか。

こんにちは、なんでも GitHub Actions に移行する会会長の じゅりあん (@windyakin) です。 (そのような会は存在しません)

2年ほど前に GitHub による CI/CD ツールとして発表された GitHub Actions ですが、ペパボでは GitHub Enterprise Server (以下、GHES) を利用しているため昨年の正式リリースからようやく GitHub Actions が使えるようになりました。 GitHub Actions の魅力といえばやはり GitHub との強力な連携、また様々なデベロッパーによって開発されている数多の拡張機能を利用できることにあると思います。そんな GitHub Actions を新たな CI/CD ツールとして導入を進めている方々も多いと思われますが、現在私はカラーミーショップの新環境へのデプロイツールとして GitHub Actions を使用しています。

この構築したデプロイフローの中に「アプリケーションコードを含んだ Docker イメージを作成して GitHub Packages へ push する」というジョブが存在するのですが、トリガーとしているリリースの作成が行われるたびにどんどん Docker イメージが増えていくので、日に日にコスト面を気にするようになってきました。世の中に無限に無料のクラウドストレージがあればよいのですが、実際はそうでもありませんからね。今回はその対応を取るために作成したオリジナル Actions の紹介と作ってみて気づいた注意点などをお伝えしたいと思います。

古いバージョンは削除したいが最新n件は残したい

まず、 GitHub Packages にひたすら溜まっていく Docker イメージを削除するために公式の Actions である delete-package-versions を利用することを考えました。しかしながらこの Actions の挙動は「実行したときに一番古いバージョンからn件分を削除する」というものなので、たとえばバージョンを追加するジョブと削除するジョブが別の Workflow で定義されている場合に、新しいバージョンを追加するジョブの実行が行われないまま削除するジョブが複数回実行されてしまうといつの間にか全てのバージョンが削除されてしまうことが発生してしまいます。

この問題点は新たなバージョン (今回の場合は Docker のイメージ) が作成されるジョブと削除するジョブの実行頻度が不均衡なパッケージであるときに起きやすく、実際に我々の運用ではデプロイ時のリリース作成をトリガーとして新たな Docker イメージのバージョンを追加するジョブが実行されますが、それに対してバージョンを削除ジョブは cron で実行されるようになっており実行のタイミングは別になっているのが現状です。また直近追加されたイメージは現在進行系で使われていることも考えられる上、インシデントなどでアプリケーションの巻き戻し対応が必要になった場合などに、必要以上にイメージが削除されてしまっていると巻き戻し先のイメージがすぐに取得できないということが発生してしまうことも容易に想像できます。

これらを解決するための要件を delete-package-versions では満たすことができないため、自分が見た夢も「ここまでか」と思いつつ途方に暮れながら Actions のソースコードを眺めはじめたところ、幸いにもやっていることは割と単純そうに見えたので、この Actions をベースにして自分が求めている挙動をしてくれる Actions を自作することにしました。

欲しい Actions の要件をまとめて実装する

改めて今回実装したい Actions の内容をまとめると以下になります。

  • 指定したパッケージのバージョン一覧を取ってくる
  • 最新n件を残してそれ以外を削除する
    • 何件を残すかについては Actions 呼び出し時の引数で指定できる
  • n件未満の場合は何もせず終了する(正常系)

この内容のうち delete-package-versions では「バージョンの一覧を取得する」「削除のリクエストを飛ばす」というところはほぼ同じなので大いに参考にしつつ、 GraphQL や Actions で渡される入力情報のクラス以外はほぼフルスクラッチで書いて実装を行いました。

実際にできたものがこちらです

実装したときの話は一旦置いておいて、まずは今回作った Actions の紹介をします。ソースコードは GitHub にも公開しており、 Marketplace にも登録してあるのでみなさんのお手元の環境でも利用することができるようになっています。

使い方のサンプル

使い方としてはジョブの steps に以下を追記することで、ジョブの実行時に処理を実行してくれます (下記の例は最新10件のみを残すという設定) 。

- uses: windyakin/delete-package-versions-keep-latest@v1
  with:
    owner: windyakin
    repo: package-available-repository
    package-name: package-name
    keep-latest-package-versions: 10

既知の問題点

とりあえず自分の要件だけ満たせればよいということで作ったので、わかっているだけでも以下の問題点があります。

  • GitHub Container Registry (ghcr.io) 上に公開しているリポジトリには対応していない
  • バージョンを指定した除外設定ができない
  • 1回に削除できるのは100件まで

ただ、現状自分で使う限りでは十分に事足りているので、今後利用者が増えてきた場合にアップデートとして対応していこうと思っています。

Actions の作り方については手厚い公式の資料を参照

今回は Node.js でオリジナルの Actions を作成したのですが、作り方については以下にある手厚いぐらいの様々な情報が GitHub から公開されています。

なので作り方の詳細に関しては上記を参照してもらうことにして、ここでは実際に自分が Actions を作ったときにわからなかった・ハマったポイントをいくつか紹介・解説したいと思います。

オリジナルの Actions を作るときにハマったポイント

ライブラリはソースコードに含まれている必要がある

Node.js プロジェクトの場合、 package.json に必要なライブラリ類を記述して node_modules ディレクトリを .gitignore に追加するというのが定石ですが、 GitHub Actions では実行前に package.json を読み込んで npm install をする…なんてことはしてくれないので、成果物となる Git リポジトリ上には使用するライブラリのコードも完全に含まれている必要があります。

一番手っ取り早いのは node_modules ディレクトリごと Git にトラックさせる方法ですが、 node_modules 以下には大量のファイルが存在するので気持ち悪さは拭えきれません。一つの解決方法として npm install した結果の node_modules を配置するというものもありますが、 Node.js のライブラリには OS に依存するライブラリも存在するため npm install する実行環境への配慮が必要になったりと面倒です。

公式のテンプレートリポジトリ ではこの問題の解決方法として、 ncc という Node.js 向けのバンドラーツールを使用しています。

vercel/ncc: Compile a Node.js project into a single file. Supports TypeScript, binary addons, dynamic requires.

これを使ってコンパイルを行うと webpack のように成果物が1つのファイルになります。出てきた成果物だけ Git で管理すればよいので node_modules を直接配置するよりかはよほど管理が楽になるはずです。

ただ ncc によるビルドを手元で実行してコミットを積む運用だと、ビルドを忘れたまま push してしまうということを自分自身幾度とやらかしているので、できれば成果物のビルド自体を Actions 化してしまう、または CI などでビルド忘れを検知できるようにするとよいでしょう。

どこでデフォルト値を与えるかは一考の余地あり

Actions の入力に対する未入力時のデフォルト値を与える処理はコード側で補完できるのはもちろんのこと、 Actions の内容を定義する action.yml にもデフォルト値を定義することができます。

inputs:
  sample-input-value:
    required: false
    default: "10"

結局どっちがいいかというのは自分でも掴みかねていますが、プログラムのコードに書くよりかは action.yml に定義しておくほうが実装言語を読めない人にも伝わりやすく、 YAML でデータが構造化されるのでプログラマブルに Actions のデフォルト値の情報を取得できるというメリットもあるため、できる限り action.yml に載っているほうがよいと思います。もちろん実装側でしか取得できないような情報などをデフォルト値としている場合にはその限りではないですが。

ゆるやかなバージョン指定の運用は難しい

GitHub 公式で用意されている Actions ではよく actions/checkout@v2 のようにメジャーバージョンだけを指定するとそのメジャーバージョンの最新版を拾ってくれることがあります。自分はてっきり Semver 形式でタグを切っておくと GitHub Actions がいい感じにしてくれるのだろうと思っていたのですが、実際のところはそんなことはなく、この呼び出し方法の実現のためには v1 という名前のブランチを作るか、 1.x.x 系のタグを作ったときに v1 のタグを付け替える (またはそれを実現する Workflow を書く) などの対応を取らなくてはなりません。完全に自分向けの Actions であれば @main などを指定することも選択肢に入りますが、 GitHub Marketplace で Public に公開しており多数の人が使う可能性を考えると破壊的変更を加えられなくなるのであまり好ましくないと思います。

おわりに

今回は自作した Actions の紹介、またその作成にあたっていくつかの注意点をお伝えしました。ただ自作 Actions の作成に必要な情報は全て GitHub によって手厚いドキュメントやサンプルなどで集められるようになっているので、新規開発の障壁も少なく気軽に作ることができるようになっています。実際に自分も今回紹介した Actions 以外にもいくつか社内向けの自作 Actions を作って業務で利用するなど、拡張性の高さやその手軽さの恩恵に預かっています。みなさんも自作の Actions を作ってみてはいかがでしょうか。