AWS に Rails のアプリをデプロイする方法 ~本番環境を構築する~

AWS に Rails のアプリをデプロイする方法 ~本番環境を構築する~

link です。

この記事は前回 AWS に Rails のアプリをデプロイする方法 ~ Rails アプリを起動するまで の続きになります。

前回は Rails アプリを起動してアクセスするまでをやりました。

今回は実際に外部公開する Rails アプリの本番環境を EC2 インスタンス上で構築する手順を紹介します。

想定環境

  • Windows 11
  • Amazon Linux 2023
  • Ruby 3.2
  • Ruby on Rails 7

本番環境構築について

今回、本番環境を構築するにあたって Unicorn+nginx で Rails アプリを立ち上げます。

Rails アプリの起動は rails s で行えます。

しかし rails s で立ち上がる Rack アプリサーバーは負荷を分散させるための機能がありません。

そのため、実運用していく上で、大勢の人がサイトを見たときなどにとても重くなってしまいます。

そこで Unicorn+nginx を使ってアクセス時の負荷を分散できるようにして Web アプリを起動させます。

Unicorn と nginx について

nginx

nginx はクライアントからのリクエストを受け、何らかの処理を行う Web サーバーです。

nginx は masterworker の 2 プロセスに分かれています。

master プロセスがアプリのソースを保持し、 worker プロセス群が実際のリクエストを処理するようになっています。

1 つの worker プロセスが複数のリクエストを処理できるようになっているため、大量のリクエストを処理できることが特徴です。

しかし、 Rails は Rack アプリケーションであるため、 nginx から直接リクエストを受け取ることができません。

そこで Unicorn を間に挟んで nginx から Rails アプリにリクエストを流せるようにします。

Unicorn

Unicorn は Rack アプリケーション用の Web サーバーです。

Unicorn は送信されてきた多数のリクエストをさばき、分散して Rack アプリケーションに伝達するという機能を持ちます。

nginx と同様、 masterworker の 2 プロセスに分かれています。

worker プロセスは 1 つのリクエストを 1 つのプロセスで処理します。

リクエスト処理の流れは以下のようになっています。

  1. nginx からリクエストが Unicorn に渡される
  2. Unicorn は Rack をとおして Rails アプリケーションのルーターに処理を渡す
  3. Rails アプリケーションの結果を Unicorn が受け取る
  4. Unicorn はこれを Web サーバーに渡し、最終的にクライアントへと渡る

Unicorn+nginx+Railsのイメージ

Unicorn+nginx+Railsのイメージ

Unicorn のインストールと設定

ローカルの Rails のプロジェクトフォルダ内の Gemfile に Unicorn を追加します。

Gemfile
group :production do
  gem 'unicorn'
end

config/unicorn.rb を作成して以下の内容を記述します。

config/unicorn.rb
# Rails のルートパスを求める
app_path = File.expand_path('../../', __FILE__)

# Unicorn は複数のワーカーで起動するのでワーカー数を定義
worker_processes 1

# Unicorn の起動コマンドを実行するディレクトリを指定します
working_directory app_path

# プロセスの停止などに必要なPIDファイルの保存先を指定
pid "#{app_path}/tmp/pids/unicorn.pid"

# ポートを設定
listen "#{app_path}/tmp/sockets/unicorn.sock"

# Unicorn のエラーログと通常ログの位置を指定
stderr_path "#{app_path}/log/unicorn.stderr.log"
stdout_path "#{app_path}/log/unicorn.stdout.log"

# 接続タイムアウト時間
timeout 60

# Unicorn の再起動時にダウンタイムなしで再起動を行う
preload_app true

check_client_connection false

run_once = true

# USR2 シグナルを受けると古いプロセスを止める
before_fork do |server, worker|
  defined?(ActiveRecord::Base) &&
    ActiveRecord::Base.connection.disconnect!

  if run_once
    run_once = false
  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 の起動・停止スクリプトを作成します。

スクリプト生成
$ rails g task unicorn

これで lib/tasks/unicorn.rake が生成されるので中身を以下のように書き換えます。

lib/tasks/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("/home/vagrant/myapp/tmp/unicorn.pid").to_i
    rescue Errno::ENOENT
      raise "Unicorn does not seem to be running"
    end
  end

end

EC2 インスタンスにログインして環境変数 SECRET_KEY_BASE を設定します。

~/.bash_profile に以下の内容を追記します。

bash_profile
$ export SECRET_KEY_BASE=`bundle exec rake secret`

これで Unicorn を使う準備は完了です。

最後に、変更を commit してリモートリポジトリに push したあと、 EC2 インスタンス内のプロジェクトフォルダで git pull して変更を反映します。

nginx のインストールと設定

EC2 インスタンスに nginx をインストールします。

nginxインストール
$ sudo yum -y install nginx

/etc/nginx/conf.d/rails.conf を作成して以下の内容を記述します。

/etc/nginx/conf.d/rails.conf
# Unicorn と連携させるための設定
upstream app_server {
  server unix:/var/www/AwsRails/tmp/sockets/unicorn.sock;
}

server {
  # このプログラムが接続を受け付けるポート番号
  listen 80;
  # 接続を受け付けるリクエスト URL
  server_name EC2 インスタンスの IP アドレス;
  # クライアントからアップロードされてくるファイルの容量の上限
  client_max_body_size 2g;
  # 接続が来た際の root ディレクトリ
  root /var/www/AwsRails/public;
  # assets ファイル (CSS や JavaScript のファイルなど)にアクセスが来た際に適用される設定
  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }

  try_files $uri/index.html $uri @unicorn;
  # Unicorn のリバースプロキシ
  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://app_server;
  }

  error_page 500 502 503 504 /500.html;
}

今のままで POST メソッドを実行すると nginx が Rails アプリがあるディレクトリへのアクセス権限を持っていないため 403 が発生してしまいます。

そこで /var/lib/nginx の権限を変更して POST メソッドを実行できるようにします。

権限変更
$ cd /var/lib
$ sudo chmod -R 775 nginx

nginx のインストールと設定はこれで完了です。

本番環境でのアプリ起動

Rails アプリを起動させます。

Unicorn インストール時に作成したスクリプトで Unicorn を使って Rails アプリを起動します。

アプリ起動
$ rake unicorn:start

unicorn -c /home/{ユーザ名}/AwsRails/config/unicorn.rb -E production -D の 1 行だけ表示されれば起動成功です。

アプリを停止させるときは rake unicorn:stop を実行します。

http://EC2 インスタンスの IP アドレス にアクセスして、 Web アプリが利用できることを確認しましょう。

これでアクセス負荷を分散させつつ、 Web アプリを動作させられるようになったはずです。

参考サイト

まとめ

今回は実際に外部公開する Rails アプリの本番環境を EC2 インスタンス上で構築する手順を紹介しました。

AWS に Rails のアプリをデプロイする方法の紹介は以上になりますが、 セキュリティの関係上、実際に公開するには SSL 証明書を取得して HTTPS でアクセスできるようにする必要があります。

そちらの手順についてはおいおい解説したいと思います。

それではまた、別の記事でお会いしましょう。

linkohta