AWS ECSデプロイ

はじめに

これまで作成してきたRailsとNext.jsのアプリケーションをAWS上にECSを使ってデプロイをしていきます。

今回はインフラの領域を取り扱います。
これまでとは毛色がだいぶ異なるので苦手意識を持たれる方もいるかもしれません。(かくいう私もインフラは苦手です!)

ですが、どんなサービスもデプロイがされているものです。
参画されたプロダクトのデプロイされる過程やアプリケーションの構成を理解しているのといないのとでは、Issueが起きたときの対応ができなかったり、アプリケーションの機能を十分に実装できなかったりします。
そのため、目指されているキャリアがインフラエンジニアでなく、バックエンドエンジニアやフロントエンドエンジニアであっても最低限のインフラの知識は経験は持っておくとよいです。

ここではAWS上にデプロイする方法を紹介します。AWSはハブリッククラウドの利用シェアの9割を誇るサービスです。みなさんが実務につかれた際、もっとも高い確率で利用されるサービスになるでしょう。また、もし、別のクラウドサービスであったとしても構築するネットワークの概念は同じです。
変わるのは利用するサービスとその設定方法が異なるのみです。

パブリッククラウドの利用には一部料金がかかります。
また、AWSには1年間の無料利用枠がありますが、今回利用するサービスには有料のものが含まれます。躊躇してしまう気持ちはわかりますが、通常通りに設定を行えば書籍を一冊購入する程度の料金で済むはずです。ここは惜しまずに投資と思って乗り切ってください。

それでは、はじめていきましょう!

ゴールの確認

Railsを本番環境で起動できる準備ができる
Next.jsを本番環境で起動できる準備ができる

アプリケーションのネットワークを構築できる

ECSでデプロイできる

デプロイ中のトラブルシュートができる

ここでの差分はgithubにあげています。困った際には参考にしてみてください

本番環境で動作できるようにする

ある程度実装を進めてから初めて本番環境で起動しようとすると大抵は何かしらの原因で失敗します。
それをデプロイして別のサーバーに上げながら解決しようとすると難易度が跳ね上がります。
そのため、まずは手元の環境で本番環境で起動できるかどうかを確かめておくとよいです。

Railsを本番環境で起動する

まずはRailsから本番稼働できるようにしましょう。
現在開発環境で使っているDockerfileは開発用のため、コンテナ内にはGemfileとGemfile.lockしか入れていません。それ以外のコードは自分のハードにあるアプリケーションのファイルとマウントするような設定になっています。
(DockerfileでCOPYしているものと、docker-compose.ymlのvolumes:からその設定が示されています)
ですが、デプロイするにあたっては全てのソースコードをコンテナの中に含める必要があるので、今開発で利用しているDockerfileとは別に本番環境起動用のDockerfileを作成します。
ファイル名はDockerfile.prodとして本番環境利用のものとわかるようにしましょう

FROM --platform=linux/x86_64 ruby:3.2

ENV LANG=C.UTF-8 \
    TZ=Asia/Tokyo

WORKDIR /app
RUN apt-get update -qq && apt-get install -y nodejs default-mysql-client vim
COPY / /app/
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install

CMD ["rails", "server", "-b", "0.0.0.0", "-e", "production"]

COPY / /app/
が追加されています。これでカレントディレクトリを全てコンテナのappディレクトリの中に入れています。
また、railsの起動コマンドで-e productionを指定しています。これによりこのコンテナを立ち上げるときにproductionモードで起動されます。
もう一点、一行目も異なります。これはfargateで起動しようとしたときに

exec /usr/local/bundle/bin/rails: exec format error

というエラーに遭遇したため、付与しました。私のPCがm1シリコンを利用しており、m1シリコンで作成されたコンテナをfargateで起動しようとするとこのようなエラーになることがあるそうです。platformを指定してあげることで回避しています。

それでは、作成したDockerfileを使ってコンテナイメージを作成しましょう。

cd ./backend
docker build -f Dockerfile.prod .

backendディレクトリに移動しつつ、Dockerfile.prodのファイルを指定してbuildを行っています。
次に先ほど作成したイメージを確認して、そのイメージIDをもとにコンテナを実行します。(下記コマンド)

docker images
docker run -p 3000:3000 [IMAGE ID]

もしこの手順の中でArgumentError: Missing `secret_key_base` for ‘production’ environment, set this string with `rails credentials:edit`のエラーに遭遇された場合は次のことを試してみてください

backend/config/credentials.yml.encを削除する
EDITOR=vim rails credentials:edit を実行する

このエラーはアプリケーションの認証情報を保存するためのcredentials.yml.encをパースするためのキーがないと言われてしまっています。キーがないのでそのキーを作成し直してあげる必要があります

うまくいくと、Railsの起動プロセスが行われ、localhost:3000にアクセスできるようになります。
ですが、このままだとエラーになると思われます。
エラーの原因はDBとのコネクションエラーでしょう。
今はdocker-composeを使わずにRailsのサーバーだけを起動しようとしています。DBコンテナはdocker-composeで起動していたので、今はDBが起動しておらずDBとのコネクションエラーになります。

ここからは、手元の環境で本番環境を動作させるためだけに必要な作業になりますので、詳細は説明は省きますが、少しでもデプロイの不安を今のうちに拭って置くためにあらかじめ正常に動作するかどうかは確かめておきたいものです。
確認のために、docker-compose.ymlをいじります。
docker-composeのbackendの中で指定しているDockerfileをDockerfile.prodにし、commandで-e productionを指定するようにしてください。
また、database.ymlの中身を編集します。database名、username、passwordを開発環境と同じになるようにセットしましょう。
本番環境で実行しているとき、アプリケーションの起動ログがターミナルに出力されなくなります。確認しやすくするために、production.rbのif ENV["RAILS_LOG_TO_STDOUT"].present?の部分をif trueにしておくとよいです。

productionモードで正常にRailsが動作したら、ついでに本番環境デプロイしたときのためにdatabase.ymlを修正しておきます。

production:
  <<: *default
  database: techport-db
  username: app
  password: <%= ENV["APP_DATABASE_PASSWORD"] %>
  host: <%= ENV["APP_DATABASE_HOST"] %>

databaseの名前をわかりやすいものに。
hostを追加して、host名は環境変数のAPP_DATABASE_HOSTを指定します。
APP_DATABASE_PASSWORDとAPP_DATABASE_HOSTの環境変数はタスク定義のところで指定することになります。

Next.jsを本番環境で起動する

次にNext.jsを本番環境で起動できるようにしていきましょう。
こちらもRailsのときと同様に本番用のDockerfileを準備します。

FROM --platform=linux/x86_64 node:20.13.1

WORKDIR /app

COPY ./ /app

CMD ["sh", "-c", "npm run build && npm start"]

FROM –platform=linux/x86_64 node:20.13.1
今回もm1シリコンで起動したコンテナをfagateで起動できるようにするためにplatformを指定します

COPY ./ /app
カレントディレクトを全てコンテナのappディレクトリにコピーします。

CMD [“sh”, “-c”, “npm run build && npm start”]
起動コマンドも変えています。buildとstartをコンテナ実行時に行うようします

やることは以上です。
ですが、こちらも同様に実行できるか確認してみましょう

cd ./frontend
docker build -f Dockerfile.prod .
docker images
docker run -p 3000:3000 [IMAGE ID]

今回も起動には失敗すると思われます。失敗の原因がfetch failedであれば問題ないです。
buildのタイミングでページ作成に必要なリソースをbackendから取得しようとするのですが、backendコンテナが起動していないのでうまく取得できずエラーになっています。
同じように手元で起動することをしっかり確認しておきたいところです

ここからは手元で動かすために必要な作業でAWSデプロイでは不要になるものです。
Railsのときと同様にbackendコンテナに依存しているので、Next.jsのコンテナもdocker-composeを使って立ち上げるようにします。
docker-composeのfrontendのdockerfileをDockerfile.prodにしましょう。commandもnpm run dev & npm run startとしておきます。
また、本番起動の場合、環境変数は.env.productionを見にいくため、このファイルを作成し、.env.developmentと同じ内容を書いておきます。

手元で作業するのは以上です

ネットワークの構築

それではいよいよAWSへのデプロイをしていきましょう。

まだAWSのアカウントを作成していない場合は作成しましょう。
AWSのアカウント作成はemailや住所の登録やクレずっとカードの登録が必須です。いずれも英語の入力フォームなので頑張って入力していきましょう。

登録が完了すると、AWSのマネジメントコンソールが開かれます

ここではアカウント作成したら最初にやっておきたい、いわゆるday1対応の説明は省いています。
ですが、day1対応はやっておくべき項目です。特にiamユーザーの作成と予算とその通知はやっておくべきです。

インフラの全体像

これから作成するAWSサービスの全体像です。

VPCの中にサブネットをpublicとprivateで2つずつ作成し、
インターネットゲートウェイを作成。ルートテーブルを使ってサブネットと関連づけをします。
publicサブネットにはロードバランサーを用意してECSへトラフィックを流せるようにします。
ECSはECRからコンテナイメージを引っ張ってくるようにするのでECRを作成します。
privateサブネットにはrdsでmysqlを準備します。

VPCの作成

VPCを作成していきます。VPCはアプリケーションを配置するための仮想スペースです。
家でいうところの土地や家全体のようなものです

VPCを開くとすでにdefaultで作成されているものがあると思います。これはAWSのアカウントを作成した段階で最初から作成されているリソースで、これを使うとこのVPC内にサブネットやルートテーブルがある程度いい感じに設定されているのですが、あえてこれとは別に作成していきましょう

ここには写っていないですが、リソース名は「techport-rails-nextjs-prod-vpc」としました。

リソース名はこれからいろんなAWSのサービスを立ち上げるときに求められます。
その都度テキトウにつけてしまうと何だったのか忘れてしまいます。
一定の命名規則を持たせるとよいです。
私の場合は{productName}-{environment}-{resourceName}とすることが多いです

サブネットの作成

サブネットは全部で4つ作成します。パブリックサブネットを2つとプライベートサブネットを2つです。
サブネットはVPCをさらに用途ごとに分割したようなものです。

まずはパブリックサブネットを作成していきます。
パブリックサブネットはインターネットに接続できるものが配置される場所です。ECSのリソースを配置します。イメージとしては家というプライベートな空間の中だけど、比較的他人に見られやすい限界やリビングのようなものです。他人に見られやすいので大事なものは置けない場所です。

これはpublicサブネットの1aです。

これの他にもう一つ、パブリックサブネット1cを作成します。
設定内容はほとんど同じですが、変えて欲しいのは
サブネット名は1cにすること・アベイラビリティゾーンをap-northeast-1cのものを選ぶこと・ipv4アドレスを192.168.3.0/24とすること
です。
要は二つのAZに跨ってサブネットを作成したいのです。その際にipアドレスの左から3つ目の数字はズラす必要があります。(192.168.2.0はプライベートサブネット1aに割り当てたいのでその分もずらしている)

次にプライベートサブネットを作成していきます。
プライベートサブネットといえど、設定内容はほとんど同じです。
名前を***-private-subnet-1a(や1c)とし、ap-northeast-1a(1c)、ipv4アドレスを192.168.2.0/24(1cは192.168.4.0/24)としましょう。
こちらも、要は二つのAZに跨ってプライベートサブネットを作成する必要があります

インターネットゲートウェイの作成

続いてインターネットゲートウェイを作成します。
これがないとVPCの中のリソースに対して通信が通らないままです。
家の中と外を繋げる玄関口のようなイメージです。

入力内容は名前のみです。

作成が完了したら、それを選択>アクション>VPCにアタッチ
をして作成したインターネットゲートウェイをVPCに割り当てましょう

ルートテーブルの設定

次にルートテーブルを設定します。
ルートテーブルはインターネットゲートウェイからの通信をサブネットへ渡す役割です。
家での例えは思いつきませんが、、大きなマンションのコンシェルジュみたいな感じでしょうか?
これがあることでインターネットゲートウェイで受け取った通信がサブネットまで届くようになります。

おそらくVPCを作成したタイミングでそのVPCのデフォルトのルートテーブルが作成されていると思います。そのルートテーブルを選択>アクション>ルートを編集
送信先を0.0.0.0/0。ターゲットに先ほど作成したインターネットゲートウェイを選択してください。

これにて、ネットワーク周りの構築は終了です。

ここまでで色々なサービスを作成・設定してきたのでそろそろ動作確認をしたくなると思います。ですが、現時点ではなにか特定の機能をデプロイしたわけではないため確認できる動作がありません。(家の枠組みはできたけど、中身はなにもないから家としての機能はなく住めない状態のイメージ)
とはいえ、このまま先にすすんでいざ動作させようとしたときに上手くいってないとき、原因がどこにあるか把握しづらくなってしまいます。
そこで、動作確認のために作成したVPC・サブネットの中にEC2インスタンスを起動してそこからインターネットの通信ができるかどうかを確認してみるとよいです。
ここからは、アプリケーションのデプロイ上は不要な作業に入ります。
EC2インスタンスを起動をしましょう。起動の設定はほとんどデフォルトでよいですが、
ネットワークの設定はデフォルトのままだとdefaultのVPCが指定されているため、編集が必要です。サブネットはpublic-subnet-1aあたりを選択しておきましょう。(デフォルトのままにしている主な設定は、AMI: Amazon Linux 2023 AMI 無料利用枠の対象、インスタンスタイプ: t2.micro 無料利用枠の対象、キーペア: なしで続行、セキュリティグループ: 新規に作成 です)
設定ができたらインスタンスを起動をします。
インスタンスが無事実行中になったら、そのインスタンスを選択>接続>EC2 Instance Connectを使用して接続>接続
をします。
接続が成功したら、ping google.comと実行してみて、エラーとかにならなければ問題なさそうです。
そもそも接続に失敗する場合や、pingコマンドをしてもRequest timeoutなどのように通信が失敗しているような状態だとこれまでの設定に問題がありそうです。
→動作の異常パターンをみたい場合は、例えばルートテーブルのルート編集からインターネットゲートウェイへのルートを削除して接続を試みてください。接続に失敗すると思われます。

確認が済んだらこのインスタンスは不要なのでアクション>インスタンスを終了をしておきましょう。

RDSの作成

データベースのインスタンスを作成していきます。
無料利用枠の範囲で作成しますが、一般的にアプリケーションの運用コストはDB周りが大半を占めます。DBのコストは意識して事故を避けるようにしましょう

エンジンはMysqlを選択。テンプレートには無料利用枠を選択してください

マスターユーザー名はdatabase.ymlにappで直接入れているのでこれはappでないといけません。環境変数にしていればAWSの画面からいくらでも変えられるのでなんでもよいですが。

マスターパスワードは設定したものをECSのタスク定義で入力するのでどこかに控えておきましょう

※パブリックアクセスを「あり」にしています。DBは非常に秘匿性の高いものなのでガチガチのセキュリティにしておくべきですが、最初の接続だけうまくいくかデバッグしたいのでありにした上で、後でなしに変更します。
もし、ありにして「The specified VPC does not support DNS resolution, DNS hostnames, or both. Update the VPC and then try again」となってうまく作成できないときは、VPCの設定編集>DNSホスト名の解決を有効にしてください。

DBの作成の完了には5分ほどかかります。ネットワークの状況によってはもっとかかるかもしれません。気長に待ちましょう。

DBのステータスが利用可能になったら試しにDBクライアント(DBeaver)から接続してみましょう。

Server Hostは各々異なります。DBインスタンスのエンドポイントを確認してください
テスト接続して成功していれば問題ないです。

ECRの作成

それではいよいよアプリケーションをデプロイするのに直接必要な作業をしていきます。

まずはECRの作成をします。
ECRはコンテナイメージを保管するためのものです。そもそもECSはコンテナイメージを持ってきてそれをデプロイするものです。そのため持ってくるコンテナイメージ自体がどこかに保管されている必要があります。その保管場所として利用するためのものです。
ECSのサービスのサイドメニューにECRへのリンクがあるのでそこから移動しましょう。

設定はリポジトリ名を指定するのみで他はデフォルトのままでよいです。

backendとfrontendで2つのコンテナを登録する場所が必要なので二つ作成しましょう。

backend: techport-rails-nextjs-prod-backend-ecr
frontend: techport-rails-nextjs-prod-frontend-ecr

ECRを作成できたら手元で作成したコンテナイメージをECRにプッシュします。
ECRにコンテナイメージをプッシュするコマンドはリポジトリの詳細ページにプッシュコマンドを表示のボタンがあるのでそれをクリックすると表示されます。
ですが、buildのコマンドはDockerfile指定のため工夫が必要なので、私のコマンドを載せておきます。

まずはbackendのイメージをプッシュするまでのコマンドです

cd ./backend
aws ecr get-login-password --profile techport --region ap-northeast-1 | docker login --username AWS --password-stdin 654654409077.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -f Dockerfile.prod -t techport-rails-nextjs-prod-backend-ecr  .
docker tag techport-rails-nextjs-prod-backend-ecr:latest 654654409077.dkr.ecr.ap-northeast-1.amazonaws.com/techport-rails-nextjs-prod-backend-ecr:latest
docker push 654654409077.dkr.ecr.ap-northeast-1.amazonaws.com/techport-rails-nextjs-prod-backend-ecr:latest

このコマンドではaws cliコマンドが使われます。
aws cliはご自身のPCにインストールする必要があります。
インストール方法はOSによって異なりますし、後々変更されている可能性があります。公式サイトからインストールの手順を確認してください。
また、コマンドにprofileの指定もしています。
IAMユーザーを作成してそのユーザーのアクセスキーを作成し、これをaws configure –profile techportで設定しています。
IAMのプロファイル指定をしない場合、–profileのオプションなしでコマンド実行できますが、仕事用とプライベート用のawsアカウントがごっちゃになるのが嫌なのでdefaultは作成せずに全てにprofileを設定するようにしています。

プッシュが完了したらECRを確認してlatestのイメージタグのものが追加されていることを確認してください

次はfrontendです

cd ./frontend
aws ecr get-login-password --profile techport --region ap-northeast-1 | docker login --username AWS --password-stdin 654654409077.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -f Dockerfile.prod -t techport-rails-nextjs-prod-frontend-ecr .
docker tag techport-rails-nextjs-prod-frontend-ecr:latest 654654409077.dkr.ecr.ap-northeast-1.amazonaws.com/techport-rails-nextjs-prod-frontend-ecr:latest
docker push 654654409077.dkr.ecr.ap-northeast-1.amazonaws.com/techport-rails-nextjs-prod-frontend-ecr:latest

上手くいきましたでしょうか?
ECRの設定は以上です。

ECSの作成

それではいよいよECSの作成・設定を行います。
ECSの中に概念が複数入り混じっていて、それらを駆使しながらアプリケーションを構築していくことになります。その概念がなかなかに難しいのであらかじめ全体像をお伝えします。

まずは大きくECSクラスターが存在します。アプリケーションの大きな枠組みです。
その中にサービスを作成します。今回はbackendのサービスとfrontendのサービスをそれぞれ作成することになります。サービスがrailsのアプリケーションの一個に対応するかと言われると、今回は対応するように作成していますが、そうとは限りません。一つのサービスの中にrailsアプリケーションとNext.jsアプリケーションを両方入れることも可能です。ただし、ロードバランサーとサービスが1対1の関係になります。
サービスはタスク定義をもとにタスクを作成します。
タスクはサービスの中に複数もつことができます。トラフィックが多くなると複製して負荷分散もできます。ここがEC2に対応してくるようなイメージです。
タスクの中にコンテナを持つことができます。タスクの中にコンテナは複数作成できます。

ECSクラスターの作成

それではECSクラスターから作成していきましょう
選択項目はあまりないです。クラスター名を指定するのみですが、ポイントはインフラストラクチャにAWS Fargateを選択していることです。

タスク定義の作成

ここからはbackendのみをまずデプロイすることを目指します。
理由は同時に行うのは難しいのと、そもそもfrontendはbackendが正常に動作していないことにはbuildすることができないのでまずはbackendが正常に動作していることをめざします。
backendのデプロイが完了したらfrontendをデプロイします。設定はほとんどbackendのときと同じです。

一つのインスタンスの設定を作成するので、ここは設定項目が多いです。
間違えても問題はないですが、原因の追求が面倒なのでなるべくミスのないように慎重にやっていきます。

タスク定義名はtechport-rails-nextjs-prod-backend-task-dfにしました。dfはdefinitionの略です。

インフラストラクチャの要件はデフォルトのままでよいです。(タスクロールだけ後で設定します)

コンテナは一つだけです。

イメージURIはECRからlatestのURIをコピーして貼りつけてください
コンテナポートが3000なのはrailsは3000番ポートを使って起動するのがデフォルトの設定で、そこは特にいじっていないからです。

環境変数を追加します。

RAILS_LOG_TO_STDOUT → true
RAILS_SERVE_STATIC_FILES → true
APP_DATABASE_PASSWORD → password
APP_DATABASE_HOST → techport-db.cvou2......aws.com

を入れておきましょう。
RAILS_LOG_TO_STDOUTはログを標準出力します。これによりcloudwatchから本番環境のログを見ることができ、デバッグがしやすくなります。
RAILS_SERVE_STATIC_FILESはcssなどの静的ファイルの読み込みに関する設定です。scssおよびbootstrapを入れなおかつwebサーバーがpumaのままなのでこの設定がないとうまくcssを読み込めませんでした。

タスク定義に設定する項目はまだありますが、他はデフォルトのままでよいです。作成をしましょう。

ECSサービスの作成

次にサービスを作成します。
ECSクラスターからサービスのタブの作成ボタンをクリックして作成します。こちらもbackendのみまず作成します。

デプロイ設定のファミリーから先に作成したタスク定義を選択しましょう。
サービス名も入力してください

ネットワークではVPCがdefaultVPCのままなので、これを作成したVPCに変更しましょう。サブネットはpublic-subnet-1aと1cの両方を選択します。
セキュリティグループは3000番ポートでのアクセスとhttpのアクセスをフルオープンにします。

ロードバランシングではロードバランサーを作成していきます。
ALBを選択して
ロードバランサー名を入力
リスナーやターゲットグループを新しく作成します。
ヘルスチェックについてはbackendのrailsアプリケーションですが、私が/でのページを作成していないため、ステータスコードは404にしておきたいです。この画面上では設定できないので作成した後で設定します。

入力が完了したらサービスを作成してください。
サービスが作成されるとタスクが起動してロードバランサーがヘルスチェックをはじめます。上記でお伝えしたとおり、今のままではヘルスチェックが通らないので、設定とネットワーク>作成されたロードバランサー>ターゲットグループ>ヘルスチェック>編集と進んで成功コードを404にしてください。

どうでしょうか?成功しましたでしょうか?

DBのマイグレーションを実行しないことにはアプリケーションの機能を動かせないので、マイグレーションをします。マイグレーションまでの手順はfagate execコマンドの章を参考にしてください

DBのマイグレーションができたら、careerやblogのコンテンツを保存していきましょう!

デバッグ

このタスクが正常に起動するところがいろんな問題が露呈してうまくいきにくいところです。
私自身これまで何度やってもすんなり成功したことないです。
そこで、うまくいかないときにどうやって解決するかをお伝えします。

まず、デプロイのための起動プロセスやヘルスチェックにはある程度時間がかかるのでサービスを作成して起動してすぐにうまくいった・いかないの結果はでないです。都度結果の反映には時間がかかるものです。
待った結果、タスクが起動しない・ヘルスチェックが通らないなどの異常があるようなときは、とにかくその原因を知ることに努めてください。闇雲に設定を見直してもなかなかうまくいかないです。

まず、タスクは起動しているでしょうか?
サービスのタスク一覧画面からステータスのフィルタリングをいじってで落ちたタスクがないか探してみましょう。落ちているタスクがあれば、そのタスクのページにはタスクが落ちた理由が書いてあるかもしれません。
Essential container in task exited や、Task failed ELB health checksあたりがよくあるエラーメメッセージです。
Essential container in task exitedは必須のコンテナがうまく起動しなかったよというものです。
その場合、railsの起動プロセスに問題が起きている可能性が高いです。

タスクの詳細ページのログのタブからログをみて、いつものRailsの起動コマンドが完了しているか確認しましょう。起動していない場合は何かしらのエラーが表示されていると思います。これを手掛かりに修正しましょう

Task failed ELB health checksの場合はヘルスチェックが通っていないのでタスクが正常に起動していると判断されず落とされてしまっています。ヘルスチェックが通らない主な原因はrailsの起動にあまりにも時間がかかっていてヘルスチェックが早々にダメだと判断されている。ヘルスチェックで見に行っているパスがエラーを出している。ELBとECSのサービスのファイヤーウォール(セキュリティグループ)の設定が噛み合っていない。などがありえます。

また、他の情報収集の方法として、ECSのタスクにはパブリックipアドレスが付与されます。これを直接ブラウザのurlに指定して、開かれるか確認しましょう(13.231.141.137:3000 のように)ロードバランサーを介さないため、原因の切り分けに役立ちます。これでそもそもrailsのエラー画面ではない場合、railsの起動そのものに失敗しています。(本番環境ではエラー画面は開発環境と異なりますので注意)

ずっとローディング中になるような場合はセキュリテイグループが悪さをしている可能性が高いです。
想定しているトラフィックはelbのDNSにアクセスするとelbにトラフックが到着する。elbはbackendのecsタスクに3000番ポートでアクセスする。の流れです、
そのため、ECSのサービスのセキュリティグループのインバウンドルールが3000番ポートやhttp(80番ポート)からのアクセスをフルオープンにしているか、ALBのセキュリティグループがhttp(80番)からのアクセスをフルオープンにしているかを確認してください。

fargate execコマンド

また、問題を直接解決したり原因を確かめるためにはコンテナ内でrailsのコマンドを実行してみたり、railsコンソールを開いてみるとよい場合があります。
そんなときはfargate execコマンドがあります。

aws ecs execute-command --profile techport --region ap-northeast-1 --cluster techport-rails-nextjs-prod-cluster --task [TASK ID] --container backend --interactive --command "bash"

ただしサービスに対して初めてこのコマンドを実行するときにエラーになると思われます。
原因はサービスの設定でenableExecuteCommandというものがあり、これがfalseでAWSの画面からは操作できない設定です。

確認方法

aws ecs describe-services --profile techport --region ap-northeast-1 --cluster techport-rails-nextjs-prod-cluster  --services techport-rails-nextjs-prod-backend-service | grep 'enableExecuteCommand'

falseだとダメ
$ "enableExecuteCommand": false

なのでaws cliを使ってこれを編集してあげる必要があります。
これを変更するコマンドは

aws ecs update-service --profile techport --region ap-northeast-1 --cluster techport-rails-nextjs-prod-cluster --service techport-rails-nextjs-prod-backend-service --enable-execute-command

なのですが、

An error occurred (InvalidParameterException) when calling the UpdateService operation: The service couldn't be updated because a valid taskRoleArn is not being used. Specify a valid task role in your task definition and try again.

となり、タスクロールに適切な権限がないと変更できません。そのため、タスクロールを設定します。
IAMポリシーを作成して、そのポリシーをタスクロールに付与して、このタスクロールをサービスに割り当てます。

IAMポリシー (techport-rails-nextjs-prod-task-executable-policy)

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ssmmessages:CreateControlChannel",
                "ssmmessages:CreateDataChannel",
                "ssmmessages:OpenControlChannel",
                "ssmmessages:OpenDataChannel"
            ],
            "Resource": "*"
        }
    ]
}

このポリシーを付与したIAMロールを作成しましょう。

タスクロールはタスク定義で設定できるので、新しいリビジョンを作成からタスクロールを設定し、作成しましょう

サービスの詳細画面からサービスを更新>新しいデプロイの強制にチェックを入れる>更新
をしてタスクをリフレッシュします。

リフレッシュしたら更新コマンドができるようになるので、更新コマンドを実行しましょう。
更新コマンドを実行してもその結果はまだ今のタスクには反映されないので、もう一度新しいデプロイの強制をします。
それでやっとfargate execコマンドが実行可能になります。

コンテナに入って、railsのマイグレーションをします。(RAILS_ENVを指定してproductionモードで行うようにします)

rails db:create RAILS_ENV=production
rails db:migrate RAILS_ENV=production

また、下記のようなエラーが出る場合にはprecompileする必要があります

ActionView::Template::Error (The asset "application.css" is not present in the asset pipeline.
rails assets:precompile

frontendのデプロイ

backendが正常に動作していることが確認できたら次はfrontendのデプロイを行います。

まず、backendのドメインがalbのDNSで決まったのでこれを環境変数に設定します。
環境変数は.env.productionファイルを読みにいきますので、frontend/.env.production を作成して

BACKEND_DOMAIN=http://techport-.....amazonaws.com

を設定し、コンテナイメージをもう一度作成してECRにプッシュしましょう。

クラスターはbackendと共通なので、タスク定義を作成します。

コンテナポートはrails同様に3000番です。
他はデフォルトのままでよいです。

次にサービスを作成します。
ファミリーには先ほど作成したタスク定義を指定して最新のリビジョンにしましょう
ネットワーキングでVPCとサブネットの指定を忘れないようにしましょう
ロードバランシングでは同様にalbを新規に作成し、リスナーとターゲットグループを作成します。
ヘルスチェックも/は作成済み(careerページ)なのでこのままで問題ないです。

サービスを作成してタスクが正常に起動するか確認しましょう。

問題があるときはデバッグの章にあるように原因を特定してみてください。
タスクのログをみてbuildが成功しているか、ELBはタスクにアクセスできているか、ヘルスチェックは通るのか、パブリックIPアドレスを直打ちしてアプリケーションは表示されるか…etc

やっていないこと

インフラ周りでやっておくとよいがやれていないことがまだまだあります。

  • ドメインの取得とSSL化

現在はアプリケーションのurlをalbのDNS名のままになっていますが、ドメインを取得してそのレコードにalbのDNSを割り当てるとご自身の任意のドメインにできます。任意ドメインの方がなんのアプリケーションをわかりやすく伝えることができるのでやっておくとよいです。
また、ドメインを取得したらSSL化をしてhttpsでアクセスできるようにすべきです。httpのサイトは撲滅される傾向にありますのでhttpsでアクセスできるようにすべきです。

  • CI/CD

デプロイをするのに手元でコンテナイメージを作成してAWSのマネジメントコンソールを操作してあげる必要がありますが、デプロイの度にこれを行うのは大変です。簡単なトリガーをきっかけに自動テスト・ビルトを実行してデプロイまで自動で行えるとよいです。

おわりに

backendもfrontendも全てデプロイできたら終了です。おつかれ様でした!

これまでのプログラミング言語を使っての実装と雰囲気がガラッと変わる内容でしたが、いかがでしたでしょうか?
なにをやるにしてもエラーになったり時間がかかるのは当たり前です。焦らず少しずつ経験を積んで慣れていきましょう。

パブリッククラウドの設定方法自体は頻繁に更新されますし、いろんな便利なサービスが日々リリースされています。なので具体的な設定の手順やサービス名・プロパティを真剣に覚える意味はあまりないと考えています。
大事なのは、ネットワークの構成や構築したいアプリケーションのトラフィックの流れをしっかり頭の中で組み上げて、それを実践できることです。
具体的な手順はその都度調べて確認するようにしましょう。

このアプリケーションが不要になった際にはAWS上のリソースを削除して、最後はアカウントの解約をしないと課金がされるようになってしまいます。
リソースの削除の順番はある程度決まっています。例えばいきなりVPCを削除しようとしてもこれの上にいろんなサービスが乗っかっているので削除はできません。
削除は最後に作成したリソース順に削除するとうまくいきやすいです。
また、データベースは放置すると高額になりやすいので優先的に削除しましょう。

コメント

タイトルとURLをコピーしました