AWS RailsアプリケーションのCapistranoによるデプロイ

# はじめに
デプロイ作業をコマンドひとつで簡単に出来るようにするために、Ruby製のデプロイツールであるcapistranoを使ってみます。

今回は、AWSにアプリケーションがとりあえずサーバー構築出来た状態からCapistranoをセットアップしていきたいと思います。

以下の、2つの記事でVPCとEC2の構築と、unicornとnginxの導入が出来ている状態からセットアップしていきます。
[AWS VPCによるネットワーク構築とEC2によるサーバー構築](http://qiita.com/shizuma/items/396ad15ff574717dfa1d)
[RailsアプリケーションのAWSによる公開｜unicorn + nginx](http://qiita.com/shizuma/items/d4ae7d4374aeb90df648)

Rails5系の Nginx+Pumaの記事も書いているので5系の方はこちらをどうぞ↓

[Rails5アプリケーションのAWSによるネットワーク構築 Nginx+Puma+Capistranoな環境とAWS構築(VPC EC2 RDS CloudFlont Route53 etc)](http://qiita.com/shizuma/items/0bfa71f70573e587669f)

なお、上記記事と同様に「protospace」というアプリケーションをサーバーに「shizuma」というユーザーで作成していきます。
作業は、「local」と「server」の作業どちらか確かめながら進めて下さい。コマンドを記載したエリアの左端に記述しています。

また、改善点がありましたらコメント頂けますと幸いです。

# Capistranoの導入

```rb:local
[protospace] vi Gemfile
-----------------------------
# gemファイルの中にunicorn、capistrano関連のgemを追記して下さい。
group :production, :staging do
  gem 'unicorn'
  gem 'capistrano'
  gem 'capistrano-bundler'
  gem 'capistrano-rails'
  gem 'capistrano-rbenv'
  gem 'capistrano3-unicorn'
end
-----------------------------
[protospace] bundle install
[protospace] bundle exec cap install
-----------------------------
# 以下のようにファイルが生成されます
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/staging.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified
-----------------------------
```

# 各種ファイルの編集

## unicornの設定ファイル

```rb:local
[protospace] vi config/unicorn.conf.rb
# 以下のように記述
-----------------------------
  # set lets
  $worker  = 2
  $timeout = 30
  $app_dir = "/var/www/rails/protospace/current" #自分のアプリケーション名、currentがつくことに注意。
  $listen  = File.expand_path 'tmp/sockets/.unicorn.sock', $app_dir
  $pid     = File.expand_path 'tmp/pids/unicorn.pid', $app_dir
  $std_log = File.expand_path 'log/unicorn.log', $app_dir
  # set config
  worker_processes  $worker
  working_directory $app_dir
  stderr_path $std_log
  stdout_path $std_log
  timeout $timeout
  listen  $listen
  pid $pid
  # loading booster
  preload_app true
  # before starting processes
  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
        Process.kill "QUIT", File.read(old_pid).to_i
      rescue Errno::ENOENT, Errno::ESRCH
      end
    end
  end
  # after finishing processes
  after_fork do |server, worker|
    defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
  end
-----------------------------
```

## Capfileの編集

```rb:local
[protospace] vi Capfile
-----------------------------
# Load DSL and set up stages
require 'capistrano/setup'

# Include default deployment tasks
require 'capistrano/deploy'

# Include tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#   https://github.com/capistrano/passenger
#
# require 'capistrano/rvm'
require 'capistrano/rbenv' #コメントアウトをはずす
# require 'capistrano/chruby'
require 'capistrano/bundler' #コメントアウトをはずす
require 'capistrano/rails/assets' #コメントアウトをはずす
require 'capistrano/rails/migrations' #コメントアウトをはずす
# require 'capistrano/passenger'
require 'capistrano3/unicorn' #追記

# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.task').each { |r| import r }
-----------------------------
```

## capistrano/tasks/unicorn.taskの設定

```rb:local
[protospace] vi lib/capistrano/tasks/unicorn.task
# unicornのtaskの記述。unicornのセットアップです。
-----------------------------
namespace :unicorn do
  task :environment do
    set :unicorn_pid,    "#{current_path}/tmp/pids/unicorn.pid"
    set :unicorn_config, "#{current_path}/config/unicorn.conf.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
-----------------------------
```

```rb:local
[protospace] vi config/deploy/production.rb
# production環境のサーバー情報
-----------------------------
#下記を追記する
server '00.00.000.000', user: 'shizuma', roles: %w{app} #serverのipとuser名は適宜
set :ssh_options, keys: '~/.ssh/first_aws_rsa' #ssh_keyの名前は適宜
-----------------------------
```

```rb:local
[protospace] vi config/deploy.rb
-----------------------------
# config valid only for current version of Capistrano
lock '3.4.0'

# アプリケーション名
set :application, 'protospace'

# deployするレポジトリ
set :repo_url, 'git@github.com:kuboshizuma/protospace.git'

# Default branch is :master
# deployするブランチ。デフォルトはmasterなのでなくても可。
set :branch, 'master'
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp

# Default deploy_to directory is /var/www/my_app_name
# deploy先のディレクトリ
set :deploy_to, '/var/www/rails/protospace'

# Default value for :scm is :git
# set :scm, :git

# Default value for :format is :pretty
# set :format, :pretty

# Default value for :log_level is :debug
# set :log_level, :debug

# Default value for :pty is false
# set :pty, true

# Default value for :linked_files is []
# シンボリックリンクをはるファイル。今回はgemのconfigを使用して、production.ymlを共通化。
set :linked_files, fetch(:linked_files, []).push('config/settings/production.yml')

# Default value for linked_dirs is []
# シンボリックリンクをはるフォルダ。
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }

# Default value for keep_releases is 5
# 保持するバージョンの個数
set :keep_releases, 5

# rubyのバージョン
set :rbenv_ruby, '2.1.3'

namespace :deploy do
  desc 'Restart application'
  task :restart do
    invoke 'unicorn:restart'
  end
  desc 'Create database'
  task :db_create do
    on roles(:db) do |host|
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:create'
        end
      end
    end
  end
  desc 'Run seed'
  task :seed do
    on roles(:app) do
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:seed'
        end
      end
    end
  end
  after :publishing, :restart
  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 :rake, 'cache:clear'
      # end
    end
  end
end
-----------------------------
```

## 環境変数の設定
今回はconfigというgemを使っています。dotenvとかでもいいと思います。
この設定は適宜必要に応じて設定して下さい。(ただし、secret_key_baseの設定は必要になります。)

```:local
[protospace] vi config/database.yml
-----------------------------
production:
  <<: *default
  database: protospace_production
  username: <%= Settings.database[:user_name] %>
  password: <%= Settings.database[:password] %>
-----------------------------

[protospace] vi config/secret.yml
-----------------------------
production:
  secret_key_base: <%= Settings.production[:secret] %>
-----------------------------

[protospace] rake secret RAILS_ENV=production
erfffer34gfdv19b5ec7523a23tgrege45gertf3d15b94dfgvere23r34g87253a6f6b7b80c6cb47c598e000500b4654143gergfwer3r4cca04d
```

```rb:server
[shizuma|protospace] mkdir -p shared/config/settings/
[shizuma|protospace] vi shared/config/settings/production.yml
-----------------------------
database:
  user_name: 'root'
  password:
production:
  secret: 'erfffer34gfdv19b5ec7523a23tgrege45gertf3d15b94dfgvere23r34g87253a6f6b7b80c6cb47c598e000500b4654143gergfwer3r4cca04d'
-----------------------------
```

## nginxの設定
releaseバージョンはcurrentディレクトリなので微調整。

```rb:server
[shizuma|~] sudo yum install nginx
[shizuma|~]$ cd /etc/nginx/conf.d/
[shizuma|conf.d]$ sudo vi protospace.conf 
----------------------------
  # log directory
  error_log  /var/www/rails/protospace/current/log/nginx.error.log; #currentつける
  access_log /var/www/rails/protospace/current/log/nginx.access.log; #currentつける
  # max body size
  client_max_body_size 2G;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/var/www/rails/protospace/current/tmp/sockets/.unicorn.sock fail_timeout=0; #currentつける
  }
  server {
    listen 80;
    server_name 127.0.0.1;
    # nginx so increasing this is generally safe...
    keepalive_timeout 5;
    # path for static files
    root /var/www/rails/protospace/current/public; #currentつける
    # page cache loading
    try_files $uri/index.html $uri.html $uri @app;
    location @app {
      # HTTP headers
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
      proxy_pass http://app_server;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/protospace/current/public; #currentつける
    }
  }
----------------------------
[shizuma|protospace]$ sudo nginx -s reload # nginxの再起動
```

# githubへ変更をpush
```rb:local
[protospace] git add .
[protospace] git commit -m "Add for deploy by capistrano"
[protospace] git push origin master
```

# デプロイ
```:local
[protospace] bundle exec cap deploy production
```

これで、完了！

# エラー対応
## rails_config
gemでrails_configを使っていたが、エラーが出たので、configというgemに変更。

## mysql2
バージョン指定で対応。

[Rails mysql2でrake db:createがエラー問題](http://qiita.com/shizuma/items/0f9660d5d46a0012eb9e)

## /tmp/mysql.sockエラー
シンボリックリンクで対応。

```
$ ln -s /var/lib/mysql/mysql.sock /tmp/mysql.sock
```

# 参考
[AWSはこれで大丈夫｜unicorn + nginx + capistrano + slackistrano のコマンド一覧](http://qiita.com/Yama-to/items/1e1d889e6d5f7cc7f281)