Edited at

CodePipelineで自動テスト&ビルドして、ECSへBlue/Greenデプロイしてみた


0.はじめに

CI/CDの勉強をする一環で、AWSCodePipelineを使って自動テスト&ビルドして、ECSへのBlue/Greenデプロイさせるまでを試してみたので、やり方を備忘録も兼ねて残しておくものです。

初投稿故、イマイチなところがあってもお許しください。


1.事前準備:アプリケーション & ECSクラスター & ECSタスク定義作成


アプリケーション

アプリ自体はDjangoで作成した、ただのノーマルな感じのAPIなため割愛。


ECSクラスター

こちらも特筆すべき点なしのため割愛。


ECSタスク定義

以下3コンテナイメージの構成で、ecs-cli composeを使ってタスク定義しています。


  • balance_app ⇒ 今回デプロイ対象とするアプリケーション用コンテナ

  • nginx

  • xray-daemon 


ecs-compose.yml(ご参考。パラメータファイルは割愛)

version: '2'

services:
balance_app:
image: '<AccountId>.dkr.ecr.us-west-2.amazonaws.com/msa_balance_app:latest'
mem_limit: 256000000
ports:
- '5000:5000'
command: ./uwsgi.sh
environment:
AWS_REGION: us-west-2
AWS_XRAY_DAEMON_ADDRESS: '127.0.0.1:2000'
logging:
driver: awslogs
options:
awslogs-group: msa_app
awslogs-region: us-west-2
awslogs-stream-prefix: balance_app
balance_nginx:
image: '<AccountId>.dkr.ecr.us-west-2.amazonaws.com/msa_nginx:latest'
mem_limit: 128000000
links:
- balance_app
ports:
- '80:80'
command: >
/bin/sh -c "envsubst '$$SERVER_NAME'
< /etc/nginx/nginx.conf.template >
/etc/nginx/nginx.conf
&& nginx -g 'daemon off;'"
environment:
SERVER_NAME: localhost
logging:
driver: awslogs
options:
awslogs-group: msa_app
awslogs-region: us-west-2
awslogs-stream-prefix: balance_nginx
aws-xray-daemon:
image: 'amazon/aws-xray-daemon'
mem_limit: 128000000
ports:
- '2000:2000/udp'
command:
- "/usr/bin/xray"
- "--bind"
- "0.0.0.0:2000"
- "--region"
- "us-west-2"
- "--buffer-memory"
- "64"
- "--log-level"
- "dev"
- "--log-file"
- "/dev/stdout"
logging:
driver: awslogs
options:
awslogs-group: msa_app
awslogs-region: us-west-2
awslogs-stream-prefix: balance_xray-daemon

また、Codeシリーズでテストやビルドやデプロイをするための各定義ファイルも配置して、CodeCommitへgit pushしておきます。


プロジェクト全体のディレクトリ構成

project/

├ .git
├ root/
│ ├ msa_app/
│ ├ balance/
│ ├ manage.py
│ └ requirements.txt
├ appspec.yaml  ⇒ Blue/Greenデプロイ用
├ buildspec.yml  ⇒ アプリケーション自動ビルド用
├ Dockerfile
├ taskdef.json  ⇒ Blue/Greenデプロイ用
├ testspec.yml  ⇒ アプリケーション自動テスト用
└ uwsgi.sh


2.CodeBuildでアプリケーションをUNITテスト


テスト実行用specファイルの作成

まず、リポジトリルート直下に配置したtestspec.ymlをこんな感じで書いて、アプリ稼働に必要なライブラリをpip installした後、テストコードを実行するように定義します。


testspec.yml

version: 0.2

phases:
install:
commands:
- echo install started
- cd root
- pip install -r requirements.txt
build:
commands:
- echo test started
- python manage.py test --settings msa_app.ci_settings


テスト用のCodeBuildプロジェクト作成

次に、CodeBuildでビルドプロジェクトを作っていきます。

リポジトリはCodeCommitを選択。

source.png

UbuntuのPython3.6を選択。

env.png

Buildspecには、先ほど作成したtestspec.ymlを指定。

今回はテスト実行時には成果物を何も作らないので、Artifactsは初期値のままでOK。

spec.png


3.CodeBuildでDockerイメージをビルド


ビルド実行用specファイルの作成

テスト時と同じように、リポジトリルート直下に配置したbuildspec.ymlをこんな感じに書きます。


buildspec.yml

version: 0.2

phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- 'docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .'
- >-
docker tag $IMAGE_REPO_NAME:$IMAGE_TAG
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
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_NAME:$IMAGE_TAG
- >-
printf '[{"name":"balance_app","imageUri":"%s"}]'
$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
> artifacts.json
artifacts:
files: artifacts.json

buildステップでdocker buildコマンドによるdockerイメージ作成&タグ付けを行い、

post_buildステップでECRリポジトリへの登録を行っています。

なお、Dockerfileはこんな感じで特に変哲もない、

必要ライブラリをpip install後、uwsgiでDjangoアプリを動かすものです。


Dockerfile

FROM python:3.6

RUN groupadd -r uwsgi && useradd -r -g uwsgi uwsgi
COPY /root/requirements.txt /requirements.txt
RUN pip install -r /requirements.txt
COPY /root /app
RUN chown -R uwsgi /app
COPY uwsgi.sh /uwsgi.sh
RUN chmod +x /uwsgi.sh


Dockerイメージビルド用のCodeBuildプロジェクト作成

デプロイ用のDockerイメージをビルドする準備ができたので、ビルドプロジェクトを作成していきます。

リポジトリはテスト時と同じくCodeCommitを選択。

source.png

今度はDockerイメージを作成するので、ランタイムにDockerを指定。

env.png

ビルド時に環境変数からリポジトリ名等を設定するので、環境変数も設定しておきます。

env_para.png

Buildspecには、先ほど作成したbuildspec.ymlを指定。

今回はビルド成果物としてDockerイメージがありますが、ECRへ直接pushしているため、Artifactsは初期値でOK。

spec.png

これでテスト&ビルド用のプロジェクトがそれぞれ作成できました。


4.CodeDeployでBlue/Greenデプロイ

ここからはデプロイ用のアプリケーション&デプロイグループの作成ですが、こちらはCodeDeployのコンソール画面ではなく、ECSサービスを作成することで自動作成できます。


ECSサービスの作成

デプロイ方式としてBlue/greenを選択することで、Blue/Greenデプロイ対応のサービスが作成され、

合わせて自動的にCodeDeploy用のアプリケーション&デプロイグループも作成されます。

service1.png

なお、Blue/Greenデプロイを選択するには、以下の権限を持ったIAMロールを事前に作成しておく必要があります。


  • ポリシーのアタッチ : AWSCodeDeployRoleForECS

  • 信頼ポリシー設定

{

"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": "codedeploy.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}

ネットワークはawsvpcモードでタスク定義しているので、配置するVPCとサブネット、適用するセキュリティグループを選択します。

service2.png

事前に作成しておいたロードバランサー(今回は後々APIGatewayにVPCリンク張りたかったため、NLBを作成済)を選択し、負荷分散用のコンテナとしてロードバランサーからトラフィックを流す先のコンテナを設定します。(ここで設定したコンテナ名:ポートを、appspec.yamlで設定することになります)

次にロードバランサー側のポート設定として、本番トラフィックを受け付けるポートをリスナーポートに、Blue/Greenデプロイ時にテスト用トラフィックを受け付けるポートをテストリスナーポートへ設定します。

service3-1.png

TargetGroupを二つ設定します。(CodeDeployからECSへBlue/Greenデプロイが走った際、ここのターゲットが自動で切り替わることで本番/テスト用トラフィックが入れ替わることになります。)

service3-2.png

今回は特に必要ないですが、DNSの設定も行っています。

service4.png


CodeDeployのデプロイ設定修正

これでECSサービス作成が完了し、CodeDeploy側でもアプリケーション&デプロイグループが自動作成されましたが、デフォルトではデプロイ時に即トラフィックが本番へ切り替わる設定になっているため、せっかくのBlue/Greenデプロイの利点が活かせません。


そこで、本番への切り替え前にテスト用トラフィックで検証できるよう、CodeDeployのコンソール画面から設定を修正します。

下記の設定ではデプロイ後の6時間はトラフィックが切り替わらず、テスト用ポート8080を使っての検証が可能となります。

6時間以内に検証を終え、リリース可能となった場合にはCodeDeployコンソール上から手動で再ルーティングすることで、本番トラフィックへの切り替えが行われます。

一方、6時間以内に検証が終わらずに手動で再ルーティングボタンできなかった場合には、Deploy失敗と見なされてその時点でDeploy処理が中断されます。

deploy_setting.png


5.CodePipelineでテスト&ビルド&デプロイを自動化

最後に、CodePipelineで全部繋げていきます。

なお、本当は1本のPipelineで繋げようとしたのですが、Deployステージでのコンテナイメージ名取得がうまいこといかず、テスト~ビルド&デプロイの二本立てになってます。

(BuildステージからDeployステージへのイメージ名の受け渡しがうまくできなかった...)


テスト~ビルド


①Sourceステージ

リポジトリへのpushをトリガーにPipeline起動させるためにCloudWatch Eventsで変更検出を行い、リポジトリに格納されているソースコードを後続のTestステージ&Buildステージで使用するためにSourceArtifactを出力します。

source.png


②Testステージ

入力アーティファクトにはSourceステージの出力を指定、プロジェクト名には事前作成済のテスト用ビルドプロジェクトを指定します。

出力成果物はないので、出力アーティファクトは空欄でOK。

test.png


③Buildステージ

テスト同様、入力アーティファクトにはSourceステージの出力を指定、プロジェクト名には事前作成済のビルド用ビルドプロジェクトを指定します。

ここも出力アーティファクトは空欄でOKです。

build.png

ここまでで、テスト~ビルドを行うPipelineの完成です。

test&build.png


デプロイ

次に、デプロイ用のPipeline作成です。

Deployステージの入力ファイルとしてappspec&taskdef.jsonが必要となるため、事前に作成しておきます。


appspec.yaml

version: 0.0

Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "balance_nginx"
ContainerPort: 80

TaskDefinition部分はCodeDeployが良しなに変換してくれるので、上記の記載でOK。

ContainerName&Portはロードバランサーからトラフィックを流す先のものを設定します。(ECSサービス作成時に負荷分散用に選択したコンテナ名:ポート)

今回はポート80のnginxコンテナ経由でアプリケーションコンテナへトラフィックを流すので、上記の設定としています。


taskdef.json

{

"executionRoleArn": null,
"containerDefinitions": [

"image": "<IMAGE1_NAME>",
"name": "balance_app"

},

タスク定義は長いので省略しますが、ポイントはデプロイするアプリケーション用コンテナのimageを上記の記載とする点で、ここをCodeDeployが良しなにイメージURIを変換してくれます。

その他は事前に作成したタスク定義のJson定義をECSコンソールから引っ張ってきて作るのでOKです。


①Sourceステージ

インプットになるSourceとして、ECRのDockerイメージとCodeCommitのソースリポジトリを指定します。


イメージ

デプロイ対象のイメージを指定。

出力アーティファクトは後続のDeployステージにて、イメージ名の取得に使用されます。

image.png


ソース

ビルド時と同様にCodeCommitのソースリポジトリを指定しますが、Deploy用Pipelineはソースのpush時ではなくBuild用Pipelineの完了後に実行させたいので、変更検出オプションにCloudWatchEventsは選択しません。

出力アーティファクトは後続のDeployステージにて、appspec.yamlとtaskdef.jsonを入力ファイルとするために使用されます。

source2.png


②Approvalステージ

一応、デプロイ前の手動承認も加えておきます。

approval.png


③Deployステージ

最後にデプロイステージです。

●アクションプロバイダ ⇒ ECSへのBlue/Greenデプロイを選択

●入力アーティファクト ⇒ Sourceステージで出力したSource&ImageArtifactを指定

●CodeDeployアプリケーション&デプロイグループ ⇒ ECSで自動作成されたものがリストに出てくるので選択

●ECSタスク定義 ⇒ ソースリポジトリから持ってきたtaskdef.jsonを指定

●AppSpecファイル ⇒ ソースリポジトリから持ってきたappspec.yamlを指定

●入力アーティファクトとイメージ詳細 

 ⇒ Sourceステージで出力したImageArtifactを選択し、プレースホルダテキストにはIMAGE1_NAMEを指定。

   ここの指定により、appspec.yaml内でIMAGE1_NAMEと指定した部分がイメージURIに置き換わります。                 

deploy.png

以上でデプロイ用のPipeline作成も完了です。

deploy.png


6.Pipelineを実行

これで、ソースリポジトリへgit pushするとPipelineが実行され、テスト&ビルド&デプロイが自動実行されます。

ちなみにPipelineが2本に分かれていますが、1本目のBuildステージでECRへのpushが行われたことをトリガーに2本目のPipelineも動き出すようです。(ECRリポジトリをSourceに指定しているとそうなる仕様...?)

無事、テスト&ビルドが自動実行され、Blue/Greenデプロイできました。

deploy.png


7.最後に

各specファイルの作成やらいろいろ悩んだ(詰まった)ところはありましたが、手順としては割とシンプルにできるあたりマネージドサービス群の威力を感じました。