2019.01.12

【AWS】DockerイメージをECRヘデプロイするCI環境をCodePipelineで構築する【Docker】

構成

  • AWSに構築したCI環境
  • CI環境を構築するCloudformationテンプレートと構築手順
  • 本手順の注意点
  • まとめ

AWSに構築したCI環境

AWS構成図

以下のような環境を構築しました。
2019 01 12 1

コードリポジトリにはGithubを使用し、ここで Dockerfile と AWS CodeBuild に渡す buildspec.yaml をコード管理しています。

CIの流れ

CIの流れは以下のようになります。

  1. 開発者がGithubにpush
  2. 1.で変更がある場合はCodePipelineが起動 (trigger or pollilng)
  3. CodePipelineがGithubリポジトリからコードをpull
  4. CodeBuildにコードが渡り、Dockerイメージビルド&ECRへのpushを実行 (buildspec.yamlの内容)

CodePipelineとCodeBuildは、それぞれAWS内部リソースへアクセスするため、サービスロールという形で権限付与する必要があります。

また、CodePipelineはパイプラインの各ステージ間で、ソースコードやビルド後の成果物を共有する際に(後段のステージに渡す際に)S3バケットを必要とします。

CI環境を構築するCloudformationテンプレートと構築手順

Cloudformationテンプレート

上記の構成を実現するCloudformationテンプレートを作成しました。
cloudformationテンプレート

このテンプレート使用したCI環境構築は後ほど説明します。

buildspec.yaml

buildspecには以下のコマンドが記述されています。

  • Dockerイメージビルドのコマンド
  • ECRへイメージpushするコマンド

<ECRレジストリURI>、<リポジトリ名>は、ECR作成後に取得して、追記が必要です。 後ほど説明します。

buildspec.yml

version: 0.2

phases:
  pre_build:
    commands:
    - echo Logging in to Amazon ECR...
    - aws --version
    - $(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)
    - REPOSITORY_URI=<ECRレジストリURI>
    - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
    - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
    - echo Build started on `date`
    - echo Building the Docker image...
    - docker build -t $REPOSITORY_URI:latest .
    - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
    - echo Build completed on `date`
    - echo Pushing the Docker images...
    - docker push $REPOSITORY_URI:latest
    - docker push $REPOSITORY_URI:$IMAGE_TAG
    - echo Writing image definitions file...
    - printf '[{"name":"<リポジトリ名>","imageUri":"%s"}]' 
$REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
  files: imagedefinitions.json

構築手順

上記テンプレートを使用して、実際に構築する手順を紹介します。

  1. DockerイメージのGithubリポジトリを用意する
  2. GithubアカウントのOAuthTokenを取得する
  3. Cloudformationsスタックの作成
  4. Githubリポジトリへpush

1. DockerイメージのGithubリポジトリを用意する

Dockerfileとbuildspec.yamlが含まれたGithubリポジトリを用意します。
ファイル構成は以下です。
既存のGithubリポジトリに適用する場合も、所定のDockerfileと、上記で説明したbuildspec.yamlを追加するだけです。

.
├── Dockerfile
└── buildspec.yaml

2. GithubアカウントのOAuthTokenを取得する

Githubへログインし、[Settings]→[Developer settings]→[Personal access tokens]へ移動し、「Generate new token」をクリック
2019 01 12 2

Token description と Select scopesの「repo」を全選択し、「Generate Token」をクリック
2019 01 12 3

生成完了後、トークンが表示されるのはこの画面だけです。
画面を移動する前にトークンをコピーしておきましょう。

余談ですが、このトークンはセキュリティ上重要なもので、漏洩すると不正攻撃に利用されてしまいます。
LastPassやOnePasswordなど、セキュア情報管理サービスを利用するなどして安全に保存するのがおすすめです。
Githubリポジトリ内のコードへのハードコーディングは絶対にやめましょう。

3. Cloudformationsスタックの作成

先ほど紹介したテンプレートを使用して、CI環境のAWSスタックを構築します。
Cloudformationのコンソールを開き「スタックの作成」をクリック 2019 01 12 4

「テンプレートを Amazon S3 にアップロード」を選択し、テンプレートファイルを指定する 2019 01 12 5

テンプレートファイルは、以下リポジトリよりダウンロードできます。
cloudformationテンプレート

本テンプレートは以下のパラメータを指定できます。(必須)

  • Env - 環境名
  • CodeRepository - Githubリポジトリ名
  • Branch - Githubリポジトリのデプロイ対象とするブランチ名
  • ImageRegistry - 作成するECRレジストリ名
  • Owner - Githubアカウント名
  • OAuthToken - 「2. GithubアカウントのOAuthTokenを取得する」で取得したOAuth Token

例として、以下のように設定します。 2019 01 12 6

オプション設定はそのままで次へ 2019 01 12 7

「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」にチェックを入れて、「作成」をクリック 2019 01 12 8

Cloudformationに作成したスタックが表示されているはずです。 作成には少し時間がかかります。 以下のように「CREATE_COMPLETE」となれば完了です。 2019 01 12 9

4. Githubリポジトリへpush

CodePipelineに作成したスタック名と同じpipelineが作成されています。
まずは、SourceステージのGithubリポジトリからのPullが成功しているかを確認します。
失敗している場合、エラー内容をみて、リポジトリ名やOAuth Tokenに誤りがないかなど確認しましょう。

このままでもpipelineは起動しますが、buildspec.yamlにECRリポジトリURLを記述していないため、ビルドに失敗します。
「1. DockerイメージのGithubリポジトリを用意する」で用意したGithubリポジトリのbuildspwc.yamlの<リポジトリURL>と<リポジトリ名>を埋めて、pushしましょう。
これらはECRの画面から確認できます。

リポジトリ名とURI欄をコピーして、それぞれbuildspec.yamlにペーストします。 2019 01 12 10

追記したbuildspec.yamlをpushすると、ビルドも成功するはずです。 2019 01 12 11

ECRにも、Dockerイメージがpushされています。 2019 01 12 12

本手順の注意点

Githubリポジトリの変更検出方式がポーリングである

本記事で紹介している方式では、「CodePipelineはGithubの変更によってトリガーされて起動する」ような書き方をしています。

これはイメージ的にわかり易くするためですが、実際にはトリガーされるのではなく、「CodePipelineがGithubリポジトリをポーリング」しています。

以前はこの方式しかなかったのですが、2018年5月にGithubのWebhookイベントに対応し、本当にGithubからCodePipelineをトリガーすることができるようになりました。
https://aws.amazon.com/jp/about-aws/whats-new/2018/05/aws-codepipeline-supports-push-events-from-github-via-webhooks/

Webhookに対応するやり方は、改めてまとめたいと思います。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/pipelines-webhooks-migration.html

CI環境を構築する度にS3バケットと各種サービスロールが作成される

これには2つ問題があります。

  • 作成されるリソースが多くなるため管理が煩雑化
  • アカウント毎に作成可能なリソース上限に引っかかる

一つ目は特に説明は不要かと思いますし、それほど大きな問題でもないですが、二つ目に関しては結構重大な問題になる場合があります。
S3バケットはAWSアカウント毎に 100個までしか作成できません。
https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/BucketRestrictions.html

CodePipelineには、アーティファクトバケット(Githubからpullしたコードなどを各ステージへ受け渡すための保存用のバケット)が必要です。
本記事で紹介したテンプレートを使用してスタックを構築する度に、S3バケットが増えていくことになります。
もし、他の要因で大量のバケットが存在している場合や、100を超えるCI環境を構築したい場合には、アーティファクトバケットをパラメータで指定するなどの工夫が必要です。

また、IAM Roleについても同じことが言えます。
こちらはAWSアカウント毎に1000オブジェクトまで作成可能なので、S3バケットほどすぐには問題になりませんが、管理煩雑化を低減するためにも、S3と同様の対策をしておいたほうが良さそうです。
https://docs.aws.amazon.com/jajp/IAM/latest/UserGuide/referenceiam-limits.html

本問題について、対策したものは改めてまとめたいと思います。

まとめ

「DockerイメージをECRへデプロイするCI環境」を構築するCloudformationテンプレートと構築手順を紹介しました。
Dockerは、今後使われていく機会が増えていくと思いますので、単にECRへのデプロイを自動化するCI環境も、サクッと構築したい場合に参考になるかと思います。
ぜひご活用ください。

CodePipelineは、ECSへのデプロイにも対応しており、ECRへのDockerイメージpushと同時にECSへのデプロイも自動化したCICD環境を作ることも可能です。
この環境についても、改めてまとめたいと思います。