ソースリポジトリ
ソースは以下のリポジトリを参照してください。
https://github.com/katsuhiko/sample_app_rails_4
バージョン
version |
---|
rails 4.2.3 |
capistrano 3.4.0 |
capistrano-rails 1.1.3 |
capistrano-rbenv 2.0.3 |
capistrano3-unicorn 0.2.1 |
unicorn 4.9.0 |
unicorn-worker-killer 0.4.3 |
概要
Ansible を使って EC2 に Railsサーバーを立ち上げる
で作成した EC2 インスタンスに対して sample_app_rails_4 をデプロイします。
インストール
Gemfile に追加します。
追加分のみを記載します。Gemfile 全体は https://github.com/katsuhiko/sample_app_rails_4/blob/master/Gemfile を参照してください。
# Use Unicorn as the app server
gem 'unicorn'
gem 'unicorn-worker-killer'
group :development do
# Use Capistrano for deployment
gem 'capistrano', '3.4.0'
gem 'capistrano-rails'
gem 'capistrano-rbenv'
gem 'capistrano-bundler'
gem 'capistrano3-unicorn'
end
bundle install
を実施し、capistrano の設定ファイルの雛形を作成します。
$ bundle install
$ bundle exec cap install
デプロイファイルと unicorn 関連ファイル
Capfile
'capistrano/rails'
を読み込めば 'capistrano/bundler'
, 'capistrano/rails/assets'
, 'capistrano/rails/migrations'
を読み込む必要はありません。
unicorn を使うために 'capistrano3/unicorn'
を読み込みます。
# 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
require 'capistrano/rbenv'
require 'capistrano/rails'
require 'capistrano3/unicorn'
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r }
deploy.rb
ある程度、環境変数で切り替えれるようにしています。
AWS のタグ情報を利用しデプロイするため、aws_region, ssh_keys, tag_role を用意しています。
rails_config で環境を切り替えるため、linked_files は database.yml ではなく production.yml を指定しています。
rails_config については
Rails4 rails_config を使って環境ごとの情報を切り替える
を参照してください。
# config valid only for current version of Capistrano
lock '3.4.0'
set :application, ENV['APPLICATION'] || 'sample_app_rails_4'
set :repo_url, 'https://github.com/katsuhiko/sample_app_rails_4.git'
# 独自の設定項目
set :aws_region, ENV['AWS_REGION'] || 'ap-northeast-1'
set :aws_tag_role, ENV['AWS_TAG_ROLE'] || 'rails'
set :deploy_user, ENV['DEPLOY_USER'] || 'ec2-user'
set :deploy_ssh_keys, ENV['DEPLOY_SSH_KEYS'] || '~/.ssh/amazon.pem'
# Default branch is :master
# ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp
set :branch, ENV['BRANCH'] || 'master'
# Default deploy_to directory is /var/www/my_app_name
# set :deploy_to, '/var/www/my_app_name'
set :deploy_to, "/var/apps/#{fetch(:application)}"
# 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 []
# set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.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')
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle')
# 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
# rbenv の設定 see: https://github.com/capistrano/rbenv/
set :rbenv_type, :user # :system or :user
set :rbenv_ruby, '2.2.2'
set :rbenv_prefix, "#{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w(rake gem bundle ruby rails)
set :rbenv_roles, :all # default value
# Rails の設定 see: https://github.com/capistrano/rails/
set :rails_env, 'production'
# Unicorn の設定 see: https://github.com/tablexi/capistrano3-unicorn
# shared_path から取得するディレクトリが意図したディレクトリにならないため修正しました。
# http://stackoverflow.com/questions/20789080/capistrano-3-wrong-path-in-the-shared-path-variable
# set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"
set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" }
set :unicorn_config_path, "config/unicorn.rb"
set :unicorn_rack_env, 'deployment' # "development", "deployment", or "none"
namespace :deploy do
desc 'Restart application'
task :restart do
on roles(:app), in: :sequence, wait: 5 do
invoke 'unicorn:restart'
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
/var/apps/ 配下にデプロイします。
unicorn の socket ファイルは shared 配下に置きます。
アプリに関するファイルすべてを /var/apps/ 配下に集中させたいと考えているからです。
nginx の実行ユーザーは「nginx」、unicorn の実行ユーザーは「ec2-user」で socket ファイルは apps ディレクトリ配下にあるため、以下のように apps ディレクトリに other 実行権を付与します。
drwxr-xr-x 3 ec2-user ec2-user 4096 Jul 18 15:20 apps
production.rb
http://qiita.com/ShotaKameyama/items/c66d64551411897082a1
こちらを参考にし、aws-sdk v2 を利用したデプロイを行います。
今はローカル環境からデプロイしているため Public IP アドレスを取得しています。
本当は、デプロイする EC2 の VPC 内にデプロイサーバーを立ち上げ、Private IP アドレス経由としたほうが良いと思います。
不要な SSH ポートは解放しないほうが良いと思います。
デプロイ対象となるサーバーを動的に取得しているため、staging 等の環境による記述内容の違いは無くなっています。
production.rb の内容は deploy.rb に移動しても良いかもしれません。
require 'aws-sdk-core'
# Shared Credentials を利用する。 see: http://muramasa64.fprog.org/diary/?date=20150217
ec2 = Aws::EC2::Client.new(region: fetch(:aws_region))
# タグと起動中の EC2 で対象を絞り込む。
ec2_filtered = ec2.describe_instances(
filters:[
{name: "tag:env", values: [fetch(:rails_env)]},
{name: "tag:role", values: [fetch(:aws_tag_role)]},
{name: 'instance-state-name', values: ['running']}
])
# TODO deploy 用のサーバーを用意し、private_ip_address を利用して、VPC内から SSH したほうが良い。
instances = ec2_filtered.reservations.map(&:instances).flatten.map(&:public_ip_address)
role :app, *instances
role :web, *instances
role :db, [instances.first]
server *instances,
user: fetch(:deploy_user),
ssh_options: {
forward_agent: true,
auth_methods: ['publickey'],
# if you want to debug capistrano set verbose to debug
# verbose: :debug,
keys: fetch(:deploy_ssh_keys)
}
unicorn.rb
http://unicorn.bogomips.org/examples/unicorn.conf.rb
https://github.com/tablexi/capistrano3-unicorn/blob/master/examples/unicorn.rb
上記の2つをテンプレート/参考として利用しています。
unicorn.sock, unicorn.pid ともに shared 配下に持っていくようにしています。
ENV['BUNDLE_GEMFILE'] に値を設定している箇所は重要です。
この設定がない場合、Gemfile を変更しても unicorn worker プロセスに反映されなません。
woker_processes 数が気になると思いますが、woker プロセス数は capistrano タスクで増減させることができます。デプロイ後、状況に応じて調整します。
# template: http://unicorn.bogomips.org/examples/unicorn.conf.rb
# see: https://github.com/tablexi/capistrano3-unicorn/blob/master/examples/unicorn.rb
app_path = File.dirname(File.dirname(Dir.pwd))
# Sample verbose configuration file for Unicorn (not Rack)
#
# This configuration file documents many features of Unicorn
# that may not be needed for some applications. See
# http://unicorn.bogomips.org/examples/unicorn.conf.minimal.rb
# for a much simpler configuration file.
#
# See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
# documentation.
# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
#worker_processes 4
worker_processes 2
# Since Unicorn is never exposed to outside clients, it does not need to
# run on the standard HTTP port (80), there is no reason to start Unicorn
# as root unless it's from system init scripts.
# If running the master process as root and the workers as an unprivileged
# user, do this to switch euid/egid in the workers (also chowns logs):
# user "unprivileged_user", "unprivileged_group"
# Help ensure your application will always spawn in the symlinked
# "current" directory that Capistrano sets up.
#working_directory "/path/to/app/current" # available in 0.94.0+
working_directory "#{app_path}/current"
# listen on both a Unix domain socket and a TCP port,
# we use a shorter backlog for quicker failover when busy
#listen "/path/to/.unicorn.sock", :backlog => 64
#listen 8080, :tcp_nopush => true
listen "#{app_path}/shared/tmp/sockets/unicorn.sock", :backlog => 64
# nuke workers after 30 seconds instead of 60 seconds (the default)
#timeout 30
timeout 60
# feel free to point this anywhere accessible on the filesystem
#pid "/path/to/app/shared/pids/unicorn.pid"
pid "#{app_path}/shared/tmp/pids/unicorn.pid"
# By default, the Unicorn logger will write to stderr.
# Additionally, ome applications/frameworks log to stderr or stdout,
# so prevent them from going to /dev/null when daemonized here:
#stderr_path "/path/to/app/shared/log/unicorn.stderr.log"
#stdout_path "/path/to/app/shared/log/unicorn.stdout.log"
stderr_path "#{app_path}/shared/log/unicorn.stderr.log"
stdout_path "#{app_path}/shared/log/unicorn.stdout.log"
# combine Ruby 2.0.0dev or REE with "preload_app true" for memory savings
# http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
preload_app true
GC.respond_to?(:copy_on_write_friendly=) and
GC.copy_on_write_friendly = true
# Enable this flag to have unicorn test client connections by writing the
# beginning of the HTTP headers before calling the application. This
# prevents calling the application for connections that have disconnected
# while queued. This is only guaranteed to detect clients on the same
# host unicorn runs on, and unlikely to detect disconnects even on a
# fast LAN.
check_client_connection false
# local variable to guard against running a hook multiple times
run_once = true
# Gemfile を変更したときにも読み込まれるようにする。 see: http://qiita.com/tachiba/items/7eef03cce6a917a957dc
before_exec do |server|
ENV['BUNDLE_GEMFILE'] = "#{app_path}/current/Gemfile"
end
before_fork do |server, worker|
# the following is highly recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
defined?(ActiveRecord::Base) and
ActiveRecord::Base.connection.disconnect!
# Occasionally, it may be necessary to run non-idempotent code in the
# master before forking. Keep in mind the above disconnect! example
# is idempotent and does not need a guard.
if run_once
# do_something_once_here ...
run_once = false # prevent from firing again
end
# The following is only recommended for memory/DB-constrained
# installations. It is not needed if your system can house
# twice as many worker_processes as you have configured.
#
# # This allows a new master process to incrementally
# # phase out the old master process with SIGTTOU to avoid a
# # thundering herd (especially in the "preload_app false" case)
# # when doing a transparent upgrade. The last worker spawned
# # will then kill off the old master process with a SIGQUIT.
# old_pid = "#{server.config[:pid]}.oldbin"
# if old_pid != server.pid
# begin
# sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
# Process.kill(sig, File.read(old_pid).to_i)
# rescue Errno::ENOENT, Errno::ESRCH
# end
# end
old_pid = "#{server.config[:pid]}.oldbin"
if File.exist?(old_pid) && server.pid != old_pid
begin
sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
Process.kill(sig, File.read(old_pid).to_i)
rescue Errno::ENOENT, Errno::ESRCH => e
logger.error e
end
end
#
# Throttle the master from forking too quickly by sleeping. Due
# to the implementation of standard Unix signal handlers, this
# helps (but does not completely) prevent identical, repeated signals
# from being lost when the receiving process is busy.
# sleep 1
end
after_fork do |server, worker|
# per-process listener ports for debugging/admin/migrations
# addr = "127.0.0.1:#{9293 + worker.nr}"
# server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
# the following is *required* for Rails + "preload_app true",
defined?(ActiveRecord::Base) and
ActiveRecord::Base.establish_connection
# if preload_app is true, then you may also want to check and
# restart any other shared sockets/descriptors such as Memcached,
# and Redis. TokyoCabinet file handles are safe to reuse
# between any number of forked children (assuming your kernel
# correctly implements pread()/pwrite() system calls)
end
config.ru
unicorn を安定稼働させるために unicorn-worker-killer を設定します。
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
# Unicorn self-process killer
require 'unicorn/worker_killer'
# Max requests per worker
max_request_min = 3072
max_request_max = 4096
use Unicorn::WorkerKiller::MaxRequests, max_request_min, max_request_max
# Max memory size (RSS) per worker
oom_min = (192 * (1024**2))
oom_max = (256 * (1024**2))
use Unicorn::WorkerKiller::Oom, oom_min, oom_max
run Rails.application
デプロイサーバー(or ローカル環境)のファイル
credentials
AWS への接続は Shared Credentials を利用します。
デプロイするサーバーに ~/.aws/credentials を作成し Key 情報を設定します。
Key 情報を作成するのは以下サイトが参考になると思います。
http://dev.classmethod.jp/cloud/aws-cli-credential-config/
[default]
aws_access_key_id = XXXXXXXX
aws_secret_access_key = XXXXXXXX
amazon.pem
SSH 接続できるように pem ファイルを用意し、deploy.rb に指定した場所に配置します。
EC2 サーバー(Rails サーバー)のファイル
タグ作成
AWS コンソールから、デプロイする EC2 サーバーに以下のタグを作成します。
キー | 値 |
---|---|
env | production |
role | rails |
production.yml
bundle exec cap production deploy:check
を実施します。
linked_files を用意していないためエラーは発生しますが、必要なディレクトリを作成してくれます。
secret_key_base は bundle exec rake secret
で生成した値を設定します。
DBについては、各環境に合わせて設定します。
secret:
secret_key_base: xxxxxxx
databases:
pool: 5
host: endpoint
port: 3306
username: xxxxxxxx
password: xxxxxxxx
production:
database: sample_app_rails_4_production
nginx.conf
/var/apps/ 配下にアプリに関するファイルをすべて持ってきたいため、nginx.conf を /var/apps/sample_app_rails_4/shared/config/ 配下に作ります。
本来は、サブドメイン名でバーチャルホストを切るのですのが、今回は、ポート番号でわけています。
upstream sample_app_rails_4 {
server unix:/var/apps/sample_app_rails_4/shared/tmp/sockets/unicorn.sock fail_timeout=0;
}
# redirect https from http
#server {
# listen 80;
# server_name sample.example.com;
#
# location / {
# return 301 https://$host$request_uri;
# }
#}
server {
listen 443;
#server_name sample.example.com;
access_log /var/log/nginx/sample_app_rails_4.access.log;
error_log /var/log/nginx/sample_app_rails_4.error.log;
ssl on;
ssl_certificate /etc/nginx/certs/local-ssl.crt;
ssl_certificate_key /etc/nginx/certs/local-ssl.key;
root /var/apps/sample_app_rails_4/current/public;
proxy_connect_timeout 70;
proxy_read_timeout 70;
proxy_send_timeout 70;
location / {
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_redirect off;
if (!-f $request_filename) {
proxy_pass http://sample_app_rails_4;
break;
}
}
}
/etc/nginx/nginx.conf には、/var/apps/ 配下の confg を読み込むようにします。include 部分が該当の処理です。
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
#error_log /var/log/nginx/error.log notice;
#error_log /var/log/nginx/error.log info;
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;
#
server_tokens off;
#
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
include /etc/nginx/conf.d/*.conf;
include /var/apps/*/shared/config/nginx.conf;
index index.html index.htm;
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;
location / {
}
# redirect server error pages to the static page /40x.html
#
error_page 404 /404.html;
location = /40x.html {
}
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
}
作成した nginx.conf を読み込むために nginx を restart します。
$ sudo service nginx restart
デプロイの実施
これでデプロイの準備ができました。
デプロイサーバー(or ローカル環境)で sample_app_rails_4 を clone したディレクトリに移動し、デプロイを実施します。
$ bundle exec cap -t production deploy
長かったですが、デプロイについては以上です。
Shared Credentials の内容が正しいにも関わらず、以下のエラーが発生する場合、時刻にズレが発生していないか確認してください。
Aws::EC2::Errors::AuthFailure: AWS was not able to validate the provided access credentials
よく使う capistrano タスク
よく使うタスクを載せます。
デプロイする
-t (--trace) オプションをつけています。どんな順番でタスクが処理されているかがわかるので好きです。
$ bundle exec cap -t production deploy
環境変数を渡したい場合、後ろに指定しています。
$ bundle exec cap -t production deploy BRANCH=feature/new-actions
タスクを確認する
どんなタスクがあるかを確認できます。
$ bundle exec cap -T
unicorn を停止する
設定を反映させるために停止したい場合、使います。
個別に kill するより便利です。
$ bundle exec cap -t production unicorn:stop
unicorn を開始する
unicorn を開始するときにも capistrano は便利です。
$ bundle exec cap -t production unicorn:start
unicorn worker プロセスを増やす
unicorn worker プロセス数も capistrano から調整できます。
$ bundle exec cap -t production unicorn:add_worker
unicorn worker プロセスを減らす
$ bundle exec cap -t production unicorn:remove_worker
感想
情報はたくさんあるので調査しやすかったです。
ソース/デプロイサーバー/EC2 サーバーとそれぞれに記載することがあり、分かりづらくなっています。読み返しながら整理していく予定です。
デプロイツール/構成管理ツール、それぞれやるべきことがあると思います。
どちらか一方のみではなく、上手に両方を使っていきたいです。