取り組んだこと
本記事は、GitHub Actions Advent Calendar 2023の 20日目の記事です。
以前投稿したこちらの記事で、workflowを作成しました。
このときのworkflowは、とりあえず動くものでした。
作業前のworkflow
今回は、キャッシュを利用して、実行時間短縮を図ろう!
ということで作業を行っていました。
キャッシュは以下の2カ所に設定していきます。
- python関係のライブラリをインストール
- DockerイメージのPushまでの過程
python関係のライブラリをインストール
github actions pip cache
と検索すると、以下リポジトリが見つかりました。
関連する実装を色々確認して、以下の実装を使わせていただきました。
Pythonとpipのそれぞれのレイヤーでキャッシュを活用する方法です。
I have one more suggestion that I decided to use, because I want to avoid downloading all pip dependencies everytime any of them changes - to introduce a second cache just for pip cache.
(訳)pip依存関係が変更されるたびに、すべての再ダウンロードするのは非効率的だと考えたためです、すべてのpip依存関係を毎回ダウンロードすることを避けるため、pipのキャッシュ専用の2つ目のキャッシュを導入することにしました。
- name: "Cache: Cache Python"
id: python-cache
uses: actions/cache@v3.3.1
with:
path: ${{env.pythonLocation}}
key: ${{env.pythonLocation}}-${{hashFiles('requirements.txt')}}
- name: "Shell: Get pip cache dir"
id: pip-cache-dir
if: steps.python-cache.outputs.cache-hit != 'true'
run: |
python -m pip install -U pip
pip install -U wheel
echo "pip-cache-dir=$(pip cache dir)" >> ${GITHUB_OUTPUT}
- name: "Cache: Cache pip"
if: steps.python-cache.outputs.cache-hit != 'true'
uses: actions/cache@v3.3.1
with:
path: ${{steps.pip-cache-dir.outputs.pip-cache-dir}}
key: 3.12-${{hashFiles('requirements.txt')}}
restore-keys: |
3.12-${{hashFiles('requirements.txt')}}-
3.12-
- name: "Shell: Install pip dependencies"
if: steps.python-cache.outputs.cache-hit != 'true'
run: pip install -r requirements.txt
続けて、Dockerイメージのキャッシュに関する作業に入ります。
DockerイメージのPushまでの過程
続いて、DockerイメージをPushするまでにキャッシュを使って、時間短縮を図ります。
build-push-actionの利用
GitHub Actionsエコシステムにある便利なアクションです。Dockerイメージのビルド、プッシュ、キャッシュを管理してくれます。
build-push-actionのオプションを追加します。
build-push-actionのExamplesにあった、Cache management
から確認しました。
大きく4種類ありそうです。
- Inline cache
- Registry cache
- GitHub cache(experimental)
- Local cache
inline
推し、みたいです。しかしinline
は、min キャッシュモードのみサポートのようです。
レジストリキャッシュエクスポーターを cache-to オプションに設定することで、max キャッシュモードを使用可能なようです。
min , maxについては以下の通りです。(参考)
- mix:最終レイヤーのみキャッシュ
- max:中間レイヤー含めてキャッシュ
今回はせっかくなので、GitHub cache(experimental)を使います。
余談:キャッシュ保存先はs3も選択可能
Cache management
のページにあったリンクから、詳細を確認してみました。
s3とazblobがunreleased
となっています。
https://docs.docker.com/build/cache/backends/
リリース前か、と思いましたが、
以下ページではS3 cache (experimental)
でした。
動作確認してみたので、気になる方はご覧ください。(記事の最後で)
GitHub cache(experimental)
基本的にはドキュメント通りで、Scope
は確認しました。
Scope is a key used to identify the cache object. By default, it is set to buildkit. If you build multiple images, each build will overwrite the cache of the previous, leaving only the final cache.
(訳)複数のイメージに対してキャッシュを利用したい場合に、どのイメージを何というキー名で設定するか、明記してください。scope
に何も設定しないと、最後のイメージしかキャッシュされませんよ~
今回私はイメージを一つしか利用しませんが、使ってみます。
一旦まとめると、build-push-action
部分は以下の通りです。
- name: Docker build and push to ECR
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
tags: ${{ secrets.AWS_REGISTRY_URL }}:latest
push: true
cache-from: type=gha,scope=FastAPI-backend-dev
cache-to: type=gha,scope=FastAPI-backend-dev,mode=max
続いて、参考にしていたyamlファイルに、setup-buildx-action
とあるので、これを確認します。
setup-buildx-actionの利用
GitHub Action to set up Docker Buildx.
Buildxとは?
buildx is a Docker CLI plugin for extended build capabilities with BuildKit.
BuildKitとは?
BuildKit is a toolkit for converting source code to build artifacts in an efficient, expressive and repeatable manner.
BuildKitを使うために
- GitHub ActionでBuildxを使うためには、
setup-buildx-action
が必要 - Buildxが「BuildKitを使った拡張ビルド機能を提供するためのプラグイン」
setup-buildx-action
をyamlファイルに記述すれば、BuildKitが使えそう。
「docker-buildxとmulti-platform build周りについてまとめ」も参考になりました!
コンテナイメージを作成する際にamd64アーキテクチャ向けのイメージだけじゃなくて、arm向けのイメージとかも作りたいときに使うものだと思えばOK
ちなみに、setup-buildx-action
を定義せずworkflowを実行すると、エラーとなりました。
buildx failed with: ERROR: Cache export feature is currently not supported for docker driver. Please switch to a different driver (eg. "docker buildx create --use")
ここまでを踏まえて、setup-buildx-action
部分は以下の通りです。(参考)
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
version: latest
driver-opts: image=moby/buildkit:latest
# build-push-actionをこれ以降に実行
AWSのLambda関数更新に失敗
以下2つについて、実装が完了しました。
build-push-action
-
setup-buildx-action
あと、Lambda関数更新で終わり!と思って動作確認すると、
InvalidParameterValueException: The image manifest or layer media type for the source image ***:latest is not supported.
マネジメントコンソールからECRの画面を確認すると、おかしいな。。。
犯人探し
今回追加した以下2つを調べます。setup-buildx-action
はエラーないので、まずはbuild-push-action
から確認しました。
setup-buildx-action
build-push-action
build-push-action内の容疑者
build-push-action ECR
と検索すると、以下の記事が見つかりました。
今回とは異なる部分もありますが、ECRにPushしたら謎な状態に、という質問でした。
以下、回答のコメントです。別の回答にもprovenance
とあり、少し調べました。
Just add provenance: false in docker/build-push-action@v4
provenanceというオプション
私は全然知らない領域の話がでした。SLSA
というものが関係しているようです。
ソフトウェアのサプライチェーン、つまり開発からビルドを経てデプロイされるまでの一連の過程において、外部の攻撃からその完全性を守るためのフレームワークがSLSAである。
ここは掘り下げしないで、進みます!
provenance: false
とすることで、Lambda関数の更新に成功しました。
setup-buildx-action内の容疑者
setup-buildx-action
側には問題がないのか確認します。
マネジメントコンソールからECRの画面を確認すると、おかしい。。。
この文章を記載した箇所で載せた画像にある通り、
アーティファクトタイプがImage Index
、エラー文にはimage Manifest
とあります。両者は関係ありそうなので、調べました。
- Image Manifest:特定のアーキテクチャとオペレーティングシステム向けの単一のコンテナイメージの設定とレイヤーのセットを提供
- Image Index:様々なアーキテクチャとオペレーティングシステムにまたがるイメージセットに関する情報を含んでおり、(上位マニフェスト)コンシューマはインデックスを処理できるようにしておくべき
mediaTypeの確認
workflow失敗時に出力されていたmetaデータを見ました。
エラー文にmedia type
という単語もあったので、調べます。
{
"containerimage.descriptor": {
"mediaType": "application/vnd.oci.image.index.v1+json",
"digest": "sha256:40ec8fca07e81d832bae55168d7c5d8ec4e359f3d1dd886238a8ccd8eb623548",
"size": 856
},
"containerimage.digest": "sha256:40ec8fca07e81d832bae55168d7c5d8ec4e359f3d1dd886238a8ccd8eb623548",
"image.name": "***:latest"
}
以下を確認しました。
あれ、ociのv1以上だよな???なんで動かないんだ?
調べる中で、、まさに起きている事象通りのissueがありました。
v0.11.0: unsupported manifest media type and no default available: application/vnd.oci.image.manifest.v1+json moby/buildkit#3491
(訳)バージョン0.11.0では、デフォルトで対応できるフォーマットはありません。
バージョンの問題が悪さをしている模様です。
記載の通り、moby/buildkit
のバージョンを下げました。このときは
provenance: false
を削除しても、Lambda関数の更新に成功しました。
まとめると・・・
- moby/buildkitのバージョンを最新で進めるなら、
provenance: false
を記述 - moby/buildkitのバージョンを下げるなら、
provenance: false
は不要
実行時間は短縮できた?
詳しく話していない部分もありますが、最終的に以下のworkflowを実行しました。
- Linterツール(ruff)によるチェック
- アプリケーションテスト
- DockerイメージのBuild、ECRへPush、Lambda関数の更新
実行時間を半分近く短くできました!
キャッシュを使いたくない場合
キャッシュを使った話が中心でしたが、workflow_dispatchを用意すれば、手動実行でキャッシュを使わない形のworkflowを実行できます。
yamlファイルの内容
ここでは、pipの方は省略します。
on:
push:
workflow_dispatch:
inputs:
docker-no-cache:
description: "Build docker images with no cache" # ブラウザでチェックボックス横に表示
default: false
required: false
type: boolean
# 略
- name: Docker build and push to ECR
uses: docker/build-push-action@v5
with:
# 略
push: true
no-cache: ${{ inputs.docker-no-cache == true }} # チェックありなら、キャッシュを利用しない
cache-from: type=gha,scope=${{ github.workflow }}
cache-to: type=gha,mode=max,scope=${{ github.workflow }}
感想と今後
最終的なworkflow
workflowで自分自身でキャッシュまで導入したのは初めてだったので、勉強になりました。公式ドキュメントの漁り方も少しは理解できた気がします。
Github Actionsについて、理解すべきことはたーくさんありそうなので、少しずつやっていきたいと思いました。記事を書いた時点では、以下のようなことが気になっています。
- AWSの認証情報に関して、OIDCを使ってセキュアなワークフローに(参考)
- AWS Lambdaはマルチアーキテクチャ対応のイメージはビルドできないようなので、そのあたりの追加確認(参考)
- Lambda関数更新で利用したリポジトリなどを参考に、Custom Action作成方法やOSSライセンスに関する理解
余談続:s3へのキャッシュアップロード
余談:s3とazblobはもう選択可能?
以下のように実装を変更して実施してみました。そしてうまくいきましたね。
- name: Docker build and push to ECR
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
tags: ${{ secrets.AWS_REGISTRY_URL }}:latest
push: true
cache-from: type=s3,region=ap-northeast-1,bucket=github-cache-bucket,name=${{ github.workflow }}
cache-to: type=s3,region=ap-northeast-1,bucket=github-cache-bucket,name=${{ github.workflow }},mode=max