Earthly を GitHub Actions で使ってみよう
ビルドツール Earthly を使ってみよう第二弾です.
前回の記事はこちら
今回は CI 利用編ということで,GitHub Actions で Earthly を利用する例を紹介したいと思います.
サンプルコードは前回と同じリポジトリを用います.
CI での Earthly の実行
ローカルで Earthly を実行する場合はビルドキャッシュがローカルに保存されますが, GitHub Actions などの CI プラットフォームでは CI の実行環境はジョブごとにまっさらな状態で提供されるため,ローカルのビルドキャッシュは利用できません[1].しかし, Earthly には inline cache と explicit cache と呼ばれる異なる実行環境間で共有可能なリモートキャッシュ機能を備えています.この記事ではこれらのキャッシュの利用方法を重点的に説明していきます.
サンプルコード
今回利用したサンプルコードの GitHub Actions ワークフローと Eathfile は以下のような実装となっています.
次の節から,ワークフローのジョブ単位で解説をしていきます.
docker
ジョブ
最初に docker
ジョブから説明していきます.
このジョブは gRPC サーバが動く docker イメージをビルドし, GHCR (GitHub Container Registry) にアップロードするジョブです.このジョブは main
ブランチに変更があったときのみ実行されるようにしています.
Earthly のセットアップ
このジョブの42行目から45行目のステップで,earthly/actions-setup という Earthly 公式のアクションを利用しています.このアクションによって,GitHub Actions で簡単に Earthly を実行できます.ちなみにこのアクションの README では,使用する Earthly バージョンを version: 0.6.20
や version: ^0.6.20
で指定できると書いてますが,自分の確認したv1.0.1
時点ではこの指定方法では動かず,version: v0.6.20
のように指定してあげる必要がありました.
Earthly の実行
このジョブの52行目から56行目のステップで,Earthly を実行し,+docker
ターゲットのビルドを行なっています.この実行でいくつかオプションを利用しているので,オプションごとに説明していきます.
--push
オプション
--push
オプションは,生成したイメージをリモートレジストリにアップロードするためによく用いられます.たとえば,Earthfile
の +docker
ターゲット内で SAVE IMAGE
コマンドを --push
オプション付きで利用している場合に, earthly --push +docker
と実行するとビルドしたイメージをリモートレジストリにアップロードできます.少しややこしいですが,SAVE IMAGE
コマンドと earthly
実行時の両方で --push
オプションが必要であることに注意してください.
また,earthly
コマンドの --push
オプションは,--push
オプション付きの RUN
コマンドを実行するためのオプションでもあります.このユースケースは,外部に影響を与えるコマンドを定義し,--push
オプション付きで実行したときだけそのコマンドを実行するといったものです.たとえば, +deploy
ターゲットを定義し,そのターゲットの中で環境へのデプロイ操作は RUN --push
内に記述することで,earthly --push +deploy
を実行したときだけ外部環境にデプロイするといったことが可能です.
SAVE IMAGE --push
と似た挙動ですが,SAVE IMAGE --push
は --push
なしで実行してもイメージのローカルへの保存は行われるので微妙に違いがあります.
--push
Instructs Earthly to push any docker images declared with the --push flag to remote docker registries and to run any RUN --push commands. For more information see the SAVE IMAGE Earthfile command and the RUN --push Earthfile command.
--push
The --push options marks the image to be pushed to an external registry after it has been loaded within the docker daemon available on the host.
The actual push is not executed by default. Add the --push flag to the earthly invocation to enable pushing.
--push
Push commands are not run by default. Add the --push flag to the earthly invocation to enable pushing.
Push commands were introduced to allow the user to define commands that have an effect external to the build. This kind of effects are only allowed to take place if the entire build succeeds. Good candidates for push commands are uploads of artifacts to artifactories, commands that make a change to an external environment, like a production or staging environment.
後述しますが,--push
オプションはリモートキャッシュのアップロードの可否にも関わります.リモートキャッシュを利用したいだけの時は --push
なし,キャッシュのアップロードも行いたい時は --push
ありにしましょう.
--ci
オプション
--ci
オプションは CI で Earthly コマンドを実行するのに役立ついくつかのオプションを有効にしてくれるオプションです.具体的には,以下のオプションを指定したのと同じ意味になります[2].
--use-inline-cache --save-inline-cache --no-output --strict
これらの内,--use-inline-cache
オプションと --save-inline-cache
オプションは inline cache 機能を用いるためのオプションです.inline cache はアップロードしたイメージをビルドのキャッシュとして用いるための仕組みです.--use-inline-cache
オプションはビルド時に inline cache を読み込むようにするオプションで, --save-inline-cache
はイメージをアップロードする時にそのイメージを inline cache として読み込めるようにメタデータを埋め込むようにするオプションです[3].
inline cache はアップロードされたイメージがビルドのキャッシュとして使えるものでないと効果がありません.マルチステージングビルドのようにビルドをするためのイメージと実際の成果物のイメージが分離していて,かつ最終成果物のイメージのみアップロードしている場合はあまり有効に働かないことが予想できます.後述しますが,今回の例では --remote-cache
オプションを用いて explicit cache を利用しています.explicit cache と inline cache は現状併用できない[4]のでこのサンプルは inline cache を活用できていません.しかし,inline cache には,設定が簡単で利用することのオーバーヘッドが少ないという利点もあるので,効果的な場面があれば explicit cache の代わりに利用できると良さそうです.今のところあんまりユースケースが思いつかないので explicit cache 使えばいいかなと思ってますが.
inline cache の詳しい説明は Earthly Doc の Guides にある以下のドキュメントを参照してください.
--no-output
オプションと --strict
オプションは,それぞれイメージやアーティファクトを出力しないようにするオプションと再現性のない可能性のある処理を実行しないようにするオプションです.ここでの再現性のない可能性のある処理とは,LOCALLY
コマンドや RUN --interactive
コマンドを指してします.
--no-output
Instructs Earthly not to output any images or artifacts. This option cannot be used with the artifact form or the image form.
--strict
Disallow usage of features that may create unrepeatable builds.
--remote-cache
オプション
--remote-cache
オプションは explicit cache を利用するためのオプションです.explicit cache は inline cache と異なり,ビルドキャッシュのためだけのイメージを指定し,そのイメージをキャッシュとして読み込んだり,アップロードしたりします.今回の例では,ghcr.io/emiksk/earthly-example-cache:cache
を指定してビルドキャッシュを読み書きしています.
explicit cache はビルドに指定したターゲットのすべてのレイヤーと SAVE IMAGE --push
を含むターゲットのレイヤーをキャッシュしますが,SAVE IMAGE --cache-hint
コマンドを用いることで,依存するターゲットのキャッシュも含めることができます.たとえば,今回の例では +docker
ターゲットが依存する +proto-go
ターゲットや +deps
ターゲットに SAVE IMAGE --cache-hint
コマンドを追加しています.これにより,ビルドするためのイメージと最終的に生成されるイメージが分離してる場合もキャッシュの恩恵が得られます.
--cache-hint
Instructs Earthly that the current target should be included as part of the explicit cache.
ただし,explicit cache は inline cache と違い,追加のアップロードコストがかかります.なんでもかんでもキャッシュしようとするとキャッシュの効果が相殺されかねません.また,explicit cache はキャッシュをダウンロードすることによって機能するため,ダウンロード量が多くて時間がかかる処理よりも計算量が多くて時間がかかる処理の方が効果的に働きます[5].純粋なダウンロード処理をキャッシュするだけだとアップロードコストによって逆に CI の処理時間が悪化するかもしれません[6].
explicit cache の詳しい説明は Earthly Doc の Guides にある以下のドキュメントを参照してください.
--remote-cache
オプションも例に漏れず,--push
オプションが指定されているときだけキャッシュをアップロードします.メインブランチで実行される CI でだけ --push
オプションを付けてキャッシュをアップロードし,トピックブランチで実行される CI では --push
オプションを付けずにキャッシュを参照するだけにするなどの使い方ができます.
環境変数での指定
Earthly コマンドのオプションは環境変数を用いて渡すことも可能です.CI で指定するオプションが多くなった時は環境変数で指定すると少し見やすくなるかもしれません.今回の例だと以下のように書き直すことが可能です.
- name: Build and push docker image as latest
working-directory: grpc
env:
EARTHLY_PUSH=true
EARTHLY_CI=true
EARTHLY_REMOTE_CACHE="ghcr.io/emiksk/earthly-example-cache:cache"
run: |
TAG=$(date +%Y%m%d-%H%M)-$(git rev-parse --short=10 HEAD)
earthly +docker --DOCKER_TAG=$TAG
lint
ジョブと test
ジョブ
次に lint
ジョブと test
ジョブを説明します.
lint
ジョブと test
ジョブはそれぞれ Earthfile
で定義された +lint
ターゲットと +test
ターゲットを実行するジョブです.これらのジョブは main
ブランチ以外のブランチの変更でも実行されるようになっていて,Pull request が作成されるようなトピックブランチでも実行されます.
これらのジョブでも --remote-cache
オプションが指定されており,docker
ジョブでアップロードされたキャッシュを読み込むようになっています.一方で,--push
オプションは指定されていないため,このジョブが実行されてもリモートキャッシュはアップロードされません.
複数リモートキャッシュの読み込み
--remote-cache
オプションは単一のイメージしか指定できないため,メインブランチでキャッシュをアップロードし,メインブランチとトピックブランチの両方で参照することはできますが,トピックブランチの CI でそのトピックブランチでのキャッシュとメインブランチでのキャッシュの両方を参照する使い方はできません.しかし,CI において,トピックブランチでのキャッシュを見てヒットしなければメインブランチのキャッシュを見るといった処理は一般的です.たとえば,GitHub Actions の actions/cache ではキャッシュのキーを複数渡せるようになっています.
そこで,Earhly に --cache-from
オプションを追加し,複数の異なるイメージを参照できるようにする Pull request を送りました.
この変更はすでにマージされているので,次回のリリースで使用できるはずです (おそらく 0.6.22
). この機能は以下のように使用できます.
earthly --push \
--remote-cache=ghcr.io/emiksk/earthly-example-cache:topic-branch \
--cache-from=ghcr.io/emiksk/earthly-example-cache::main \
+docker
指定されたリモートキャッシュは優先度付きになっており,--remote-cache
オプションで指定されたイメージが最優先で,その次は--cache-from
オプションで先に指定されたイメージ順に優先度が高くなります.
この利用方法の難点として,リモートキャッシュのアップロードは --push
オプションも必要となるため,単純に +docker
ターゲットを指定すると,トピックブランチの CI でも最終成果物のイメージがアップロードされてしまうことです.このため,トピックブランチでの実行方法は多少工夫が必要そうです.
(余談) 異なる環境で同じ Earthly の実行環境を提供する Earthly Satellites
ここまで長々とリモートキャッシュの話をしてきましたが,Earthly の実行環境が同じであればローカルのビルドキャッシュが利用できるため,リモートキャッシュを用いるよりも CI の実行時間が短くなります.また,ローカルでビルドするときも,チームメイト間で同じ実行環境を利用できればビルドキャッシュを共有できます.これをモチベーションのひとつとして開発,提供されているのが Earthly Satellites です.
Satellite と呼ばれる Earthly 実行用のインスタンスを立ち上げ,Earthly 実行時に --sat
オプションで Satellite を指定することで,その Satellite 上でビルドを行えるようです. Earthly Satellites とは別にシークレットを管理するための Cloud secrets も提供されており,セキュアに利用できるようです.
2022年8月時点では Earthly Satellites も Cloud secrets もまだ正式版ではない (それぞれ Beta と Experimental) ですが,気になる方は是非試してみてください.自分はまだ触っていないです.
ちなみに,Earthly CI というのもリリース予定らしいです.既存の CI プラットフォーム (特に GitHub Actions) のエコシステムはかなり強固な感じがしますが,新たな選択肢となり得るのか乞うご期待って感じです.
Earthly CI (coming soon): A cloud-based continuous integration / continuous delivery system that allows you to continuously build your code in the cloud.
おわりに
以上 Earthly の CI 利用編でした.本編では語りませんでしたが,ローカルと CI で全く同じ環境でビルドを実行できるというのは嬉しい場面も多そうだなと感じました.今のところあまり Earthly 使ってますという人を見ることはないですが,ポテンシャルを秘めたツールだと思うので,ぜひみなさん使ってみてください.
ちなみに,Earthly のビルドはやはり Earthly を使っていて,ビルドがとても楽でした.
-
self-hosted runners を利用すればビルドキャッシュをローカルに保持したまま,複数のワークフローを実行することも可能です.https://docs.github.com/ja/actions/hosting-your-own-runners/about-self-hosted-runners ↩︎
-
--image
オプションや--artifact
オプションをつけた際は--use-inline-cache
オプションと--save-inline-cache
オプションのみが有効になります. ↩︎ -
--save-inline-cache
オプションは--push
オプションもないと意味がないので,--use-inline-cache
と--push
の組み合わせだけで十分だったのではないかと思わないでもない. ↩︎ -
explicit cache に関する説明の中で "It is currently not possible to push both inline and explicit caches currently." と書かれていました (この記事を書いてる時に気付いた) . ↩︎
-
キャッシュの効果に関しては inline cache でも同じことが言えますが,inline cache のキャッシュアップロードの追加コストがない (元々アップロードされるイメージをキャッシュとして利用する) 点は explicit cache との大きな違いです. ↩︎
-
純粋なダウンロード処理でも,依存ライブラリの取得のような大量の並列ダウンロードが発生する処理はレイヤーごともってきた方が効率良いんじゃないかと思ってますが,検証はできていないです.Earthly のドキュメントによると
go mod download
は純粋なダウンロード処理らしい.https://docs.earthly.dev/docs/guides/shared-cache#compute-heavy-vs-download-heavy ↩︎
Discussion