【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) | |
ストレージ割り当て | 20 | GiB(無料利用枠最大) |
ストレージ自動スケーリング | チェックしない | 無料利用枠の場合、チェックすると拡張した際に料金が発生してしまうので注意 |
項目 | 設定値例 | 備考 |
---|---|---|
接続 | ||
Compute resource | Don'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
(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を両方起動してサイト公開
おわりに…
だいぶ前に実施した手順ですが、概ねこのレシピで環境が作れると思います。
おわり