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サーバー
UnicornはRack
- Rackとは、アプリケーションとWebサーバーの間のインターフェース
- 独自の仕様を持つ各Webサーバーの差異を無くす(ように見せてくれる)
以下、インストールと設定の手順。
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
のように)みんな大好きTCPやUDPを使えばいい。
※ただ、ここで気になっているのは、UNIXドメインソケットとTCP/IPではそもそも層が違うよねという点。厳密にはTCPでもソケットという概念は登場するし、単純に対比して語るのは違う気がする。何かこんがらがってる。とりあえず、Unicornではソケットでもポート番号でも通信を受ける準備ができるよ、ということを理解しておけば良さそう。
—–ソケットについてここまで—–
ともあれ、ソケットを指定してあげた理由は、(Nginxを使う場合)Unicornはネットワーク外からのアクセスを直接受け止めないから。
クライアント(ブラウザ) → Nginx →(ソケット間通信)→ Unicorn → Railsアプリ
こんな経路のイメージ。
別に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
$ 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.conf
やrails.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
インストール
以前の記事を参照。
それから、RailsでMySQLを使うためには、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>
できた。