Capistrano3でUnicorn+Nginxな環境にRailsをデプロイする:初心者向け


この手の記事は、もう散々出尽くしている感が満載ですが、そんな記事を見ても1発で完了できず、微妙に考え方の違う複数ある記事をつまみ食いして死亡という、スキル不足な初心者にありがちなパターンを王道でいってしまったので、初心者が初心者に送る一つの道標になればと。

# 前提

- Capistrano v3.2.1
- Rails v4.1
- rbenv v0.4.0
- Ruby v2.1.2
- Unicorn v4.8.3
- Nginx v1.4.7
- Git
- GitHub

こんな感じで動作確認済み。

Webサーバ（nginx）とAppサーバ（unicorn）は同一のステージングサーバにデプロイするのを想定した構築メモです。GitでRailsアプリをバージョン管理し、ローカルからステージングサーバへCapistranoでGitリポジトリ経由でデプロイします。

## 設定

### GitHub

GitHubからデプロイするためにDeploy keysを設定する必要があります。対象リポジトリページの「Settings」→「Deploy keys」に移動し、デプロイ対象サーバの公開鍵を登録します。

### Gemライブラリ

デプロイに必要な部分のみ記載しておきます。適宜、アプリケーションに必要なものを追記してください。

```ruby:Gemfile
gem 'dotenv-rails' #環境変数を簡単に扱うのに使用

group :development, :test do
  gem 'capistrano', '~> 3.2.1'
  gem 'capistrano-rails'
  gem 'capistrano-rbenv'
  gem 'capistrano-bundler'
  gem 'capistrano3-unicorn' #Unicornの起動を簡単に行える
end

group :production, :staging do
  gem 'unicorn'
end
```

```command
$ bundle install
```

人によっては、`dotenv-rails`や`capistrano3-unicorn`は使わず、自前で行うかもしれないですが、これを使ったほうが楽だったので。

### デプロイ設定

Capistranoは初期設定コマンドがあるので、それを使ってもいいんだけど、使わなくてもいいです。単に初期テンプレートファイルを作ってくれるだけなので。

```
$ bundle exec cap install
```

```ruby:/Capfile
require 'capistrano/setup'
require 'capistrano/deploy'

# rbenvを使用している場合
require 'capistrano/rbenv'

# デプロイ先のサーバで、ユーザディレクトリでrbenvをインストールしている場合
set :rbenv_type, :user
set :rbenv_ruby, '2.1.2'

require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'

require 'capistrano3/unicorn'

# Rails4から分離したsecrets.ymlの環境変数を .envファイルで管理する
set :linked_files, %w{config/secrets.yml .env}

# タスクを読み込むけど、今回は特に使わない
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
```

```ruby:/config/deploy.rb
lock '3.2.1'

set :application, 'myapp'
set :repo_url, 'GitのリポジトリURL'
set :branch, 'master'
set :deploy_to, '/var/www/myapp'
set :scm, :git
set :log_level, :debug
set :pty, true
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets bundle public/system public/assets}
set :default_env, { path: "/usr/local/rbenv/shims:/usr/local/rbenv/bin:$PATH" }
set :keep_releases, 5

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do

  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart'
  end
end
```

前述したsecret_key_baseを環境変数で設定するため、`staging`の項目を追加するのみでOK。

```ruby:/config/secrets.yml
development:
  secret_key_base: bbbb # 適当

test:
  secret_key_base: aaaa # 適当

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

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

Railsアプリの稼働ディレクトリを仮に`/var/www/myapp`と設定。

```ruby:/config/unicorn/staging.rb
@app_path = '/var/www/myapp'
working_directory @app_path + "/current"

worker_processes 2
preload_app true
timeout 30
listen "/tmp/unicorn.sock", :backlog => 64
pid "/var/www/myapp/shared/tmp/pids/unicorn.pid"

stderr_path "#{@app_path}/log/unicorn.stderr.log"
stdout_path "#{@app_path}/log/unicorn.stdout.log"

before_fork do |server, worker|
  ENV['BUNDLE_GEMFILE'] = File.expand_path('Gemfile', ENV['RAILS_ROOT'])
end

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

  old_pid = "#{server.config[:pid]}.oldbin"
  if File.exists?(old_pid) && server.pid != old_pid
    begin
      Process.kill("QUIT", File.read(old_pid).to_i)
    rescue Errno::ENOENT, Errno::ESRCH
    end
  end
end

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

`/config/environments/staging.rb`は、特に指定がなければ`production.rb`をそのままコピー。

```ruby:/config/environments/staging.rb
Rails.application.configure do
  config.cache_classes = true
  config.eager_load = true
  config.consider_all_requests_local       = false
  config.action_controller.perform_caching = true
  config.serve_static_assets = false
  config.assets.js_compressor = :uglifier
  config.assets.compile = false
  config.assets.digest = true
  config.log_level = :info
  config.i18n.fallbacks = true
  config.active_support.deprecation = :notify
  config.log_formatter = ::Logger::Formatter.new
  config.active_record.dump_schema_after_migration = false
end
```

デプロイ先のサーバ名とSSHのログインIDを指定。デプロイ元から、秘密鍵でデプロイ先サーバにアクセス可能なのが前提条件です。`ssh_options`で鍵などの細かい指定は可能ですが、そのままでいけるなら、設定不要。

```ruby:/config/deploy/staging.rb
set :rails_env, "staging"
set :unicorn_rack_env, "staging"

role :app, %w{user@example.com}
role :web, %w{user@example.com}
role :db,  %w{user@example.com}

server 'example.com', user: 'user', roles: %w{web app}

set :ssh_options, {
  keys: %w(/home/vagrant/.ssh/id_rsa),
  forward_agent: false,
  auth_methods: %w(publickey)
}
```

## ステージングサーバの環境構築

ステージングサーバ自体がRailsが稼働できるようにしておく必要があります。今どきならChefとかを使ってローカルでもステージングサーバでも同一の環境になっていたりすると思いますが。

### 環境変数の設定

ただ一つだけ、環境変数の管理を直接サーバで行っておきます。

```bash:/var/www/myapp/shared/.env
SECRET_KEY_BASE="hogehoge"
```

```bash:/var/www/myapp/shared/config/secrets.yml
staging:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
```

dotenvというGemライブラリを使っているのだけど、これで使い方が正しいのかはちょっと微妙…。

### nginxの設定

nginxは単純にUnicornの前に立ってるだけなのでシンプル。

```/etc/nginx/conf.d/hoge.conf
upstream unicorn_server {
    server unix:/tmp/unicorn.sock
    fail_timeout=0;
}

server {
    listen 80;
    client_max_body_size 4G;
    server_name _;

    keepalive_timeout 5;

    # Location of our static files
    root /var/www/myapp/public;

    location ~ ^/assets/ {
        root /var/www/myapp/shared/public;
    }

    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;

        if (!-f $request_filename) {
            proxy_pass http://unicorn_server;
            break;
        }
    }

    error_page 500 502 503 504 /500.html;
    location = /500.html {
        root /var/www/myapp/public;
    }
}
```

## デプロイ実行

デプロイ自体は非常に簡単なコマンドです。

```command
$ bundle exec cap staging deploy
```

実行前に、簡単なチェックも可能。

```command
$ bundle exec cap staging deploy:check
```

これで、上手く行けばデプロイ先のステージングサーバにUnicornのプロセスが起動しているはずです。`ps`コマンドで確認してみてください。

### Unicornの停止と再起動とか

#### 起動

```
$ bundle exec unicorn -c /var/www/myapp/current/config/unicorn/staging.rb -E deployment -D
```

#### 停止

```
$ kill -QUIT `cat /var/www/myapp/shared/tmp/pids/unicorn.pid`
```

#### 再起動

```
$ kill -HUP `cat /var/www/myapp/shared/tmp/pids/unicorn.pid`
```