🤖

FargateにRailsをデプロイする (CI/CDまでの道⑦)

2022/01/26に公開

はじめに

前回EC2Dockerをインストールしてコンテナを直接起動しました。

しかし、AWSでコンテナを扱うのであればCI/CDの観点から考えてECSにデプロイする方が望ましいです。
本日はFargateを利用してデプロイに挑戦していきます。

作成に利用するリポジトリはこちら

注意

このハンズオンでは以下の書籍を用います。

AWSではじめるインフラ構築入門 安全で堅牢な本番環境のつくり方

AWSのインフラ環境構築に利用します。書籍で説明が足りている部分については説明を割愛させていただきます。

本ハンズオンの構成は以下となります。

ci/cdまでの道シリーズ

環境

  • wsl2 (ubuntu20.04)
  • docker 20.10.9
  • docker-compose 1.29.1
  • git 2.25.1
  • vscode

AWSインフラ環境の構築

書籍と以下のサイトを用いてFargateにデプロイするところまでのインフラ構築していきます。
(独自ドメインの設定はデプロイができてから説明します)

【ポートフォリオをECSで!】Rails×NginxアプリをFargateにデプロイするまでを丁寧に説明してみた(VPC作成〜CircleCIによる自動デプロイまで) 前編

【ポートフォリオをECSで!】Rails×NginxアプリをFargateにデプロイするまでを丁寧に説明してみた(VPC作成〜CircleCIによる自動デプロイまで) 後編

VPC

書籍4.2通りに作成します。

サブネット

書籍4.3通りに作成します。

インターネットゲートウェイ

書籍4.4通りに作成します。

ルートテーブル

書籍4.6のsample-rt-publicのみ作成します。

セキュリティグループ

まず書籍4.7のsample-sg-elbだけを作成します。

そのあとは以下を作成します。

■ ECSのセキュリティグループ

項目名
セキュリティグループ sample-sg-ecs
説明 ecs security group
VPC sample-vpc
インバウンドルール タイプ: HTTP
ソース: 0.0.0.0/0

それ以外はデフォルト

■ RDSのセキュリティグループ

項目名
セキュリティグループ sample-sg-db
説明 db security group
VPC sample-vpc
インバウンドルール タイプ: MYSQL/Aurora
ソース: 0.0.0.0/0

それ以外はデフォルト

RDS

書籍8章通りに作成するが2点変更する

  1. パスワードは本ハンズオンではpasswordとします。

  1. セキュリティグループはRDSようにする

書籍でdefaultにしているところを、先ほど作成したsample-sg-dbに変更します。

ECRの設定

ここからは記事を参考にECRの設定を行っていきます。
コンソール画面から「Elastic Container Service (以下
ECS)」のダッシュボードを開きます。

「Amazon ECR」の「リポジトリ」の画面を開き、「リポジトリを作成」をクリックします。

まず、Rails用のリポジトリを作成します。

項目
リポジトリ名 sample-rails

それ以外はデフォルトで「リポジトリを作成」ボタンをクリック

次にNginx用のリポジトリを作成します。

項目
リポジトリ名 sample-nginx

それ以外はデフォルトで「リポジトリを作成」ボタンをクリック

ファイルの修正

Fargateにデプロイするにあたってdocker-compose.production.ymlを利用しないので、必要な設定(コマンドなど)をDockerfileに記述していきます。

Dockerfile
FROM ruby:alpine3.13

ARG UID

RUN adduser -D app -u ${UID:-1000} && \
      apk update \
      && apk add --no-cache gcc make libc-dev g++ mariadb-dev tzdata nodejs~=14 yarn

ENV RAILS_ENV=production

WORKDIR /myapp
COPY Gemfile .
COPY Gemfile.lock .
RUN bundle install
COPY --chown=app:app . /myapp
RUN yarn install
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

RUN chmod +x ./bin/webpack
RUN NODE_ENV=production ./bin/webpack

RUN mkdir -p tmp/sockets
RUN mkdir -p tmp/pids

VOLUME /myapp/public
VOLUME /myapp/tmp

CMD /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec puma -C config/puma.rb"

変更点は

  • 環境変数の設定(本番)
  • rootユーザーに変更 (本番ではファイル編集をしない)
  • コンパイルをwebpackに変更 (前回はSprockets(asset:precompile)でコンパイルしてました)
  • volumeの追加 (FargateでNginxにボリュームを共有するため)
  • CMDで起動コマンドを実行

ここでポイントとなるのは、

VOLUME /myapp/public
VOLUME /myapp/tmp

の部分です。Fargateではdocker-compose(ymlファイル)を利用しないので、ボリュームのマウントをするのにVolumeで利用したところをNginxに共有できるように後ほどFargateに設定します。

この設定がないとデプロイしたときにRailsで以下のようなエラーが発生したり、CSSJSが適応されなかったり、Nginxで500エラーになったりします。

no such file or directory @ realpath_rec - /myapp/tmp/sockets
connect() to unix:///myapp/tmp/sockets/puma.sock failed (2: no such file or directory) fargate

また、ENV RAIlS_ENV=productionを設定しないと、

RUN NODE_ENV=production ./bin/webpack

JSしかコンパイルされないようでCSSはコンパイルされずにエラーになりました。Nginxで500エラーになるので必要です。また、pumaの起動にも環境変数が必要なので設定しておいて損なしです

次にcontainers/nginx/Dockerfileを以下に変更します。

containers/nginx/Dockerfile
FROM nginx:alpine

# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*

# Nginxの設定ファイルをコンテナにコピー
ADD /containers/nginx/nginx.conf /etc/nginx/conf.d/myapp.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

ADDのパスを変更しました。
変更しないとbuildで引っかかるためです。

次にcontainers/nginx/nginx.confを修正します。

containers/nginx/nginx.conf
# プロキシ先の指定
# Nginxが受け取ったリクエストをバックエンドのpumaに送信
upstream myapp {
  # ソケット通信したいのでpuma.sockを指定
  server unix:///myapp/tmp/sockets/puma.sock;
}

server {
  listen 80;
  # ドメインもしくはIPを指定
  server_name localhost; # 修正
(省略)

server_nameが前回利用したパブリックIPになっているのでlocalhostに変更します。

次にentrypoint.shを以下に変更します。

entrypoint.sh
#!/bin/sh
set -e
rm -f /myapp/tmp/pids/server.pid
bundle exec rails db:create RAILS_ENV=production
bundle exec rails db:migrate RAILS_ENV=production
bundle exec rails db:seed RAILS_ENV=production
exec "$@"

ENTRYPOINT ["entrypoint.sh"]のタイミングでDB作成などを行います。
注意としてはdb:createは1度のみしか行わないので、1度実行したらコメントアウトして再度ECRにPushします。(あとでやります)

最後にconfig/credential.enc.ymlがあれば削除して、config/credential.enc.ymlconfig/master.keyを用意してください。こちらについては第5回で説明しています。

ECRにPushする

以下の手順でRailsとNginxのDockerイメージをECRにPushします。

RailsのPush

作成したsample-railsを開きます。

右上のプッシュコマンドを開き、4つのコマンドが出てくるのでコピーしてWSL2上で実行します。

ECR(sample-rails)で登録されていることが確認できました。

NginxのPush

やり方はほとんど同じですが、build(2つ目のコマンド)を以下に変更します。

docker build -f ./containers/nginx/Dockerfile  -t sample-nginx .

Dockerfileの場所がカレントディレクトリにないため-fをつけます
あとはRails同様に行います。

ECSの設定

コンソール画面から「Elastic Container Service (以下
ECS)」のダッシュボードを開きます。

■ タスク定義の作成

「Amazon ECR」の「タスク定義」の画面を開き、「新しいタスク定義の作成」をクリックします。

デフォルト値から変更する値を表に示します。

項目名
起動タイプの互換性の選択 Fargate
タスク定義名 sample-ecs
タスクロール ecsTaskExecutionRole
タスクメモリ (GB) 0.5GB
タスク CPU (vCPU) 0.25 vCPU
コンテナの追加 コンテナ名: rails
イメージ: [ECRのsample-railsのURL]
ポートマッピング: 3000
環境変数
DB_USERNAME: admin
DB_PASSWORD: password
DB_DATABASE: myapp
DB_HOST: [RDSのエンドポイント]
--------------------------------------
コンテナ名: nginx
イメージ: [ECRのsample-nginxのURL]
ポートマッピング: 80
ボリュームソース: rails

Fargateを選択
「次のステップ」をクリック

タスク定義名、タスクロール、タスクメモリ(GB)、タスクCPU(vCPU)を設定

「コンテナの追加」をクリック

  1. Railsコンテナの追加

コンテナ名、イメージ、ポートマッピング、環境変数を入力

イメージはECRで確認可能

「追加」をクリック

  1. Nginxコンテナの追加

コンテナ名、イメージ、ポートマッピング、ボリュームソースを設定して「追加」をクリック

ボリュームソースを設定することでrailsのDockerfileで設定したVolumeの内容を共有することができます

「作成」をクリックしてタスク定義を作成します。

クラスター設定

「Amazon ECR」の「クラスター」の画面を開き、「クラスターの作成」をクリックします。

デフォルト値から変更するものを表に示します。

項目名
クラスターテンプレートの選択 ネットワークのみ
クラスター名 sample-cluster

ネットワークのみを選択

クラスター名を入力して「作成」をクリック

作成したクラスター(sample-cluster)をクリック

「サービス」→「作成」をクリック

デフォルト値から変更するものを表に示す

項目
起動タイプ FARGATE
サービス名 sample-service
タスクの数 1
クラスターVPC sample-vpc
サブネット sample-subnet-public01
セキュリティグループ sample-sg-ecs

「次のステップ」をクリック

クラスターVPC、サブネット、セキュリティグループを設定

「次のステップ」→「次のステップ」→「作成」をクリック

デプロイの確認

「クラスター」→「sample-cluster」→「sample-service」からFargateのステータスを確認 (RUNNINGになればOK)

「タスク(英数字列)」をクリックして「パブリックIP」を確認

ChromeでパブリックIP/testにアクセスします。

/testを忘れないようにしてください。

※ 500エラーの場合はコンパイルがうまくいっていない事が多いのでローカルで起動して、/log/production.logを確認していきます

独自のドメインでアクセスできるようにする

ドメインの取得

書籍10.4を参考に取得します

SSLサーバー証明書発行

書籍10.7.1を行います。

ECRの更新

entrypoint.shdb:createをしましたが、既に1度行っており、再度実行するとエラーになってしまうのでコメントアウトしておきます。また、migrateseedも現在必要ではないので合わせてコメントアウトしています。

entrypoint.sh
#!/bin/sh
set -e
rm -f /myapp/tmp/pids/server.pid
# bundle exec rails db:create RAILS_ENV=production
# bundle exec rails db:migrate RAILS_ENV=production
# bundle exec rails db:seed RAILS_ENV=production
exec "$@"

ECRの既に登録したイメージを削除してください。
そのあと先ほど実行したプッシュコマンドでrails-sampleを更新します。

ロードバランサーの設定

「ロードバランサー」→「ロードバランサーの作成」を行い、書籍の7.2.4の流れで作成します。修正箇所だけ表に示します。

項目
Listener Protocol: HTTPS
Port: 443
Forward to: sample-tg
Default SSL certificate from ACM: 取得したドメイン

ターゲット作成は以下で設定します。

項目
choose a target type IP address
Target group name sample-tg
Network sample-vpc
IPv4 address ECSのプライベートIP

Include as pending belowを押して登録します。
「crate target group」で作成します。

ロードバランサーの設定には作成したターゲットグループを指定します。HTTPSも加えます。

Default SSL certificateに先ほど追加した証明書を選択します。
ここではwww.our-aws.link

設定したら「create load balancer」をクリックします。

Route53の設定

「Route53」から「ホストゾーン」画面に移動して取得したドメインをクリックします。

「レコードを作成」をクリックします。

「シンプルルーティング」を選択して「次へ」

「シンプルなレコード定義」をクリックします。

項目
レコード名 www
値/トラフィックのルーティング先 Application Loadbalancer
リージョン ap-northeast-1
ロードバランサーを選択 作成したロードバランサー

値を入れていきます。

「シンプルなレコードを定義」→「レコードを作成」をクリックします。

ECSの再作成

先ほど作成したsample-serviceを削除します。

そして、「作成」をクリックして先ほどの手順でサービスを作成していきます。

今回は「ロードバランシング」の設定を行います。

項目
ロードバランサーの種類 Application Load Balancer
ロードバランサー名 sample-elb
コンテナ名:ポート nginx:80:80 (ロードバランサーに追加クリック)
ターゲットグループ sample-tg

それ以外は前回と同じ手順になります。

デプロイの確認

ドメイン/testにアクセスします。

/testが必要です。表示されれば成功です。
コンテナの共有の問題で表示まで時間がかかるかもしれないです(502エラーやエラー画面になる)

おわりに

作成したものはこちらのリポジトリに用意しています。

一度Fargateにデプロイに成功してそこから記事完成までに7時間もかかりましたがなんとか完成させることができました。

やっとCI/CDの道のスタート地点に立てました。次回はCodeBuildに挑戦していこうかなと思います。

参考

GitHubで編集を提案

Discussion