GitHub Actions上でDockerイメージをビルドしてECRにPushするサンプル(キャッシュ付き)
広島の吉川です。
GitHub Actionsエコシステムにはdocker/build-push-actionというDockerイメージのビルド+プッシュ+キャッシュをよしなに行ってくれる便利なアクションがあります。
これを使ってECRにプッシュしたい、というのは結構多いシチュエーションだと思うのですが、意外にまだ情報が少ない気がしたため調査・検証してみました。
workflowの実行時間短縮のためキャッシュ設定も入れていきます。
TLDR
ECRへのプッシュ+actions/cacheでビルドをキャッシュするサンプルコードです。
# .github/workflows/build.yml on: push permissions: id-token: write contents: read env: AWS_ROLE_ARN: arn:aws:iam::xxxxxxxxxxxx:role/my-aws-role # GitHub Actions OIDC用IAMロール ECR_REGISTRY: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com # ECRレジストリURL ECR_REPOSITORY: my-ecr-repository # ECRリポジトリ名 jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: docker/setup-buildx-action@v1 - uses: actions/cache@v2 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} restore-keys: | ${{ runner.os }}-buildx- - uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ap-northeast-1 - uses: docker/login-action@v1 with: registry: ${{ env.ECR_REGISTRY }} - uses: docker/build-push-action@v2 with: push: true tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max - name: Move cache run: | rm -rf /tmp/.buildx-cache mv /tmp/.buildx-cache-new /tmp/.buildx-cache
actinos/cacheを使わず、docker/build-push-actionのtype=ghaを使ったキャッシュの場合は以下になり、より記述はすっきりします。ただ、type=ghaの指定はExperimentalステータスである点に注意する必要があります。
# .github/workflows/build.yml on: push permissions: id-token: write contents: read env: AWS_ROLE_ARN: arn:aws:iam::xxxxxxxxxxxx:role/my-aws-role # GitHub Actions OIDC用IAMロール ECR_REGISTRY: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com # ECRレジストリURL ECR_REPOSITORY: my-ecr-repository # ECRリポジトリ名 jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: docker/setup-buildx-action@v1 - uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ap-northeast-1 - uses: docker/login-action@v1 with: registry: ${{ env.ECR_REGISTRY }} - uses: docker/build-push-action@v2 with: push: true tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }} cache-from: type=gha cache-to: type=gha,mode=max
事前準備
事前に
- ECRの作成
- GitHub Actions用のIAMロール作成
が必要です。本記事では割愛しています。
GitHub Actions用のIAMロール作成については下記をご覧ください。
GitHub Actions OIDCでconfigure-aws-credentialsでAssumeRoleする | DevelopersIO
docker/build-push-actionを使ってイメージをビルド+ECRにプッシュする
docker/build-push-actionを使ってビルドとプッシュを行います。
ECRのログインは
- Docker公式 docker/login-action
- AWS公式 aws-actions/amazon-ecr-login
の2つのアクションが選択肢に上がりますが、今回はdocker/login-actionを使います。おそらくaws-actions/amazon-ecr-loginでもやりたいことは実現できるのですが、docker/build-push-actionと組み合わせるという観点でdocker/login-actionの方が情報やサンプルコードが多いと感じたためです。
特に、
- docker/login-actionのREADMEにaws-actions/configure-aws-credentialsとdocker/login-actionを組み合わせるサンプルコード
- docker/build-push-actionのREADMEにdocker/login-actionとdocker/build-push-actionを組み合わせるサンプルコード
をそれぞれ参考にできるのが大きく、これら一次情報のサンプルコードを合体するだけで「aws-actions/configure-aws-credentials+docker/login-actionでECRにログインし、docker/build-push-actionでビルドとプッシュを行う」が実現できました。
それが以下のworkflowファイルになります。
# .github/workflows/build.yml on: push permissions: id-token: write contents: read env: AWS_ROLE_ARN: arn:aws:iam::xxxxxxxxxxxx:role/my-aws-role # GitHub Actions OIDC用IAMロール ECR_REGISTRY: xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com # ECRレジストリURL ECR_REPOSITORY: my-ecr-repository # ECRリポジトリ名 jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: docker/setup-buildx-action@v1 - uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ap-northeast-1 - uses: docker/login-action@v1 with: registry: ${{ env.ECR_REGISTRY }} - uses: docker/build-push-action@v2 with: push: true tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
- uses: docker/setup-buildx-action@v1
についてですが、Buildxというプラグインを導入することで既存のdockerコマンドの機能を拡張できるようです。
Docker Buildx | Docker Documentation
Docker Buildx is a CLI plugin that extends the docker command with the full support of the features provided by Moby BuildKit builder toolkit. It provides the same user experience as docker build with many new features like creating scoped builder instances and building against multiple nodes concurrently.
そして、docker/build-push-actionの説明に
GitHub Action to build and push Docker images with Buildx(強調は引用者による)
とあり、Buildxの使用が前提になっているように見えます。READMEのサンプルコードでもBuildx導入の記述があるため倣っています。
キャッシュの仕組みを加える
docker/build-push-actionでキャッシュする方法についてはリポジトリのcache.mdをチェックすると一通り分かるようになっています。
一次情報でここまでまとめてくれているのは助かりますね。
今回はこの中から3つの方法を試しました。
方法1 actions/cacheを使う
actions/cacheを使ってキャッシュするのはGitHubActionsでは王道な方法だと思います。シンプルで汎用性が高いActionなので、一度使い方を押さえればDocker以外のキャッシュにも横展開的に適用できるのが嬉しいです。
後述のtype=ghaがExperimentalステータスという点も鑑みると、現状最も安牌な方法といえそうです。
# .github/workflows/build.yml(一部抜粋) jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: docker/setup-buildx-action@v1 + - uses: actions/cache@v2 + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx- - uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: ${{ env.AWS_ROLE_ARN }} aws-region: ap-northeast-1 - uses: docker/login-action@v1 with: registry: ${{ env.ECR_REGISTRY }} - uses: docker/build-push-action@v2 with: push: true tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }} + cache-from: type=local,src=/tmp/.buildx-cache + cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max + - name: Move cache + run: | + rm -rf /tmp/.buildx-cache + mv /tmp/.buildx-cache-new /tmp/.buildx-cache
name: Move cache
については、
⚠️ At the moment caches are copied over the existing cache so it keeps growing. The Move cache step is used as a temporary fix (see https://github.com/moby/buildkit/issues/1896).
とあるように、現状、キャッシュがどんどん増えてしまう問題があるため、このように都度クリアする処理が必要なようです。将来的にはこの処理を書かなくても済むように解決されると思われます。
方法2 type=ghaを使う
actinos/cacheを使わず、docker/build-push-actioのcache-from・cache-toにtype=ghaを指定する方法は2行追加するだけで実現できます。驚くほど簡潔に済むので一番採用したいですが、まだExperimentalである点に注意する必要があります。
build-push-action/cache.md at master · docker/build-push-action
? This cache exporter is considered EXPERIMENTAL until further notice. Please provide feedback on BuildKit repository if you encounter any issues.
# .github/workflows/build.yml(一部抜粋) - uses: docker/build-push-action@v2 with: push: true tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max
方法3 type=registry+ECRを使う(失敗)
最後はRegistry Cacheを使う方法です。今回はイメージをECRにプッシュするのでキャッシュもECRで行いたいです。
ということで以下の記述を行いました。
# .github/workflows/build.yml(一部抜粋) - uses: docker/build-push-action@v2 with: push: true tags: ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }} + cache-from: type=registry,ref=${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:buildcache + cache-to: type=registry,ref=${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:buildcache,mode=max
ところがGitHub Actionsを走らせるとエラーが発生しました。
Error: buildx failed with: error: failed to solve: error writing manifest blob: failed commit on ref "sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx": unexpected status: 400 Bad Request
調べてみたところ、どうもBuildxのmanifestと呼ばれるファイル形式にAmazon ECR側が未対応のようでした。
- [ECR] [request]: support cache manifest · Issue #876 · aws/containers-roadmap
- ECR: support manifest lists · Issue #505 · aws/containers-roadmap
- How to export cache in buildkit · Issue #699 · moby/buildkit
これらのIssueで報告されており、機能リクエストも行われているようですが、まだ対応はされていないようです。
実際に試せてはいないですが、DockerHubやGitHub Container Registryであれば使えそうです。
キャッシュの効果を計測してみた
試しに以下のようなシンプルなPHPプロジェクトを作って実験してみました。
Dockerfile
ucan-lab/docker-laravelを参考にしつつ下記のDockerfileを作成しました。
FROM php:7.3 RUN apt-get update && \ apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \ apt-get clean RUN docker-php-ext-install intl pdo_mysql zip bcmath WORKDIR /app COPY index.php index.php
今回は特に使いませんが、PHP拡張をインストールしています。キャッシュの効果を見れるようにビルド時間をやや長くしたいためです。
本当はCMDやENTRYPOINTも設定しないといけないですが、実際にサーバ起動するまではやらないので省略しています。
index.php
<?php echo 'hello';
検証手順
まず、コードをgit pushして一度GitHub Actionsを走らせます。これによりキャッシュが作成されます。
その状態でアプリケーションコードのindex.phpに変更を入れてcommit+pushします。
<?php - echo 'hello'; + echo 'world';
変更したのはindex.phpのみなので、それより前のプロセスはキャッシュが使われます。
FROM php:7.3 RUN apt-get update && \ apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \ apt-get clean RUN docker-php-ext-install intl pdo_mysql zip bcmath WORKDIR /app # ↑↑↑ここより上はキャッシュが使われる(再ビルドしない)↑↑↑ COPY index.php index.php
結果
条件 | 実行時間 |
---|---|
キャッシュなし | 1m55s |
actions/cacheを使ったキャッシュ | 1m13s |
type=ghaを使ったキャッシュ | 49s |
狙い通り、キャッシュにより高速化がなされているようです。その中でも「type=ghaを使ったキャッシュ」が「actions/cacheを使ったキャッシュ」より高速なのが興味深いです。なぜ差が出ているのかの詳細までは追いきれていませんが、ますますtype=ghaが使いたくなり、Stableになるのが待たれます。
参考
- GitHub ActionsのイメージビルドをDockerレイヤキャッシュで高速化(翻訳)|TechRacho by BPS株式会社
- docker/build-push-action@v2 のキャッシュ方法は cache.md を読むと参考になる - ざきの学習帳(旧 zackey推し )
- CI での Docker Build のベストプラクティスを考えてみた - Tech Blog - Recruit Engineer
- docker/build-push-action: GitHub Action to build and push Docker images with Buildx
- GitHub Actions上のRustアプリのDockerイメージビルドを高速化する - blog.endflow.net
- Caching dependencies to speed up workflows - GitHub Docs
- Github Actions で AWS ECR 向け Docker コンテナの Build and Push
- [ECR] [request]: support cache manifest · Issue #876 · aws/containers-roadmap
- ECR: support manifest lists · Issue #505 · aws/containers-roadmap
- How to export cache in buildkit · Issue #699 · moby/buildkit
- moby/buildkit: concurrent, cache-efficient, and Dockerfile-agnostic builder toolkit
- [Web フロントエンド] esbuild が爆速すぎて webpack / Rollup にはもう戻れない | Kabuku Developers Blog