AWSアカウントをまたいでECSにアプリケーションをデプロイする 〜ECRへのPushをCodePipelineのソースとして〜

ECSに乗せるアプリケーションができた!開発環境での確認はOKだ!さぁ本番リリースだ!
あっ、でも開発環境と本番環境でAWSアカウントは分けるんだった。。どうしよう。。

みなさんそんな経験はないでしょうか。 本日はそんな課題に対する1つの解決策を紹介したいと思います。

今回の構成

以下のような構成を構築します。

GitリポジトリにはCodeCommitを利用し、devブランチへPushされた場合は開発アカウントに、masterブランチへPushされた場合は本番アカウントにそれぞれデプロイします。

本番アカウントのECSにデプロイするまでのフローは以下のようになります。

  1. Gitリポジトリを開発アカウントに作成
  2. GitリポジトリのdevブランチへのPushされた場合は、開発アカウント上のCodePipeline(dev-pipeline)を実行し開発アカウントのECSにデプロイ
  3. 開発アカウントでの動作確認後、masterブランチへマージ
  4. masterブランチへのマージをトリガーに開発アカウント上のCodePipeline(dev-to-prd-pipeline)を実行
    1. 開発アカウントのCodeBuildでDockerイメージを作成
    2. 本番アカウントのECRにDockerイメージをPush
  5. 本番アカウントのECRへのPushをトリガーに本番アカウントのCodePipeline(prd-pipeline)を実行
    1. 本番アカウントのECRからDockerイメージを取得し本番アカウントのECSにデプロイ

本記事では2以外の部分を構築していきます。こちらの部分については弊社のハンズオンなどを参考に構築していただければと思います。

それでは早速構築していきましょう!

開発アカウントでの作業①

Gitリポジトリ、CodePipelineの構築

CloudFormationで以下のリソースを作成します。パラメータのprdAccountIdには、本番アカウントを設定します。

  • CodeCommit(コンテナ化するアプリケーションのリポジトリ:sample-app-codecommit)
  • CodePipeline
    • SourceAction(sample-app-codecommitのmasterブランチをトリガーとする)
    • CodeBuild(sample-app-build-projectをビルドプロジェクトとする)

本番アカウントECRへのPushはビルドプロジェクトのsample-app-build-projectにて行ないます。 sample-app-build-projectの設定はポイントは以下の通りです。

ビルドプロジェクト(Buildspec)

開発アカウントから本番アカウントのECRにイメージをPushするため、開発アカウントから本番アカウントのECRにログインします。 ログインする際にはAssumeRoleで本番アカウントのロールにスイッチします。 ※スイッチロールせずイメージをPushしても本番アカウントのCodePipelineが起動しないため

version: 0.2

env:
  variables:
    DOCKER_BUILDKIT: "1"
    
phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - aws --version
      - export AWS_CONFIG_FILE=config
      - $(aws ecr get-login --registry-ids ${REGISTRY_IDS} --no-include-email --profile prd-account-role)
      - REPOSITORY_URI=${REGISTRY_IDS}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NAME}
      - IMAGE_TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}
  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:$IMAGE_TAG
      - docker push $REPOSITORY_URI:latest

ビルドプロジェクト(Environment)

以下の環境変数をビルドプロジェクトに設定します。

  • AWS_ACCOUNT_ID:開発アカウントID
  • IMAGE_NAME:Dockerイメージ名
  • CONTAINER_NAME:ECSにデプロイするコンテナ名
  • REGISTRY_IDS:本番アカウントID

本番アカウントでの作業

ECS環境の構築

CloudFormationでECS環境を構築します。

完了後、CloudFormationの出力タブより作成されたALBのURLをクリックします。

nginxのデフォルトの画面が表示されればOKです。

CodePipelineの構築

CloudFormationで本番アカウントのCodePipelineを構築します。

  • CodePipeline
    • SourceAction(sample-app-ecrへのPutImageをトリガーとする)
    • CodeBuild(sample-app-build-prd-projectをビルドプロジェクトとする)
    • Deploy2Service(本番アカウントのECSへデプロイする)

ビルドプロジェクトsample-app-build-prd-projectの設定はポイントは以下の通りです。

ビルドプロジェクト(Buildspec)

ECSへデプロイするためにimagedefinitions.jsonを作成します。

version: 0.2

phases:
  build:
    commands:
      - REPOSITORY_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${IMAGE_NAME}
      - IMAGE_TAG=latest
      - echo "[{\"name\":\"${CONTAINER_NAME}\",\"imageUri\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}]"
      - echo "[{\"name\":\"${CONTAINER_NAME}\",\"imageUri\":\"${REPOSITORY_URI}:${IMAGE_TAG}\"}]" > imagedefinitions.json
artifacts:
    files: imagedefinitions.json

ビルドプロジェクト(Environment)

以下の環境変数をビルドプロジェクトに設定します。

  • AWS_ACCOUNT_ID:本番アカウントID
  • IMAGE_NAME:Dockerイメージ名
  • CONTAINER_NAME:ECSにデプロイするコンテナ名

AssumeRoleの作成

開発アカウントが本番アカウントにスイッチするためのロールを作成します。

別アカウントのS3バケットを利用する手順

上記記事の「アカウントAでロール (Role-A) を作る」を参考にロールを作成します。ロール名はprd-account-roleとし、AmazonEC2ContainerRegistryFullAccessポリシーをアタッチします。

開発アカウントでの作業②

CodeCommitへソースコードをプッシュする

サンプルアプリケーションおよびProfile情報を定義した以下の3ファイルをsample-app-codecommitへPushします。configのXXXXXXXXXXXXの部分は本番アカウントIDに変換してください。

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "healthy!!!!")
	})

	log.Fatal(http.ListenAndServe(":80", nil))
}

FROM golang:alpine
 
ADD . /go/src/

EXPOSE 80
CMD ["/usr/local/go/bin/go", "run", "/go/src/server.go"]
[profile prd-account-role]
role_arn = arn:aws:iam::XXXXXXXXXXXX:role/prd-account-role
credential_source=EcsContainer

しばらくすると開発アカウントのCodePipeline、本番アカウントのCodePipelineが順に実行されます。

最後に先ほどはnginxのデフォルトの画面が表示されたURLをもうリロードしてみましょう。

アプリケーションが変わっていることが確認できました!!

最後に

AWSアカウントをまたいでECSにアプリケーションをデプロイしてみました。アカウントをまたぐリリースは「CodeCommitの共有」や「S3の共有」などいくつかの方式で実現することはですが、どの方法もIAM周りで考慮しなければならない箇所が多く設定がやや複雑になります。それに比べ今回紹介したCodeBuildとECRでアカウントをまたぐ場合は割とシンプルな構成になります。

CodeCommit、ECSの構成でアカウントをまたいでリリースしたい方はぜひ試してみてください。 ちなみにGitHubを使うとアカウントまたぎの苦しみはないのでこちらもおすすめです。