Capistrano で Rails アプリケーションの自動デプロイ

# Capistrano とは
Ruby 製の自動デプロイ (& サーバー操作) ツール。

複雑な Rails 製アプリケーションのデプロイ作業を
コマンド数行でさくっと片付けられるようになる。

Capistrano 自体の説明は以下の資料によくまとまっていた。

* [入門 Capistrano 3 ~ 全ての手作業を生まれる前に消し去りたい](http://labs.gree.jp/blog/2013/12/10084/) 

本記事では Vagrant を利用して、 Rails 4.2 系 + Unicorn + Nginx 環境で
Capistrano を利用してデプロイ作業をさくっとデプロイできるようになるまでの流れを
(主に自分用に) まとめた。

# 前提条件
* 用意する開発・本番環境
 * development (開発用と同一サーバー), production (開発用と別サーバー)
* アプリケーションサーバー
  * Unicorn
* Ruby のバージョン管理
  * RVM
*  Webサーバー
  * Nginx
* 使用するデータベース
  * MySQL
* git のリポジトリ管理
  * GitHub

# 環境構築
下準備は [Chef-Solo + Berkshelf で Rails 4 の開発環境を構築](http://qiita.com/Salinger/items/267f7ac4720f44eb6bfe) を参考に。
2台分 (開発用・本番用) をVagrant で用意しておく。
プライベートIPは他の環境と別のものを割り当てる。

今回は
* 開発用サーバー (dev_server): 192.168.33.20
* 本番用サーバー (pro_server): 192.168.33.21
を割り当てた。

以下 `Vagrantfile` の例

```ruby:Vagrantfile
# -*- mode: ruby -*-
VAGRANTFILE_API_VERSION = "2"

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  # 全マシンで共通の設定
  config.vm.box = "centos6.5"
  config.vm.box_url = "https://github.com/2creatives/vagrant-centos/releases/download/v6.5.3/centos65-x86_64-20140116.box"
  config.vm.provider "virtualbox" do |vb|
    vb.customize ["modifyvm", :id, "--memory", "2048"]
  end

  # development サーバーの設定
  config.vm.define :dev_server do |dev_server|
    dev_server.vm.network "private_network", ip: "192.168.33.20"
  end

  # production サーバーの設定
  config.vm.define :pro_server do |pro_server|
    pro_server.vm.network "private_network", ip: "192.168.33.21"
  end
end
```

仮想環境を2台分作成した後、[Chef-Solo + Berkshelf で Rails 4 の開発環境を構築](http://qiita.com/Salinger/items/267f7ac4720f44eb6bfe) の
手順に従い、2台それぞれに knife-solo コマンドを適用し必要なアプリケーションをインストールする。

また、本番環境に開発用環境から、また開発環境からそのマシン自身に
SSHでログイン出来るように設定する。

まずは vagrant の鍵ファイルを本番環境に転送しておく。次のコマンドをホストのマシンで実行。

```
$ scp -i ~/.vagrant.d/insecure_private_key ~/.vagrant.d/insecure_private_key vagrant@192.168.33.20:/home/vagrant/.ssh/
```

次に開発用環境に`vagrant ssh dev_server で`ログインし、`~/.ssh/config` を次のように設定する。
`~/.ssh/config` が無ければ新規作成。

```
Host 192.168.33.20
  HostName 192.168.33.20
  IdentityFile ~/.ssh/insecure_private_key
  User vagrant

Host localhost
  HostName localhost
  IdentityFile ~/.ssh/insecure_private_key
  User vagrant

Host 192.168.33.21
  HostName 192.168.33.21
  IdentityFile ~/.ssh/insecure_private_key
```

その後、`~/.ssh/config` の権限を適切な状態になるように設定する。

```
$ chmod 600 ~/.ssh/config
```

# Nginx の設定
開発用、本番用のどちらも同じ作業を行う。

```
$ sudo mkdir -p /var/www/nginx/capistrano_sample
$ sudo chown vagrant /var/www/nginx/capistrano_sample
$ sudo cp /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/local.conf
$ sudo mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bk
$ sudo emacs /etc/nginx/conf.d/local.conf
```

設定ファイルを下記のように書き換える。

```ruby:/etc/nginx/conf.d/local.conf
upstream unicorn {
  server unix:/var/www/nginx/capistrano_sample/shared/tmp/sockets/unicorn.sock;
}

server {
  listen 80 default_server;
  server_name [server_name];

  access_log /var/log/nginx/access.log;
  error_log /var/log/nginx/error.log;

  root /var/www/nginx/capistrano_sample;

  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 service nginx restart
```

# データベースの準備
本番用のサーバーに capistrano_sample, capistrano_sample_dev データベースを作成しておく。
本番用サーバーで以下の作業を行う。

```
$ mysql -u root -pvagrant
mysql> GRANT ALL PRIVILEGES ON client_data.* TO root@"%" IDENTIFIED BY 'vagrant' WITH GRANT OPTION;
mysql> CREATE DATABASE capistrano_sample;
mysql> CREATE DATABASE capistrano_sample_dev;
mysql> GRANT ALL ON capistrano_sample.* TO root;
mysql> GRANT ALL ON capistrano_sample_dev.* TO root;
mysql> SHOW databases;
+-----------------------+
| Database              |
+-----------------------+
| information_schema    |
| capistrano_sample     |
| capistrano_sample_dev |
| mysql                 |
| performance_schema    |
+-----------------------+
5 rows in set (0.00 sec)
```

# Rails プロジェクト作成
開発用の環境にログインし、以下のコマンドを実行していく。

```
$ mkdir -p ~/projects/capistrano_sample
$ cd ~/projects/capistrano_sample
$ bundle init
```

作成されたGemfileを以下のように書き換える。

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

gem 'rails', '4.2.0'
```

以下のコマンドを実行。

```
$ bundle install --path vendor/bundle
$ bundle exec rails new .
...
Overwrite /home/vagrant/projects/capistrano_sample/Gemfile? (enter "h" for help) [Ynaqdh] Y
...
```

Gemfile を下記のように書き換える。

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

gem 'rails', '4.2.0'
gem 'mysql2', '~> 0.3.11'
gem 'sqlite3'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0'
gem 'therubyracer', platforms: :ruby
gem 'jquery-rails'
gem 'turbolinks'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'bcrypt', '~> 3.1.7'
gem 'unicorn'
gem 'rb-readline'

group :development, :test do
  gem 'byebug'
  gem 'web-console', '~> 2.0'
  gem 'spring'
end

group :deployment do
  gem 'capistrano', '~> 3.2.1'
  gem 'capistrano-rails'
  gem 'rvm-capistrano'
  gem 'capistrano-bundler'
  gem 'capistrano3-unicorn'
end
```

下記コマンドを実行。

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

# テスト用アプリ作成
`config/database.yml` を下記のように編集。

```yaml:config/database.yml
default: &default
  pool: 5
  timeout: 5000

development:
  <<: *default
  adapter: mysql2
  database: capistrano_sample_dev
  username: root
  password: vagrant
  host: 192.168.33.21
  encoding: utf8

production:
  <<: *default
  adapter: mysql2
  database: capistrano_sample
  username: root
  password: vagrant
  host: 192.168.33.21
  encoding: utf8

test:
  <<: *default
  adapter: sqlite3
  database: db/development.sqlite3
```

開発用サーバーで以下のコマンドを実行していく。

```
$ bundle exec rails generate scaffold board title:string text:string
$ bundle exec rake db:migrate RAILS_ENV=development
$ bundle exec rake db:migrate RAILS_ENV=production
```

`config/routes.rb` を下記のように編集

```ruby:config/routes.rb
# -*- coding: utf-8 -*-
Rails.application.routes.draw do
  resources :boards
  root "boards#index"
end
```

下記コマンドでサーバーを起動し、ローカルマシンのブラウザから
[http://192.168.33.20:3000](http://192.168.33.20:3000)
が正しく表示されるかどうか確認しておく。

```
$ bundle exec rails server -b 0.0.0.0
```


![ad42291646d3df976e8887a1b5480a17.png](https://qiita-image-store.s3.amazonaws.com/0/58390/b0cc1f0e-6fee-08c9-f478-30b0915808e1.png)

# Unicorn の設定
開発用サーバーで Rails のルートディレクトリ以下に、
* `config/unicorn/production.rb`
* `config/unicorn/development.rb`
を作成する。今回はどちらの環境も同じ内容で良い。

```
$ mkdir config/unicorn
$ emacs config/unicorn/production.rb
$ emacs config/unicorn/development.rb
```

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

app_path = '/var/www/nginx/capistrano_sample'
app_shared_path = "#{app_path}/shared"
working_directory "#{app_path}/current/"

listen "#{app_shared_path}/tmp/sockets/unicorn.sock"

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

pid "#{app_shared_path}/tmp/pids/unicorn.pid"

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

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
```

# Capistrano の設定
## 設定ファイル作成
引き続き開発用環境の Rails の root ディレクトリで下記コマンド実行。

```
$ bundle exec cap install STAGES=development,production
```

以下のようなディレクトリ構成になる。

```
[rails root dir]
├─ Capfile
├─ config
│ ├─ deploy
│ │ ├─production.rb
│ │ └─staging.rb
│ └─deploy.rb
└─ lib
  └─capistrano
  └─tasks
```

## Capfile の記述

以下のように記述する。

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

require 'capistrano/rails'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'rvm/capistrano'
require 'capistrano/bundler'
require 'capistrano3/unicorn'

Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
```

## デプロイ設定
### 共通部分
共通部分の設定を `config/deploy.rb` に記述する。
:repo_url の部分は、後ほど作成するGitのリポジトリURLと合わせておく。

```ruby:config/deploy.rb
# -*- coding: utf-8 -*-
require 'rvm/capistrano'
lock '3.2.1'

set :application, 'capistrano_sample'
set :repo_url, 'git@github.com:Salinger/capistrano_sample.git'
set :deploy_to, '/var/www/nginx/capistrano_sample'

set :default_stage, "development"
set :scm, :git
set :deploy_via, :remote_cache

set :log_level, :debug
set :pty, true # sudo に必要
# Shared に入るものを指定
set :linked_files, %w{config/database.yml config/secrets.yml}
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets bundle public/system public/assets}
# RVM
set :rvm_type, :system
set :rvm1_ruby_version, '2.1'
# Unicorn
set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"
# 5回分のreleasesを保持する
set :keep_releases, 5

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  # アプリの再起動を行うタスク
  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      execute :mkdir, '-p', release_path.join('tmp')
      execute :touch, release_path.join('tmp/restart.txt')
    end
  end

  # linked_files で使用するファイルをアップロードするタスク
  # deployが行われる前に実行する必要がある。
  desc 'upload important files'
  task :upload do
    on roles(:app) do |host|
      execute :mkdir, '-p', "#{shared_path}/config"
      upload!('config/database.yml',"#{shared_path}/config/database.yml")
      upload!('config/secrets.yml',"#{shared_path}/config/secrets.yml")
    end
  end

  # webサーバー再起動時にキャッシュを削除する
  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      within release_path do
        execute :rm, '-rf', release_path.join('tmp/cache')
      end
    end
  end

  # Flow の before, after のタイミングで上記タスクを実行
  before :started, 'deploy:upload'
  after :finishing, 'deploy:cleanup'

  # Unicorn 再起動タスク
  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart' # lib/capustrano/tasks/unicorn.cap 内処理を実行
  end
end
```

### 環境別
 `config/deploy/development.rb`, `config/deploy/production.rb` に
各環境の設定を記述する。

```ruby:config/deploy/development.rb
set :stage, :development
set :branch, 'development'

role :app, %w{vagrant@localhost}
role :web, %w{vagrant@localhost}
role :db, %w{vagrant@localhost}

set :ssh_options, {
  keys: [File.expand_path('~/.ssh/insecure_private_key')],
  forward_agent: true
}
```

```ruby:config/deploy/production.rb
set :stage, :production
set :branch, 'master'

role :app, %w{vagrant@192.168.33.21}
role :web, %w{vagrant@192.168.33.21}
role :db, %w{vagrant@192.168.33.21}

set :ssh_options, {
  keys: [File.expand_path('~/.ssh/insecure_private_key')],
  forward_agent: true
}
```

Unicorn の起動等の設定を`lib/capistrano/tasks/unicorn.cap`に記述する。

```ruby:lib/capistrano/tasks/unicorn.cap
namespace :unicorn do
  task :environment do
    set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"
    set :unicorn_config, "#{current_path}/config/unicorn/#{fetch(:rails_env)}.rb"
  end

  def start_unicorn
    within current_path do
    execute :bundle, :exec, :unicorn, "-c #{fetch(:unicorn_config)} -E #{fetch(:rails_env)} -D"
    end
  end

  def stop_unicorn
    execute :kill, "-s QUIT $(< #{fetch(:unicorn_pid)})"
  end

  def reload_unicorn
    execute :kill, "-s USR2 $(< #{fetch(:unicorn_pid)})"
  end

  def force_stop_unicorn
    execute :kill, "$(< #{fetch(:unicorn_pid)})"
  end

  desc "Start unicorn server"
  task :start => :environment do
    on roles(:app) do
      start_unicorn
    end
  end

  desc "Stop unicorn server gracefully"
  task :stop => :environment do
    on roles(:app) do
      stop_unicorn
    end
  end

  desc "Restart unicorn server gracefully"
  task :restart => :environment do
    on roles(:app) do
      if test("[ -f #{fetch(:unicorn_pid)} ]")
        reload_unicorn
      else
        start_unicorn
      end
    end
  end

  desc "Stop unicorn server immediately"
  task :force_stop => :environment do
    on roles(:app) do
      force_stop_unicorn
    end
  end
end
```

# secrets.yml の設定

下記コマンドを実行。

```
$ bundle exec rake secret
```

出力された文字列を保存しておき、secrets.yml の
production: -> secret_key_base: XXXX 
の部分をその値で書き換える。 

本番用サーバーの環境変数に仕込んでも良いが、今回は手間を省く。

# Git 周り
ここまでで、一度 GitHub に Push しておく。
両方のサーバーでSSH公開鍵を新規に作成し GitHub に登録後、
capistrano_sample リポジトリを GitHub 側に作成する。
master と development リポジトリを作成して Push。

```
$ ssh-keygen -t rsa 
(Enter 連打)
$ cat ~/.ssh/id_rsa.pub 
XXXXX (<- これをGitHubに登録する)
```

以下、開発用サーバーの Rails のルートディレクトリ以下で作業。
`.gitignore` ファイルを下記のように編集する。

```.gitignore
# Ignore bundler files.
/.bundle
vendor/bundle

db/*.sqlite3
/log/*
/tmp/*

# Important configures
config/database.yml
config/config/secrets.yml

# For emacs
*~
```

その後、下記コマンドを実行。

```
$ git init
$ git add .
$ git commit -m "Initial commit."
$ git remote add origin git@github.com:[user name]/capistrano_sample.git
$ git push -u origin master
$ git checkout -b development
$ git push -u origin development
```

※　注意
config/database.yml
config/config/secrets.yml
は Git で管理されないので、別途セキュアな方法・場所で管理する。

# デプロイ
## development 環境のデプロイ

```
$ export RAILS_ENV=development
$ bundle exec cap development deploy:upload # database.yml のアップロード 
$ bundle exec cap development deploy:check  # deploy可能か（権限のチェック等）
$ bundle exec cap development deploy
```

[http://192.168.33.20/](http://192.168.33.20/) にアクセスして、以下の画面が見えればOK。

![ad42291646d3df976e8887a1b5480a17.png](https://qiita-image-store.s3.amazonaws.com/0/58390/3be65bed-8c09-b824-8e65-16509c780d8f.png)

## production 環境のデプロイ

```
$ export RAILS_ENV=production
$ bundle exec cap production deploy:upload
$ bundle exec cap production deploy:check
$ bundle exec cap production deploy
```

[http://192.168.33.21/](http://192.168.33.21/) にアクセスして、以下の画面が見えればOK。

![ad42291646d3df976e8887a1b5480a17.png](https://qiita-image-store.s3.amazonaws.com/0/58390/3be65bed-8c09-b824-8e65-16509c780d8f.png)

# おわりに
1. ローカルの開発環境で git clone して開発 
2. ローカルでデプロイして確認しつつ開発
3. 動作確認取れたら本番環境のWebアプリケーションサーバにデプロイ

という流れがスムーズに行える環境が整った。

Chef-Solo + Berkshelf で Rails 4 の開発環境簡単につくれるようにしておいたため、
Unicorn & Capistrano & Nginx 周りのテストが非常に捗った。

# 参考資料
[Capistrano3でrailsをdeployしてみる](http://qiita.com/ea54595/items/12ab7b3a8213b35cca10)
[Capistrano 3系でRails4.1のデプロイ[rbenv][rvm][ruby2.1]](http://morizyun.github.io/blog/capistrano3-rails-deploy-multi-rbenv/)