とりとめも

藻類が学んだり感じたりしたことを未来の自分のために書き留めるところ

CentOS7にRailsとUnicornとNginxをインストールして連携させる(あとMySQLも)

はじめに

Ruby on Railsをインストールして、さらにUnicornとNginxもインストールして、RailsのWebサーバーの設定をNginxに切り替えます。
それから、以前の記事でインストールしたMySQLもついでに使えるようにします。

また前提として、前回の記事で行った通り、rubyやrbenvやgitの導入までは済んでいるものとします。

バージョン

導入済

CentOS:7.3.1611(minimal ISO)
MySQL:5.7.17
ruby:2.3.0

今回入れる

Rails:5.0.1
Unicorn:5.2.0
Nginx:1.10.3
Nginxは2月25日時点の安定板です。

NginxとUnicornって何

Railsのことは割愛するとして、UnicornとかNginxとか、名前はよく聞くけどいまいち分かってなかった。
それぞれの意味と関係性をざっくり整理する。

  • NginxはWebサーバー

    • デフォルトのRailsで言えばWEBrickに相当
    • 軽量だけど高い処理能力があるらしい
    • 詳しい機能はWikipediaで(ひどい)
  • UnicornはRack

    • Rackとは、アプリケーションとWebサーバーの間のインターフェース
    • 独自の仕様を持つ各Webサーバーの差異を無くす(ように見せてくれる)
  • RailsとNginxの仲介役がUnicorn

    • 元々の目的はNginxなんだけど、デフォルトのRailsでは対応していない
    • Unicornを経由することで使えるようになる
    • ブラウザ→Nginx→unucorn→Rails
    • UnicornRubyのgemとして導入可能
    • Nginxはyumで導入

以下、インストールと設定の手順。

Ruby on Rails

インストール

$ gem install rails

これでOKです。
バージョン確認などもしておく。

$ rails -v
Rails 5.0.1

できた。

ただ、この状態でアプリを作ったりrails sすると色々エラーが出るので、以下で解消しておく。

初期設定

まず、試しにアプリを作ってみる。

$ rails new marimo_app
(略)
An error occurred while installing sqlite3 (1.3.13), and Bundler cannot
continue.
Make sure that `gem install sqlite3 -v '1.3.13'` succeeds before bundling.
$ sqlite3 -version
3.7.17 2013-05-20 00:56:22 118a3b35693b134d56ebd780123b7fd6f1497668

SQLite3のバージョンが違うっぽい?
いずれMySQLに置き換えるつもりだけど、素直に従ってSQLite3を入れることにする。

$ gem install sqlite3 -v '1.3.13'
(略 色々エラーメッセージ)
sqlite3.h is missing. Try 'brew install sqlite3',
'yum install sqlite-devel' or 'apt-get install libsqlite3-dev'
and check your shared library search path (the
location where your sqlite3 shared library is located).
(略)

sqlite-develが必要らしいので、まずはそれから入れることにする。

$ yum install sqlite-devel

$ gem install sqlite3 -v '1.3.13'

入った。
これでテストアプリが動くはずなので、サーバーを立ち上げる。

$ rails s
Could not find gem 'sass-rails (~> 5.0)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.

…bundle installしてから再実行する。

$ bundle install

$ rails s
(色々エラーメッセージ)

またエラー!
JavaScript Runtimeが無いことが原因らしい。

(こちらの記事とほとんど同じことしてる・・・)

$ vi Gemfile
(略)
gem 'therubyracer', platforms: :ruby
#↑ここのコメントアウトを外す

それから、therubyracerをbundle installするためにはg++が必要とのことなので、yumでインストールする。

$ yum install gcc-c++.x86_64
$ bundle install

これで、今度こそrailsサーバーが上がるはず。

$ rails s
(略)
* Listening on tcp://localhost:3000
Use Ctrl-C to stop

できた。

(補足)VirtualBox上のRailsアプリへのアクセス

rails sした後、自端末であれば http://localhost:3000 でアプリにアクセス可能です。

しかしVirtualBox上のゲストOSの場合、たとえば http://<ゲストのIP>:3000 としてもアクセスできませんでした。

こちらの記事によれば、デフォルト設定のWEBRickではlocalhostからのアクセスしか受け付けないためらしいです。
上記記事の通り、bindingでIPを指定してあげれば解消可能です。
また、その前にfirewalldを無効にしてあげる必要があります。

$ systemctl stop firewalld
$ rails s -b 0.0.0.0

これで、ホストOSのブラウザから「http://<ゲストOSのIP>:3000」でアクセス可能になります。

ほかにも、ポートフォワーディングでホストOSのlocalhost:3000をゲストOSのlocalhost:3000に繋ぐ方法もあると思います。
ただ、そちらは少し試したものの上手くいきませんでした・・・。何が悪いんだろう。
ともあれ、前述の方法で動くには動くので、ひとまず良いことにしておきます。

ようやくRailsを動かせたので、以下ではUnicornとNginxの導入&設定に移ります。

Unicorn

インストール

RailsアプリのGemfileに以下を追記。

gem 'unicorn', '5.2.0'

バージョンは作業時点で最新のもの(gem search unicornで確認)を指定しています。

あとはbundle installを実行。

初期設定

RAILS_ROOT/config/unicorn.rbを作成する。(ファイルの場所は自由)
内容は、こちらの記事に倣った。各行にコメントがついていてわかりやすかったです。
ここではコメントを外して私が設定した内容だけ記載する。

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

ENV['BUNDLE_GEMFILE'] = rails_root + "/Gemfile"

worker_processes 2

working_directory rails_root

timeout 30

stderr_path File.expand_path('../../log/unicorn_stderr.log', __FILE__)
stdout_path File.expand_path('../../log/unicorn_stdout.log', __FILE__)

###注
# Nginxで使用する場合は以下の設定を行う。
# listen File.expand_path('/var/run/unicorn/unicorn.sock', __FILE__)
# Unicorn単体で使う場合は以下。
listen 8080

pid File.expand_path('../../tmp/pids/unicorn.pid', __FILE__)


preload_app true

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

  old_pid = "#{server.config[:pid]}.oldbin"
  if old_pid != server.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
    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

上記のファイルを作成後、次のコマンドでUnicornを起動。
実行前にRAILS_ROOTのディレクトリに移動しておくことを忘れない。

$ bundle exec unicorn_rails -c config/unicorn.rb -D -E development

これで、ホストOSのブラウザからhttp://<ゲストOSのIP>:8080でアプリにアクセス可能になる。
このとき指定するポートは、設定ファイル内の「listen 8080」で指定した値。
前述の設定ファイルで「###注」と書いてある部分。 まだUnicornの設定の詳細はよくわかっていないけど、この箇所については調べたので後述する。

Unicornを停止する際は次のコマンド。

$ kill -QUIT `cat tmp/pids/unicorn.pid`

ここで指定しているファイルも、設定ファイル内の pid File.expand_path('../../tmp/pids/unicorn.pid', __FILE__) で設定しているもの。

(補足)Unicornの受付ポートについて

前述の設定ファイルのうち、下記の部分について調べたことを整理する。

###注
# Nginxで使用する場合は以下の設定を行う。
# listen File.expand_path('/var/run/unicorn/unicorn.sock', __FILE__)
#Unicorn単体で使う場合は以下
listen 8080

まず、listenで定義しているのは、Unicornが何によって通信を受け取るか。
つまり、listen 8080の場合、OSの8080番ポートに届いた通信をUnicornは受け止めることになる。

クライアント(ブラウザ) → 8080番ポート(Unicorn) → Railsアプリ

こんな経路でクライアントはRailsアプリにたどり着く。

対して、Nginxで使用する場合の次の行はどんな意味なのか。

# Nginxで使用する場合は以下の設定を行う。
# listen File.expand_path('../../tmp/sockets/unicorn.sock', __FILE__)

これは、指定したソケットunicorn.sockに届いた通信をUnicornで受け取ることを意味する。

ここで、ソケットについてもよくわかってないので調べた。

—–ソケットについて—– ソケットはOSI参照モデルにおけるセッション層に位置するインターフェースで、今回のUnicornやらNginxやらが絡む文脈で「ソケット」といえば「UNIXドメインソケット」を指すらしい。
UNIXドメインソケットは、通信対象の特定方法(TCPにおけるIPアドレス+ポート番号)としてファイルシステムを用いている。つまり、上の例で言えば「unicorn.sock」がそれ。
そして、こうしたソケットが用いられるのは、同一マシン上でのプロセス間で通信を行う場合が該当する。
ネットワークを介してマシン間での通信を行う場合は、(listen 8080のように)みんな大好きTCPUDPを使えばいい。
※ただ、ここで気になっているのは、UNIXドメインソケットとTCP/IPではそもそも層が違うよねという点。厳密にはTCPでもソケットという概念は登場するし、単純に対比して語るのは違う気がする。何かこんがらがってる。とりあえず、Unicornではソケットでもポート番号でも通信を受ける準備ができるよ、ということを理解しておけば良さそう。
—–ソケットについてここまで—–

ともあれ、ソケットを指定してあげた理由は、(Nginxを使う場合)Unicornはネットワーク外からのアクセスを直接受け止めないから。

クライアント(ブラウザ) → Nginx →(ソケット間通信)→ UnicornRailsアプリ

こんな経路のイメージ。
別にUnicorn単体のときと同じようにTCPで指定してあげてもいいけど、マシン内で通信が完結するときはソケットを使ってあげた方が早いらしい。
続いてNginxを導入した後は、この箇所の設定を変えてあげることを忘れないようにする。

というわけで、以下ではNginxの導入と設定について。

Nginx

インストール

Nginxはyumリポジトリには登録されていないらしい。
ので、まずはNginxのリポジトリをインストールする。

$ yum install http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

続いて、このリポジトリからNginxをインストールする。

$ yum install --enablerepo=nginx nginx

バージョン確認する。

$ nginx -v
nginx version: nginx/1.10.3

できた。

初期設定

デフォルトの設定ファイル

Nginxの設定ファイルは/etc/nginx/conf.dに格納されている。
デフォルトのファイル名はdefault.confで、これを編集して設定してもよいけど、今回の設定では別のファイル(適当にrails.confなど)を作成することにする。
その前に、これから作るファイルと設定が干渉し合わないように、default.confを編集しておく。

server {
    #listen       80;
    #server_name  localhost;
(以下略)

受付ポートはこれから作るファイルで定義するのでコメントアウト

設定ファイルを作る

デフォルトの設定ファイルを編集した次は、Railsアプリ用の設定ファイルを作成する。

$ vi /etc/nginx/conf.d/rails.conf

内容は以下。
<>で囲っている箇所は、環境に合わせて設定する。

upstream unicorn {
    server unix:<unicornで指定したunicorn.sock>;
}

server {
    listen 80;
    server_name <OSのIP>;

    root <railsアプリのROOTディレクトリ>/public;

    #後述のnginx.confでも設定されているため、ここでは設定しないことにする。
    #access_log <nginxのログ用ディレクトリ>/<アクセスログファイル名>;  
    #error_log <nginxのログ用ディレクトリ>/<エラーログファイル名>;

    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;
    }
}

上記のlistenで指定しているポート番号で、Nginxは通信を受け取ることが出来るようになる。
また、upstream unicorn{}のなかでは、unicornと通信するためのソケットファイルを指定している。
ソケットについては前述の通り。unicorn側の設定ファイルRAILS_ROOT/config/unicorn.rbでも、該当箇所のコメントアウトを以下の通り修正しておく。

###注
# Nginxで使用する場合は以下の設定を行う。
listen File.expand_path('/var/run/unicorn/unicorn.sock', __FILE__)
# Unicorn単体で使う場合は以下。
# listen 8080

さらに、ここで指定したソケットファイルはunicornの起動と同時に生成されるけど、格納ディレクトリが存在しないとエラーになる。
ので、作成する。

$ mkdir /var/run/unicorn/unicorn.socket
$ chmod 755 /var/run/unicorn/unicorn.socket

また、通信を許可するためにSELinuxを無効化する。

$ setenforce 1

(補足)ソケットファイル配置場所に関するあれこれ

ちなみに、前述のソケットファイルの配置場所は、当初は<RAILSアプリのROOT>/tmp/socketディレクトリにしていた。
しかしこれだと何度やっても502 Bad Gatewayのエラーとなった。
nginxのエラーログによればパーミッションエラーらしく、ソケットを読み込むことができていなかった(unicornまでたどり着くことができなかった。)
その後、上述の通りソケットの配置場所を変更したところうまくいった。
でも、変更前後でファイルのパーミッションはともに変わっていないので、何だか変な感じ。
ググってみると、/tmp以下にソケットファイルを配置するとパーミッションエラーになるという記事が複数ヒットした。Railsアプリのtmpディレクトリも同様なのだろうか。
また、<RAILSアプリのROOT>/tmp/socketに配置する場合でも、unicorn起動時に生成する.socketファイルのオーナーをnginxユーザーにできたりすれば上手くいく気がしているけれど、その方法がわからなかった。
というわけで、今回のソケットの配置場所は先ほどの通りに設定している。まあ、ソケットの場所に(今の所)こだわりは無いのだけれど、エラーの原因が推測止まりになってしまったのはもやもや。

ともあれ、これでNginxの初期設定は済んだので、Nginxを起動する。

Nginxの起動と動作確認

Nginx起動の前に、unicornを再起動して、ソケットファイルで通信を受け取るようにする。

$ bundle exec unicorn_rails -c config/unicorn.rb -D -E development
$ kill -QUIT `cat tmp/pids/unicorn.pid`

それから、Nginxを起動する。Nginxの起動や停止はsystemctlで操作できる。

$ systemctl start nginx

また、Nginx起動時にエラーが出てしまう場合は、以下のコマンドで.confファイルの構文エラー等をある程度見つけてくれる。

$ nginx -t

前述のソケットファイル配置場所に関するエラーは検出されなかったので、このコマンドを通っても安心はできないけれど、Nginx単体での起動に関する誤りは見つけてくれると思う。

(補足)nginx.confについて

上記で編集したdefault.confrails.conf以外に、Nginxの設定ファイルには/etc/nginx/nginx.confというものが存在する。
大雑把なイメージとしては、nginx.confは「Nginx全体の設定を決める」もので、/etc/nginx/conf.d/○○.confは「個別のWEBサーバーの設定を決める」もの。
デフォルトのnginx.confを見てみると、なんとなく分かるはず。

$ cat /etc/nginx/nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

最後の記述されている以下の行

include /etc/nginx/conf.d/*.conf;

で、先ほど作ったrails.confのような設定ファイルを読み込んでいるのがわかる。
つまり、rails.confのようなファイルは複数存在していてもよく、その場合は、listen 80のような箇所を変えてあげることで、複数のWEBサーバーを起動できるみたい。
今回はログファイルの位置はデフォルトのnginx.confで定義したけれど、nginx.conf側の記述を削除して、rails.confで定義してもよかった。
言い方を変えると、今回のように一つだけWEBサーバーを上げたい場合には、rails.confに設定を分けなくても、nginx.confにlisten等を追記してあげればNginxは使える模様。

MySQL

インストール

以前の記事を参照。

それから、RailsMySQLを使うためには、MySQLの開発用パッケージであるmysql-develが必要。(入れずに後述の手順を行なっていたらエラーになった)
なのでまずはこれを入れる。

$ yum install mysql-devel

RailsアプリでMySQLを使う

新しく作るアプリのDBをMySQLにするのと、デフォルトのSQLiteが設定済みのアプリのDBをMySQLにするのとでは、当然ながら手順が違う。
以下ではそれぞれの場合で試してみる。

新規作成アプリでMySQLを使う場合

$ rails new <アプリ名> -d mysql

これで、Gemfileにsqlite3の代わりにmysql用の行が記述される。
あと、Gemfileからtherubyracerのコメントアウトを外すのも忘れない。

まだ設定は完了していないけど、SQLite3から切り替える場合について書く。

SQLite3から切り替える場合

やることは大きく2つ。

①Gemfileを書き換える

新規作成のときの手順で作成されたGemfileと同じように書く。
具体的には以下。

###↓↓これを追記↓↓
# Use mysql as the database for Active Record
gem 'mysql2', '>= 0.3.18', '< 0.5'
###↑↑追記ここまで↑↑
###↓これをコメントアウト
gem 'sqlite3'

忘れずにBundle Installする。

②config/database.ymlを編集する

Railsアプリのdatabase.ymlを編集する。
内容に関しては、一度rails new <アプリ名> -d mysqlして、出来上がったdatabase.ymlをコピーするのが楽そう。

MySQL連携用の初期設定

上記の「新規作成パターン」「SQLite3から切り替えるパターン」に共通の初期設定は以下の通り。

database.ymlの編集

とりあえずdevelopment環境とtest環境用の箇所だけ編集する。

development:
  <<: *default
  username: <MySQLのユーザー>
  password: <パスワード>
  host: <OSのIP>
  database: <データベース名>
development:
  <<: *default
  username: <MySQLのユーザー>
  password: <パスワード>
  host: <OSのIP>
  database: <データベース名>

ユーザー名やパスワードは、次に行うユーザー作成と同じものを入力する。

MySQLのユーザー作成・権限設定

MySQLのユーザーを作ったりする。

mysql> grant all on *.* TO 'MySQLのユーザー'@'%' identified by '<パスワード>' with grant option;

grantは権限設定のコマンドだけど、対象ユーザーが存在しない場合は一緒に作成してくれる。
grant all on の対象DBは細かく指定してもいいと思う。

DB作成
$ rails db:create rails_env=development
Created database <dev環境用DB>
Created database <test環境用DB>

できた。

参考リンク

https://hack-le.com/nginx/

http://www.kakiro-web.com/memo/mysql-create-user.html