概要
ruby on railsをcapistranoでdeployするための設定方法。
nginxとunicornを使用する。
railsプロジェクトはconfig/database.ymlとconfig/secrets.ymlがgitignoreされており、
config/database.example.ymlとconfig/secrets.example.ymlがレポジトリ内に
存在するとしておく。
全体の流れ
- サーバ設定など
- Gemfileに追加し
bundle
cap install
- Capfile, deploy.rb編集
- tasksファイル(unicorn, nginx関係)とテンプレート作成
- staging.rb, production.rb編集
cap production deploy:check
- server上で
database.yml
,secrets.yml
などを直す cap production deploy
サーバ上での前設定
新しいサーバで必要な作業。すでにdeployユーザの作成などができている場合は飛ばして良い。
deployユーザの作成
sudo adduser deploy
deployユーザをsudoerにする。
sudo adduser deploy sudo
deployするためのディレクトリを作成。deployユーザに権限を与える。
sudo mkdir /var/www
sudo chown deploy /var/www
sudo chgrp deploy /var/www
bundlerなど実行できるようにしておく。
nginxをインストールし、defaultの設定を消しておく。
sudo aptitude install nginx
sudo rm /etc/nginx/sites-enabled/default
log formatでltsvを使いたいので/etc/nginx/conf.d/ltsv.confを作成。
log_format ltsv 'time:$time_local\t'
'host:$remote_addr\t'
'request:$request\t'
'status:$status\t'
'size:$body_bytes_sent\t'
'referer:$http_referer\t'
'ua:$http_user_agent\t'
'reqtime:$request_time\t'
'upsttime:$upstream_response_time';
sudoが必要なnginxの起動や、unicornサーバの起動、また設定ファイルの配置などを
deployユーザがパス無しでできるように設定しておくために/etc/sudoersに以下を追記する。
/etc/sudoersの編集はvisudoなどで行う。
(sudo visudo
で編集ができ、閉じるときにSyntax errorがある場合は、表示してくれる。)
# 以下を追記
deploy ALL=NOPASSWD: /usr/bin/apt-get up*
deploy ALL=NOPASSWD: /usr/sbin/service /usr/sbin/nginx*
deploy ALL=NOPASSWD: /bin/rm -f /etc/nginx/sites-enabled/default
deploy ALL=NOPASSWD: /bin/mv /tmp/unicorn.service /etc/systemd/system/unicorn*
deploy ALL=NOPASSWD: /bin/mv /tmp/nginx_conf /etc/nginx/sites-enabled/*
deploy ALL=NOPASSWD: /bin/mv /tmp/nginx_default_conf /etc/nginx/sites-enabled/*
deploy ALL=NOPASSWD: /usr/sbin/service nginx *
deploy ALL=NOPASSWD: /bin/systemctl * unicorn*
deploy ALL=NOPASSWD: /bin/systemctl daemon-reload
deploy ALL=NOPASSWD: /bin/mkdir -p /var/log/nginx/*
capistranoの設定
Gemfileにcapistrano関連を登録する。
group :development do
gem 'capistrano-rails'
gem 'capistrano-rbenv', '~> 2.0.4'
gem 'capistrano-bundler'
end
bundleして、ライブラリをインストールした後、
capistranoで必要なファイルをcap installで作成。
% bundle
% bundle exec cap install kenmbp.local
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
Capfile
Capfileを修正。
rbenv, bundler, rails/assets, rails/migrationsは最初はコメントアウトされている。
rbenv_typeは:userと:systemの二種類だが、rbenvをローカルインストールしている場合は:userを指定。
rbenv_rubyで使用するrubyのバージョンを指定。
require 'capistrano/setup'
require 'capistrano/deploy'
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
set :rbenv_type, :user
set :rbenv_ruby, '2.3.1'
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
config/deploy.rb
config/deploy.rbには、stagingとproductionで共通に使う設定を書いていく。
capistrano内でset設定した変数はfetchで取得できる。
他のset変数をsetで使いたい場合は、lamda(->となっているところ)式で呼び出す。
これで変数が後から代入することができるようになる。
デプロイ先:
/var/www/app_name/production
/var/www/app_name/staging
set :default_stage, 'staging'
set :deploy_to, -> { "/var/www/app_name/#{fetch(:stage)}" }
set :deploy_via, :remote_cache
set :scm, :git
set :repo_url, 'repository_url'
set :keep_releases, 5
set :pty, true
set :ssh_options, {
forward_agent: true,
auth_methods: %w(publickey)
}
set :untracked_files, %w(config/database.yml,
config/secrets.yml,
config/email.yml)
append :linked_files,
'config/database.yml',
'config/secrets.yml',
'config/email.yml'
append :linked_dirs,
'log',
'tmp/pids',
'tmp/cache',
'tmp/sockets',
'vendor/bundle',
'public/system',
'bin'
自分でタスクを定義
基本的なタスク
lib/capistrano/tasks/deploy.rakeにいくつか共通し使う関数とタスクを定義しておく。
namespace :deploy do
def remote_file_exists?(full_path)
test "[ -e #{full_path} ]"
end
def template(from, to)
erb = File.read(File.expand_path("../../templates/#{from}", __FILE__))
upload! StringIO.new(ERB.new(erb).result(binding)), to
end
task :untracked_files do
on roles(:web) do
unless remote_file_exists?("#{shared_path}/config/database.yml")
upload! File.open('config/database.example.yml'),
"#{shared_path}/config/database.yml"
end
unless remote_file_exists?("#{shared_path}/config/secrets.yml")
upload! File.open('config/secrets.example.yml'),
"#{shared_path}/config/secrets.yml"
end
puts "Now edit the config files in #{shared_path}."
end
end
after 'deploy:check:make_linked_dirs', 'deploy:untracked_files'
desc 'Make sure local git is in sync with remote.'
task :check_revision do
on roles(:web) do
unless `git rev-parse HEAD` == `git rev-parse origin/#{fetch(:branch)}`
puts "WARNING: HEAD is not the same as origin/#{fetch(:branch)}"
puts 'Run `git push` to sync changes.'
exit
end
end
end
before 'deploy', 'deploy:check_revision'
end
nginx関連のタスク及び設定
nginxの設定もcapistranoで作成するようにする。
lib/capistrano/templates/nginx_unicorn.erbに以下のような設定を作成。
upstream unicorn_<%= fetch(:application) %> {
server unix:/tmp/unicorn.<%= fetch(:application) %>.sock fail_timeout=0;
}
server {
listen 80;
server_name <%= fetch(:server_name) %>;
root <%= current_path %>/public;
<%= "auth_basic \"Restricted\";\n auth_basic_user_file /etc/nginx/.htpasswd;" unless fetch(:rails_env) == 'production' %>
client_max_body_size 100m;
error_page 404 /404.html;
error_page 500 502 503 504 /500.html;
keepalive_timeout 10;
access_log /var/log/nginx/app_name/<%= fetch(:stage) %>/<%= fetch(:application) %>.log ltsv;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
try_files $uri/index.html $uri @unicorn_<%= fetch(:application) %>;
location @unicorn_<%= fetch(:application) %> {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://unicorn_<%= fetch(:application) %>;
}
}
production以外ではbasic認証を書けるように設定している。
(nginxでbasic認証)
lib/capistrano/tasks/nginx.rakeを作成。
namespace :nginx do
set :nginx_conf, -> { "/etc/nginx/sites-enabled/#{fetch(:application)}" }
desc 'Setup nginx configuration for this application'
task :setup do
on roles(:web) do
sudo "mkdir -p /var/log/nginx/app_name/#{fetch(:stage)}"
template 'nginx_unicorn.erb', '/tmp/nginx_conf'
sudo "mv /tmp/nginx_conf #{fetch :nginx_conf}"
sudo 'service nginx restart'
end
end
task :initialize do
on roles(:app) do
invoke 'nginx:setup' unless remote_file_exists? fetch :nginx_conf
end
end
after 'deploy:check', 'nginx:initialize'
%w(start stop restart).each do |command|
desc "#{command} nginx"
task command do
on roles(:web) do
sudo "service nginx #{command}"
end
end
end
end
nginx:setupを実行するとtemplateから作成したnginxの設定ファイルをサーバに配置できる。
ここではafter "deploy:check", :setupを設定しており、
後ほど実行するdeploy:checkの時に自動的に実行されるので、
今は実行しなくても良い
cap prodution nginx:setup
ただし、nginx設定ファイルを書きかえた場合にはnginx:setupを実行する必要がある。
またnginxをcapistranoからstart, stop, restartできるようにタスクを作ってある。
cap production nginx:start
cap production nginx:stop
cap production nginx:restart
unicorn関連のタスク及び設定
nginxと同様にunicornもtemplateを作成しておく。
unicornは起動スクリプトと、unicornの設定の二種類のtemplateを作成する。
unicornの設定のtemplateをlib/capistrano/templates/unicorn.rb.erbに作成。
working_directory "<%= current_path %>"
pid "<%= current_path %>/tmp/pids/unicorn.pid"
stderr_path "<%= shared_path %>/log/unicorn.log"
stdout_path "<%= shared_path %>/log/unicorn.log"
listen "/tmp/unicorn.<%= fetch(:application) %>.sock"
worker_processes 2
timeout 30
unicorn起動のためのスクリプトtemplateをlib/capistrano/templates/unicorn.service.erbに作成。
[Unit]
Description=Unicorn server for <%= fetch(:application) %>
[Service]
SyslogIdentifier=unicorn-<%= fetch :application %>
User=deploy
PIDFile=<%= current_path %>/tmp/pids/unicorn.pid
WorkingDirectory=<%= current_path %>
Type=forking
Environment=RAILS_ENV=<%= fetch :rails_env %>
ExecStart=/home/deploy/.rbenv/bin/rbenv exec bundle exec unicorn -D -c <%= shared_path %>/config/unicorn.rb -E <%= fetch :rails_env %>
ExecStop=/bin/kill -s QUIT $MAINPID
ExecReload=/bin/kill -s HUP $MAINPID
[Install]
WantedBy=multi-user.target
これがあるとサーバを再起動した時などに、
自動的にunicornを起動させることができる。
unicorn関連のタスクをlib/capistrano/tasks/unicorn.rakeに設定
namespace :unicorn do
desc 'Setup Unicorn initializer and app configuration'
task :setup do
on roles(:app) do
execute "mkdir -p #{shared_path}/config"
template 'unicorn.rb.erb', "#{shared_path}/config/unicorn.rb"
template 'unicorn.service.erb', '/tmp/unicorn.service'
target_file = "/etc/systemd/system/unicorn_#{fetch(:application)}.service"
sudo "mv /tmp/unicorn.service #{target_file}"
sudo 'systemctl daemon-reload'
sudo "systemctl start unicorn_#{fetch(:application)}"
end
end
task :initialize do
on roles(:app) do
init_file_path =
"/etc/systemd/system/unicorn_#{fetch(:application)}.service"
config_file_path = "#{shared_path}/config/unicorn.rb"
unless remote_file_exists?(init_file_path) &&
remote_file_exists?(config_file_path)
invoke 'unicorn:setup'
end
end
end
after 'deploy:check', 'unicorn:initialize'
%w(start stop reload status is-active).each do |command|
desc "#{command} unicorn"
task command do
on roles(:app) do
sudo "systemctl #{command} unicorn_#{fetch(:application)}"\
"#{command == 'status' ? ' | cat' : nil}"
end
end
end
after 'deploy:finished', 'unicorn:reload'
after 'deploy:finished', 'unicorn:is-active'
end
nginxの場合と同様に、unicorn:setupを実行すると
templateから作成したunicornの設定ファイルをサーバに配置できる。
ここでもafter "deploy:check", :setupを設定しており、
後ほど実行するdeploy:checkの時に自動的に実行されるので、
今は実行しなくても良い
cap production unicorn:setup
設定を変更した場合は、再びunicorn:setupを実行する必要がある。
またnginxと同様にローカルからstart, stop, restartができるようになっている。
cap production unicorn:start
cap production unicorn:stop
cap production unicorn:restart
staging及びproduction独自の設定
staging, production独自の設定に関しては、
それぞれconfig/deploy/staging.rbとconfig/deploy/production.rb
に書く。
set :application, 'app_name_staging'
set :branch, :staging
set :rails_env, :staging
server '0.0.0.0', user: 'deploy', roles: %w{app db web}
set :server_name, 'staging.example.com'
set :application, 'app_name'
set :branch, :master
set :rails_env, :production
server '0.0.0.0', user: 'deploy', roles: %w{app db web}
set :server_name, 'example.com'
capistranoによるサーバ上のファイル設定
cap deploy:checkで設定ファイルをサーバ上に設置する。
cap production deploy:check
deploy:checkはdeployのときに実行されるタスクではあるが、
database.yml, secrets.ymlがただしく設定できていないので、deployを実行するのではなく
deploy:checkを実行する。
たまにdeploy:checkあたりで、gitにアクセス出来ないというエラーが出ることがある。
Please make sure you have the correct access rights
and the repository exists.
git stderr: Nothing written
これはdeployユーザの公開鍵をrepositoryサーバに登録しなくても、ローカルのユーザの
鍵を使ってpullできるようにuser agentの登録をローカルで行えば良い。
ssh-add ~/.ssh/id_rsa
サーバ上のdatabase.ymlやsecret.ymlを変更した後、cap deployでdeployができる。
cap production deploy
参考
capistranoタスクの流れ
deploy
deploy:starting - start a deployment, make sure everything is ready
deploy:started - started hook (for custom tasks)
deploy:updating - update server(s) with a new release
deploy:updated - updated hook
deploy:publishing - publish the new release
deploy:published - published hook
deploy:finishing - finish the deployment, clean up everything
deploy:finished - finished hook
rollback
deploy:starting
deploy:started
deploy:reverting - revert server(s) to previous release
deploy:reverted - reverted hook
deploy:publishing
deploy:published
deploy:finishing_rollback - finish the rollback, clean up everything
deploy:finished
deploy:check
deploy:check:directories
deploy:check:linked_dirs
deploy:check:make_linked_dirs
deploy:check:linked_files
#実際に陥りやすいポイント
unicorn.rbが移行されず、エラーが出て止まる
上記の順番で実行した時に、unicorn.rbがうまくshared/config/
以下に移せなかった。
この時には、cap production unicorn:setup
を実行するとよい。
もしかすると、remote_exist_file?
らへんの条件がうまく機能していないのかもしれない。
No passrd for user '....' というエラーで止まる
database.ymlの設定で、dbにつなげていない場合に起こる。
解決方法の一つとして、deploy
でログインして、createdb app_db_name
を実行(Postgresの場合)し、deployとそのパスワードをdatabase.ymlに書くことで解決するかも。
## The deploy has failed with an error
The deploy has failed with an error: Exception while executing as deploy@example.com: rake exit status: 1
rake stdout: config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:
* development - set it to false
* test - set it to false (unless you use a tool that preloads your test environment)
* production - set it to true
rake aborted!
cp config/environments/production.rb config/enviroments/staging.rb
を実行し、staging.rb
を作る
PG::ConnectionBad: FATAL: password authentication failed for user "deploy"
database.ymlの情報ミス
sudo stdout: appname_staging: unrecognized service
aborted!
...
SSHKit::Command::Failed: sudo exit status: 1
...
Tasks: TOP => unicorn:restart
(See full trace by running task with --trace)
sudo stderr: Nothing written
deploy staging unicorn:setup
して解消