CodeDeployでRails(Docker)をBlue/Greenデプロイする (CI/CDまでの道⑨)
はじめに
前回はCodeBuildを利用してCIを行いました。今回はタイトルにある通り、CodeDeploy
を利用してCD
に挑戦していきます。いままでの記事の技術をすべて利用して完成させていきますので、説明があいまいな個所は以前の記事で詳しく話していますので合わせて確認してください。
作成に利用するリポジトリはこちら
CI/CDまでの道シリーズ
- rails6+mysqlのdocker環境構築 (CI/CDまでの道①)
- dockerにwebpacker環境構築(jquery, bootstrap5, vue) (CI/CDまでの道②)
- rails(docker)に必要なgemを追加する (CI/CDまでの道③)
- rails(docker)にnginxを導入する (CI/CDまでの道④)
- rails(docker)をproductionモードで起動してみる (CI/CDまでの道⑤)
- ec2にdocker-composeでrailsをデプロイする (CI/CDまでの道⑥)
- Fargateにrailsをデプロイする (CI/CDまでの道⑦)
- Rails(Docker)をCodeBuildでCIする (CI/CDまでの道⑧)
- CodeDeployでRails(Docker)をBlue/Greenデプロイする (CI/CDまでの道⑨)
環境
- wsl2 (ubuntu20.04)
- docker 20.10.9
- docker-compose 1.29.1
- git 2.25.1
- vscode
注意
このハンズオンでは以下の書籍を用います。
AWSではじめるインフラ構築入門 安全で堅牢な本番環境のつくり方
AWSのインフラ環境構築に利用します。書籍で説明が足りている部分については説明を割愛させていただきます。
本ハンズオンの構成は以下となります。
インフラ環境構築
VPC
書籍4.2通りに作成します。
サブネット
書籍4.3通りに作成します。
インターネットゲートウェイ
書籍4.4通りに作成します。
ルートテーブル
書籍4.6のsample-rt-public
のみを作成します。
セキュリティグループ
書籍4.7のsample-sg-elb
をまず作成します。
そのあと第7回を参考にsample-sg-ecs
とsample-sg-db
を作成します。
データベース
書籍8章通りに作成します。パスワードはpassword
で設定します。
ECR
第8回を参考にsample-rails
とsample-nginx
のリポジトリを作成します。
CodeDeploy
準備
まずは、CodeDeployを行うためにファイルを修正していきます。
最初のデプロイ時にDBを作成するのでentrypoint.sh
を修正します。
#!/bin/sh
set -e
rm -f /myapp/tmp/pids/server.pid
bundle exec rails db:create
bundle exec rails db:migrate
bundle exec rails db:seed
exec "$@"
※ このスクリプトはCodeBuild
のtest
のタイミングにはdb:create
をコメントにしてください。
イメージをPushします。
NginxのDockerfileを修正します。
FROM nginx:alpine
# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*
# Nginxの設定ファイルをコンテナにコピー
# ADD nginx.conf /etc/nginx/conf.d/myapp.conf
# docker buildのときは以下のコメントをはずす
ADD /containers/nginx/nginx.conf /etc/nginx/conf.d/myapp.conf
# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf
第7回を参考にECRのコマンドでNginx
をPushします。
Rails
は2つ目のコマンドを以下に変更します。
$ docker build --target build --no-cache -t sample-rails .
そのあと、本番で利用するイメージをsample-rails
にPushします。実際に利用するイメージにはタグstagingをつけることにします。
$ docker build --target production --no-cache -t sample-rails .
$ docker tag sample-rails:latest [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-rails:staging
$ docker push [アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-rails:staging
config/credentials.yml.enc
を削除して、作成したcredentials.yml.enc
とmaster.key
をconfig/
に配置してください。
CodeBuildに必要なファイル
このあとCodePipeline
を利用するにあたって必要なファイルが2つあるのでカレントディレクトリに作成します。
まずはappspec.yml
を作成します。
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "nginx"
ContainerPort: 80
PlatformVersion: "LATEST"
次にtaskdef.json
を作成します。このファイルは前回タスク定義したときの内容をymlファイルで書いたものとなります。
{
"family": "sample-ecs",
"cpu": "256",
"memory": "512",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"executionRoleArn": "arn:aws:iam::[アカウントID]:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::[アカウントID]:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"name": "rails",
"image": "[アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-rails:staging",
"portMappings": [
{
"hostPort": 3000,
"protocol": "tcp",
"containerPort": 3000
}
],
"environment": [
{
"name": "DB_USERNAME",
"value": "admin"
},
{
"name": "DB_PASSWORD",
"value": "password"
},
{
"name": "DB_DATABASE",
"value": "myapp"
},
{
"name": "DB_HOST",
"value": "[RDSのエンドポイント]"
},
{
"name": "SECRET_KEY_BASE",
"value": "[master.keyの値]"
}
],
"essential": true
},
{
"name": "nginx",
"image": "[アカウントID].dkr.ecr.ap-northeast-1.amazonaws.com/sample-nginx",
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 80
}
],
"volumesFrom": [
{
"sourceContainer": "rails",
"readOnly": true
}
],
"essential": true
}
]
}
taskdef.json
はそれぞれのアカウントIDとmaster.key、RDSエンドポイントで書き換えしてください。
この2つがCodePipeline
でCodeBuild
を利用するのに必要になります。
Gitのリポジトリの作成
前回を参考にしてGitHubのリポジトリを作成してPushを行います。
ここではCICD_Road_zennとします。
IAMロールの作成
CodeDeploy
に利用するロールを作成します。
項目 | 値 |
---|---|
ユースケースの選択 | CodeDeploy(CodeDeploy ECS) |
ロール名 | CodeDeployRole |
追加でポリシーをアタッチします。
AmazonS3FullAccess
とAmazonECS_FullAccess
を追加しました。かなり強い権限になりますので実際に運用する場合は調べてみてください。
S3の作成
CodeDeployをCodePipelineで使う際には、ビルドで作成されるZipファイルの中にappspec.yml
とtaskdef.json
が入った状態にしなければなりません。
そこでbuildspec.yml
でアーティファクトを設定して出力するZipファイルに2つのファイルが入るように修正を加えます。
version: 0.2
env:
variables:
DOCKER_BUILDKIT: "1"
phases:
pre_build:
commands:
- echo Logiging 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
- docker pull $AWS_ECR_BUILD_REPOSITORY:latest
- docker tag $AWS_ECR_BUILD_REPOSITORY:latest build:latest
- IMAGE_TAG=$(echo ${CODEBUILD_RESOLVED_SOURCE_VERSION} | head -c 7)
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build --target production --cache-from build:latest --build-arg SECRET_KEY_BASE=$SECRET_KEY_BASE --build-arg BUILDKIT_INLINE_CACHE=1 -t $IMAGE_NAME:$IMAGE_TAG .
- docker build --target build --cache-from build:latest --build-arg SECRET_KEY_BASE=$SECRET_KEY_BASE --build-arg BUILDKIT_INLINE_CACHE=1 -t build:latest .
- docker tag $IMAGE_NAME:$IMAGE_TAG $AWS_ECR_BUILD_REPOSITORY:$IMAGE_TAG
- docker tag $IMAGE_NAME:$IMAGE_TAG $AWS_ECR_BUILD_REPOSITORY:staging
- docker tag build:latest $AWS_ECR_BUILD_REPOSITORY:latest
post_build:
commands:
- echo Build completed on `data`
- echo Pushing the Docker image...
- docker images
- docker push $AWS_ECR_BUILD_REPOSITORY:latest
- docker push $AWS_ECR_BUILD_REPOSITORY:$IMAGE_TAG
- docker push $AWS_ECR_BUILD_REPOSITORY:staging
- echo "[{\"name\":\"sample-rails\",\"imageUri\":\"${AWS_ECR_BUILD_REPOSITORY}:${IMAGE_TAG}\"}]" > imagedefinitions.json
artifacts:
files:
- imagedefinitions.json
- appspec.yml
- taskdef.json
また、Zipを保存するためのS3
を用意してください。
ここではsample-deploy-22131
としました。
ターゲットグループとロードバランサー
今回はCodeDeployでBlue/Greenデプロイ
をするのでそれを考慮してロードバランサーの設定をしていきます。
EC2のダッシュボードからターゲットグループを開いて以下を作成します。
項目名 | 値 |
---|---|
choose target type | IP addresses |
Target group name | BlueGreenTarget1 |
VPC | sample-vpc |
Health check path | /test |
ターゲット登録は行わないで大丈夫です。
次にロードバランサーを作成します。
項目名 | 値 |
---|---|
Select load balancer type | Application Load Balancer |
Load balancer name | sample-elb |
VPC | sample-vpc |
Mappings | ap-northeast-1a: sample-subnet-public01 ap-northeast-1c: sample-subnet-public02 |
Security group | default sample-elb |
Listeners and routing | Foward to: BlueGreenTarget1 |
タスク定義とFargate
初めは手作業でFargateを作成します。
ECSのタスク定義
を作成します。第7回を参考に作成してください。
変更点はRails
に2つあります。
イメージを変更します。
タグをstagingにします。
Rails
の環境変数にSECRET_KEY_BASE
を追加して、値にmaster.key
の値を登録します。
クラスターも記事を参考に作成してください。
サービスはBluee/Greenデプロイ
に対応できるようにするためにすこし設定が変わります。
第7回との変更点だけを表に示します。
項目名 | 値 |
---|---|
デプロイメントタイプ* | Blue/Green デプロイメント (AWS CodeDeploy を使用) |
CodeDeploy のサービスロール | CodeDeployRole |
nginx:80:80をロードバランサーに追加 | |
プロダクションリスナーポート | 80:HTTP |
テストリスナーポート | 新規作成: 8080 |
ターゲットグループ 1 の名前 | BlueGreenTarget1 |
ターゲットグループ 2 の名前 | 新規作成: BlueGreenTarget2 |
Health check path | /test |
作成のタイミングでCodeDeployのデプロイメントも作成されます。既にある場合はエラーになるのでCodeDeployのアプリケーションとECSのサービスを削除してから再度作成してください。
起動確認
起動したタスクのパブリックIP/test
でアクセスして起動できれば成功です。起動できない場合はCloudWatchのログをみて元のDockerfileや環境変数の設定を修正します。
CSSやJavascriptも問題ありませんでした。
リポジトリの更新
これからCodeDeployをしていくうえで、entrypoint.sh
のdb:create
を行うとエラーになってしまうので修正していきます。
#!/bin/sh
set -e
rm -f /myapp/tmp/pids/server.pid
# bundle exec rails db:create
# bundle exec rails db:migrate
# bundle exec rails db:seed
exec "$@"
今回はmigrateするようなものもないので合わせてコメントにしました。
この状態でリポジトリにPushして更新してください。
CodeBuild
第8回を参考にbuildspec.yml
とtestspec.yml
を動かすビルドプロジェクトを作成してください。
buildspec.yml
でビルドのプロジェクトを作成するときには、アーティファクト
を設定します。
項目名 | 値 |
---|---|
タイプ | Amazon S3 |
バケット名 | 作成したバケット |
アーティファクトのパッケージ化 | Zip |
テストのプロジェクトは記事通りで大丈夫です。
CodePipeline
先ほどサービスを作成した際にすでにCodeDeployは作成されているので、CodePipelineを作成します。
作成は第8回を参考に行いますが、今回はデプロイステージをスキップせずに設定します。
記事と変更する箇所のみ表に示します。
項目名 | 値 |
---|---|
高度な設定:アーティファクトストア | カスタムロケーション |
バケット | 作成したバケット |
デプロイプロバイダー | Amazon ECS(ブルー/グリーン) |
AWS CodeDeploy アプリケーション名 | AppECS-sample-cluster-sample-service |
AWS CodeDeploy デプロイグループ | DgpECS-sample-cluster-sample-service |
Amazon ECS タスク定義 | 入力アーティファクトを選択する: BuildArtifact taskdef.json: taskdef.json |
AWS CodeDeploy AppSpec ファイル | 入力アーティファクトを選択する: BuildArtifact appspec.yml |
パイプラインを作成して、テストステージも追加します。
そのあとで、変更をリリースして正しく動作するかを確かめます。
CodeDeploy
の「アプリケーション」→「デプロイの履歴」からBlue/Greenデプロイ
の状態を確認できます。
Green
が100%になったら「元のタスクセットの終了」をクリックして入れ替え完了です。
いくら待っても50%から進まない時
私はここでinstall step(50%)
がずっと終わらない現象が起きました。
この現象はロードバランサーが疎通できていない、コンテナが正しく立ち上がっていないのどちらかが考えられます。
まずはECSの起動しているタスクのパブリックIPから正しくアクセスできるかを確認します。できない場合は、タスクの更新からCloud Watchの自動ログ出力にチェックをいれて、再度サービスを作成して起動してエラーの原因を突き止めます。
ロードバランサーの場合は、health check pathにアクセスして200OKが返ってきていないので先に進んでいないことが考えられます。今回の場合は/test
しか用意していないため/
にアクセスするとNginxエラー(404)になるので疎通が確認できないという自体になります。改めてロードバランサー、ターゲットグループ、ECSを作り直して設定を確かめると治るかと思います。
また、タスクの更新をECSサービスから行うことでCodeDeploy(Blue/Green)
を動かすことも可能です。
ドメイン設定
第7回のドメインの取得、SSLサーバー証明書発行、Route53の設定の手順で行います。
今回はロードバランサーをドメインでアクセスできるようにする設定だけで大丈夫です。
おわりに
作成したものはこちらのリポジトリに用意しています。
ついに開発環境構築からCI/CDまで行うことができるようになりました!
第10回はCloud Formation
によるIaCに挑戦して終わりたいと思います。
Discussion