速く、もっと速く!Docker Build Cloudでイメージビルド時間を短縮する

2024/1/23に Docker Build Cloudが発表されました

www.docker.com

早速Docker Build Cloudを試してみました

Docker Build Cloudとは

サイト: https://www.docker.com/ja-jp/products/build-cloud/
ドキュメント: https://docs.docker.com/build/cloud/

イメージビルドをDocker社のCloud環境で行える機能です。 これによりどのようなメリットがあるかというと

  • イメージビルドキャッシュの共有
    • ローカルでイメージビルドするとき、もし他のユーザがビルド済みであればそのキャッシュを使うことができビルド時間が短縮できる
    • CI環境では毎回サーバーが違うため--mount=type=cacheが効かないがDocker Build Cloudであれば効く(!!)
    • ビルドキャッシュが存在するため--cache-fromのようにregistry cacheを使わなくてもよい。通常registry cacheを使うとかなりビルドが速くなるがregistry cache自体をpullする時間, pullしたイメージをextractする時間に地味に時間がかかるがそれがなくなる。
  • マルチアーキテクチャビルドが可能
  • CIツールとのシームレスな統合
    • ローカルのDockerやDocker Compose、GitHub ActionsやCircle CIなどのCIサービスと簡単に統合ができる

デメリットは、

  • Docker Build Cloudでビルドしたイメージをpullする時間がかかる
    • ただ変更があったimage layerだけがpullされるのでpullされるimage layerサイズが小さい場合はすぐ終わる
    • 2024/02/03現在 US Eastリージョンしかないため、Asiaからpullすると非常に時間がかかる。今後EuropeやAsiaリージョンのサポート予定
  • ビルドコンテキストをDocker Build Cloudに転送する時間がかかるケースがある
    • .dockerignoreを適切に設定していればビルドコンテキストサイズはそれほど大きくならないはずですが、何かしらの理由で大きなサイズの場合転送時間がネックになる可能性はあります。
  • planに含まれているビルド時間を超えてビルドしたい場合はビルド時間購入料金がかかる

料金に関して

無料PlanのDocker Personalだと 無料ビルド時間は 50分/月 です。かなり少ないですね。 Docker Teamに入っていれば 400分/月 のビルド時間が付与されますが、これは組織アカウント単位であり、ユーザ数あたりではないので注意。 このように無料付与枠がかなり少ないので本格的にDocker Build Cloudを使用する場合は Docker Build Cloud Team Planに入る必要があります。 年間払いだと $5/ユーザー/月、 月払いだと $6/ユーザー/月。 付与されるビルド時間は 200分/ユーザー/月 です。 これでも足りない場合はビルド時間を追加購入ことができます。

10,000分購入しても$500なのでかなり安い印象。

ちなみに、Docker Personal Planの無料枠の50minを使い切るとビルド時に

ERROR: failed to solve: FailedPrecondition: build cannot proceed build minutes limit of 50 reached

というエラーがでました。

使い方

https://build.docker.com/ にアクセスし、自分のprofileを選択します。

するとこういう画面がでるのでCreate new cloud builderからcloud builderを作成します。(この画面ではすでにmy-builderというcloud builderを作成済み)

作成したcloud builderをクリックすると、以下のような画面になります。

あとはSetup instructionsに表示される内容に沿って進めば簡単にDocker Build Cloudが使用できます。

実際速くなるのか?

イメージサイズが680MBになるDockerfileを使って試してみました。

ローカルでの使用

ローカルでのイメージビルドにDocker Build Cloudを使うメリットの一つはビルドキャッシュを共有出来る点です。
チームで開発をしている場合、フルビルドする場合イメージビルドに時間がかかります。 ビルドキャッシュを共有できれば他の人がすでにビルドした結果をpullすればよくなります。かなりイメージビルドが速くなりそうですよね。 しかし試してみると、Docker Build Cloudでビルドされたイメージをローカルにpullするのに長い時間がかかりました。 680MBのイメージをまるまるpullするのに6分くらい。更新されたimage layerだけをpullする場合でも330MBのサイズで190sくらい。更新されたimage layerのサイズが小さければ(1MB以下)なら数秒で終わりました。
これはDocker Build Cloudが2024/02/03現在、US Eastリージョンしかないため転送速度に時間がかかっているものと思われます。今後EuropeやAsiaリージョンのサポート予定だそうです。 Asiaリージョンのサポートがされてローカルでのpullが速くなればローカルでのDocker Build Cloudの使用も現実的になってくるかと思います。

CI上での使用

今回はCircleCIで試しました。
CircleCIはサーバーがUSリージョンにあるため、Docker Build Cloudでビルドしたイメージのpullにそれほど時間がかからないと見込んだからです。(GitHub Actionsは未調査)

CI上でのイメージビルドにDocker Build Cloudを使うメリットの一つはローカルでの使用と同様にビルドキャッシュを共有出来る点ですが、ポイントがローカルとは若干異なります。

  • Docker Build Cloudサーバーにビルドキャッシュ存在するため--cache-from, --cache-toのregistry cacheを使用しなくてよい。これによりregistry cacheイメージをpull&pushする時間, pullしたimageをextractする時間を削減できる
  • --mount=type=cacheが効く
    • たとえregistry cacheを使っていたとしてもアプリケーションで使うパッケージを更新した場合、ビルド時間がかかっていました。--mount=type=cacheを使うと追加/変更されたパッケージのみインストールすればよくなるためビルドが速くなりますが、CIでは毎回サーバーが異なるため--mount=type=cacheが使えないという課題がありました。Docker Build Cloudではビルドキャッシュが使えるため--mount=type=cacheが効きます。これによりアプリケーションで使うパッケージ更新時のイメージビルドが速くなります。

またもう一つのメリットはCIのサーバースペックを下げられる点です。イメージビルドを高速に行うにはサーバースペックが要求されるため、例えばCircleCIではresource classで大きめのclassを指定していた人も多いかと思います。Docker Build Cloudを使うとDocker Build CloudのサーバーはDocker Build Cloud Team Planの場合16 vCPU、32GB RAMのサーバーが割り当てられます。これによりCI側のサーバースペックを下げることができコスト削減ができる可能性があります。

さてローカルではDocker Build Cloudでビルドしたイメージのpullに時間がかかっていましたが、CircleCI上では680MBのイメージをまるまるpullするのに約20s、更新されたimage layerだけをpullする場合でも330MBのサイズで約10sでした。非常に速いですね。18倍も速いです。ただこれはDocker Build CloudのサーバーがAWS上にあり転送処理がAWSネットワーク内ですんでいるためかもしれません。
また--mount=type=cacheの指定も効いており、パッケージの更新処理も非常に速いです。registry cacheも必要ないためシンプルになります。

キャッシュサイズ

注意する点があるとすればビルドキャッシュサイズになります。
Docker Build Cloudではビルドキャッシュが保存されるわけですが、Docker Build Cloud Team Planの場合最大で200GiBになります。もし数多くのイメージをビルドする場合キャッシュ上限に達してしまう可能性があります。 なおキャッシュ上限に達すると、古いキャッシュは自動的に削除されます。
See: How do I manage the build cache with Docker Build Cloud?

FAQ

https://docs.docker.com/build/cloud/faq

まとめ

自分もそうですが、CI上でのイメージビルド高速化に苦労している人は少なくないと思います。ビルドキャッシュさえ存在すれば細かいことを考えずにすむのになと…よく思っていました。 Docker Build Cloudを使えば手軽かつ安価にイメージビルドの高速化ができそうです。 Productionで使われているイメージビルド処理が速くなるかは実際に試してみないと分からない部分もあるので仕事で検証をしていきたいと思います。

OpenSearchの手動スナップショットリポジトリ登録スクリプトをRubyで書く

OpenSearchで手動スナップショットを作成するためには、事前にスナップショットリポジトリの登録をしなければなりません。

docs.aws.amazon.com

公式ドキュメントのサンプルにはpythonのコードが掲載されています。
Using the sample Python client

この登録スクリプトRubyで書いてみました。

region = 'ap-northeast-1'
service = 'es'

# AWSの認証情報を取得
credentials = Aws::CredentialProviderChain.new.resolve
aws_signer = Aws::Sigv4::Signer.new(service: service, region: region, credentials_provider: credentials)

host = 'OpenSearchドメインエンドポイント'
path = '_snapshot/スナップショットリポジトリ名'
url = URI(host + path)

bucket_name = 'スナップショットS3バケット名'
role_arn = 'スナップショット IAM Role ARN'

payload = {
  type: 's3',
  settings: {
    bucket: bucket_name,
    region: region,
    role_arn: role_arn
  }
}.to_json

# AWSの署名付きリクエストを作成
signed_request = aws_signer.sign_request(
  http_method: 'PUT',
  url: url.to_s,
  headers: { 'Content-Type' => 'application/json' },
  body: payload
)
headers = signed_request.headers
headers['Content-Type'] = 'application/json'
response = Faraday.put(url, payload, signed_request.headers)

puts response.status
puts response.body

AWSの認証情報を取得の部分は各環境に応じて変更してください。
参考: AWSのSigV4署名を付与してIAM認証のAPI GatewayにHTTPリクエストを送るRubyとGoのサンプルコード #Ruby - Qiita

OpenSearchでVPC内にあるドメイン同士でRemote Reindexをする

はじめに

Amazon OpenSearch Serviceで、同一VPC内にあるドメイン同士でRemote Reindexをする方法を紹介します。

まず、公式ドキュメント docs.amazonaws.cn

前提条件

注意すべきは前提条件の部分です。

ドメインでデータ ノードに T2 または T3 インスタンス タイプを使用している場合、リモート再インデックスは使用できません。

検証用でT3インスタンスを使っている場合はインスタンスタイプを変更しましょう。

リモートドメインは、ローカルドメインからアクセス可能である必要があります。VPC 内に存在するリモートドメインでは、ローカルドメインVPC へのアクセスが必要です。このプロセスはネットワーク構成によって異なりますが、VPN またはマネージドネットワークへの接続、もしくは、ネイティブの VPC エンドポイント接続の使用が必要となる場合がほとんどです。詳細については、「VPC 内で Amazon OpenSearch Service ドメインを起動する」を参照してください

これは分かりにくいですが非常に重要です。次で説明をします。

組み込みのVPCエンドポイント接続機能

Migrating Amazon OpenSearch Service indexes using remote reindex - Amazon OpenSearch Service に説明がありますが、同一VPC内にあるドメイン同士であっても、組み込みのVPCエンドポイント接続機能を使ってローカルドメイン(データのコピー先)からリモートドメイン(データのコピー元)へのアクセスができるようにする必要があります。(てっきり同一VPC内にあれば設定いらんやろと思って読み飛ばしていたら必要でハマりました…)

組み込みのVPCエンドポイント接続機能とは?と思いますが、 このセクションReindex data with the Amazon Web Services Management Consoleにやり方が書いてますので、Remote Reindex APIの実行手前までそれ通り進めます。コンソールをぽちぽちやれば終わるはずです。

Remote Reindex API

さて、VPCエンドポイント接続設定が終わったらいよいよRemote Reindex APIの実行です。
ドキュメントには以下のように書かれていると思います。

POST _reindex
{
   "source":{
      "remote":{
         "host":"endpoint",
         "username":"username",
         "password":"password"
      },
      "index":"remote-domain-index-name"
   },
   "dest":{
      "index":"local-domain-index-name"
   }
}

きめ細かなアクセスコントロールを有効にしていて、かつユーザー管理にIAMを使用していない場合でなければusername, paswordはパラメーターとして渡さなくて大丈夫なはずです。
そうすると以下のようになりますが、

POST _reindex
{
   "source":{
      "remote":{
         "host":"endpoint"
      },
      "index":"remote-domain-index-name"
   },
   "dest":{
      "index":"local-domain-index-name"
   }
}

実行すると

  "error" : {
    "type" : "null_pointer_exception",
    "reason" : null
  }

といったエラーが返ってきます。

ドキュメントには書いてないですが、"external": trueオプション必要です。 medium.com の記事を見てやっとエラーが解消できました…。

最終的に以下の形になります。

POST _reindex
{
   "source":{
      "remote":{
         "host":"endpoint",
         "external": true
      },
      "index":"remote-domain-index-name"
   },
   "dest":{
      "index":"local-domain-index-name"
   }
}

なお"host":"endpoint"の部分の endpointOpenSearchドメインエンドポイントにport番号:443をつける必要があります。 つまりこんな感じになります。
"host": "https://hogehoge.ap-northeast-1.es.amazonaws.com:443"

まとめ

Amazon OpenSearch Serviceで、同一VPC内にあるドメイン同士でRemote Reindexをする方法を紹介しました。 remote reindexは snapshot&restoreと比べると snapshot用のS3バケットの作成, snapshotリポジトリの登録、それ用のIAM Role作成などをしなくていいので楽ですね。

ECRにリモートキャッシュ(--cache-to)のmode=maxでイメージプッシュする

2023/11/16 に BuildKit クライアント用の Amazon ECR でのリモートキャッシュサポートの発表 | Amazon Web Services ブログ という記事が発表されて、イメージビルド時のcache-toのオプションmode=maxが使えるようになった。
待望のアップデート。このissueを見たらどれだけ待ち望まれていたかが分かる。

以前まではECRはリモートキャッシュマニフェストに対応していなかったため、リモートキャッシュ先にECRを指定する場合はtype=inlineを指定する必要があった。(参考: Github Actionsのdocker/build-push-actionのcache-toにECRを指定する)

type=inlineだとmode=minしか指定出来ない。mode=minだと結果イメージのレイヤーしかキャッシュされず、マルチステージビルドを使用している場合中間イメージがキャッシュされず、イメージビルドが速くならないという問題があった。
max=modeはすべてのレイヤーをキャッシュするので、中間イメージが更新されてもキャッシュが効いてイメージビルドが速くなる。

リモートキャッシュに関しては Cache storage backends | Docker Docs を参照。

設定例

GitHub Actions

- name: Login to Amazon ECR
  id: login-ecr
  uses: aws-actions/amazon-ecr-login@v1

- uses: docker/build-push-action@v3
  with:
    push: true
    tags: ${{ steps.login-ecr.outputs.registry }}/${{ env.REPOSITORY_NAME }}:${{ github.sha }}
    provenance: false
    cache-from: type=registry,ref=${{ steps.login-ecr.outputs.registry }}/${{ env.REPOSITORY_NAME }}:buildcache
    cache-to: type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${{ steps.login-ecr.outputs.registry }}/${{ env.REPOSITORY_NAME }}:buildcache

CircleCI

- run:
    name: create builder instance
   # ビルダーインスタンスを作成しないとエラーになる
    command: docker buildx create --use  
- aws-ecr/build_and_push_image:
    account_id: ${AWS_ACCOUNT_ID}
    auth:
      - aws-cli/setup:
          role_arn: ${AWS_ROLE_ARN}
          region: ${AWS_DEFAULT_REGION}
    push_image: true
    region: ${AWS_DEFAULT_REGION}
    repo: ${REPOSITORY_NAME}
    tag: ${CIRCLE_SHA1}
    extra_build_args: >-
      --cache-from type=registry,ref=${REGISTRY_URL}/${REPOSITORY_NAME}:buildcache
      --cache-to type=registry,mode=max,image-manifest=true,oci-mediatypes=true,ref=${REGISTRY_URL}/${REPOSITORY_NAME}:buildcache
      --provenance=false

provenance=false にしている理由は docker/build-push-action v3.3.0で導入されたprovenanceオプションにまつわる問題 - chroju.dev を参照

EKS on FargateでのDatadog Cluster Agentの動かし方

概要

Fargate上でEKSのPodを動かしている場合に、Datadog Cluster Agentのマニフェストはどう定義すればいいか分かりづらかったのでメモとして残しておきます。

公式ドキュメント: https://docs.datadoghq.com/ja/integrations/eks_fargate/#cluster-agent-%E3%81%BE%E3%81%9F%E3%81%AF-cluster-checks-runner-%E3%81%AE%E5%AE%9F%E8%A1%8C

設定例

Datadog Cluster Agentをhelmでインストールします。 valuesファイルは以下のようにする。

# see: https://github.com/DataDog/helm-charts/tree/main/charts/datadog#values  
datadog:  
  apiKeyExistingSecret: datadog
agents:  
  enabled: false  
clusterAgent:  
  enabled: true  
  replicas: 2  
  shareProcessNamespace: true  
  tokenExistingSecret: datadog-cluster-agent
  env:  
  - name: DD_EKS_FARGATE  
    value: "true"  
  - name: DD_KUBERNETES_KUBELET_NODENAME  
    valueFrom:  
      fieldRef:  
        apiVersion: v1  
        fieldPath: spec.nodeName
  - name: DD_CLUSTER_NAME
    valueFrom:
      configMapKeyRef:
        key: DD_CLUSTER_NAME
        name: datadog
  resources:  
    requests:  
      cpu: 200m  
      memory: 256M  
    limits:  
      cpu: 200m  
      memory: 256M

datadog ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: datadog
data:
  DD_CLUSTER_NAME: my-cluster-name

datadog Secret

apiVersion: v1
kind: Secret
metadata:
  name: datadog
data:
  api-key: my-api-key

datadog-cluster-agent Secret

apiVersion: v1
kind: Secret
metadata:
  name: datadog-cluster-agent
data:
  token: my-datadog-cluster-agent-token # 32文字以上にすること。じゃないとエラーになる

Fargate上でPodを動かす場合、Datadog AgentコンテナはSidecarとして動かすことになります。 そのDatadog AgentコンテナにDatadog Cluster Agentコンテナと通信するための環境変数を渡してあげます。 必要な環境変数DD_CLUSTER_AGENT_ENABLEDDD_CLUSTER_AGENT_URLDD_CLUSTER_AGENT_AUTH_TOKENDD_ORCHESTRATOR_EXPLORER_ENABLEDです。

Sidecarの定義

spec:
  containers:
  - image: public.ecr.aws/datadog/agent:7.49.1
    name: datadog-agent
    env:
    - name: DD_API_KEY
      valueFrom:
        secretKeyRef:
          key: api-key
          name: datadog
    - name: DD_SITE
      value: datadoghq.com
    - name: DD_EKS_FARGATE
      value: "true"
    - name: DD_PROCESS_AGENT_ENABLED
      value: "true"
    - name: DD_KUBERNETES_KUBELET_NODENAME
      valueFrom:
        fieldRef:
          fieldPath: spec.nodeName
    - name: DD_CLUSTER_NAME
      valueFrom:
        configMapKeyRef:
          key: DD_CLUSTER_NAME
          name: datadog
    - name: DD_CLUSTER_AGENT_ENABLED
      value: "true"
    - name: DD_CLUSTER_AGENT_URL
      value: https://<CLUSTER_AGENT_SERVICE_NAME>.<CLUSTER_AGENT_SERVICE_NAMESPACE>.svc.cluster.local:5005
    - name: DD_CLUSTER_AGENT_AUTH_TOKEN
      valueFrom:
        secretKeyRef:
          key: token
          name: datadog-cluster-agent
    - name: DD_ORCHESTRATOR_EXPLORER_ENABLED # Kubernetes リソースビューを取得するために必要です
      value: "true"

うまくいかない場合

1, ログを確認

Datadog Cluster Agentコンテナのログ、Sidecarとして動かしているDatadog Agentコンテナのログを確認します。 INFOレベルログでうまくいかない原因が出力されていることもあります…。(DD_CLUSTER_AGENT_AUTH_TOKENの長さは32文字以上じゃないといけないとか)

2, agent statusで確認

Sidecarとして動かしているDatadog Agentコンテナにはいり、agent statusを実行し、 Datadog Cluster Agent欄を確認する。 Successfully Connected to the Datadog Cluster Agent.と出ていれば正常に設定がされています。

3, Sidecarとして動かしているDatadog AgentコンテナのPodのdnsPolicyDefaultになっていないか確認

DD_CLUSTER_AGENT_URLにDatadog Cluster Agent ServiceへURLを設定していますが、dnsPolicyDefaultでは名前解決できずエラーになります。 CoreDNSなどはdnsPolicyDefaultなのでそういったPodはDD_CLUSTER_AGENT_ENABLEDをfalseにするとよいでしょう。(他に良い解決方法があれば教えてください)

`RUN --mount=type=cache`のキャッシュをGitHub Actionsのキャッシュとして保存しイメージビルドを速くする

背景

CI環境でのDockerイメージビルドの速度向上は、多くの開発者にとって切実な課題です。

簡単な対策は

しかしこれらのキャッシュはCI側で有効期限があり、キャッシュ保存量制限もあり、CircleCIの場合はコスト(料金)もかかります。

なので次によく取られる対策は、キャッシュをイメージレジストリに保存するやり方になります。
参考: https://shepherdmaster.hateblo.jp/entry/2022/06/11/123419

これによりCI環境でもレイヤーごとのキャッシュは可能になりました。 しかしパッケージが変更された場合(例えばpackage.jsonやgo.modやGemfileが変更になった場合)はパッケージのインストールはフルで実行されてしまいます。 ローカルで実行するのであれば追加/変更したパッケージだけインストールすればすむのにCI環境では少しでも変更があると毎回フルインストールが走ってビルド時間が延びるのが悩みです。

さて、このイメージビルド時のパッケージのインストールにキャッシュを効かせて速くする方法があります。
RUN --mount=type=cacheです。
https://docs.docker.com/build/guide/mounts/
これはローカルマシンでは効果がありますが、同一マシンじゃないとキャッシュが効かないため、マシンが毎回変わるCIでは効果がありません。またキャッシュ保存先がDockerが管理するディレクトリに保存されるためキャッシュが取り出しづらいという問題があります。

reproducible-containers/buildkit-cache-danceという救世主

RUN --mount=type=cacheのキャッシュをGitHub Actions上で保存できるようにするためのActionがreproducible-containers/buildkit-cache-danceです。
これを使えば、apt-getやパッケージ(npm, Goライブラリ, Gem等)のインストール結果をキャッシュすることができるようになります。
このActionやってることはシンプル(だが泥臭い)で、

という流れになっています。

apt-getのキャッシュの仕方

https://github.com/reproducible-containers/buildkit-cache-dance/tree/main#examples を参考

bundle installのキャッシュの仕方

Dockerfile:

WORKDIR /app/  
COPY Gemfile Gemfile.lock ./  
RUN bundle config set path .cache/bundle  
RUN --mount=type=cache,sharing=locked,target=/app/.cache/bundle \  
    bundle install && \  
    cp -ar .cache/bundle .bundle && \
    bundle config set path .bundle  

Action:

jobs:  
  build:  
    name: container build  
    runs-on: ubuntu-latest  
  
    steps:  
      - uses: actions/checkout@v3  
  
      - name: Set up Docker Buildx  
        uses: docker/setup-buildx-action@v3  
  
      - name: Cache bundle-install  
        uses: actions/cache@v3  
        with:  
          path: bundle-install-cache  
          key: bundle-cache-${{ hashFiles('Dockerfile') }}  
  
      - name: inject bundle-install-cache into docker  
        uses: reproducible-containers/buildkit-cache-dance@v2.1.3  
        with:  
          cache-source: bundle-install-cache  
          cache-target: /app/.cache/bundle  
  
      - uses: docker/build-push-action@v5  
        with:  
          context: .  
          tags: user/app:latest

その他

試してないですが、npmやGoライブラリなどのキャッシュも同様に出来るはずです。

注意点

CI上でのキャッシュのrestore、save、そしてイメージへのキャッシュのinject、イメージからのキャッシュのextractはキャッシュサイズが大きくなればなるほど時間がかかるようになります。 そのためRUN --mount=type=cacheのキャッシュが効くことによる削減時間よりも先述したオーバーヘッドのほうが大きくなる場合があるので注意が必要です。

まとめ

RUN --mount=type=cacheのキャッシュをGitHub Actionsのキャッシュとして保存し、イメージビルドを速くする方法を紹介しました。 キャッシュのサイズによりますがDependabotやRenovateでパッケージのアップデートを頻繁に行う場合でもイメージビルドが速くなるのは嬉しいですね。

Github Actionsのdocker/build-push-actionのcache-toにECRを指定する

TL;DR

- uses: docker/build-push-action@v3
  with:
    context: .
    tags: ${{ steps.login-ecr.outputs.registry }}/${{ env.MY_REPOSITORY }}:${{ env.MY_TAG }},${{ steps.login-ecr.outputs.registry }}/${{ env.MY_REPOSITORY }}:${{ env.MY_CACHE_TAG }}
    push: true
    cache-from: type=registry,ref=${{ steps.login-ecr.outputs.registry }}/${{ env.MY_REPOSITORY }}:${{ env.MY_CACHE_TAG }}
    cache-to: type=inline,ref=${{ steps.login-ecr.outputs.registry }}/${{ env.MY_REPOSITORY }}:${{ env.MY_CACHE_TAG }}
    outputs: type=registry

説明

まずdocker/build-push-actionではcache-toにtype=ghaを使うのが簡単で一般的だと思う。 しかしこれは内部でGithub Actions Cache APIを使っているのでキャッシュ先はactions/cacheと同様で、actions/cacheのキャッシュ制限事項が適用されると思われる。 そうだとすると、7日間アクセスがなければキャッシュが破棄され、またリポジトリごとの10G制限も発生する。actions/cacheのキャッシュサイズが大きい場合キャッシュが効きづらくなりそうだ。

そこで、cache-toにtype=registryを使いたい。 これならキャッシュ保存先がimageレジストリになるのでキャッシュ制限を考えなくてよくなる。 type=registry はimageとcache manifestを別々にレジストリにpushするが、ECRはcache manifestをサポートしていない(2022/06/11現在)
ただ2022/5/7にサポートを検討するコメントがついているのでしばらくしたらtype=registryが使えるようになりそう。

で、type=registryが使えないのでtype=inlineを使うことになる。 type=inlineはimage内にキャッシュ用メタデータを埋め込む方法。 type=inlineのデメリットとしてはmode=maxが使えないので、マルチステージビルドを使っている場合、中間レイヤーイメージがキャッシュされない。
もし中間レイヤーもキャッシュしたいのであれば「キャッシュのためにDockerビルドで中間イメージをタグ付けしレジストリにPushする - 🤖」を参考にするとよさそう。

また、docker/build-push-actionを使う際はtagsにメインのimageだけじゃなくcache用のimageも指定しないと、cache用のimageがpushされないので注意。