AWS CodePipelineがSourceとしてAmazon ECRを指定できるようになりました! #reinvent
はじめに
おはようございます、加藤です。「re: Invent組のブログが飛び交っていますね!」
AWS CodePipeline(以降、CodePipeline)がAmazon ECR(以降、ECR)というDockerコンテナレジストリへの更新をデータソースとして使用できるようになりました!
例えば、2つのリポジトリを運用していたとします。
- node.jsがインストールされたbase-image
- base-imageに作成したAppを追加したhello-world
base-imageはDockerfileなどへの変更(CodeCommitへのPush)を検知して、CodePipelineを実行する事ができます。
hello-worldもapp.jsなどへの変更を検知して、CodePipelineを実行する事ができます。
hello-worldはbase-imageに依存しているので、base-imageに変更があった場合もCodePipelineを実行してビルドを行う必要がありますね。つまりECRへのPushをCodePipelineのソースにしたいということです。今回のアップデートで可能になったのはこの部分です!!
やってみた
図のような構成を作成してみます。下記のブログを参考にやってみました。
CodeBuildサービスロールの作成
CodeBuildが使用するロールを作成します、AWS CLIで行いました。まず2つのJSONを用意します。
create-role.json
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "codebuild.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
put-role-policy.json
{ "Version": "2012-10-17", "Statement": [ { "Sid": "CloudWatchLogsPolicy", "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": [ "*" ] }, { "Sid": "CodeCommitPolicy", "Effect": "Allow", "Action": [ "codecommit:GitPull" ], "Resource": [ "*" ] }, { "Sid": "S3GetObjectPolicy", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:GetObjectVersion" ], "Resource": [ "*" ] }, { "Sid": "S3PutObjectPolicy", "Effect": "Allow", "Action": [ "s3:PutObject" ], "Resource": [ "*" ] } ] }
JSONが存在するディレクトリで下記のコマンドを実行します。
aws iam create-role --role-name CodeBuildServiceRole --assume-role-policy-document file://create-role.json aws iam put-role-policy --role-name CodeBuildServiceRole --policy-name CodeBuildServiceRolePolicy --policy-document file://put-role-policy.json aws iam attach-role-policy --role-name CodeBuildServiceRole --policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
Baseイメージ作成環境
ECRの作成
node.jsがインストールされたDockerコンテナを格納するリポジトリを作成します。
aws ecr create-repository --repository-name base-image
CodeCommitの作成
Baseイメージ用のCodeCommitを作成します。
mkdir base-image && cd base-image export BASE_IMAGE_CLONE_URL_HTTP=$(aws codecommit create-repository --repository-name base-image | jq -r '.repositoryMetadata.cloneUrlHttp') git init git config credential.helper '!aws codecommit credential-helper $@' git config credential.UseHttpPath true git remote add origin ${BASE_IMAGE_CLONE_URL_HTTP}
ファイルの作成
Dockerfileを作成します。node.jsをインストールするだけです。
Dockerfile
FROM alpine:3.8 RUN apk add nodejs --update --no-cache
buildspec.ymlを作成します。REPOSITORY_URI
のAWSアカウント番号は環境に合わせて変更してください。
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=012345678910.dkr.ecr.ap-northeast-1.amazonaws.com/base-image - 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
作成したファイルをリポジトリにPushします。
git add . git commit -m '初回コミット' git push -u origin master
CodePipelineの作成
パイプライン名はbase-image
、サービスロールが存在しない場合は新規作成してください。
作成したCodeCommitをソースとして使用します。
ビルドにはCodeBuildを使用します。まだ作成していないので、Create projectから作成します。
プロジェクト名はbase-image
にします。
AWSが用意してくれているマネージド型イメージを使用します。
Key | Value |
---|---|
OS | Ubuntu |
ランタイム | Docker |
ランタイムバージョン | aws/codebuild/docker:17.09/0 |
イメージのバージョン | aws/codebuild/docker:17.09.0-1.5.0 |
サービスロール | CodeBuildServiceRole (最初に作成したロールです) |
CodePipelineの画面に戻ってきました。
デプロイステージは不要なのでスキップします。
最終確認でOKすると、パイプラインが走ります。
ECRを確認するとイメージが格納されていました!
App(Hello World)イメージ作成環境
ECRの作成
アプリケーションが組み込まれたDockerコンテナを格納するリポジトリを作成します。
aws ecr create-repository --repository-name hello-world
CodeCommitの作成
Appイメージ用のCodeCommitを作成します。
mkdir hello-world && cd hello-world export HELLO_WORLD_CLONE_URL_HTTP=$(aws codecommit create-repository --repository-name hello-world | jq -r '.repositoryMetadata.cloneUrlHttp') git init git config credential.helper '!aws codecommit credential-helper $@' git config credential.UseHttpPath true git remote add origin ${HELLO_WORLD_CLONE_URL_HTTP}
ファイルの作成
Dockerfileを作成します。app.jsをコピーするだけです。
FROM
のAWSアカウント番号は環境に合わせて変更してください。
Dockerfile
FROM 012345678910.dkr.ecr.ap-northeast-1.amazonaws.com/base-image ENV PORT=80 EXPOSE $PORT COPY app.js /app/ CMD ["node", "/app/app.js"]
app.jsを作成します。Hello Worldと表示するだけです、ポートは3000で受け付けます。
app.js
var http = require('http'); http.createServer(function (request, response) { response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World\n'); }).listen(3000);
buildspec.ymlを作成します。REPOSITORY_URI
のAWSアカウント番号は環境に合わせて変更してください。
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=012345678901.dkr.ecr.ap-northeast-1.amazonaws.com/hello-world - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - IMAGE_TAG=build-$(echo $CODEBUILD_BUILD_ID | awk -F":" '{print $2}') 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 detail file... - printf '[{"name":"hello-world","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json artifacts: files: - 'imageDetail.json'
作成したファイルをリポジトリにPushします。
git add . git commit -m '初回コミット' git push -u origin master
CodePipelineの作成
パイプライン名はhello-world
、サービスロールが存在しない場合は新規作成してください。
base-image
のリポジトリをソースとして使用します。
base-image
の時と同様にCodeBuildを作成します。
CodePipelineの画面に戻ってきました。
デプロイステージは不要なのでスキップします。
SourceがECRしか無いため、失敗します。
SourceにCodeCommitを追加します。
出力アーティファクトをSourceAtrifact
と設定してください。
このままだと、Source内で出力アーティファクトが重複してしまうので、ECRの方の出力アーティファクトをBaseImage
に変更します。
変更のリリースボタンを押下して、CodePipelineを起動します。
成功しました!!
動作テスト
base-image
にインストールするパッケージを増やして動作を確認してみます。rubyを追加しました。
Dockerfile(base-image)
FROM alpine:3.8 RUN apk add nodejs ruby --update --no-cache
CodeCommitにPushします。
git add . git commit -m 'rubyのインストールを追加' git push -u origin master
無事に完了しました!base-image
のCodePipeline完了後に自動でhello-world
のCodePipelineが起動しています。
Appのコードは変わっていないでの、Commitメッセージは変化なしです。
次にApp側もテストしてみます。無駄に!を足してみました。
app.js
var http = require('http'); http.createServer(function (request, response) { response.writeHead(200, {'Content-Type': 'text/plain'}); response.end('Hello World!!!!!!!!!!!\n'); }).listen(3000);
CodeCommitにPushします。
git add . git commit -m '!を足した' git push -u origin master
今度はhello-world
のCodePipelineだけが動きました!
あとがき
今回のようなシンプルな例では、意味がないですがBaseイメージへインストールするパッケージが増えたり設定変更なども行っている場合だとビルドの高速化が期待できそうですね!