Rails 初心者が Capistrano3 で AWS EC2 にデプロイするためにがんばったこと。

# 追記

http://qiita.com/kizashi1122/items/6e22b2a27fa4da487884
に更新版を載せました。

# はじめに

最初に構成ですが、以下のとおりです。

- クライアント
    - Mac 上の仮想環境(CentOS)
    - Capistrano3 もここで動きます
- サーバ
    - EC2(AWS)
- 構成管理
    - Github

ミドルウェアは、以下のとおりです。Nginx は `yum` でインストールしておいてください。

- Nginx
- Unicorn

Capistrano3 で、クライアントからコマンド一発でサーバにデプロイすることを目的としてます。

しかし、Capistrano3 や Unicorn 関連の情報は Web 上にたくさんあり、それぞれ少しずつ設定が違ってたりして部分的につまみ食いすると設定に食い違いがでてしまって動かなくなります。

私はまだまだ初心者ですがとりあえず動くところまで行ったので共有したいと思います。

# 各種設定ファイル

## Nginx

Nginx はインストールしている前提です。
Unicorn とは同一サーバ上で動かしているため、`Unix Domain Socket` で接続します。

```nginx
user  nginx;
worker_processes  1;
error_log  /var/log/nginx/error.log;
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;
    keepalive_timeout  65;
    server_tokens off;                  # 1
    include /etc/nginx/conf.d/*.conf;
    index   index.html index.htm;
    upstream sub.example.com {          # 2
        server unix:/tmp/unicorn.sock;  # 3
    }
    server {
        listen       80;
        server_name  localhost;
        root         /usr/share/nginx/html;
        location ~ ^/assets/ {
            root   /home/ec2-user/deploy/current/public; # 4
        }
        location / {
		proxy_pass http://sub.example.com;       # 5 
        }
        error_page  404              /404.html;
        location = /40x.html {
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
        }
    }
}
```

コメントにつけた番号について注意点をあげておきます。

| 番号 | 説明 | 
|:----|:-----|
| 1 | セキュリティのためです。今回の設定とは無関係です。|
| 2 | ちゃんとFQDNにしておかないとアプリでフルパスを取得するとき失敗してしまいました。|
| 3 | socket ファイルの指定。unicorn の設定と合わせる必要あり。|
| 4 | コンパイル後の css, js が生成されるパスを指定する。|
| 5 | http:// より後ろは 2 と合わせておいてください。|


## Capistrano 3

Capistrano 3 のインストールをします。

```rb:Gemfile
gem 'capistrano', '~> 3.1.0'
gem 'capistrano-rails', '~> 1.1.0' 
gem 'capistrano-bundler'           
gem 'capistrano-rbenv', "~> 2.0"   
gem 'unicorn'
```

capistrano-unicorn なる便利そうな gem もあるみたいですが、まだ使っていません。入れなくてもデプロイはできます。

次にインストールします。

```shell-session
$ bundle install
$ bundle exec cap install
```

するとフォルダやファイルが生成されます。

```
├── Capfile
├── config
│   ├── deploy
│   │   ├── production.rb
│   │   └── staging.rb
│   └── deploy.rb
└── lib
    └── capistrano
            └── tasks
```

次に、Capfile を編集します。私のファイルの有効行だけを紹介します。

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

require 'capistrano/rbenv'
set :rbenv_type, :user         # ADDED
set :rbenv_ruby, '2.0.0-p451'  # ADDED

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

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

ほとんどそのまま使っていますが、#ADDED のところは追加しています。
次に、deploy.rb です。

```rb:config/deploy.rb
lock '3.1.0'
set :application, 'rails_app'           ## 1
set :repo_url, 'git@github.com:kizashi1122/rails_app.git' ## 2
set :deploy_to, '/home/ec2-user/deploy' ## 3
set :scm, :git
set :log_level, :debug
set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets public/system public/assets}
set :default_stage, "production"
namespace :deploy do
  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      # execute :touch, release_path.join('tmp/restart.txt')
      invoke "unicorn:restart" ## 4
    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
```

| 番号 | 説明 | 
|:----|:-----|
| 1 | アプリ名です |
| 2 | デプロイ対象のアプリです。git のURLパスです。|
| 3 | サーバ側のデプロイ先のディレクトリです。/var/www などのディレクトリを使っているサンプルが多かったのですが、パーミッションの問題もあるだろうと思い、ssh ユーザがアクセスできるように、ホームディレクトリ以下にしました。このディレクトリは先に mkdir しています。|
| 4 | deploy コマンドをたたいたときには unicorn を起動してもらわないといけないので追加しました。|

デプロイするソースは master から取得する前提なので明示的に指定はしていません。

ssh と書きましたが、まだ ssh の接続に関する設定をしていません。これは production.rb でします。以下、有効行のみです。

```rb:config/deploy/production.rb
set :stage, :production
set :rails_env, 'production'
server 'sub.example.com', user: 'ec2-user', roles: %w{web app db}
set :ssh_options, {
  keys: [File.expand_path('~/.ssh/aws.pem')],
}
```

stage, rails_env は明示的に指定するようにしました。なぜか unicorn 起動時の引数に渡されないことがあったからです。
もともとのファイルの上部には role の設定などがあり困惑しましたがそれらはコメントアウトしています。

## Unicorn

つぎに Unicorn の設定です。
すでに、Gemfile に `gem 'unicorn'` と書いて `bundle install` しているのでインストールは終わっています。

あとは設定です。これがややこしいです。
unicorn.cap は capistrano で unicorn を停止起動するためのラッパー関数が書かれた設定ファイルです。

Capfile の最下行に lib/capistrano/tasks/ 以下の *.cap ファイルを自動で import する記述があるのでこのファイルは自動的にインポートされます。

一応ファイルの中身も記載しますが、http://qiita.com/satococoa/items/9b0cc416ffc042680b9b のままです。

```rb: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.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
```

Unicorn を起動・停止するラッパー処理は、`capistrano-unicorn` を gem でインストールしたら書く必要ないような気もしてきました。

それはともかく、ここで大事なのは以下の2行です。

```rb:抜粋
    set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"
    set :unicorn_config, "#{current_path}/config/unicorn.rb"
```

1行目の `unicorn.pid` は unicorn のプロセスIDを保持するテキストファイルです。このファイルを使ってプロセスIDを取得し停止などの操作をしているわけです。しかしこの `pid` の指定は2行目の起動設定ファイルにも記述する必要があります。 **つまり二重管理になります。** さらに、unicorn.rb では同様に `#{shared_path}` という書き方ができないので、明示的にフルパスを書かないといけないことに注意してください。

```rb:config/unicorn.rb
worker_processes 2

listen '/tmp/unicorn.sock'  # 1
pid "/home/ec2-user/deploy/shared/tmp/pids/unicorn.pid" #2 

stderr_path File.expand_path('unicorn.log', File.dirname(__FILE__) + '/../log')
stdout_path File.expand_path('unicorn.log', File.dirname(__FILE__) + '/../log')
preload_app true
```

| 番号 | 説明 | 
|:----|:-----|
| 1 | nginx.conf に記述する unicorn の socket ファイルのパスと合わせてください。|
| 2 | unicorn.cap で指定した pid のパスを変数を使わずに書きます。|

あとは、`git push` して、以下のデプロイコマンドを打ってください。

```shell-session
$ bundle exec cap production deploy
```

特にどこにも指定したつもりはないですが、assets のプリコンパイルも走ってます。

# 課題

`cap deploy` をするとズラズラーとコマンドの結果がコンソールに表示されますが、結構 `failed` が出ています。ファイルやフォルダの存在のテストだけならいいんですが、結構気持ち悪いです。

また内部的に git で接続してる最中に

```
Error reading response length from authentication socket.
```

が表示されているのが気持ち悪い。処理自体はうまくいっているぽいのだが・・。