Un peu d’infra aujourd’hui! Tout d’abord un grand merci à Bertrand Paquet de m’avoir formé à ces outils.
Pour faire tourner une application rails en production, on utilise souvent le duo passenger/apache. Personnellement j’ai lâché apache depuis longtemps pour Nginx. On ne va pas rentrer dans le débat du pourquoi mais essentiellement car Nginx est plus léger et plus performant.
Du coup pour mes applications rails j’utilisais le module nginx passenger. Sauf que celui ci est plein de bugs et très gourmand en ressources. La solution : unicorn.
Unicorn est un serveur rails léger pouvant être en écoute sur une socket unix. L’idée est donc de faire en sorte que Nginx et Unicorn communiquent à travers une socket unix. Un autre avantage du duo unicorn/nginx est qu’il permet d’heberger des applications rails sur autre chose que la racine d’un nom de domaine (par exemple dreamisle.net/monappli), hors avec passenger c’est très compliqué à faire.
Mais voyons un peu un exemple, admettons que je souhaite héberger une application Rails sur /monappli dans sur l’url mesapplis.dreamisle.net.
Nginx
Voyons déjà la configuration Nginx
upstream unicorn_upstream {
server unix:/home/leakim/ruby/monappli/shared/unicorn.sock fail_timeout=0;
}
server {
access_log /var/log/nginx/rails-access.log;
error_log /var/log/nginx/rails-access.log;
server_name mesapplis.dreamisle.net;
root /home/leakim/ruby/dl/shared/www;
location /monappli {
proxy_set_header Host $http_host;
if (!-f $request_filename) {
proxy_pass http://unicorn_upstream;
break;
}
}
error_page 401 403 /401.html;
error_page 404 /404.html;
error_page 500 501 502 503 504 505 /500.html;
}
La partie interessante se situe dans le location /monappli. L’idée ici est que si nginx ne trouve pas le fichier demandé, il forward la requête sur la socket unix. la déclaration de la socket unix en haut du fichier de configuration est tout ce qu’il y a de plus classique.
Unicorn
Unicorn est en fait une commande shell pour lancer un serveur rails que l’on configure via un script ruby. Configurons maintenant Unicorn.
Script de configuration unicorn
Celui-ci se configure via un script ruby. Voici un exemple de configuration.
worker_processes 3
app_directory = '/home/leakim/ruby/monappli'
working_directory "#{app_directory}/current"
listen "unix:#{app_directory}/shared/unicorn.sock", :backlog => 2048
timeout 600
preload_app true
pid "#{app_directory}/shared/pids/unicorn.pid"
stderr_path "#{app_directory}/shared/log/unicorn.stderr.log"
stdout_path "#{app_directory}/shared/log/unicorn.stdout.log"
if GC.respond_to?(:copy_on_write_friendly=)
GC.copy_on_write_friendly = true
end
before_fork do |server, worker|
old_pid = "#{app_directory}/shared/pids/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
# the following is recomended for Rails + "preload_app true"
# as there's no need for the master process to hold a connection
if defined?(ActiveRecord::Base)
ActiveRecord::Base.connection.disconnect!
end
end
after_fork do |server, worker|
if defined?(ActiveRecord::Base)
ActiveRecord::Base.establish_connection
end
end
Config.ru
Il vous faut aussi compléter le fichier config.ru à la racine de votre application avec quelque chose de similaire :
# This file is used by Rack-based servers to start the application.
require ::File.expand_path('../config/environment', __FILE__)
map ActionController::Base.config.relative_url_root || "/" do
run Monappli::Application
end
Script de lancement d’unicorn
Notez le RAILS_RELATIVE_ROOT_URL qui indique à unicorn l’url relative utilisée.
#! /bin/sh
### BEGIN INIT INFO
# Provides: unicorn
# Required-Start: $local_fs $remote_fs $network $syslog
# Required-Stop: $local_fs $remote_fs $network $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: starts unicorn web server
# Description: starts unicorn web server
### END INIT INFO
NAME="unicorn"
USER="leakim"
DAEMON="/home/$USER/.rvm/bin/rvm-shell"
APP_DIRECTORY="/home/leakim/ruby/monappli"
PID_FILE="$APP_DIRECTORY/shared/pids/unicorn.pid"
CONFIG_FILE="$APP_DIRECTORY/shared/unicorn.conf.rb"
UNICORN_CMD="unicorn_rails"
export RAILS_RELATIVE_URL_ROOT="/monappli"
test -x $DAEMON || exit 4
test -d "$APP_DIRECTORY/current" || exit 5
test -f $CONFIG_FILE || exit 6
CMD="cd $APP_DIRECTORY/current && source .rvmrc && bundle exec $UNICORN_CMD -E production -D -c $CONFIG_FILE"
set -e
. /lib/lsb/init-functions
kill_unicorn() {
SIGNAL=$1
if [ ! "$PID_FILE" = "" ]; then
if [ -f $PID_FILE ]; then
kill $SIGNAL `cat $PID_FILE` || true
fi
fi
}
case "$1" in
start)
echo -n "Starting $NAME: "
start-stop-daemon -c $USER --start --exec $DAEMON -- -c "$CMD" || exit 1
echo "$NAME."
;;
stop)
echo -n "Stopping $NAME: "
kill_unicorn
echo "$NAME."
;;
graceful_restart)
echo -n "Graceful restarting $DESC: "
kill_unicorn "-USR2"
echo "$NAME."
;;
restart)
echo -n "Restarting $NAME: "
kill_unicorn
sleep 1
start-stop-daemon -c $USER --start --exec $DAEMON -- -c "$CMD" || exit 1
echo "$NAME."
;;
status)
status_of_proc -p $PID_FILE $UNICORN_CMD $UNICORN_CMD && exit 0 || exit $?
;;
*)
echo "Usage: $NAME {start|stop|restart|status|graceful_restart}" >&2
exit 1
;;
esac
exit 0
Capistrano
Capistrano est un outil de gestion de déploiement distribué. L’idée est de pouvoir depuis son poste local, taper “cap deploy” pour deployer l’application en production. Capistrano peut aussi être utilisé en mode multistage pour utiliser plusieurs serveurs/environnements différents pour déployer. Je vous invite à découvrir l’outil ici : Capistrano. Ce fichier nommé deploy.rb doit être placé dans le repertoire config/ de votre application rails. Il vous permettra une fois la gem install (gem install capistrano), de lancer en local “cap deploy” pour déployer.
Si vous n’utilisez pas RVM il faut bien entendu supprimer tous les appels à celui-ci.
set :ssh_options, { :forward_agent => true } # utilise la clef ssh local et utilise l'option ssh forward agent, ce qui permet par exemple à git de cloner avec votre clef local
set :default_shell, "PATH=$HOME/.rvm/bin:$PATH bash"
set :user, 'leakim'
set :rails_relative_url_root, "/monappli"
set :deploy_to, "/home/leakim/ruby/monappli/"
set :use_sudo, false
server "monserver.net", :web, :app, :db
set :scm, :git
set :application, "monappli"
set :repository, "git@github.com:user/repo.git"
set :deploy_via, :remote_cache
set :branch, 'master'
namespace :deploy do
task :start do
run "/etc/init.d/unicorn start"
end
task :stop do
run "/etc/init.d/unicorn stop"
end
task :restart, :roles => :app do
run "/etc/init.d/unicorn restart"
end
end
after 'deploy:finalize_update', 'rvm', 'bundle', 'db_config', 'db_migrate', 'precompile_assets'
task :rvm, :roles => :app do
run "cd #{release_path} && source .rvmrc"
end
task :bundle, :roles => :app do
run "cd #{release_path} && source .rvmrc && bundle --without development"
end
task :precompile_assets, :roles => :app do
prefix = rails_relative_url_root && rails_relative_url_root.size > 0 ? "RAILS_RELATIVE_URL_ROOT=#{rails_relative_url_root}" : ""
run "cd #{release_path} && source .rvmrc && #{prefix} RAILS_ENV=production rake assets:precompile --trace"
end
task :db_config, :roles => :app do
run "cd #{release_path} && ln -s #{shared_path}/database.yml config/database.yml && ln -s #{shared_path}/configuration.yml config/configuration.yml"
end
task :db_migrate, :roles => :db do
run "cd #{release_path} && source .rvmrc && RAILS_ENV=production rake db:migrate --trace"
end