Edited at

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

More than 3 years have passed since last update.


Capistrano とは

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

複雑な Rails 製アプリケーションのデプロイ作業を

コマンド数行でさくっと片付けられるようになる。

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

本記事では Vagrant を利用して、 Rails 4.2 系 + Unicorn + Nginx 環境で

Capistrano を利用してデプロイ作業をさくっとデプロイできるようになるまでの流れを

(主に自分用に) まとめた。


前提条件


  • 用意する開発・本番環境


    • development (開発用と同一サーバー), production (開発用と別サーバー)



  • アプリケーションサーバー


    • Unicorn



  • Ruby のバージョン管理


    • RVM



  • Webサーバー


    • Nginx



  • 使用するデータベース


    • MySQL



  • git のリポジトリ管理


    • GitHub




環境構築

下準備は Chef-Solo + Berkshelf で Rails 4 の開発環境を構築 を参考に。

2台分 (開発用・本番用) をVagrant で用意しておく。

プライベートIPは他の環境と別のものを割り当てる。

今回は

* 開発用サーバー (dev_server): 192.168.33.20

* 本番用サーバー (pro_server): 192.168.33.21

を割り当てた。

以下 Vagrantfile の例


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 の開発環境を構築

手順に従い、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

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


/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を以下のように書き換える。


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 を下記のように書き換える。


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 を下記のように編集。


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 を下記のように編集


config/routes.rb

# -*- coding: utf-8 -*-

Rails.application.routes.draw do
resources :boards
root "boards#index"
end

下記コマンドでサーバーを起動し、ローカルマシンのブラウザから

http://192.168.33.20:3000

が正しく表示されるかどうか確認しておく。

$ bundle exec rails server -b 0.0.0.0


Unicorn の設定

開発用サーバーで Rails のルートディレクトリ以下に、

* config/unicorn/production.rb

* config/unicorn/development.rb

を作成する。今回はどちらの環境も同じ内容で良い。

$ mkdir config/unicorn

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


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 の記述

以下のように記述する。


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と合わせておく。


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

各環境の設定を記述する。


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
}



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に記述する。


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 ファイルを下記のように編集する。

# 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/ にアクセスして、以下の画面が見えればOK。


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/ にアクセスして、以下の画面が見えればOK。


おわりに


  1. ローカルの開発環境で git clone して開発

  2. ローカルでデプロイして確認しつつ開発

  3. 動作確認取れたら本番環境のWebアプリケーションサーバにデプロイ

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

Chef-Solo + Berkshelf で Rails 4 の開発環境簡単につくれるようにしておいたため、

Unicorn & Capistrano & Nginx 周りのテストが非常に捗った。


参考資料

Capistrano3でrailsをdeployしてみる

Capistrano 3系でRails4.1のデプロイ[rbenv][rvm][ruby2.1]