CentOS 6.8上でApache httpd + UnicornでRailsアプリを動かす


# 前提

とある事情でCentOS6.8上でRailsアプリを動かすことになったので、その時のメモです。

セキュリティについてはあまり考慮していません。具体的にはSE LinuxはOffにし、Apache httpdも全てに対してアクセス許可をしています。

## 各種バージョン

- OSはCentOS 6.8 64bit版
- Apache httpdは`yum install httpd`で入れたバージョンである2.2です。
- Rubyは2.3.3
- Railsは4.2.8

# 手順
## OSの環境構築

`railsapp`グループを作成します。

```
$ sudo groupadd railsapp
```

既存のユーザを`railsapp`グループに属すようにします。例えば、`dev1`ユーザを`railsapp`グループに属すようにするには次のようにします。

```
$ sudo usermod -aG railsapp dev1
```

EPELのリポジトリをインストールします。

```
$ sudo yum -y install epel-release
```

Rubyのインストールに必要な各種ライブラリをインストールします。この情報は、rbenvのpluginである[ruby-buildのページ](https://github.com/rbenv/ruby-build/wiki#suggested-build-environment)に記載があります。

```
$ sudo yum install -y gcc bzip2 openssl-devel libyaml-devel libffi-devel readline-devel zlib-devel gdbm-devel ncurses-devel sqlite-devel git
```

Nodejsのインストールを行います。単にtherubyracerをインストールしたくないためです。これについては、[NodejsのWebサイト](https://nodejs.org/ja/download/package-manager/)に記載があります。

```
$ sudo su -
# curl --silent --location https://rpm.nodesource.com/setup_6.x | bash -
# yum -y install nodejs
```

Apacheのインストールを行います。

```
$ sudo yum install -y httpd
```

SE LinuxをOffにします。まずは設定ファイルである`/etc/selinux/config`を開きます。

```
$ sudo vim /etc/selinux/config
```

SELINUX=enforcingとなっている行をSELINUX=disabledに変更します。

```/etc/selinux/config

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of these two values:
#     targeted - Targeted processes are protected,
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted
```

その後、システムを再起動します。

```
$ sudo reboot
```


## Rubyのインストール

Rubyのインストールはrbenvを使います。特定のユーザではなく、システム全体で使えるようにします。

```
$ cd /usr/local
$ sudo git clone https://github.com/rbenv/rbenv.git rbenv
$ sudo chgrp -R railsapp rbenv
$ sudo chmod -R g+rwxXs rbenv
```

環境変数の設定を行います。`/etc/profile`の末尾に次の記述を追加します。

```/etc/profile
export RBENV_ROOT=/usr/local/rbenv
export PATH="$RBENV_ROOT/bin:$PATH"
eval "$(rbenv init -)"
```

書き終わったら、再読込して有効化します。

```
source /etc/profile
```

次にruby-buildをインストールします。

```
$ mkdir /usr/local/rbenv/plugins
$ cd /usr/local/rbenv/plugins
$ sudo git clone https://github.com/rbenv/ruby-build.git
$ sudo chgrp -R railsapp ruby-build
$ sudo chmod -R g+rwxs ruby-build
```

また、rbenv-default-gemsを入れます。これを入れるとRubyのインストール時に自動的に設定したgemもインストールされます。

```
$ cd /usr/local/rbenv/plugins
$ git clone https://github.com/rbenv/rbenv-default-gems.git
```

次に`/usr/local/rbenv/default-gems`というファイルを作り、1行に1つのgemを書きます。

```/usr/local/rbenv/default-gems
bundler
pry
```

Rubyをインストールします。

```
$ rbenv install 2.3.3
```

インストール後、使うRubyのバージョンをglobal設定としておきます。

```
$ rbenv global 2.3.3
```

今回Railsは4.2.8を使うので、対象バージョンのRailsをインストールします。

```
$ gem install rails -v 4.2.8 --no-doc
```

## Railsアプリの作成

動かすRailsアプリを作ります。サンプルなのでテストファイルは作らずに。`Gemfile`を編集したあとで`bundle install`するので`rails new`のタイミングでは`bundle install`はしないようにします。

```
$ rails _4.2.8_ new rails_sample -B -T --skip-turbolinks
```

Gemfileを編集します。development環境やtest環境などのgemは記していません。

```Gemfile
source 'https://rubygems.org'

gem 'rails', '4.2.8'
gem 'sqlite3'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'jquery-rails'
gem 'jbuilder', '~> 2.0'

gem 'unicorn'
```

Gemfileを編集後に`bundle install`します。

```
$ bundle install --path vendor/bundle
```

動作確認のために`rails g scaffold`で適当にアプリを作ります。

```
$ bundle exec rails g scaffold post title:string body:text
$ bundle exec rake db:migrate RAILS_ENV=production
```

`config/routes.rb`を編集してroot pathを設定します。

```config/routes.rb
Rails.application.routes.draw do
  resources :posts
  root 'posts#index'
end
```

JavaScriptやCSSをprecompileします。

```
$ bundle exec rake assets:precompile
```

環境変数`SECRET_KEY_BASE`に設定する値を生成します。

```
$ bundle exec rake secret
```

生成した値は、Apache httpdの実行ユーザの環境変数として設定します（下記のxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxの部分は`bundle exec rake secret`で生成された値を入れます）。

```/home/dev1/.bash_profile
export SECRET_KEY_BASE=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

## Unicornの設定

`config/uniorn.rb`というファイルを作成します。

```config/unicorn.rb
worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true  # 更新時ダウンタイム無し

listen 8080, :tcp_nopush => true
pid "/tmp/unicorn.pid"

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
    ActiveRecord::Base.establish_connection
end

# ログの出力
stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
```

これで次のコマンドでUnicornが起動するようになります。

```
$ bundle exec unicorn_rails -c config/unicorn.rb -E production
```

rakeコマンドを作り、Unicornの起動や終了ができるようにします。

```lib/tasks/unicorn.rake
namespace :unicorn do
  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("/tmp/unicorn.pid").to_i
    rescue Errno::ENOENT
      raise "Unicorn doesn't seem to be running"
    end
  end

end
```

これでUnicornはRailsアプリのROOTディレクトリ上で以下のコマンドを実行します。

```
$ bundle exec rake unicorn:start
```

で起動することができるようになります。

また、RailsアプリのROOTディレクトリ上で

```
$ bundle exec rake unicorn:stop
```

でUnicornの停止ができるようになります。

## Apache httpdの設定

Apache httpdの設定ファイルを記述します。管理しやすいように`/etc/httpd/conf/httpd.conf`に追記するのではなく、別ファイルに記述します。

アクセス制御はとりあえず全てに対して許可するようにします。

RailsアプリのROOTディレクトリ上は`/home/dev1/work/rails_sample`とした場合、次のような設定となります。

```/etc/httpd/conf.d/railsapp.conf
<VirtualHost *:80>
  DocumentRoot /home/dev1/work/rails_sample/public
  <Directory "/home/dev1/work/rails_sample/public">
    AllowOverride All
    Options -MultiViews
    Order deny,allow
    Allow from all
  </Directory>
  ServerName サーバ名
  ErrorLog logs/app-error_log
  CustomLog logs/app-access_log common

  <Proxy *>
    Order deny,allow
  </Proxy>

  ProxyRequests Off

  ProxyPass /assets/ !
  ProxyPass /fonts/ !
  ProxyPass /robots.txt !
  ProxyPass /favicon.ico !

  ProxyPass / http://localhost:8080/
  ProxyPassReverse / http://localhost:8080/
  ProxyPreserveHost on
</VirtualHost>
```

また、Apache httpdの実行ユーザと実行グループを変更します。

```/etc/httpd/conf/httpd.conf
## （前略）
User dev1
Group railsapp
## （後略）
```

これで後はUnicornの起動とhttpdを起動してブラウザでアクセスすればOKです。

Unicornの起動はRailsアプリのROOTディレクトリ上で以下のコマンドを実行します。

```
$ bundle exec rake unicorn:start
```

で、httpdの起動は

```
$ sudo service httpd start
```

です。