【AWS】Rails/Reactの実行環境レシピ

ベース環境は「【AWS】最低限のネットワーク環境レシピ」を参照のこと

EC2構築

ステップ1:Amazonマシンイメージ(AMI)

  • Amazon Linux2 AMI(HVM) - Kernel 5.10, SSD Volume Type
  • 64ビット(x86)

ステップ2:インスタンスタイプの選択

  • t2.micro  ※無料利用枠

ステップ3:インスタンスの詳細の設定

ネットワークベース環境のVPCを指定
サブネットベース環境のPUBLIC(外部接続可能)なサブネットを指定
自動割り当てパブリックIP有効

※上記以外はデフォルト
※自動割り当てパブリックIDは、ここで「有効」でも、サブネット側の設定がされていないと割り当てされない

ステップ4:ストレージの追加

サイズ(GiB)30

※上記以外はデフォルト

ステップ5:タグの追加

キー名Name
test-ec2(目的、オブジェクトがわかる名前を推奨)

ステップ6:セキュリティグループの設定

セキュリティグループ名test-sg-ec2
説明EC2 createed YYYY-MM-DD…
タイプSSH
ポート22
ソース0.0.0.0/0

ステップ7:インスタンス作成の確認

特になし

補足

※ペアキーは既に作成済みの場合は流用可能

※DNSホスト名の有効化

 ① VPCを選択して、アクションから「DNSホスト名を編集」
   ここで「DNSホスト名」を有効化して保存
   基本的にEC2間でSSH接続する場合などで必要な設定なので、標準的に必要かどうかは不明
   過去インシデントで設定していたので以来設定するようにしている

 ② VPCを選択して、アクションから「DNS解決を編集」
   ここで「DNS解決」を有効化して保存
   ※VPC作成時にデフォルトで「有効」となっているはず

RDS構築

1.EC2にRDS接続用のセキュリティグループを作成

項目設定値例備考
セキュリティグループ名test-sg-rds目的、オブジェクトがわかりやすい名前で命名
説明EC2からRDSへの接続を許可
VPC接続するEC2と同じVPC
ルールの追加
 インバウンドルール
  タイプPostgreSQL
  ソースカスタム/「EC2のセキュリティグループ」または接続元EC2のプライベートIP

2.RDSで「データベースの作成」

項目設定値例備考
データベースの作成方法標準作成
エンジンのタイプオプション
 エンジンのタイプPostgreSQL
 バージョン12.8-R1無料利用枠で選択可能な最新Ver(各自アプリと整合する必要あり)
テンプレート無料利用枠
項目設定値例備考
設定
 DBインスタンス識別子test-rds目的、オブジェクトがわかるように命名
 マスターユーザー名postgresデフォルト値のまま
 パスワードの自動生成チェックしない
 マスターパスワード**********各自設定
 パスワードを確認**********同上
項目設定値例備考
DBインスタンスタイプ
 DBインスタンスクラスバースト可能クラス(tクラスを含む)
db.t2.microリージョンによりdb.t3.microの場合あり
 以前の世代のクラスを含めるOFF
項目設定値例備考
ストレージ
 ストレージタイプ汎用(SSD)
 ストレージ割り当て20GiB(無料利用枠最大)
 ストレージ自動スケーリングチェックしない無料利用枠の場合、チェックすると拡張した際に料金が発生してしまうので注意
項目設定値例備考
接続
 Compute resourceDon't connect to an EC2 compute resource「Connect to an EC2 compute resource」選択の場合、既設EC2と同一ネットワーク上に自動設定となる
 VPCベース環境のVPCを選択
 サブネットグループ新規にサブネットグループを作成
 パブリックアクセス可能なし
 VPCセキュリティグループ既存の選択
上記1のセキュリティグループ
 アベイラビリティゾーン指定なし
項目設定値例備考
データベース認証パスワード認証

※「データベースの作成」後は10分少々経過してステータスが「利用可能」となる

フロント(React)/API(Rails)のWeb環境設定

以下手順はターミナルからのSSHしての操作内容で記述

1.パッケージ関係インストール

(1)yum最新化(RedHat系なのでパッケージ管理はyum)

sudo yum update -y

(2)必要パッケージをインストール

sudo yum -y install gcc-c++ make patch git curl zlib-devel openssl-devel ImageMagick-devel readline-devel libcurl-devel libffi-devel libicu-devel libxml2-devel libxslt-devel

(参考にしたサイト)
https://pikawaka.com/rails/ec2_deploy

(3)Nodeインストール(最新化)

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
. ~/.nvm/nvm.sh
nvm install node
node -e "console.log('Running Node.js ' + process.version)"

node -v
// バージョンが表記されればOK

npm -v
// バージョンが表記されればOK

nvm --version
// バージョンが表記されればOK

(参考にしたサイト)
https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html

(4)yarnインストール

基本的にはnpmがあればOKだが、Reactではyarnが必要となるためインストールしておく

curl -o- -L https://yarnpkg.com/install.sh | bash
// ターミナル再ログイン

source ~/.bashrc
yarn -v
// バージョンが表記されればOK

(参考にしたサイト)
https://qiita.com/i-f/items/af88bec7438abbc83f0b

(5)Rubyインストール

git clone https://github.com/sstephenson/rbenv.git ~/.rbenv

// パス追加&bash追加
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source .bash_profile

git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv rehash

rbenv install -l
// インストール可能なバージョンのリストを確認

rbenv install 2.7.6
rbenv rehash
rbenv global 2.7.6
ruby -v
// バージョン確認

(参考にしたサイト)
https://qiita.com/inouet/items/478f4228dbbcd442bfe8

(6)PostgreSQLインストール

wget https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm
sudo rpm -Uvh --nodeps pgdg-redhat-repo-latest.noarch.rpm
sudo sed --in-place -e "s/\$releasever/7/g" /etc/yum.repos.d/pgdg-redhat-all.repo
sudo yum -y update
sudo yum search postgres
// ver12があることを確認

sudo yum install postgresql12

psql --version
// バージョンが表記されればOK

sudo yum install postgresql-devel.x86_64

2.ディレクトリ作成とアプリケーション配置

(1)公開ディレクトリ作成

sudo mkdir /var/www/
sudo chown ec2-user /var/www/
cd /var/www

// 今回は「test-app」を公開ディレクトリルート
mkdir test-app
cd test-app

(2)APIを配置(githubから)

git clone {githubからhttpsのURLをコピー&ペースト}

// ディレクトリ名は適宜変更 (今回は「api」とする)
mv {githubからクローンしたディレクトリ名} api

(3)フロントエンドを配置(githubから)

git clone {githubからhttpsのURLをコピー&ペースト}

// ディレクトリ名は適宜変更 (今回は「front」とする)
mv {githubからクローンしたディレクトリ名} front

(4)ログ出力先とエラーページ用ディレクトリを作成

mkdir nginx_log
mkdir error_page

3.API側のセットアップ

(1)パッケージインストール

cd /var/www/test-app/api
bundle install --without test development

※基本は同梱のGemfileに従ってインストールされるが、エラーが発生した場合は適宜対処

※エラーインシデント①
 開発環境とのRubyバージョン管理システムが異なる場合、インストール可能なバージョンが異なる
 場合がある(rbenv/rvm)。
 これに起因したエラーの場合、EC2上のインストール可能バージョンに合わせてGemfile記述を変
 更して再度bundle installする。
※エラーインシデント②
 「pg」でエラーの場合、以下のいずれかの対処で解決。
  1) Gemfileでpgのバージョン記述を「1.2.3」に変更してbundle install
  2) 依存パッケージの不足のため「postgresql-libs」「postgresql-devel」をsudo yum install

(2)SECRET_KEY_BASEの変更

a. Railsプロジェクトの変更

Railsプロジェクトに同梱されている「credentials.yml.enc」は暗号化されているので、そのままでは参照/編集できない。

①VSCodeでターミナル表示 & SSHでEC2に接続
②VSCodeのターミナル上で、apiにカレントディレクトリを移動
③VSCodeのターミナル上で「EDITOR='code --wait' rails credentials:edit」を入力すると、
 VSCode上にファイルが表示される
④credentials.yml.encを環境変数を参照するように変更
 変更前: secret_key_base: 何らかのコード
 変更後: secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

b. 環境変数の設定

①SECRET_KEY_BASEを確認

// apiにカレントがある状態で実施
bin/rails secret

// キー文字列が表示される

②SECRET_KEY_BASEを環境変数に設定

sudo vi /etc/environment

// 以下、vi上で「i」で挿入モードとして編集
SECRET_KEY_BASE='上記①のキー文字列'

// 「ESC」で挿入モード解除
// 「:wq」で保存してviを抜ける

③ログアウト/ログインで環境変数を反映

(3)DB作成

a. database.ymlの編集

開発環境時点で設定済みなので対応不要。
開発環境で設定していない場合、productionの情報を設定。

b. DBとテーブルを作成
// apiにカレントがある状態で実施

// DB作成
bin/rails db:create RAILS_ENV=production
// テーブル作成
bin/rails db:migrate RAILS_ENV=production

(4)cors設定

今回の構成では、同一origin内でのapiリクエストとなるため設定不要

4.フロントエンド側のセットアップ

(1)axiosのパスを設定変更

Reactプログラム内のAPI接続先のURLを運用環境向けに設定変更する。

(2)パッケージインストール

// frontにカレントがある状態で実施

npm install
// package.jsonに従ってインストールされる

(3)ビルド

// frontにカレントがある状態で実施

npm run build
// 直下に「build」ディレクトリが作成され、中にビルド成果物が格納されている。
// Webサーバのドキュメントルートはbuild内を指定する

※インシデント
 EC2のスペックが弱小な場合、ヒープメモリ不足のエラーが発生する
 こんな感じのメッセージ

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

 解決策は一時的にヒープメモリを拡張して、再度ビルド実行

export NODE_OPTIONS="--max-old-space-size=1024"
npm run build

5.Unicornインストール

補足

この構成では表口のWebサーバには「Nginx」を配置するが、「Nginx」はRailsのAPサーバ「rack」に対応しないことから、「Unicorn」でRailsは動かすこととなる。
一方で、「Unicorn」は多重リクエストに対して弱い性質があり、対して「Nginx」は多重リクエストに強いことから補完する形で「Nginx」のプロキシで「Unicorn」を参照する設定にする。

(1)APIプロジェクトのGemfileに追記

group :production do
  gem 'unicorn'
end

(2)パッケージインストール

// apiにカレントがある状態で実施

bundle install --without test development

(3)APIプロジェクトに設定ファイル作成

config/unicorn.rbを新規作成し、以下のコードを記述

app_path = File.expand_path('../../', __FILE__)

#アプリケーションサーバの性能を決定する
worker_processes 1

#アプリケーションの設置されているディレクトリを指定
working_directory app_path

#Unicornの起動に必要なファイルの設置場所を指定
pid "#{app_path}/tmp/pids/unicorn.pid"

#ポート番号を指定
listen "#{app_path}/tmp/sockets/unicorn.sock"
#listen 3000

#エラーのログを記録するファイルを指定
stderr_path "#{app_path}/log/unicorn.stderr.log"

#通常のログを記録するファイルを指定
stdout_path "#{app_path}/log/unicorn.stdout.log"

#Railsアプリケーションの応答を待つ上限時間を設定
timeout 60

#以下は応用的な設定なので説明は割愛

preload_app true
GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true

check_client_connection false

run_once = true

before_fork do |server, worker|
  defined?(ActiveRecord::Base) &&
    ActiveRecord::Base.connection.disconnect!

  if run_once
    run_once = false # prevent from firing again
  end

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exist?(old_pid) && server.pid != old_pid
    begin
      sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
      Process.kill(sig, File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH => e
      logger.error e
    end
  end
end

after_fork do |_server, _worker|
  defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection
end

ポイントはリスナーをポート番号指定ではなく、ソケットを指定している部分。
これにより、Unicornに対しては外部からは直接接続はできない。

(4)unicorn起動用スクリプト作成

// apiにカレントがある状態で実施

rails g task unicorn
// lib/tasksにunicorn.rakeファイルが生成される

※インシデント
 ここでエラーが発生した。
 内容を見ると「listen」が関係している内容。
 Gemfileを見ると、「listen (~> …)」がdevelopmentグループ限定にされているので、bundle installで
 パッケージインストールされていないことが判明。
 Gemfile記述のdevelopmentグループ限定を外して、再度bundle install(上記(2))実施。

unicorn.rakeファイルを以下のコードを記述

namespace :unicorn do

  # Tasks
   desc "Start unicorn"
   task(:start) {
     config = Rails.root.join('config', 'unicorn.rb')
     sh "unicorn -c #{config} -E production -D"
   }

   desc "Stop unicorn"
   task(:stop) {
     unicorn_signal :QUIT
   }

   desc "Restart unicorn with USR2"
   task(:restart) {
     unicorn_signal :USR2
   }

   desc "Increment number of worker processes"
   task(:increment) {
     unicorn_signal :TTIN
   }

   desc "Decrement number of worker processes"
   task(:decrement) {
     unicorn_signal :TTOU
   }

   desc "Unicorn pstree (depends on pstree command)"
   task(:pstree) do
     sh "pstree '#{unicorn_pid}'"
   end

   # Helpers
   def unicorn_signal signal
     Process.kill signal, unicorn_pid
   end

   def unicorn_pid
     begin
       File.read("/var/www/manapp/api/tmp/pids/unicorn.pid").to_i
     rescue Errno::ENOENT
       raise "Unicorn does not seem to be running"
     end
   end

end

(5)Unicorn起動/停止の確認

// apiにカレントがある状態で実施

// 起動(上記(4)のスクリプト実行)
rails unicorn:start
// 「unicorn -c /var/www/test-app/api/config/unicorn.rb -E production -D」とだけ表示されればOK

// 状態確認
ps -ef | grep unicorn | grep -v grep
// 起動中は4行ほどプロセスの一覧が表示されればOK

// 停止
rails unicorn:stop
// 何も表示されないので、適宜状態確認で確認する

6.Nginxインストール

(1)インストール

sudo amazon-linux-extras install nginx1

nginx -v
// バージョンが表記されればOK

(2)EC2のポート設定

EC2のセキュリティグループ設定で、http(port:80)を許可設定する。

(3)Nginx起動と接続確認

sudo systemctl start nginx

ブラウザで「http://xxx.xxx.xxx.xxx/」に接続し、Nginxの画面が表示されればOK

ちなみにNginxの状態確認コマンドは以下の通り

sudo systemctl status nginx

(4)停止

sudo service nginx stop

(5)Nginxの設定ファイルを作成/編集

sudo vi /etc/nginx/conf.d/{任意の名前).conf

// 以下、記述例
// apiの接続先。unicorn.rbの設定と合わせる。
 upstream api {
   server unix:/var/www/test-app/api/tmp/sockets/unicorn.sock;
 }

 server {
   // サイトの接続情報
   listen 80;
   server_name {各自環境のIP or ドメインネーム};

   // ログとエラーページの格納先
   access_log /var/www/test-app/nginx_log/access.log;
   error_log  /var/www/test-app/nginx_log/error.log;

   // フロントエンドのドキュメントルート
   root /var/www/test-app/front/build;
   try_files $uri $uri/index.html @api;

   // 404エラー時の設定
   error_page 404 /404.html;
   location = /404.html {
     root /var/www/test-app/error_page;
     internal;
   }

   // 50xエラー時の設定
   error_page 500 502 503 504 /500.html;
   location = /500.html {
     root /var/www/test-app/error_page;
     internal;
   }

   // プロキシ設定(api)
   location @api {
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header Host $http_host;
     proxy_pass http://api;
     proxy_intercept_errors on;
   }
 }

7.エラーページ作成

echo 404 Not Found > /var/www/test-app/error_page/404.html
echo 500 Internal Server Error > /var/www/test-app/error_page/500.html

8.サイト公開

UnicornとNginxを両方起動してサイト公開

おわりに…

だいぶ前に実施した手順ですが、概ねこのレシピで環境が作れると思います。

おわり

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です