AWS環境におけるモノレポでのDockerイメージのビルド方法
調べた限り、モノレポでDockerイメージをいい感じにビルドする方法が見つからなかったので実装してみました。
AWS環境が前提で、CodeBuildを使ってDockerイメージをビルドする想定です。
GitHub Actionsを使えばもっとスマートに実装できるかもしれませんが、外部のSaaSにAWSの認証情報を持たせたくないという考えがあり、AWS内で完結させました。
本内容はAWSに限らずにモノレポでのDockerイメージのビルドについて、参考になる気がしています。
本内容のソースは以下にあります。
ディレクトリ構成
monorepo-app1とmonorepo-app2がモノレポ内に存在する各アプリケーションです。
monorepo-container-build-example ├── buildspec.yml ├── monorepo-app1 │ ├── Dockerfile │ ├── index.js │ └── package.json ├── monorepo-app2 │ ├── Dockerfile │ ├── index.js │ └── package.json ├── scripts │ └── build.sh └── terraform ├── codebuild.tf └── main.tf
CodeBuildの環境はTerraformで構築するようにしました。Terraformのリソースはterraformディレクトリにあります。
buildspec.ymlはCodeBuildが実行するビルド内容のファイルです。buildspec.ymlでは、scripts/build.shのシェルスクリプトを実行します。
実装内容
CodeBuild
CodeBuildの処理内容が今回のポイントなので、それについて説明します。
ビルドのやり方としては、モノレポに存在する各アプリケーションのビルドを実行するために、全てのアプリケーションをビルド対象にします。
その上で、レジストリに存在するイメージと差異があった場合のみ、イメージをレジストリにPushします。
以降が詳しい解説です。
まずは、buildspec.ymlの解説です。
buildspec.yml
version: 0.2 env: variables: DOCKER_BUILDKIT: 1 parameter-store: REGISTRY_URI: "/ecr/registry_uri" phases: pre_build: commands: - aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin $REGISTRY_URI - image_tag=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) build: commands: - ${CODEBUILD_SRC_DIR}/scripts/build.sh
ECRのレジストリURIを指定するのにSession Managerのパラメータストアを使用していますが、ベタ書きでも問題ないです。
image_tagはDockerイメージのタグ値になります。GitのコミットIDの先頭7文字をタグ値にします。
これ以降は、scripts/build.shを実行します。
build.shの解説です。
scripts/build.sh
#!/bin/sh -x target_dirs=`find . -type f -name "Dockerfile" -exec dirname {} \; | sort -u` for target in ${target_dirs} do container_name=$(echo $target | awk '{i=split($0,array,"/");print array[i]}') docker_filepath=${target} repository_uri=${REGISTRY_URI}/${container_name} aws ecr create-repository --repository-name ${container_name} --region ap-northeast-1 || true docker pull $repository_uri:latest || true docker build --cache-from $repository_uri:latest --build-arg BUILDKIT_INLINE_CACHE=1 -t $repository_uri:$image_tag -f ${docker_filepath}/Dockerfile ${docker_filepath} image_digest=$(docker images $repository_uri --format "{{.Tag}}\t{{.Digest}}" | awk '$1 == "'"${image_tag}"'" {print $2}') if [ "${image_digest}" == "<none>" ]; then docker tag $repository_uri:$image_tag $repository_uri:latest docker push $repository_uri:$image_tag docker push $repository_uri:latest fi done
findコマンドでDockerfileが存在するディレクトリを見つけます。ここでは、monorepo-app1とmonorepo-app2が見つかります。それぞれのDockerfileがビルド対象になります。
findコマンドの出力値はディレクトリのパスになるので、container_nameの処理でディレクトリ名だけを抽出します。
docker pull $repository_uri:latest || true
で最新のDockerイメージを取得します。
docker build --cache-from
でそのイメージを指定することで、キャッシュとして最新のDockerイメージを使用した上でイメージのビルドを行います。
この時にイメージに変更がなければ、イメージのダイジェスト値があります。変更があれば、ダイジェスト値はありません。
イメージのダイジェスト値はレジストリにPushすることで付与される値です。
イメージのダイジェスト値がない場合、最新のイメージと差異があり、レジストリにPushする必要があるイメージになります。
if [ "${image_digest}" == "<none>" ]; then
でダイジェスト値の有無を確認し、docker push $repository_uri:$image_tag
でレジストリにPushを行います。
latestのタグが今回のビルドの仕組みでは必要になるので、docker push $repository_uri:latest
も行います。
Terraform
CodeBuildのプロジェクトを作成するTerraformのコードについても解説します。
codebuild.tf
... resource "aws_codebuild_project" "container_build" { name = "container-build" artifacts { type = "NO_ARTIFACTS" } cache { modes = [ "LOCAL_DOCKER_LAYER_CACHE", ] type = "LOCAL" } environment { compute_type = "BUILD_GENERAL1_SMALL" image = "aws/codebuild/amazonlinux2-x86_64-standard:3.0" privileged_mode = true type = "LINUX_CONTAINER" } source { type = "GITHUB" location = "https://github.com/hi1280/monorepo-container-build-example.git" git_clone_depth = 1 } service_role = aws_iam_role.container_build.arn build_timeout = 30 } resource "aws_codebuild_webhook" "container_build" { project_name = aws_codebuild_project.container_build.name filter_group { filter { exclude_matched_pattern = false pattern = "PUSH, PULL_REQUEST_MERGED" type = "EVENT" } filter { exclude_matched_pattern = false pattern = "refs/heads/master" type = "HEAD_REF" } } }
aws_codebuild_projectはCodeBuildを作成するTerraformリソースです。
sourceにDockerイメージのビルドを行うリポジトリを指定します。
service_roleにはIAMロールを指定します。ECRへのIAM権限が必要です。
aws_codebuild_webhookはCodeBuildのWebhookの指定です。
いずれかのファイルがGitリポジトリにコミットされるとCodeBuildが自動的に実行されるように設定します。
まとめ
イメージのダイジェスト値を使うことで無駄にイメージをPushすることなく、モノレポでイメージをビルドできました。
ただ毎回モノレポ上の全てのアプリケーションのイメージをPullしていて、無理矢理な方法だと思うので、ほかにもっとスマートなやり方があれば教えてほしいです。