79
Help us understand the problem. What are the problem?

posted at

updated at

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

概要

現在、運営しているサークルの公式サイトを作成しているのですがデプロイ環境をEC2からECSに引っ越しました。その際に、色々ハマりながらデプロイしたので、方法を忘れないために書きます。
この方法を真似ればFargateへのデプロイが再現できてるので、ぜひ活用してください。

以下のようなインフラ構成を想定しております。

image.png

【前編】

  • 1.VPCの設定
  • 2.RDSの設定
  • 3.ECRの設定
  • 4.ECSの設定

【後編】

  • 5.独自ドメインの設定(お名前ドットコム)
  • 6.ACMの設定
  • 7.ALBの設定
  • 8.CircleCIの設定

追記
sample_public_subnetがtypoで写真内でsample_pubnet_subnetとなっていました。sample_public_subnetだと思って進めてください。

前提

Dockerの環境とAWSでIAMを管理者権限で作成していること前提で話を進めます。また、筆者は以下のような技術を使っています。

名前 バージョン
リージョン 東京
Docker 20.10.5
Ruby 2.6.5
Rails 6.1.3 (バージョン5でも可)
PostgreSQL 9.6.2

注意点

今回のECSの構成をするに当たり、料金が発生します。
お金をかけたくない場合は、すぐに作ったネットワーク構成を削除してください。

1.VPCの設定

Amazon Virtual Private Cloud(VPC)は、AWSの提供する機能の1つで、プライベートな可塑のネットワークを構成できます。このネットワークの中で、仮想サーバ「EC2」など多くのAWSのサービスが起動します。このネットワーク内にEC2やRDSなどを起動させることでアプリケーションを構築することができます。そして、サブネットとはVPCによって作成されるcidrブロックを分割したネットワーク群のことです。サブネット内にEC2やRDSなどを設置します。

以下のような設計で構成します。

1.1 VPCを作成

AWSのサービスよりVPCを選択して、VPCの作成をクリックします。
その後、以下のように設定していきます。

タグ
名前 sample_vpc
CIDRブロック 10.0.0.0/16

※テナンシーとは物理的にハードウェアを占領したいかで、必要がないためデフォルトを選択しています。

image.png

1.2 サブネットの作成

VPC内にサブネットを構成します。
サービスからサブネットを選択して、サブネットの作成を押します。
そして、以下のようにプライベートサブネット2つとパブリックサブネット2つの4つのサブネットを構成してください。(プライベートサブネットはVPC内部でのみ通信可能なサブネット(送信先が10.0.0.0/16)のことで、パブリックサブネットは外部のインターネットから通信可能なサブネットのことです。1.3のインターネットゲートウェイの作成と1.4のルートテーブルの作成の時にプライベートとパブリックの作成方法の違いを説明します。)

サブネットのCIDRブロックはVPCのCIDRブロックの16bit目(10.0)までは等しい必要があります。
それ以降は自由で構いません。

ECS用パブリックサブネット

タグ
名前 sample_public_subnet1
AZ ap-northeast-1a
CIDRブロック 10.0.0.0/24
タグ
名前 sample_public_subnet2
AZ ap-northeast-1c
CIDRブロック 10.0.1.0/24

RDS用サプネット

タグ
名前 sample_private_subnet1
AZ ap-northeast-1a
CIDRブロック 10.0.10.0/24
タグ
名前 sample_private_subnet2
AZ ap-northeast-1c
CIDRブロック 10.0.11.0/24

以下はsample_public_subnet1を作成しているときの画面です。
※VPC IDには作成したVPC(sample_vpc)を選択してください。

image.png

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

インターネットゲートウェイとはVPCとインターネットの通信を可能にするもので、これを設定することによって誰のパソコンからも自分のVPC内のアプリにアクセスできるようになります。
以下の公式リファレンスに詳しい内容が記述されています。

インターネットゲートウェイのサービスを選択して、インターネットゲートウェイの作成を選択します。

タグ
名前 sample_igw

image.png

作成が終わったら、作成したインターネットゲートウェイにチェックを入れてアクションからVPCにアタッチするを選択します。

以下でsample_vpcが表示されるので選択します。

image.png

1.4 ルートテーブルの作成

ルートテーブルとはサブネットのインスタンスがどこと通信するのかを明示的に定めるものです。ルートテーブルの作成により、サブネットがVPC内のどこと通信できるのか、また外部と通信できるのかを選択できます。ここで、インターネットゲートウェイの通信ルールを入れるか否かで先ほど作成したサブネットをプライベートサブネットにするかパブリックサブネットにするかを決められます。

ルートテーブルの作成を選択し、以下のように設定を行います。

タグ
名前 sample_route
VPC sample_vpc

image.png

ルートテーブルの作成が完了したら、作成したルートテーブルを選択してアクション→ルートの編集を選択します。
VPCをインターネットから通信できるようにルールを編集するために全てのIPアドレスを選択する0.0.0.0を送信先に設定し、ターゲットにはInternet Gateway→先程作成したターゲットを選択します。

image.png

次に、サブネットとの関連付けを行います。
この操作をすることにより1.2で作成したECS用のサブネットをインターネットゲートウェイを介して外部と通信できるようにします。
作成したルートテーブルを選択してアクション→サブネットの関連付けの編集を選択します。
そうしたらsample_public_subnet1sample_public_subnet2の2つを選択して保存を押します。

image.png

1.5 セキュリティグループの作成

セキュリティグループとはファイアウォール機能であり、インスタンスへのアクセスを許可したり、トラフィックを制御することができます。
以下ではECSとRDSのセキュリティグループを設計していきます。

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

基本的な詳細

タグ
名前 sample_ecs_security
説明 適当
VPC sample_vpc

インバウンドルール

インバウンドルールはセキュリティグループに関連付けられたインスタンスへのアクセスを制限するルールです。

※Fargateだとsshアクセスできないのでポート22番を解放する必要がありません。

タグ
タイプ HTTP
リソースタイプ 任意の場所

アウトバウンドルール

アウトバウンドルールはセキュリティグループに関連付けられたインスタンスからどの送信先にトラフィックを送信できるか決めるルールです。

変更なし

image.png

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

基本的な詳細

タグ
名前 sample_rds_security
説明 適当
VPC sample_vpc

インバウンドルール

こちらはECSのセキュリティグループからのみアクセスできるように設定します。
※MySQLや他のデータベースを使用する人は、自分のデータベースにあったタイプを選択してください。

タグ
タイプ PostgreSQL
リソースタイプ カスタム
ソース sample_ecs_security

アウトバウンドルール

変更なし

image.png

2.RDSの設定

ここからRDS周りの設定に移ります。
Amazon Relational Database Service (RDS)は、AWSのデータベース機能です。こちらを使用することにより、Amazon Aurora、PostgreSQL、MySQL、MariaDB、Oracle データベース、SQL Serveといったデータベースを簡単にセットアップできます。
AWSのアカウントを作成してはじめの1年間は無料枠を使用することによりお金をかけず使用することができます。

2.1 RDS用サブネットグループの作成

RDSの作成には2つ以上のサブネットを束ねたサブネットグループを作成する必要があります。
1.2のサブネットの作成の際に、RDS用のサブネットのAZにap-northeast-1aとap-northeast-1cの2つを選択しているため、マルチAZにできます。(マルチAZとは複数のAZを使用する構成のことで、1つのAZでしょうが板起きても他のAZで稼働を続けることができるため可用性が向上します。)

サービス検索よりRDSを検索し、サブネットグループのを選択します。すると、DBサブネットグループを作成をクリックし以下のように設定します。

サブネットグループの詳細

タグ
名前 sample_rds_subnet_group
説明 適当
VPC sample_vpc

サブネットを追加

タグ
アベイラビリティゾーン ap-northeast-1a, ap-northeast-1c
サブネット 1.2で作成したRDSのサブネットをそれぞれのAZで選択

image.png

2.2 RDSの作成

次にRDSを作成します。
RDSを検索後、データベースを選択します。すると、データベースの作成という項目があるのでクリックして以下のように設定を行います。

タグ
エンジンのオプション PostgreSQL(自分の使っているDB)
サブネット バージョン(自分の使っているDBのバージョン(異なると動作しない可能性があるのでバージョンによる差異があるのか調べたほうがいいかも))

image.png

タグ
DBインスタンス識別子 sample-db
マスターユーザー名 postgres
マスターパスワード 自分で決める
DBインスタンスサイズ db.t2.micro(無料枠)

image.png

タグ
ストレージタイプ 汎用(SSD)
ストレージ割り当て 20
ストレージの自動スケーリング 有効
最大ストレージしきい値 1000

image.png

タグ
VPC sample_vpc
サブネットグループ sample_rds_subnet_group
パブリックアクセス可能 なし
VPCセキュリティグループ 既存の選択
既存のVPCセキュリティグループ sample_rds_security
アベイラビリティゾーン 指定なし

image.png

エンドポイントはメモしておいてください。

image.png

3.ECRへイメージのプッシュ

Amazon Elastic Container Registry (ECR)はDockerイメージのレジストリサービスです。
ここからECRへ自分のDockerイメージのプッシュを行っていきます。

3.1 ECRのリポジトリの作成

まずはDockerイメージをプッシュするためのリポジトリを作成します。
このリポジトリでDockerイメージのバージョン管理を行います。少しGithubに似てますね笑
まずは、Rails用のリポジトリを作成します。
以下のように設定します。

タグ
可視性設定 プライベート
リポジトリ名 rails-sample

image.png

同様にして、nginxのリポジトリを作成してください。

タグ
可視性設定 プライベート
リポジトリ名 nginx-sample

3.2 Dockerfileの確認

(1) Rails

Dockerfileの中身

筆者のDockerfileの中身は以下のようになっています。

Dockerfile(Rails)
FROM ruby:2.6.5


# 以下の記述を追加
ENV RAILS_ENV=production


RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - && apt-get update && \
    apt-get install -y nodejs --no-install-recommends && rm -rf /var/lib/apt/lists/*

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev sudo vim

RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
    curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
    echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
    apt-get update && apt-get install -y yarn && apt-get install imagemagick


RUN yarn add node-sass

WORKDIR /app
RUN mkdir -p tmp/pids
RUN mkdir -p tmp/sockets
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
COPY yarn.lock /app
COPY package.json /app
RUN bundle install
# ここでyarn installをしないとwebpackerを実行できない
RUN yarn install
COPY . /app

# RUN yarn add jquery popper.js bootstrap

# WARNING:webpack:installをするとwebpackの設定ファイルが初期化されてjqueryなどが使えなくなってしまう
# RUN rails webpacker:install
RUN NODE_ENV=production ./bin/webpack

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# 以下の記述があることでnginxから見ることができる
VOLUME /app/public
VOLUME /app/tmp

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

※webpackerを使っていますが、sprocketsを使っている場合はrake assets:precompileで置き換えてください。
ポイントとしては、Dockerfile内でVOLUMEを設定することで、nginxコンテナからrailsコンテナのsockファイルが見えるようにしてください。

VOLUME /app/public
VOLUME /app/tmp

またentrypoint.shにdbのマイグレーションなどを行う設定をしてください。
※ 2回目以降のタスク実行時にはdb:createとdb:seedを行うとエラーになるのでコメントアウトしてください。

entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid
# WARNING:createとseedはfargateの初回のみ実行
# WARNING:タスクを個々に作って実行の方がいいかも
bundle exec rails db:create
bundle exec rails db:migrate
bundle exec rails db:seed

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
(2) nginx
Dockerfile(nginx)
FROM nginx:1.16
RUN apt-get update && \
    apt-get install -y apt-utils \
    locales && \
    echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \
    locale-gen ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8
# 初期状態の設定ファイル
ADD ./nginx/nginx.conf /etc/nginx/nginx.conf
ADD ./nginx/default.conf /etc/nginx/conf.d/default.conf

3.3 ECRの設定

まずはRailsのイメージをプッシュします。
先程作成したsample-railsというリポジトリを選択するとプッシュコマンドの表示からプッシュコマンドを確認します。

image.png

こういう感じでコマンドが出てくると思います。

terminal
# awsのログイン
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com

# dockerイメージのビルド
docker build -t rails-sample . --no-cache

# dockerイメージのタグ付け
docker tag rails-sample:latest ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/rails-sample:latest

# dockerイメージをECRのリポジトリへプッシュ
docker push ${IAM_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/rails-sample:latest

※ aws-cliが入っていない場合は以下のリンクよりaws-cliのインストールを行ってください。

※ dockerファイルがカレントディレクトリでDockerfileでない場合は、以下のように-fのオプションを付けることでDockerファイル名とパスを選択してください。

(例) nginxディレクトリにあるDockerfile.nginxというdockerファイルを選択したい場合

terminal
docker build -f ./nginx/Dockerfile.nginx -t sample-rails . --no-cache

同様にnginxのイメージについても同様の手順でECRのリポジトリへプッシュをしてください。

さらに、リポジトリへプッシュできたことが確認できたら、URIをそれぞれコピーして控えておいてください。(ECSのタスク定義で使います。)

4.ECSの作成

遂にECSの作成に入ります。
Amazon Elastic Container Service (ECS) はクラスターでコンテナをスケーラブルに実行、停止、管理できるサービスです。
簡単に言うと、クラスターと呼ばれる複数のコンテナを束ねることができる基盤の上でコンテナを実行し、サービスを構成するサービスと考えるとわかりやすいかも知れません。

4.1 タスクの作成

ECSのタスクとはコンテナを組み合わせやボリュームの設定などサービスを動作させるための設定を行う部分です。
docker-compose.ymlの役割に似ているかも知れません。
ECS→タスク定義→新しいタスク定義の作成を選択し、タスクの作成を行いましょう。

起動タイプはFARGATEを選択してください。

image.png

タグ
タスク定義名 sample
タスクロール なし
タスク実行ロール ecsTaskExecutionRole

タスクロールとはECS上のコンテナ中のアプリケーションのIAMロールのようなものでコンテナ中のアプリケーションが他のサービス(S3やElastic Searchなど)にアクセスする権限を設定することができます。
対してタスク実行ロールとはタスク定義がAWSのサービスを利用するためのルールで、タスクのログをCloud Watchに出力したり、ECRのコンテナイメージを使用するために用いられます。

↓参考

image.png

タグ
タスクメモリ 0.5GB
タスクCPU 0.25vCPU

タスクメモリとタスクCPUについては料金を最小限にするために上の設定にしていますが、自分のサービスの規模に合わせて変更してください。

image.png

次にコンテナの追加を行います。
railsコンテナとnginxコンテナの2つを追加します。

(1) Railsコンテナ
タグ
名前 rails
イメージ ECRの作成の際メモしたsample-railsのリポジトリURI
ポートマッピング 3000
環境変数 docker-compose.ymlや.envに設定している環境変数(RAILS_MASTER_KEYがCircleCIデプロイをする際に必要になるのでmaster.keyの値を入れておいてください)
ログ設定 ON

image.png

image.png

image.png

(2) Nginxコンテナ

ポイントはボリュームソースにRailsコンテナのコンテナ名(今回の場合はRailsコンテナに付けた名前のrails)を設定しています。
これにより、railsコンテナのsockファイルをnginxコンテナから見ることができるようになります。

タグ
名前 nginx
イメージ ECRの作成の際メモしたsample-nginxのリポジトリURI
ポートマッピング 80
ボリュームソース rails
ログ設定 ON

image.png

image.png

残りはデフォルトで作成を押します。
以下のようにsample-taskのステータスがActiveになっていればタスク定義成功です。

image.png

4.2 クラスターの作成

次にクラスターの作成を行います。
ECS→クラスター→クラスターの作成を選択する。
Fargateを使ってデプロイするのでネットワーキングのみを選択します。
選択したら次のステップへをクリックします。

image.png

クラスターの設定は以下のように行います。
そして作成をクリックします。

タグ
名前 sample-cluster
CloudWatch Container Insights OFF

4.3 サービスの作成

クラスターが作成できたらサービスを選択し、作成をクリックします。

image.png

サービス設定は以下のように行います。今回は料金を最小にするためにタスクの数を1にして行いたいと思います。
設定が終わったら次のステップをクリックします。

タグ
起動タイプ FARGATE
タスク定義 ファミリー sample
タスク定義 リビジョン latest
クラスター sample-cluster
サービス名 sample-service
タスクの数 1 (概要で説明した編成のようにALBによって負荷分散するためには2以上にする必要があります)

image.png

image.png

ネットワーク構成は以下のように行います。
まだ独自ドメインを取っていないためALBを設定していないので今回はロードバランサー無しで次のステップへ進みます。

タグ
クラスターVPC sample_vpc
サブネット sample_public_subnet1
セキュリティグループ sample_ecs_security
パブリック IP の自動割り当て ENABLED

image.png

image.png

Auto Scalingは今回は調整しない設定にします。
選択したら次のステップへ進みます。

image.png

最後に確認画面になりますので、内容を確認したらサービスの作成をクリックしてサービスを起動します。

image.png

タスクがRUNNINGになっているのを確認したら、railsのLogsを確認して以下の一番下のログのようにdatabaseが作成されているのを確認してください。

image.png

※ データベースとの連携ができない場合、以下のような原因が考えられます。
① ユーザー名かパスワードが誤っている
② RDSのエンドポイントが間違ってる
③ RDSのインバウンドルールにECSのセキュリティグループが設定されていない

①②の対処法はRDSサービスに一度アクセスしてユーザー名とエンドポイントがあっているか確認してください。パスワードは確認ができないため変更からパスワードの変更を行ってください。
③の場合はRDSのセキュリティグループのインバウンドルールにECSのセキュリティグループが設定されているか確認してください。また、インバウンドルールのタイプがPostgreSQLになっているか確認してください

どちらかわからない場合は、一度RDSのセキュリティグループのソースを任意の場所(0.0.0.0)に変更してタスクを再度実行してください。(確認が終わったらセキュリティ上の問題からすぐにソースをもとに戻してください)

↓のようになっていれば③はOK

image.png

4.4 サービスの再作成

4.3でデータベースの作成が終わったので以下のようにentrypoint.shをdb:createとdb:seedをコメントアウトします。

entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /app/tmp/pids/server.pid
# WARNING:createとseedはfargateの初回のみ実行
# WARNING:タスクを個々に作って実行の方がいいかも
# bundle exec rails db:create
bundle exec rails db:migrate
# bundle exec rails db:seed

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

その後、ECRのリポジトリに移動しrails-sampleを選択します。
イメージタグがlatestのものを削除します。

image.png

削除が終わったら再度ECRにentrypoint.shを変更したコンテナをプッシュします。
キャッシュが残っているとコンテナの内容が変更されていない可能性があるので --no-cacheは必ず付けてbuildを行ってください。

次にタスク定義の変更を行います。
タスクsampleに移動して最新のリビジョンを選択して新しいリビジョンの作成をクリックします。(筆者は一度データベースの生成に失敗しているためリビジョンが2になっていますが皆さんの場合は最新が1になっているはずです)

image.png

タスク内部に移動したら一度railsコンテナを削除して再度コンテナの追加より設定を行ってください。設定内容は変わらず以下のように行います。

タグ
名前 rails
イメージ ECRの作成の際メモしたsample-railsのリポジトリURI
ポートマッピング 3000
環境変数 docker-compose.ymlや.envに設定している環境変数(RAILS_MASTER_KEYがCircleCIデプロイをする際に必要になるのでmaster.keyの値を入れておいてください)
ログ設定 ON

image.png

次にクラスターに移動します。
クラスターsample-clusterを選択し、サービス名sample-serviceを選択してください。
そうしたらリビジョンを最新に変更してあとは同じままサービスを更新します。

image.png

サービスの更新が完了したらしばらく待って以下のようにタスクからタスク定義が最新になっているのとステータスがRUNNINGになっているのを確認します。
確認が終わったらタスク定義をクリックします。

image.png

パブリックIPを確認できるのでアクセスします。

image.png

以下のように自分のサービスがパブリックIPからアクセスできることが確認できるはずです。

image.png

仮にアクセスできない場合以下の可能性があります。
①タスク定義のLogsからnginxとrailsのコンテナでエラーが起きている
②タスク定義のサブネットIDをコピーしてVPC→サブネット→ルートテーブルより送信先にインターネットゲートウェイが追加されているか確認する

↓②の確認例

image.png

次回、独自ドメインの設定〜CircleCIの自動デプロイの設定まで行います。

↓後編

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
79
Help us understand the problem. What are the problem?