プログラミング

AmazonLinux2でRailsアプリケーションをNginxとUnicornで動作させる

Railsアプリケーションローカルで作成してその後、サーバーへデプロイしたい!って時ありますよね。

今回、AWSのAmazonLinux2でRailsのアプリケーションをNginxとUnicornで動作させるところまで設定したので、以下でその時に行ったことを書き残します。

 

動作環境は以下になります。(少しrubyとrailsのバージョンが古いですが)

$ cat /etc/system-release
Amazon Linux release 2 (Karoo)

# RDS
MySQL Community 8.0.16

$ ruby -v
ruby 2.6.0p0 (2018-12-25 revision 66547) [x86_64-linux]

$ rails -v
Rails 5.1.7

 

AmazonLinux2の設定

EC2のマネジメントコンソールから、

AmazonLinuxを選択してポチポチとクリックしながら作っていきます。

 

こちらは、詳細はAWSのドキュメントや先人たちの参考にできるブログなどがあるので割愛します。

 

作成後、Railsを動作させるために以下でまとめてパッケージをインストール。

# yum update
sudo yum update -y

# gitのインストール
sudo yum install -y git

# tools for ruby on rails app dependency
sudo yum install -y gcc gcc-c++ openssl-devel readline-devel zlib-devel sqlite-devel postgresql-develop mysql-devel nodejs npm

 

gitは2020年2月現在では、2.23.1がインストールされます。

$ git --version
git version 2.23.1

 

次にRailsを動作させるためにRubyの環境を構築します。

以下をコピペして適宜、名前をつけてシェルスクリプトとして〇〇.shなどと名前をつけて保存して実行してください。

#!/bin/bash

# 環境変数設定
# ------------------------------------------------------
# Rubyのバージョンを実行時に指定
ruby_version="$RUBY_VER"
# Railsのバージョンを実行時に指定
rails_version="$RAILS_VER"
# ------------------------------------------------------

# .bash_profileが既に存在する場合は、コピーして置き換える
cp ~/.bash_profile ~/.bash_profile.org

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

#cloneできたらrbenvのPATHを通す
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile

#rbenvが使えるか確認
rbenv --version

#rbenvを使用してRubyをインストール
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

#インストールできるRubyのバージョンを確認する
rbenv install --list

#rubyのバージョンを指定してインストール(例:2.6.5)
rbenv install "${ruby_version}"

#インストールできたら、Rubyを 指定したversion に切り替える
rbenv global "${ruby_version}"
rbenv rehash

#バージョンを確認する
ruby -v

# ------------------------------------------------------
#bundlerのインストール
gem install bundler
rbenv rehash

# ------------------------------------------------------
# Railsのインストール

#バージョン指定してインストール(例:5.2.4)
gem install -v "${rails_version}" rails

#Railsがインストールできたか確認
rails -v

 

以下のように実行します。

RUBY_VER=2.6.0 RAILS_VER=5.1.7 sh [適宜の名前].sh

Rubyのインストールは結構時間かかります。気長に待ちましょう。

Railsアプリのgit clone

作成したRialsアプリをgithubなどのリポジトリで管理している場合は、git cloneして本番環境へ設置します。

 

僕は、/home/ec2-user/workspaceというディレクトリを作成して、

その配下にgit cloneしました。

 

sshの設定が必要になってきますが、sshの設定については

また今度書いてみたいと思います。

 

git cloneしてきたら、bundle install を実行します。

cd /home/ec2-user/workspace/[railsのアプリのルート]

bundle install

 

gemが無事にインストールできたら、RDSの作成に移ります。

 

RDSの作成

今回、AWS環境でRailsを動作させるので、データベースはRDSを使います。

 

RDSの作成については、割愛しますが、注意点として、

RDSインスタンスへ割り当てるセキュリティグループのインバウンドのルールに

  • Postgresqlを使用するなら、5432番の通信を許可する設定
  • MySQLを使用するなら、3306番の通信を許可する設定

を追加してください。

 

RDSインスタンスをそのままマネジメントコンソールでポチポチと作成していくと、セキュリティグループはデフォルトのものが設定されている状態になっているので、EC2からRDSのデータベースへ接続する際にTCPでの通信に失敗します。

 

RDSを作成したら、接続ができるかどうかを確認します。

mysql -u [RDSを作成する際に設定したユーザ] -p -h [RDSのエンドポイント名]

 

今回は、本番環境のデータベースとして、MySQLを使用します。

なので、RailsのGemfileのproductionの箇所に以下を追記してbundle installします。

group :production do
  gem 'mysql2'
end

Railsのconfig/database.ymlを変更する

本番環境で動作させることを想定して、database.ymlのproductionの箇所を編集します。

production:
  <<: *default
  adapter: mysql2
  database: [データベース名]
  encodign: utf8
  reconect: false
  username: [RDSを作成した際のユーザ名]
  password: [RDSを作成した際のパスワード]
  socket: /var/lib/mysql/mysql.sock
  host: [RDSのエンドポイント名]

 

RailsのコマンドでDBを作成する

Railsアプリのルートで以下を実行します。

bundle exec rails db:create RAILS_ENV=production

 

これでデータベースが作成されます。

 

データベースを作成できたら、マイグレーションを実行します。

bundle exec rails db:migrate RAILS_ENV=production

 

seacret_key_baseの設定

ここで、rails severを起動したいところですが、Railsはproduction環境で動作させようとすると、config/secrets.ymlで設定してある、

production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>

この部分でseacret_key_baseの値を環境変数を見に行っているので、何も設定していなければ、ブラウザからアクセスしても、

An unhandled lowlevel error occurred. The application logs may have details.

と表示されて、railsで作成したアプリケーションのトップページが表示されません。

 

なので、以下のコマンドでproduction環境で動作させるためのseacret_key_baseの値を生成します。

bundle exec rails secret

実行するとハッシュ値らしき値が生成されるので、その値を環境変数へ設定します。

export SECRET_KEY_BASE=[先程生成された値]

 

Railsのアセットパイプラインを構築し直す

以下のコマンドを入力して、Railsのアセットパイプラインをproduction環境で構築します。

rake assets:precompile RAILS_ENV=production

 

こちらを実行していないと、サーバーを立ち上げたRailsアプリへアクセスしても、

We're sorry, but something went wrong.
If you are the application owner check the logs for more information.

などと表示されてしまいます。

一旦Railsのサーバーを立ち上げてアプリのトップページが表示されるか確認しておく

ここまで設定を終えていれば、Railsのアプリケーションサーバーを立ち上げて作成したRailsのアプリケーションが意図した通りに表示されるかを確認しておく。

 

以下のコマンドでRailsのアプリケーションサーバーを起動。

bundle exec rails s -e production

 

http://EC2インスタンスのIPアドレス:3000

でアクセスすれば無事にRailsアプリのトップページが表示されるはず。

nginxとunicornでRailsを動作させる

unicornの設定

Railsをデフォルトのアプリケーションサーバーを起動してアクセスさせることができたら、次にNginxとUnicornで動作させる設定を行う。

Gemfileに以下の追記をします。追記したら、bundle installを実行。

group :production do
  gem 'mysql2'
  gem 'unicorn', '5.4.1'←追記
end

 

config/unicorn.rb として、unicornの設定を記述します。

upstream unicorn {
  server unix:/home/ec2-user/workspace/[railsアプリケーションのルート]/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name [EC2インスタンスのIPアドレス];

  access_log /var/log/nginx/sample_access.log;
  error_log /var/log/nginx/sample_error.log;

  root /home/ec2-user/workspace/[railsアプリケーションのルート]/public;

  client_max_body_size 100m;
  error_page 404 /404.html;
  error_page 500 502 503 504 /500.html;
  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    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://unicorn;
  }
}

 

上記を記述すれば、unicornを、「bundle exec unicorn_rails -c config/unicorn.rb -E production -D」として、実行すれば起動することができますが、いちいちこのコマンドを入力して起動させるのも面倒なので、rakeタスクを作成します。

bundle exec rails generate task unicorn

lib/tasks/unicorn.rakeが作成されるので、以下の内容に編集。

namespace :unicorn do
  ##
  # Tasks
  ##
  desc "Start unicorn for production env."
  task(:start) {
    config = Rails.root.join('config', 'unicorn.rb')
    sh "bundle exec unicorn_rails -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

  def unicorn_signal signal
    Process.kill signal, unicorn_pid
  end

  def unicorn_pid
    begin
      File.read("/home/ec2-user/workspace/[railsのルート]/tmp/pids/unicorn.pid").to_i
    rescue Errno::ENOENT
      raise "Unicorn doesn't seem to be running"
    end
  end

end

 

Unicornを起動したければ、

bundle exec rake unicorn:start

停止したければ、

bundle exec rake unicorn:stop

を実行できるようになります。

nginxの設定

AmazonLinux2では、デフォルトのyumパッケージでは、Nginxは配布されていないので、amazon-linux-extras からインストールする。

sudo amazon-linux-extras install nginx1

 

/etc/nginx/conf.d/以下に、unicorn独自の設定を作っていく。

sudo vim /etc/nginx/conf.d/[railsのアプリ名].conf

以下のように書いて保存

upstream unicorn {
  server unix:/home/ec2-user/workspace/[railsアプリのルート]/tmp/sockets/unicorn.sock;
}

server {
  listen 80;
  server_name [EC2インスタンスのIPアドレス];

  access_log /var/log/nginx/sample_access.log;
  error_log /var/log/nginx/sample_error.log;

  root /home/ec2-user/workspace/[railsアプリのルート]/public;

  client_max_body_size 100m;
  error_page 404 /404.html;
  error_page 500 502 503 504 /500.html;
  try_files $uri/index.html $uri @unicorn;

  location @unicorn {
    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://unicorn;
  }
}

 

nginxの設定で文法などのエラーがないかを確認しておく。

sudo nginx -t

OKなどと返ってくればちゃんと設定できています。

unicornとnginxを起動させてRailsアプリへアクセス

 

以下のコマンドを実行し、unicornとnginxを動作させる。

bundle exec rake unicorn:start
sudo systemctl start nginx

 

http://EC2のIPアドレスへアクセスして、動作確認。

 

「502 bat gateway」と表示される場合は、 /var/log/nginx/sample_error.logの中身を確認してみる。

 

「13: Permission denied」などと表示されている場合は、nginxのプロセスを実行しているユーザーとunicornを実行しているユーザーが異なるため、unicorn.sockへのアクセス権限でファイルへのアクセスが拒否されている可能性がある。

 

対応策としては、

  • ファイルへのアクセス権を設定してworkerプロセス起動ユーザーがsockファイルへアクセスできるように設定する
  • nginxのworkerプロセス起動ユーザーをsockファイルへのアクセス権があるユーザーへ変更する

という手段が考えられるが、

今回は、2つ目の対応を行う。

nginxのworkerを動作させているユーザーは以下のコマンドで確認できる。

ps aux | grep nginx
root      3879  0.0  0.2 121484  2216 ?        Ss   16:01   0:00 nginx: master process /usr/sbin/nginx
nginx     3881  0.0  0.4 121968  4904 ?        S    16:01   0:00 nginx: worker process ←これがworkerプロセス。nginxユーザーが動作させていることがわかる
ec2-user  3914  0.0  0.0 119436   920 pts/0    S+   16:08   0:00 grep --color=auto nginx

 

unicornについては、ec2-userで起動させているので、

nginxのプロセスを実行するユーザーをec2-userへ変更する。

sudo vim /etv/nginx/nginx.conf

以下を編集する。

# For more information on configuration, see:
#   * Official English Documentation: http://nginx.org/en/docs/
#   * Official Russian Documentation: http://nginx.org/ru/docs/

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

↓以下に変更。

#user nginx; ←コメントアウトして、
user ec2-user; ←追記
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

そして、nginxを再起動。

sudo systemctl restart nginx

 

これで、http://EC2のIPアドレス

へアクセスすると、Railsアプリのトップページが表示されるはず。

 

僕自身は、この設定でめっちゃドハマリました。

nginxでエラーが出て調査するにもログが吐き出されていなかったり、

unicorn個別の設定を書いている時に、80番ポートでの設定に、default_severの設定を書いていて、重複しているとnginx -tで検証した時に怒られたりしました。

 

今回は、「とにかくサーバー上で、動作させる」という点を重視して設定したので、

セキュリティの設定については、もっと強固なやり方があるはずです。

どこまでやるかは、各プロジェクトの方針に従ったほうがいいと思います。

 

Rails勉強中の誰かの参考になれば幸いです。

ABOUT ME
moto
約5年間勤めた公務員から転職しました。 日頃の学びをここにアウトプットしていきます。 御朱印集めと筋トレが好きです。