RailsアプリをECS FargateへデプロイするCI/CD pipelineを構築する


この記事はFusic Advent Calendar 2019の20日目の記事です。

昨日は@Kta-Mの「究極のCloudFormationをたずねて三千里」でした。
CloudFormationでこのネタをやるとは、、、斬新なアイディアですね。


さて、12月2日〜6日にアメリカのラスベガスでAWS re:Invent 2019が開催されました。
私自身も参加して多数のWorkshopを体験することができました。

今回は、その中で最も実用的と感じたWorkshopの内容を、一部アレンジしてre:Playします。

作るもの

ECS Fargate上にアプリケーションをデプロイするCI/CD pipelineを構築します。
ECSは2つのサービスを起動し、Blue/Greenデプロイできるようにします。

アレンジポイント

re:Invent 2019のWorkshopでは「簡易的なPHPのWebページ」をデプロイしていましたが、
今回は「Ruby on Railsのアプリケーション」をデプロイします。

デプロイするのは下の画像のような、シンプルなScaffoldアプリです。

準備

AWS CLIのインストール

以下URLを参考にインストールしてください。バージョン2が評価リリースされていますが、この記事ではバージョン1を使います。
https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/install-cliv1.html

GithubのAccess Tokenを作る

以下URLの(c)および(d)の手順を実施してください。

CloudFormationテンプレートをCloneする

AWSリソース一式を構築するCloudFormationテンプレートが用意されているので、これをCloneします。
また、branchを fargate に切り替えておきます。

$ git clone https://github.com/awslabs/ecs-blue-green-deployment
$ cd ecs-blue-green-deployment
$ git checkout fargate

Workshopのときとはリージョンや起動するアプリが異なるので、いくつか修正を施しました。

取得するGithubリポジトリをWorkshopで使用したものから、今回私が作成したRailsアプリケーションのリポジトリに変更します。

ecs-blue-green-deployment.yaml
Parameters
   GitHubRepo:
     Type: String
-    Default: ecs-demo-php-simple-app
+    Default: ecs-rails-app
     Description: The repo name of the sample service.
     AllowedPattern: "[A-Za-z0-9_.-]*"
     MaxLength: 50

Railsアプリケーションの場合、コンテナが起動してから assets:precompile を実行するため、HelthCheckが通るようになるまで時間がかかります。
このため、HelthCheckの猶予時間を360秒に設定します。

templates/service.yaml
       LaunchType: FARGATE
       TaskDefinition: !Ref TaskDefinition
       LoadBalancers:
-        - ContainerName: simple-app
+        - ContainerName: rails-app
           ContainerPort: 80
           TargetGroupArn: !Ref TargetGroup
+      HealthCheckGracePeriodSeconds: 360
       NetworkConfiguration:
         AwsvpcConfiguration:
           AssignPublicIp: ENABLED #MENTION DISABLED if in private subnet with NAT gateway

Logを閲覧できるようにしないとデバッグがしづらいのでCloudWatch Logsへ書き出すように設定を加えています。
コンテナのWorkDirやEntrypoint、環境変数の定義を追加しています。
※master.keyはこの後の手順で作成します。

templates/service.yaml
        - FARGATE
       ExecutionRoleArn: !GetAtt TaskIamRole.Arn
       ContainerDefinitions:
-        - Name: simple-app
+        - Name: rails-app
           Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${Repository}:${Tag}
+          LogConfiguration:
+            LogDriver: awslogs
+            Options:
+              awslogs-group: /ecs/logs/ecs-rails-app-group
+              awslogs-region: !Ref "AWS::Region"
+              awslogs-stream-prefix: rails-app
+          WorkingDirectory: /app
           EntryPoint:
-            - /usr/sbin/apache2
-            - -D
-            - FOREGROUND
+            - /bin/sh
+            - -c
+            - /app/script/entrypoint_production.sh
           Essential: true
           Memory: 512
           PortMappings:
             - ContainerPort: 80
           Environment:
             - Name: Tag
-              Value: !Ref Tag
\ No newline at end of file
+              Value: !Ref Tag
+            - Name: RAILS_ENV
+              Value: production
+            - Name: RAILS_SERVE_STATIC_FILES
+              Value: true
+            - Name: RAILS_LOG_TO_STDOUT
+              Value: true
+            - Name: RAILS_MASTER_KEY
+              Value: <master.keyの内容を設定>

東京リージョンではap-northeast-1bが選択できないため、ap-northeast-1cを選択させます。

templates/vpc.yaml
     Type: AWS::EC2::Subnet
     Properties:
       VpcId: !Ref VPC
-      AvailabilityZone: !Select [ 1, !GetAZs ]
+      AvailabilityZone: !Select [ 2, !GetAZs ]
       MapPublicIpOnLaunch: true
       CidrBlock: !Ref Subnet2CIDR
       Tags:

※参考までにこちらでも変更を確認できるようにしています。
https://github.com/yuuu/ecs-blue-green-deployment/commit/4294e098993511de134221758e834d5b9ecc674b

デプロイ対象のリポジトリを準備する

上記で入力した名前のリポジトリを、自分のGithubアカウント配下に置く必要があります。
今回は私が用意したリポジトリをforkしてお使いください。
https://github.com/yuuu/ecs-rails-app

config/master.keyを生成するために、以下コマンドを入力してください。

$ rm config/credentials.yml.enc
$ EDITOR=vi bundle exec rails credentials:edit
(エディタが起動したら :wq を入力)
$ git commit -m 'update credentials'
$ git push origin master

無事に生成できたら前述の templates/service.yaml へ反映しておきましょう。

CloudFormation用のS3バケットを作成する

CloudFormationで使うS3バケットを作成します。S3バケットは他人と重複しない名前を適当に設定してください。

$ aws s3 mb s3://<unique-bucket-name>

デプロイする

環境構築&デプロイ

準備が完了しましたのでいよいよデプロイします。

先ほどCloneした ecs-blue-green-deployment のディレクトリへ移動して以下コマンドを実行します。
実行すると対話形式でパラメータの入力を求められるので、下記に従って入力してください。

$ bin/deploy

+ echo -n 'Enter S3 Bucket to host the templates and scripts > '
Enter S3 Bucket to host the templates and scripts > + read bucket
(作成したバケット名を入力)
+ echo -n 'Enter stackname to create or update the stack > '
Enter stackname to create or update the stack > + read stackname
(任意のstacknameを入力)
+ echo -n 'Enter GitHub User > '
Enter GitHub User > + read GitHubUser
(Github ユーザ名を入力)
+ echo -n 'Enter GitHubToken > '
Enter GitHubToken > + read GitHubToken
(作成したGithub Access Tokenを入力)

CloudFormationが動き出し、諸々のリソースが作成されます。

しばらくするとCodePipelineが動き出します。
Githubリポジトリからソースコードを取得してビルドしている様子が確認できます。

動作確認

デプロイが終わったらEC2のコンソールを開きます。(ECSではないので注意)
ロードバランサーの画面へ移動して、Stack名に設定した名前のロードバランサーを探します。
無事に見つかったらDNS名をコピーしましょう。

http://(ロードバランサーのDNS名)/posts へアクセスすると、アプリケーションが動いていることを確認できます。

動作確認ができたらCodePipeline上でapproveをしておきましょう。

Blue/Greenデプロイする

これから、Railsアプリケーションに変更を加えて、デプロイをします。

デプロイ後、プレ環境で変更が正しく動作することを確認できます。
CodePipelineの承認ボタンをクリックすると、ALBのリスナーとターゲットが入れ替わり
本番環境のURLで新しいバージョンのアプリケーションが閲覧できるようになります。

アプリケーションのコードを変更しデプロイする

今回は例として以下の通り変更します。

app/views/posts/index.html.erb
 <p id="notice"><%= notice %></p>

-<h1>Posts</h1>
+<h1>Posts!!!</h1>

 <table>
   <thead>

このソースコードをpushしましょう。
まもなくデプロイが始まるので、デプロイ完了となるまで待ちます。

$ git add .
$ git commit -m 'change h1 text'
$ git push origin master

動作確認

次に本番環境へアクセスします。

http://(ロードバランサーのDNS名)/posts

変更が反映される前の状態が残っています。

では次に、プレ環境を確認します。
このシステムではロードバランサーの8080ポートへアクセスするとスペア環境へ接続できるようになっています。
http://(ロードバランサーのDNS名):8080/posts

変更が反映されていますね。


変更を承認すると、Lambda FunctionによってALBのリスナーとターゲットの関係がスワップされます。
本番環境が新しいバージョン、プレ環境が古いバージョンとなることが確認できました。

http://(ロードバランサーのDNS名)/posts

http://(ロードバランサーのDNS名):8080/posts

今後の課題

Workshopの後で知ったのですが、 ECS自体にBlue/Greenデプロイ機能が存在している そうです。
今回はLambdaを使ってALBのリスナーとターゲットを入れ替えていますが、ECSの機能を使った方がよりスマートにBlue/Greenデプロイできるはずです。

現状、RDBはsqlite3を使っていますが、実際の本番環境でsqlite3を使うことはほぼ無いので、RDSインスタンスへの接続も一度試してみたいところです。

また、CodeBuildでのビルド時間、CodeDeployでのデプロイ時間が 合計10分以上かかっている ので、もう少し短縮する必要がありますね。

まとめ

re:Invent 2019のWorkshopの内容をアレンジすることで、RailsアプリをECS FargateへデプロイするCI/CD pipelineを構築することができました。

こういったWorkshopに多数参加できるre:Invent 2019はとても有意義だったなと改めて実感しました。
この経験を今後の開発に活かせるよう、精進します。

参考

https://cicd-with-aws-fargate-lambda-bg.workshop.aws/en/intro.html
https://www.enisias.cloud/docker/152/