SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our User Agreement and Privacy Policy.
SlideShare uses cookies to improve functionality and performance, and to provide you with relevant advertising. If you continue browsing the site, you agree to the use of cookies on this website. See our Privacy Policy and User Agreement for details.
Successfully reported this slideshow.
Activate your 14 day free trial to unlock unlimited reading.
1.
Practical Chef and Capistrano
For Your Rails Application
Dan Ivovich
SLS Internal Dec 2012
12/17/12
2.
Dan Ivovich
SmartLogic Solutions
http://smartlogicsolutions.com
Twitter - @danivovich
Dan Ivovich on GitHub
3.
What is the goal?
● Build a machine that can run the application
quickly and repeatedly
● Make deploying the app, upgrading
components, adding components, etc seamless
and easy
4.
Who does what?
● Chef
○ User / SSH Keys
○ Web server
○ Database
○ Postfix
○ Redis / Memcached
○ Monit
○ NewRelic Server monitoring
○ /etc/hosts
○ rbenv & Ruby
○ Application binary dependencies (i.e.
Sphinx)
5.
Who does what?
● Capistrano
○ Virtual Hosts
○ Unicorn init.d script
○ Unicorn.rb
○ Monit process monitors
○ Normal Capistrano Stuff
6.
Why both?
● Use each for what it is best at
● Chef is for infrastructure
● Capistrano is for the app
● Could have more than one Capistrano app with
the same Chef config
● Chef config changes infrequently, Capistrano
config could change more frequently
7.
How? - Chef
● Standard Recipes
● Custom Recipes
● Recipes assigned to Roles
● Roles assigned to Nodes
● Nodes with attributes to tailor the install
9.
Chef - Getting started
● Gemfile
● knife kitchen chef-repo
○ Create the folder structure you need
● Solo specific stuff (chef-repo/.chef/knife.rb)
● knife cookbook site install nginx
○ Get the nginx cookbook and anything it
needs
11.
package "logrotate"
rbenv_ruby node['your_app']['ruby_version']
rbenv_gem "bundler" do
ruby_version node['your_app']['ruby_version']
end
template "/etc/hosts" do
source "hosts.erb"
mode "0644"
owner "root"
group "root"
end
12.
directory "#{node['your_app']['cap_base']}" do
action :create
owner 'deploy'
group 'deploy'
mode '0755'
end
directory "#{node['your_app']['deploy_to']}/shared" do
action :create
owner 'deploy'
group 'deploy'
mode '0755'
end
template "#{node['your_app']['deploy_to']}/shared/database.yml"
do
source 'database.yml.erb'
owner 'deploy'
group 'deploy'
mode '0644'
end
20.
Capistrano - Getting Started
● Add capistrano and capistrano-ext
● Capify
● deploy.rb
21.
Capistrano - deploy.rb
require 'bundler/capistrano'
require 'capistrano/ext/multistage'
load 'config/recipes/base'
load 'config/recipes/nginx'
load 'config/recipes/unicorn'
load 'config/recipes/monit'
set :default_environment, {
'PATH' => "/opt/rbenv/shims:/opt/rbenv/bin:$PATH",
'RBENV_ROOT' => "/opt/rbenv"
}
set :bundle_flags, "--deployment --quiet --binstubs --shebang ruby-local-exec"
set :use_sudo, false
set :application, 'your_app'
set :repository, 'git@github.com:you/your_app.git'
set :deploy_to, '/home/deploy/apps/your_app'
set :deploy_via, :remote_cache
22.
Capistrano - deploy.rb
set :branch, 'master'
set :scm, :git
set :target_os, :ubuntu
set :maintenance_template_path, File.expand_path("..
/recipes/templates/maintenance.html.erb", __FILE__)
default_run_options[:pty] = true
ssh_options[:forward_agent] = true
namespace :custom do
desc 'Create the .rbenv-version file'
task :rbenv_version, :roles => :app do
run "cd #{release_path} && rbenv local 1.9.2-p320"
end
end
before 'bundle:install', 'custom:rbenv_version'
23.
Capistrano - recipes/base.rb
def template(from, to)
erb = File.read(File.expand_path("../templates/#{from}", __FILE__))
put ERB.new(erb).result(binding), to
end
def set_default(name, *args, &block)
set(name, *args, &block) unless exists?(name)
end
24.
Capistrano - recipes/monit.rb
set_default(:alert_email, "dan@smartlogicsolutions.com")
namespace :monit do
desc "Setup all Monit configuration"
task :setup do
unicorn
syntax
restart
end
after "deploy:setup", "monit:setup"
task(:unicorn, roles: :app) { monit_config "unicorn" }
%w[start stop restart syntax].each do |command|
desc "Run Monit #{command} script"
task command do
with_user "deploy" do
sudo "service monit #{command}"
end
end
end
end
26.
Capistrano - recipes/nginx.rb
namespace :nginx do
desc "Setup nginx configuration for this application"
task :setup, roles: :web do
template "nginx_unicorn.erb", "/tmp/nginx_conf"
sudo "mv /tmp/nginx_conf /etc/nginx/sites-enabled/#{application}"
sudo "rm -f /etc/nginx/sites-enabled/default"
restart
end
after "deploy:setup", "nginx:setup"
%w[start stop restart].each do |command|
desc "#{command} nginx"
task command, roles: :web do
sudo "service nginx #{command}"
end
end
end
31.
Capistrano - templates/unicorn.rb.erb
before_fork do |server, worker|
# Disconnect since the database connection will not carry over
if defined? ActiveRecord::Base
ActiveRecord::Base.connection.disconnect!
end
# Quit the old unicorn process
old_pid = "#{server.config[: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
32.
Capistrano - templates/unicorn.rb.erb
after_fork do |server, worker|
# Start up the database connection again in the worker
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
child_pid = server.config[:pid].sub(".pid", ".#{worker.nr}.pid")
system("echo #{Process.pid} > #{child_pid}")
end
33.
Capistrano - t/monit/unicorn.erb
check process <%= application %>_unicorn with pidfile <%= unicorn_pid %>
start program = "/etc/init.d/unicorn_<%= application %> start"
stop program = "/etc/init.d/unicorn_<%= application %> stop"
<% unicorn_workers.times do |n| %>
<% pid = unicorn_pid.sub(".pid", ".#{n}.pid") %>
check process <%= application %>_unicorn_worker_<%= n %> with pidfile <%= pid %>
start program = "/bin/true"
stop program = "/usr/bin/test -s <%= pid %> && /bin/kill -QUIT `cat <%= pid %>`"
if mem > 200.0 MB for 1 cycles then restart
if cpu > 50% for 3 cycles then restart
if 5 restarts within 5 cycles then timeout
alert <%= alert_email %> only on { pid }
if changed pid 2 times within 60 cycles then alert
<% end %>
38.
Ready?!? Here we go!
1. New VM at my_web_app in your .ssh/config
2. Create chef-repo/nodes/my_web_app.json
3. In chef-repo:
bundle exec knife bootstrap node_name
--template-file=ubuntu-12.04-lts.erb
4. bundle exec knife cook root@my_web_app
5. In app directory:
create/edit config/deploy/staging.rb
6. cap staging deploy:setup deploy:migrations
7. Hit the bars
39.
Thoughts....
● Vagrant and VMs are you friend. Rinse and repeat
● It is ok to tweak your Chef stuff and re-cook, but I always
like to restart with a fresh VM once I think I'm done
● Capistrano tweaks should be easy to apply, especially with
tasks like nginx:setup, unicorn:setup etc.
● Chef issues are harder to debug and more frustrating than
Capistrano issues, another reason to put more app specific
custom stuff in Capistrano and do standard things in Chef