この記事は「Medley (メドレー) Advent Calendar 2024」の23日目の記事です。
はじめに
今年の10月から株式会社メドレーの人材プラットフォーム本部SREチームにアサインした@gongon282828です。
さて、みなさん!
突然ですが、カスタムイメージ、使っていますか??
LambdaやCodeBuildでカスタムイメージを利用することで、 CI/CDの高速化やruntime環境内のライフサイクルをコントロールできたりと色々と便利です。
ただ、そのメリットは理解しつつも、カスタムイメージやECRを手配したりするのも面倒だったりします。
そこで今回は、CodePipelineとCodeBuildを利用して、DockerFileと変数を修正するだけで、ECRのイメージを量産する方法を紹介します。
※CodePipelineとCodeBuildでCodebuildで利用するカスタムイメージを作るので少しややこしいですね...
前提
- 本記事ではTerraformでの環境構築の方法の紹介になります。
- AWSのリソースに利用する一部変数ついては、可読性を高めるために本来は
variable.tf
等で他のmoduleから値を参照するところを、localで変数化したものを参照させています。 - 記事が冗長してしまうことを防ぐ目的で、IAMポリシーの作成とリポジトリ連携周りの構築については、省略してます。
では早速、具体的な構築方法について詳説します。
構築するリソース各種
ここからは実際に、Terraformで実際の環境を構築する方法を説明します。
以下、構築を行います。
- 作成したイメージを保管するECRの構築
- カスタムイメージを構築するCodeBuildの作成
-
buildspec.yml
ファイルの作成 - コンソールから変数入力による作成するイメージの指定ができるCodePipelineの作成
(4)のCodePipeline構築では、DockerFileを保管しているリポジトリの取り込みも行います。
作成したイメージを保管するECRの構築
ここでは、量産するカスタムイメージの保管場所ECRをTerraformで構築します。
locals{
custom_build_images = ["IMAGE_A","IMAGE_B"]
}
resource "aws_ecr_repository" "main" {
count = length(local.custom_build_images)
name = "${local.custom_build_images[count.index]}"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
tags = {
lifecycle-flag = "lifecycle-flag-${local.custom_build_images[count.index]}"
}
}
resource "aws_ecr_lifecycle_policy" "main" {
count = length(local.custom_build_images)
repository = aws_ecr_repository.main[count.index].name
policy = jsonencode(
{
rules = [
{
action = {
type = "expire"
}
description = "Keep last 15 images"
rulePriority = 1
selection = {
countNumber = 15
countType = "imageCountMoreThan"
tagStatus = "tagged"
tagPrefixList = ["lifecycle-flag-"]
}
},
]
}
)
}
解説:
- ローカル変数で定義した
custom_build_images
をaws_ecr_repository
のcountで回しています。このcustom_build_images
の配列を増やすことで、追加したいカスタムイメージ用のECRを増やせます。 -
aws_ecr_lifecycle_policy
でECRのライフサイクルを設定しています。入れ忘れてしまったりするところなので、注意しましょう。
これでECRの構築は完了です。ECR自体はいくらでも構築する機会があるので、そこまで難しくない内容だと思います。
カスタムイメージを構築するCodeBuildの作成
ここでは、カスタムイメージを構築するためのCodeBuildを作成します。
locals{
account_id = "xxxxxxxxxxxx"
region = "ap-northeast-1"
vpc_id = "x.x.x.x/16"
subnet_ids = ["x.x.0.x/24","x.x.1.x/24"]
security_group_ids = ["sg-xxxxxxxxxxx"]
}
resource "aws_codebuild_project" "main" {
name = "custom-build-image"
build_timeout = 15
queued_timeout = 5
badge_enabled = false
# roleの構築については便宜上以下の形で記載
service_role = aws_iam_role.codebuild.arn
environment {
compute_type = "BUILD_GENERAL1_LARGE"
image = "aws/codebuild/amazonlinux2-x86_64-standard:5.0"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
privileged_mode = true
environment_variable {
name = "AWS_ACCOUNT_ID"
value = "${local.account_id}"
type = "PLAINTEXT"
}
environment_variable {
name = "AWS_DEFAULT_REGION"
value = "${local.region}"
type = "PLAINTEXT"
}
}
source {
type = "CODEPIPELINE"
buildspec = file("${path.module}/buildspec.yml")
}
artifacts {
type = "CODEPIPELINE"
}
vpc_config {
vpc_id = local.vpc_id
subnets = local.subnet_ids
security_group_ids = [local.security_group_ids]
}
}
解説:
-
environment_variable
は必要最小限にとどめていますが、必要に応じて環境変数を追加してください。 -
source
はmodule内の同一階層に設置します。 -
artifacts
はイメージの量産をコントロールするためにCodePipelineを指定しています。
ここでは、vpc_config
も設定していますが、必須ではありません。ちなみに、VPCエンドポイントからS3への通信費を削減することができます。
buildspec.yml
ファイルの作成
ここでは、CodeBuildで実行するためのbuildspec.yml
を作成します。
Dockerのbuild実行、イメージタグ付与、ECRへのpushの処理もここで行います。
version: 0.2
env:
exported-variables:
- AWS
- AWS_ACCOUNT_ID
- AWS_DEFAULT_REGION
phases:
install:
run-as: root
commands:
- aws ecr get-login-password --region ${AWS_DEFAULT_REGION} |
docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com
build:
run-as: root
commands:
- docker build -t ${CUSTOM_BUILD_IMAGE_TARGET}:latest
-t ${CUSTOM_BUILD_IMAGE_TARGET}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
-f ./modules/image_build/Dockerfile/Dockerfile_${CUSTOM_BUILD_IMAGE_TARGET} . --progress plain
- docker tag ${CUSTOM_BUILD_IMAGE_TARGET}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${CUSTOM_BUILD_IMAGE_TARGET}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
- docker tag ${CUSTOM_BUILD_IMAGE_TARGET}:latest
${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${CUSTOM_BUILD_IMAGE_TARGET}:latest
post_build:
run-as: root
commands:
- docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${CUSTOM_BUILD_IMAGE_TARGET}:latest
- docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${CUSTOM_BUILD_IMAGE_TARGET}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
-
解説:
-
install
部分で、ECRにログインします。ECRへアクセスできる権限は、過不足なく行う必要があります。 -
build
部分で、実際のbuildとtag付けを行います。カスタムイメージで参照する際に最新版を参照できるように、latestタグを付与しています。また、バージョンを追いかけられるようにCODEBUILD_RESOLVED_SOURCE_VERSION
も設定しています。 -
CUSTOM_BUILD_IMAGE_TARGET
で後から構築するCodePipelineで任意の更新したいDockerFileを指定できるようにします。また、Build元のDockerFileもDockerfile_${CUSTOM_BUILD_IMAGE_TARGET}
で指定しています。 - ここでは、
./modules/image_build/Dockerfile/
ディレクトリ配下に量産用のDockerFile
を保管しています。そのため、CodePipelineでのソース取り込みは、必然的にTerraformのソースコード保管リポジトリから引っ張ってくることになります。
肝となっているのが、CUSTOM_BUILD_IMAGE_TARGET
の変数により、一つのCodeBuildで任意のカスタムイメージを作成できるようにしている点です。
コンソールから変数入力によるイメージの指定ができるCodePipelineの作成
ここではCodePipelineを作成します。
AWSコンソール上からCodePipeline手動実行時に、CodeBuildへ環境変数を渡すことにより、任意のイメージを指定して新規イメージ作成や更新を行えるようになります。
locals{
connection_arn = "xxxxxx.arn"
full_repository_id = "HOGE/TERRAFIRN_REPO"
branch_name = "main"
}
resource "aws_codepipeline" "main" {
name = "custom-build-image"
# roleの構築については便宜上以下の形で記載
role_arn = aws_iam_role.codepipeline.arn
pipeline_type = "V2"
variable {
name = "CUSTOM_BUILD_IMAGE_TARGET"
description = "Specify the build image to create [IMAGE_A/IMAGE_B]"
default_value = "null"
}
artifact_store {
location = aws_s3_bucket.codepipeline.bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeStarSourceConnection"
version = "1"
output_artifacts = ["source_artifact"]
configuration = {
DetectChanges = false
# ソース管理リポジトリ指定と紐付けについては、便宜上以下の形で記載
ConnectionArn = "${local.connection_arn}"
FullRepositoryId = "${local.full_repository_id}"
BranchName = "${local.branch_name}"
}
}
}
# Image Build
stage {
name = "Build"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["source_artifact"]
output_artifacts = ["build_artifact"]
version = "1"
configuration = {
ProjectName = "${aws_codebuild_project.main.name}"
EnvironmentVariables = jsonencode([
{
type = "PLAINTEXT"
name = "CUSTOM_BUILD_IMAGE_TARGET"
value = "#{variables.CUSTOM_BUILD_IMAGE_TARGET}"
}
])
}
}
}
}
resource "aws_s3_bucket" "codepipeline" {
bucket = "custom-build-image-codepipeline-artifacts"
}
resource "aws_s3_bucket_acl" "codepipeline" {
bucket = aws_s3_bucket.codepipeline.id
acl = "private"
depends_on = [aws_s3_bucket_ownership_controls.codepipeline]
}
resource "aws_s3_bucket_ownership_controls" "codepipeline" {
bucket = aws_s3_bucket.codepipeline.id
rule {
object_ownership = "ObjectWriter"
}
}
解説:
- パイプラインタイプをv2に指定し、実行時にパイプラインレベルで変数を渡せるようにする必要があります。
-
variable
でCUSTOM_BUILD_IMAGE_TARGET
をAWSコンソール上から変数を入力して任意のDockerFileでbuildできるようにしています。また、description
で入力内容を記載することで、他の作業者も更新しやすくしています。 -
stage
のSource
でリポジトリを引っ張ってこれるようにしています。 -
stage
のBuild
のEnvironmentVariables
で、CUSTOM_BUILD_IMAGE_TARGET
を渡すようにしています。
ここでも出てきました、CUSTOM_BUILD_IMAGE_TARGET
です。
パイプラインのV2は他にも便利な機能が追加されており、詳しくは公式ドキュメントに記載があります。
適切なパイプラインのタイプの選択 - AWS CodePipeline
V2タイプからGitタグの生成を発火条件にできるようになったのが素敵ですね。
以上でTerraformでCodePipelineとCodeBuildでECRのイメージを量産する環境の構築方法になります。
補足:新規でDockerイメージを作成する方法
以下、3STEPで完了です。
-
local.custom_build_images
の配列で新規で作りたい要素を追加し、terraform apply
します。この追加した配列がCUSTOM_BUILD_IMAGE_TARGET
の一つになります。 -
./modules/image_build/Dockerfile/Dockerfile_${CUSTOM_BUILD_IMAGE_TARGET}
のDockerfileを用意します。 - AWSコンソール上でのCodePipeline実行時に、新規で作った
CUSTOM_BUILD_IMAGE_TARGET
を指定して作成して完了です。
DockerFIleを用意するだけで、とても簡単にECRイメージを作れるようになりました!
まとめ
今回は、CodePipelineとCodeBuildでECRのイメージを量産する方法について紹介させていただきました。一度量産できる環境を構築してしまえば、インフラにあまり精通していないメンバーでも気軽にイメージを構築でき、かつカスタムイメージの作成が突発的に必要になった場合でも迅速に対応することができます。
この記事がどなたかの助けになれば幸いです!
明日は、@Daishuさんによるイベントの登壇についての記事になります!
乞うご期待です!