CodeCommit のリポジトリに Dockerfile をpush すると CodeBuild が docker build して コンテナイメージを作成し ECR のリポジトリに push する仕組みの構築方法 (docker build の自動化)

記事タイトルとURLをコピーする

こんにちは!
技術課の山本です

今年は雪が多いですね
晴れた日に雪山に登って 冠雪している遠くの山々を眺めるのが最高です
最近は八ヶ岳(北横岳と蓼科山)に行きました

f:id:swx-yamamoto:20220124113554p:plain
北横岳から見る蓼科山

f:id:swx-yamamoto:20220124113524p:plain
北横岳から見る浅間山

f:id:swx-yamamoto:20220124113922p:plain
蓼科山から遠くに北岳・甲斐駒ヶ岳・仙丈ヶ岳・

CodeCommit のリポジトリに Dockerfile をpush すると CodeBuild が docker build して コンテナイメージを作成し ECR のリポジトリに push する仕組みの構築方法 (docker build の自動化)

さて長いタイトルですね。。。
コンテナイメージのビルド環境 を CodeCommit , CodePipeline , CodeBuild を使って構成する機会がありました
実際の環境構築の際にハマったポイント等も含めてブログに書き残すことにしました

こんな構成です

  1. CodeCommit にあるリポジトリの 開発(develop)/本番環境(production) ブランチ に Dockerfile をpush
  2. CodePipeline が CodeBuild に連携
  3. CodeBuild が docker build しイメージ作成
  4. CodeBuild が 開発(develop)/本番環境(production)のAWSアカウントにある ECR に push

+ Dockerhub へのログイン情報を Secret Manager に保管します

f:id:swx-yamamoto:20220125163808p:plain
構成

前提

  • Docker Hub (https://hub.docker.com/) にアカウントを作成してあること

    • Docker Hub のアカウントを利用して pull を行うため
      • Docker Hub にログインをしていないユーザーが Docker Hub のイメージを Pull する際には IP アドレス毎に回数制限が掛かります (CodeBuild を使って pull すると パブリッククラウドの性質上 他のユーザーの pull 回数の影響を受けることがあります)
      • 詳細は:ダウンロード率制限 | Docker ドキュメント
  • 各AWSアカウントにリソースを構築するための適切な権限を持つ IAM ユーザーを持っていること

Secret Manager に Docker Hub のアカウント情報を保管する

Docker Hub のアカウント名とパスワードをSecret Manager に保管します
先ずはDocker Hub のアカウント名 (DOCKERHUB_USER) を保管します
※ 値 (xxxx) には Docker Hub のアカウント名を入れてください

f:id:swx-yamamoto:20220124161649p:plain
DOCKERHUB_USER

f:id:swx-yamamoto:20220124161734p:plain
DOCKERHUB_USER_2

f:id:swx-yamamoto:20220124161756p:plain
DOCKERHUB_USER_3

上の設定で作成します
同様に パスワードを DOCKERHUB_PASS という名前で保管してください (画像は割愛)

また後ほど使用するため各シークレットのARNをメモしておいてください

f:id:swx-yamamoto:20220124172227p:plain
シークレットのARN

CodeCommit にリポジトリを作成

f:id:swx-yamamoto:20220124125947p:plain
CodeCommitにリポジトリを作成

CodeCommit に作成したリポジトリから clone して push するまでの流れは以下のドキュメントを参照ください

フォルダ構成

f:id:swx-yamamoto:20220124171410p:plain

  • nginx
    • nginxコンテナイメージ作成用のフォルダ)
      • 仮に nginx コンテナとしています (変更可)
    • ここにDockerfileを配置
  • buildspec.yml
    • CodeBuild が docker build して コンテナイメージを作成し ECR のリポジトリに push する際に使用する仕様書
      • トップディレクトリに buildspec.yml という名前で配置しておくと CodeBuild が読みに行きます

各ファイルの中身

Dockerfile

FROM nginx

buildspec.yml (変更する箇所を下に記載します)

version: 0.2
env:
  variables:
    version: "v0.95"
  secrets-manager:
    DOCKERHUB_USER: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:DOCKERHUB_USER-xxxxxx:DOCKERHUB_USER
    DOCKERHUB_PASS: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:DOCKERHUB_PASS-xxxxxx:DOCKERHUB_PASS
phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR
      - 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
      - echo Logging in to Docker Hub
      - echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USER --password-stdin
  build:
    commands:
      - echo Build started on `date`
      - echo Build and Run the Docker image
      - docker build -f nginx/Dockerfile -t $IMAGE_REPO_NAME1:$version nginx/
      - echo docker tag $IMAGE_REPO_NAME1:$version $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME1:$version
      - docker tag $IMAGE_REPO_NAME1:$version $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME1:$version
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME1:$version
      - printf '[{"name":"<container-definition>","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME1:$version > artifacts.json

artifacts:
  files: artifacts.json
  • secrets-manager セクション
    • 以下の部分にメモしておいたシークレットのARN を記載してください ※末尾の「:DOCKERHUB_USER」や「:DOCKERHUB_PASS」を消さないように注意です
      • arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:DOCKERHUB_USER-xxxxxx
      • arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:DOCKERHUB_PASS-xxxxxx
  • pre_build セクション
    • 変更不要
      • ECR と Docker Hub にログインしています
  • build セクション
    • CodeCommit に作成したリポジトリのフォルダ名を「nginx」から変えている場合には置き換えてください
      • docker build してタグ付与をしています
  • post_build
    • 変更不要
      • ECR に push しています

またversion変数は イメージタグになります
push する際に更新すると新しいイメージタグを付与します

env:
  variables:
    version: "v0.95" #イメージタグの名前

ブランチ

develop/production ブランチを作成します

CodeBuild作成

ビルドプロジェクトを作成していきます
本番/開発環境用にそれぞれ作る必要があります
ここでは 開発環境用のビルドプロジェクトを作成します
本番環境用のビルドプロジェクトを作る際には「[ソース]セクション」を production にして作成ください

[プロジェクトの設定]セクション
f:id:swx-yamamoto:20220125113505p:plain

[ソース]セクション
develop ブランチを対象にします

f:id:swx-yamamoto:20220125130137p:plain

[環境]セクション
イメージの AMD or ARM は コンテナの実行環境に合わせてください

f:id:swx-yamamoto:20220125132111p:plain

[環境(追加設定)]セクション
以下を環境変数に設定します

  • AWS_ACCOUNT_ID
    • ECRリポジトリを作成する AWSアカウントのアカウント番号
  • AWS_DEFAULT_REGION
    • ECRリポジトリを作成するリージョン
  • IMAGE_REPO_NAME1
    • 作成するECRリポジトリの名前

f:id:swx-yamamoto:20220125130927p:plain

他のセクションは特に設定しないで「ビルドプロジェクトを作成する」を押します ※必要に応じて変更ください
f:id:swx-yamamoto:20220125131504p:plain

CodeBuild の 利用する IAM Role

CodeBuild の利用する IAM Role には以下のポリシーを追加付与します

CodeBuild の利用する IAM Role は以下から参照可能です
f:id:swx-yamamoto:20220125181425p:plain

ECR にリポジトリを作成

本番/開発環境用にそれぞれ作る必要があります
ここでは 開発環境用のECR リポジトリを作成します
ECRの作成は開発環境のAWSアカウントにログインして作業ください
本番環境用のECR リポジトリを作る際には 本番環境のAWSアカウントにログインして作業 してください

プライベートなリポジトリを作成します
CodeBuild を作成するときに 環境変数 IMAGE_REPO_NAME1 に指定した名前で作成します

f:id:swx-yamamoto:20220125135639p:plain

リポジトリポリシー

CodeBuild に作成したビルドプロジェクトからの push を許可します
そのため Principal にはビルドプロジェクトに設定した IAM Role の ARN を指定します

リポジトリポリシー

{
  "Version": "2008-10-17",
  "Statement": [
    {
      "Sid": "new statement",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::0123456789012:role/service-role/codebuild-yamamoto-test-docker-build-service-role"
      },
      "Action": [
        "ecr:BatchCheckLayerAvailability",
        "ecr:CompleteLayerUpload",
        "ecr:GetAuthorizationToken",
        "ecr:InitiateLayerUpload",
        "ecr:PutImage",
        "ecr:UploadLayerPart"
      ]
    }
  ]
}

f:id:swx-yamamoto:20220125144115p:plain

f:id:swx-yamamoto:20220125144156p:plain

※ここまで作成すると CodeBuild のビルドプロジェクトを正常に実行できます
( 実行すると develop ブランチ の Dockerfile からイメージを作成し ECR に push します)

CodePipeline の作成

本番/開発環境用にそれぞれ作る必要があります
ここでは 開発環境用のビルドプロジェクトを作成します
本番環境用のビルドプロジェクトを作る際には「[ソース]セクション」の「ブランチ名」を production にして作成ください
以下の設定で作成します

f:id:swx-yamamoto:20220125143847p:plain

f:id:swx-yamamoto:20220125145601p:plain

f:id:swx-yamamoto:20220125144649p:plain

f:id:swx-yamamoto:20220125145855p:plain

(確認画面は割愛します)

パイプラインを 作ったときに 1回パイプラインが実行されてしまう点にご注意ください (仕様なのか確認中)

f:id:swx-yamamoto:20220125150550p:plain

これにて完成です😺

動かしてみる

buildspec.yml 内のイメージタグ変数を 「v1」に変更して CodeCommit の develop ブランチに push します

env:
  variables:
    version: "v1"

CodePipeline が動きました
f:id:swx-yamamoto:20220125153055p:plain

CodeBuild による docker build が成功しています
f:id:swx-yamamoto:20220125153238p:plain

「v1」タグのイメージがECR に push されていました 🎉
(イメージ自体に変更を加えていないためタグが追加された) f:id:swx-yamamoto:20220125153512p:plain

複数のコンテナイメージをビルドするとき

複数のコンテナイメージをビルドするときは以下のようにします

  1. 新しいコンテナ用にECR リポジトリを作成
  2. CodeBuild のビルドプロジェクトに環境変数($IMAGE_REPO_NAMEx)を追加
  3. CodeCommit の対象ブランチに 新しいコンテナイメージ 用のディレクトリと Dockerファイルを追加
  4. buildspec.yml のbuild セクションと post_buildセクションを修正し push

nginx2 コンテナを追加する例

1.nginx2 コンテナ用にECR リポジトリ作成

f:id:swx-yamamoto:20220125160002p:plain

またリポジトリポリシーを設定します (割愛)

2.CodeBuild のビルドプロジェクトに環境変数($IMAGE_REPO_NAMEx)を追加
作成したECRリポジトリの情報を追加します
f:id:swx-yamamoto:20220125160224p:plain

3.CodeCommit の対象ブランチに 新しいコンテナイメージ 用のディレクトリと Dockerファイルを追加
developブランチに nginx2 用のディレクトリと Dockerファイルを追加します
f:id:swx-yamamoto:20220125160335p:plain

4.buildspec.yml のbuild セクションと post_buildセクションを修正し push
buildspec.yml ※ 「#追加」と書いてある箇所(全部で5行)になります

version: 0.2
env:
  variables:
    version: "v2.0"
  secrets-manager:
    DOCKERHUB_USER: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:DOCKERHUB_USER-xxxxxx:DOCKERHUB_USER
    DOCKERHUB_PASS: arn:aws:secretsmanager:ap-northeast-1:123456789012:secret:DOCKERHUB_PASS-xxxxxx:DOCKERHUB_PASS
phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR
      - 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
      - echo Logging in to Docker Hub
      - echo $DOCKERHUB_PASS | docker login -u $DOCKERHUB_USER --password-stdin
  build:
    commands:
      - echo Build started on `date`
      - echo Build and Run the Docker image
      - docker build -f nginx/Dockerfile -t $IMAGE_REPO_NAME1:$version nginx/
      - echo docker tag $IMAGE_REPO_NAME1:$version $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME1:$version
      - docker tag $IMAGE_REPO_NAME1:$version $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME1:$version
  # 追加
      - docker build -f nginx2/Dockerfile -t $IMAGE_REPO_NAME2:$version nginx/
      - echo docker tag $IMAGE_REPO_NAME2:$version $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME2:$version
      - docker tag $IMAGE_REPO_NAME2:$version $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME2:$version
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME1:$version
      - printf '[{"name":"<container-definition>","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME1:$version > artifacts.json
  # 追加
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME2:$version
      - printf '[{"name":"<container-definition>","imageUri":"%s"}]' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME2:$version >> artifacts.json

artifacts:
  files: artifacts.json

結果: 同じタグでpushされていました🎉

f:id:swx-yamamoto:20220125161554p:plain

f:id:swx-yamamoto:20220125161617p:plain

おわりに

docker build を各人の環境で行って ECR に push する部分が自動化できました
3ヶ月ほど運用して特に何事もなく運用できています
なにかあればまた追記しますね

山本 哲也 (記事一覧)

カスタマーサクセス部のエンジニア。2024 Japan AWS Top Engineers に選んでもらいました。

今年の目標は Advanced Networking – Specialty と Machine Learning - Specialty を取得することです。

山を走るのが趣味です。今年の目標は 100 km と 100 mile を完走することです。 100 km は Gran Trail みなかみで完走しました。残すは OSJ koumi 100 で 100 mile 走ります。実際には 175 km らしいです。「草 100 km / mile」 もたまに企画します。

基本的にのんびりした性格です。座右の銘は「いつか着く」