はじめに
本記事では、AWS CodeBuildのキャッシュを利用してビルド時間を短縮する方法について紹介します。
AWSマネージドサービスを使用したCICDパイプラインでは、AWS Codeシリーズ1を使用します。検証環境でAWS Codeシリーズを用いてCICDパイプラインを構築・運用した際、特にCodeBuildを使用したビルド時間に大幅な時間を費やしていることに対して課題と感じました。本記事では、その課題に対する改善方法についての調査結果を共有します。
尚、本記事の目的は、CodeBuildによるビルド時間を短縮する方法の情報提供となります。測定は検証環境で要したサンプルアプリ2を使用して得られたものであり、測定結果の正確性、有用性などにつきましては、一切保証するものではない点をご了承ください。
また、本記事ではAWS各種サービスについての概要、およびビルド時間短縮に対して直接的に関係しない以下の設計・構築方法については割愛させていただきます。
- ブランチ戦略(Git flowなど)
- パラメータ管理方法
- デプロイ方法(ローリングアップデート/カナリアリリース/Blue Greenデプロイなど)
検証の背景
検証環境にて、疑似システム(以降、モデルシステムと呼称)の開発を行った際、以下のCICDパイプラインを構築しました。
開発者がローカル開発環境で開発したソースコードをAWS CodeCommitへPushしたことを契機に、CICDパイプラインが起動します。アプリケーションのビルド、Dockerイメージの作成、ECRにDockerイメージをPush、CodeDeployによるECS on Fargateへのデプロイまでのプロセスを自動化しています。
なお、モデルシステムの実行環境はECS on Fargateを採用していますが、以降に説明するビルド時間短縮方法は、それ以外のAWSサービス(例えば、EC2やECS on EC2、EKS on EC2など)でも実現可能です。
課題
CICDパイプラインを構築してデリバリを自動化し、開発を効率化することができました。一方、頻繁にソースコードの更新を行い、CICDパイプラインを実行すると、アプリケーションのビルド~デプロイまでに時間がかかることがストレスに感じました。
工程ごとに処理時間を調査したところ、CodeBuildによるPROVISIONING
とPRE_BUILD
に大幅な時間を費やしていることが分かりました。
PROVISIONING
とは、ビルド用のコンテナを準備している工程です。以下のようなビルド環境の条件に左右されます。
- ビルド用のコンテナの環境イメージ
- ビルド用のコンテナをVPCに配置するか否か
- ビルド用のコンテナのスペック
例えば、ビルド用コンテナのコンピューティングリソースのスペックを上げる方法などでPROVISIONING
の処理時間を短縮できますが、本記事では対象外とします。
PRE_BUILD
とは、ビルドする前の準備工程です。具体的には、以下のビルド仕様を定義したbuildspec.yml
のpre_build
で実行している箇所になります。
version: 0.2
env:
parameter-store:
AAC_REPOSITORY_URI: "mdlsys-ssm-pram-aac-ecr-uri"
AAC_IMAGE_TAG: "mdlsys-ssm-pram-aac-img-tag"
phases:
install:
runtime-versions:
docker: 19
pre_build:
commands:
- echo Entered the pre_build phase...
- apt-get update -y
- apt-get install -y maven
- mvn -f aac/ctr/pom.xml package
build:
commands:
- echo Entered the build phase...
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
- echo Building the Docker image...
- docker build -f ./aac/ctr/src/main/docker/Dockerfile.jvm -t $AAC_REPOSITORY_URI:latest ./aac/ctr
- docker tag $AAC_REPOSITORY_URI:latest $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG
post_build:
commands:
- echo Entered the post_build phase...
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG
- printf '{"Version":"1.0","ImageURI":"%s"}' $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG > imageDetail.json
artifacts:
files:
- imageDetail.json
buildspec.yml
のpre_build
では、大きく以下の2つを実施しています。
-
apt-get update -y
とapt-get install -y maven
により、Apache Mavenをインストール -
mvn -f aac/ctr/pom.xml package
にて、ソースコードをコンパイル、テストして、ビルド出力アーティファクトにパッケージ化
特に、No.2にて、パッケージ化する際、**依存関係のあるライブラリをインターネット経由で、外部のリポジトリから取得する工程(以降、ライブラリ取得工程と呼称)**に大幅な時間を費やしていることが課題でした。
目的
一般的にCICDパイプラインを構築すると、課題のようにライブラリ取得工程に時間がかかります。
本記事では、その工程にかかる時間を短縮できる方法を調査し、結果を共有することで、少しでも読者の皆さんが構築しているCICDパイプラインの改善の参考になれば、と思い執筆しました。
AWS CodeBuildによるビルドキャッシュ方法
ライブラリ取得工程に費やす時間はキャッシュの活用により短縮できます。
キャッシュの活用方法は、以下の3つがあります。
# | キャッシュ方法 | 概要 | ユースケース |
---|---|---|---|
1 | CodeBuildのローカルキャッシュ | ビルドホストのみが利用できるキャッシュをそのビルドホストのローカルに保存する。 |
|
2 | CodeBuildのS3キャッシュ | 複数のビルドホスト間で利用できるキャッシュをAWS S3バケットに保存する。 |
|
3 | EFSキャッシュ | AWS EFSにキャッシュを保存する。CodeBuildネイティブではないため、ユーザ側でキャッシュ処理を行う必要がある。(※) |
|
※事前にキャッシュ対象をZip化しEFSにコピーする必要があります。キャッシュ利用時はCodeBuildがEFSをマウントし、EFSからZipをローカルにダウンロードし展開して利用します。
複数のビルドホスト間でキャッシュを使いまわすことがモデルシステム開発の要件であった為、#2 CodeBuildのS3キャッシュと**#3 EFSキャッシュ**について、どのくらい時間が短縮できるか検証しました。
検証
検証観点
単純にライブラリ取得工程に費やす時間を検証観点とします。
また、S3キャッシュとEFSキャッシュともに、以下のビルド用コンテナを使用し、実行環境の条件を同一にして検証を進めます。
- ビルドコンテナのイメージ :
aws/codebuild/standard:4.0-20.03.13
- コンピューティングリソース :
3 GB メモリ、2 vCPU
- ビルドコンテナ :
VPC内に配置
検証手順
S3キャッシュ
実際に検証してみましょう。まず、S3キャッシュの設定方法について簡単に説明します。
事前作業
以下の事前作業を行ってください。本記事では、手順を割愛させていただきます。
- キャッシュを保存するS3バケットの作成
手順
CodeBuildのS3キャッシュを使用する為の手順は、以下2つのステップのみです。
1. ビルドプロジェクトの編集
対象となるビルドプロジェクトを選択し、「編集」「アーティファクト」を押下します。
青枠で囲われたように、キャッシュを保存するS3バケット(事前作業にて作成したS3バケット)を指定します。
2. buildspec.yml
にて、cache
フェーズを追加
S3バケットにキャッシュとしてアップロードする対象のファイルを指定し、キャッシュを利用できるようにします。
mavenのデフォルト設定で、依存関係のあるライブラリは.m2
配下に保存されるため、今回はそれらがキャッシュ対象となります。
cache
フェーズに、キャッシュ対象を追記します。
version: 0.2
env:
parameter-store:
AAC_REPOSITORY_URI: "mdlsys-ssm-pram-aac-ecr-uri"
AAC_IMAGE_TAG: "mdlsys-ssm-pram-aac-img-tag"
phases:
install:
runtime-versions:
docker: 19
pre_build:
commands:
- echo Entered the pre_build phase...
- apt-get update -y
- apt-get install -y maven
- mvn -f aac/ctr/pom.xml package
build:
commands:
- echo Entered the build phase...
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
- echo Building the Docker image...
- docker build -f ./aac/ctr/src/main/docker/Dockerfile.jvm -t $AAC_REPOSITORY_URI:latest ./aac/ctr
- docker tag $AAC_REPOSITORY_URI:latest $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG
post_build:
commands:
- echo Entered the post_build phase...
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG
- printf '{"Version":"1.0","ImageURI":"%s"}' $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG > imageDetail.json
artifacts:
files:
- imageDetail.json
cache:
paths:
- '/root/.m2/**/*'
以上がS3キャッシュの設定方法となります。
結果
実際にCICDパイプラインを実行し、CodeBuildのフェーズ詳細を確認してみましょう。
PRE_BUILD
フェーズの時間が短縮されており、キャッシュが効いていることが分かります。
EFSキャッシュ
続いて、EFSキャッシュの設定方法を簡単に説明します。
事前作業
以下の事前作業を行ってください。本記事では、手順を割愛させていただきます。
- キャッシュを保存するEFSの作成
- ローカル開発環境にて、ビルド時に取得した
.m2
配下のライブラリをZip化し、EFSにアップロード
手順
CodeBuildのEFSキャッシュを使用する為の手順は、以下の2つのステップで設定できます。
1. ビルドプロジェクトの編集
対象となるビルドプロジェクトを選択し、「編集」「環境」を押下します。
CodeBuildがEFSをマウントできるように設定します。事前作業で作成したEFSを指定してください。
2. buildspec.ymlにて、EFSにあるZipファイルを展開する処理を追加
pre_build
フェーズに事前作業でアップロードしたZipファイルをCodeBuildのビルドホストで解凍する処理を追記します。
version: 0.2
env:
parameter-store:
AAC_REPOSITORY_URI: "mdlsys-ssm-pram-aac-ecr-uri"
AAC_IMAGE_TAG: "mdlsys-ssm-pram-aac-img-tag"
phases:
install:
runtime-versions:
docker: 19
pre_build:
commands:
- echo Entered the pre_build phase...
- apt-get update -y
- apt-get install -y maven
- unzip $CODEBUILD_MDLSYS_BUILD_CACHE/repository.zip
- mkdir ~/.m2/repository
- mv root/.m2/repository/* ~/.m2/repository
- mvn -f aac/ctr/pom.xml package -Dmaven.test.skip=true
build:
commands:
- echo Entered the build phase...
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
- echo Building the Docker image...
- docker build -f ./aac/ctr/src/main/docker/Dockerfile.jvm -t $AAC_REPOSITORY_URI:latest ./aac/ctr
- docker tag $AAC_REPOSITORY_URI:latest $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG
post_build:
commands:
- echo Entered the post_build phase...
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG
- printf '{"Version":"1.0","ImageURI":"%s"}' $AAC_REPOSITORY_URI:$AAC_IMAGE_TAG > imageDetail.json
- echo Entered the post_build phase...
artifacts:
files:
- imageDetail.json
以上がEFSキャッシュの設定方法になります。
結果
実際にパイプラインを実行し、CodeBuildのフェーズ詳細を確認してみましょう。
EFSキャッシュも同様にPRE_BUILD
フェーズの時間が短縮されており、キャッシュが効いていることが分かります。
検証結果まとめ
検証結果は以下になります。
# | キャッシュ方法 | 時間(秒) |
---|---|---|
1 | キャッシュなし | 299 |
2 | S3キャッシュ | 53 |
3 | EFSキャッシュ | 38 |
まとめ
CodeBuildのキャッシュ方法を調査し、実際に検証してライブラリ取得工程の時間を測定しました。
検証観点がライブラリ取得工程で費やす時間だと、単純にデータの読み取り(ダウンロード)速度がS3に比べて速いEFSに軍配が上がりました。
ただ、実際の開発現場だと観点が時間だけではなく、使用するAWSサービスの費用や運用負荷、シンプルな構成等が観点に入ると思います。
今回の場合、以下の理由から私はS3キャッシュを採用しています。
- S3キャッシュとEFSキャッシュの時間差が15秒と少なく、S3キャッシュの時間が許容範囲であった
- リソースの費用がS3の方が安い
また、EFSキャッシュのほうが構築作業に工数がかかるため、多数のパイプラインを作成するようなケースでは、その点も考慮する必要があります。
これらの検証結果を踏まえて、皆さんの快適なCICDライフの参考になれば幸いです。
補足
モデルシステム
モデルシステムは、以下のようにECS on Fargate上で稼働しているシンプルなアーキテクチャで構成しています。
参考情報
- Amazon Web Services およびその他のAWS 商標は,米国およびその他の諸国におけるAmazon.com,Inc.またはその関連会社の商標です。
- Javaは,Oracle Corporationおよびその子会社,関連会社の米国およびその他の国における登録商標です。
- Dockerは、Docker Inc. の米国およびその他の国における登録商標もしくは商標です。
- Mavenは、Apache Software Foundationの米国およびその他の国における登録商標もしくは商標です。
- その他記載の会社名、製品名などは、それぞれの会社の商標もしくは登録商標です。
-
AWS CodeCommit, CodePipeline, CodeBuild, CodeDeploy ↩
-
サンプルアプリは、Quarkusの初期プロジェクト作成時に自動生成されるアプリベースに作成。本記事では、サンプルアプリの名前をAACと呼称 ↩