読者です 読者をやめる 読者になる 読者になる

プログラミングノート

一からものを作ることが好きなエンジニアの開発ブログです。

さくらVPS Capistrano編

基本設定開発環境設定に引き続き、今回はCapistranoを導入して自動デプロイできるように設定。unicorn+nginx周りの設定も変更して快適にデプロイできるようになりました。

リモートリポジトリの作成

リモートサーバーにリポジトリを作成します。
今回は全て同じサーバーでやるので、自分のホームディレクトリ直下に作りました。

$ mkdir -p /home/ntaku/git/sample
$ cd ~/git/sample
$ git --bare init


後からcapistranoでアプリを配置するためのディレクトリも作成しておきます。

# mkdir /var/www

プロジェクト作成

ここからはローカルで。
新規プロジェクトを作成して、テスト用のコントローラーを追加します。

$ rails new sample -d mysql
$ cd sample
$ rails g controller top index


先ほど作った空のgitリポジトリを取得して、git関連のファイルを上記の新規プロジェクトに移します。

$ git clone ssh://ntaku@xxx.xxx.xxx.xxx:10022/home/ntaku/git/sample sample_git
$ mv sample_git/.git sample
$ rm -rf sample_git

Capistrano設定

まずはBundlerを使ってunicorncapistranoをインストールします。


# /sample/Gemfile

source 'http://rubygems.org'

gem 'rails', '3.1.1'
gem 'mysql2'

group :assets do
  gem 'sass-rails',   '~> 3.1.4'
  gem 'coffee-rails', '~> 3.1.1'
  gem 'uglifier', '>= 1.0.3'
end

gem 'jquery-rails'
gem 'unicorn'

group :deployment do
  gem 'capistrano'
  gem 'capistrano_colors'
end
$ bundle install


capifyでcapistranoの設定ファイルを生成します。

$ capify .
[add] writing './Capfile'
[add] writing './config/deploy.rb'
[done] capified!


デプロイスクリプトを修正します。
とりあえずこんな感じで動作しました。


# /sample/config/deploy.rb

# capistranoの出力がカラーになる
require 'capistrano_colors'

# cap deploy時に自動で bundle install が実行される
require "bundler/capistrano"

# RVMを利用している場合は必要
$:.unshift(File.expand_path('./lib', ENV['rvm_path']))
require 'rvm/capistrano'
set :rvm_ruby_string, '1.9.2'
set :rvm_type, :user

# リポジトリの設定
set :application, "sample"
set :scm, :git
set :repository, "ssh://ntaku@xxx.xxx.xxx.xxx:10022/home/ntaku/git/#{application}"
set :branch, "master"
set :deploy_via, :remote_cache
set :deploy_to, "/var/www/#{application}"
set :rails_env, "production"

# SSHの設定
set :user, "ntaku"
ssh_options[:port] = "10022"
ssh_options[:forward_agent] = true
default_run_options[:pty] = true

# role (全部同じIPでOK)
role :web, "xxx.xxx.xxx.xxx"
role :app, "xxx.xxx.xxx.xxx"
role :db,  "xxx.xxx.xxx.xxx", :primary => true

# precompile
load 'deploy/assets'

# cap deploy:setup 後、/var/www/sample の権限変更
namespace :setup do
  task :fix_permissions do
    sudo "chown -R #{user}.#{user} #{deploy_to}"
  end
end
after "deploy:setup", "setup:fix_permissions"

# Unicorn用に起動/停止タスクを変更
namespace :deploy do
  task :start, :roles => :app do
    run "cd #{current_path}; bundle exec unicorn_rails -c config/unicorn.rb -E #{rails_env} -D"
  end
  task :restart, :roles => :app do
    if File.exist? "/tmp/unicorn_#{application}.pid"
      run "kill -s USR2 `cat /tmp/unicorn_#{application}.pid`"
    end
  end
  task :stop, :roles => :app do
    run "kill -s QUIT `cat /tmp/unicorn.pid`"
  end
end

unicornの設定

unicornも設定ファイルがアプリ内にあるので合わせて設定しておきます。
ソケットファイル名、ログの出力場所など。


# /sample/confit/unicorn.rb

application = 'sample'

listen "/tmp/unicorn_#{application}.sock"
pid "/tmp/unicorn_#{application}.pid"

worker_processes 4;
timeout 30
preload_app true

if ENV['RAILS_ENV'] == 'production'
  shared_path = "/var/www/#{application}/shared"
  stderr_path "#{shared_path}/log/unicorn.stderr.log"
  stdout_path "#{shared_path}/log/unicorn.stdout.log"
end

before_fork do |server, worker|
  # マスタープロセスの接続を解除
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.connection.disconnect!
  end
  # 古いマスタープロセスをKILL
  old_pid = "/tmp/unicorn.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
      # someone else did our job for us
    end
  end
end

after_fork do |server, worker|
  # preload_app=trueの場合は必須
  if defined?(ActiveRecord::Base)
    ActiveRecord::Base.establish_connection
  end
end

デプロイ

修正したファイルを全てコミットします。

$ git add .
$ git commit -m 'first import'
$ git push origin master


セットアップを実行すると、/var/wwwにベースのディレクトリが作成されます。(初回のみでOK)

$ cap deploy:setup

./sample
./sample/shared
./sample/shared/system
./sample/shared/pids
./sample/shared/log
./sample/releases


deployコマンドでファイルを配置します。
ここでbundle install、rake assets:precompile も実行されます。

$ cap deploy


最後にmigrateでDB変更を反映します。この際、DBがないとエラーになるので先に作成しておく必要があります。それでも失敗するという場合はdatabase.ymlを確認。mysql.sock辺りではまりました..

$ cap deploy:migrate


これで準備が整ったのでunicornを起動します。

$ cap deploy:start


設定に問題がなければ複数プロセス起動されているはずです。

[ntaku@xxx www]$ ps -ef | grep unicorn
ntaku     5552     1 12 20:13 ?        00:00:05 unicorn_rails master -c config/unicorn.rb -E production -D
ntaku     5557  5552  0 20:13 ?        00:00:00 unicorn_rails worker[0] -c config/unicorn.rb -E production -D
ntaku     5560  5552  0 20:13 ?        00:00:00 unicorn_rails worker[1] -c config/unicorn.rb -E production -D
ntaku     5563  5552  0 20:13 ?        00:00:00 unicorn_rails worker[2] -c config/unicorn.rb -E production -D
ntaku     5566  5552  0 20:13 ?        00:00:00 unicorn_rails worker[3] -c config/unicorn.rb -E production -D


確認後、一旦止めます。

$ cap deploy:stop

nginxの設定

最後にnginxからunicornのソケットに接続できるように設定します。
参考サイトを見ながら入れたのでまだよく分かっていないパラメタもちらほら。


# /etc/nginx/nginx.conf

user nginx nginx;

worker_processes 4;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    multi_accept on;
}
http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay off;

    include /etc/nginx/mime.types;
    default_type application/octet-stream;

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

    gzip on;
    gzip_disable "msie6";

    gzip_proxied any;
    gzip_min_length 500;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    # Virtual Host Configs
    #include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}


今回作成したアプリ用の設定ファイルを追加します。
ソケット名をunicornで設定したものと合わせる必要があります。


# /etc/nginx/sites-available/default

upstream unicorn_app {
  server unix:/tmp/unicorn_sample.sock fail_timeout=0;
}
server {
    listen 80;
    server_name _;

    root /var/www/sample/current;
    access_log /var/log/nginx/sample_log;
    rewrite_log on;

    location / {
        # 全リクエストをUNIX socketへ
        proxy_pass  http://unicorn_app;
        proxy_redirect     off;

        proxy_set_header   Host             $host;
        proxy_set_header   X-Real-IP        $remote_addr;
        proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;

        client_max_body_size       10m;
        client_body_buffer_size    128k;

        proxy_connect_timeout      90;
        proxy_send_timeout         90;
        proxy_read_timeout         90;

        proxy_buffer_size          4k;
        proxy_buffers              4 32k;
        proxy_busy_buffers_size    64k;
        proxy_temp_file_write_size 64k;
    }

    # Rails 3.1's asset pipe_line用に何か必要かもしれないらしい..
    location ~ ^/(images|javascripts|stylesheets|system)/  {
      root /var/www/sample/current/public;
      expires max;
      break;
    }
}


nginxを起動します。

# /etc/init.d/nginx start

最終確認

もう一度 cap deploy:start して、ブラウザからアクセスするとつながります。

http://xxx.xxx.xxx.xxx/top/index