Provisionando Aplicações Rails com Mina, Unicorn e Nginx
Depois de um tempo usando Passenger, migrei para o Unicorn, nesse post irei descrever o setup que tenho utilizado para provisionar aplicações Rails em produção desde então.
Como o título já diz, uso Nginx como servidor http, mas nesse post não mostrarei como instalá-lo, você pode ver a wiki oficial do projeto pra isso.
Vamos começar adicionando a gema do Unicorn em nosso Gemfile
:
Como uso Unicorn apenas em produção, então deixo dentro do bloco production
.
Criaremos agora um arquivo de configuração do Unicorn, geralmente colocamos em config/unicorn.rb
, com o seguinte conteúdo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# Set your full path to application.
app_dir = File.expand_path('../../', __FILE__)
shared_dir = File.expand_path('../../../shared/', __FILE__)
# Set unicorn options
worker_processes 1
preload_app true
timeout 30
# Fill path to your app
working_directory app_dir
# Set up socket location
listen "#{shared_dir}/sockets/unicorn.sock", :backlog => 64
# Loging
stderr_path "#{shared_dir}/log/unicorn.stderr.log"
stdout_path "#{shared_dir}/log/unicorn.stdout.log"
# Set master PID location
pid "#{shared_dir}/pids/unicorn.pid"
before_fork do |server, worker|
defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!
old_pid = "#{server.config[:pid]}.oldbin"
if File.exists?(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
# someone else did our job for us
end
end
end
after_fork do |server, worker|
defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end
before_exec do |server|
ENV['BUNDLE_GEMFILE'] = "#{app_dir}/Gemfile"
end
O arquivo é auto explicativo e bem fácil de entender, você pode alterar as configurações como quiser. Uma coisa importante que devemos atentar aqui é o caminho onde o socket ficará armazenado (linha 14), precisaremos dele a seguir.
Agora, vamos configurar nossa entrada no servidor, eu costumo criar um arquivo com o nome da aplicação (nesse caso my_app
) dentro da pasta sites-enabled
no diretório do Nginx.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# /opt/nginx/sites-enabled/my_app
upstream my_app {
# socket location
server unix:/home/user/my_app/shared/sockets/unicorn.sock fail_timeout=0;
}
server {
listen 80;
server_name my_app.com www.my_app.com;
root /home/user/my_app/current/public/;
location / {
# if file does not exist, use the last, in this case, @app
try_files $uri $uri/index.html $uri.html @app;
}
location @app {
# must match the name of upstream directive which is defined above
proxy_pass http://my_app;
proxy_redirect off;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header Host $http_host;
# below line is only required for HTTPS
# proxy_set_header X-Forwarded-Proto https;
}
# cache control
location ~ ^/(assets|images|javascripts|stylesheets|swfs|system)/ {
expires max;
add_header Cache-Control public;
add_header Last-Modified "";
add_header ETag "";
break;
}
}
Veja a linha 4, ela deve apontar para o caminho onde o socket do Unicorn está.
Com isso já conseguimos executar nossa aplicação. Vamos então configurar o Mina para fazer deploy.
O setup é bem rápido e fácil, veja o guia oficial para isso.
Após fazer um deploy, precisamos reiniciar o servidor pra recarregar a aplicação, o Unicorn armazena o número do processo rodando num arquivo pid que configuramos no unicorn.rb
acima, precisamos matar esse processo e iniciar o servidor de aplicação novamente, isso fica fácil com a gema mina-unicorn
pois ela faz tudo pra nós, só precisamos inclui-la, passar o caminho do arquivo pid e chamar a tarefa unicorn:restart
, veja abaixo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# config/deploy.rb
require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rvm'
require 'mina/unicorn'
set :domain, 'XXX.XXX.XX.XXX' # ip address
set :deploy_to, '/home/user/my_app'
set :repository, 'git@github.com:user/my_app.git'
set :branch, 'master'
set :user, 'user' # Username in the server to SSH to.
set :unicorn_pid, "#{deploy_to}/shared/pids/unicorn.pid"
# Set forward_agent to use local SSH keys
set :forward_agent, true
# Manually create these paths in shared/ (eg: shared/config/database.yml) in your server.
# They will be linked in the 'deploy:link_shared_paths' step.
set :shared_paths, ['config/database.yml', 'log']
# This task is the environment that is loaded for most commands, such as
# `mina deploy` or `mina rake`.
task :environment do
# For those using RVM, use this to load an RVM version@gemset.
invoke :'rvm:use[ruby-2.1.2]'
end
# Put any custom mkdir's in here for when `mina setup` is ran.
# For Rails apps, we'll make some of the shared paths that are shared between
# all releases.
task :setup => :environment do
queue! %[mkdir -p "#{deploy_to}/shared/log"]
queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/log"]
queue! %[mkdir -p "#{deploy_to}/shared/config"]
queue! %[chmod g+rx,u+rwx "#{deploy_to}/shared/config"]
queue! %[touch "#{deploy_to}/shared/config/database.yml"]
queue %[echo "-----> Be sure to edit 'shared/config/database.yml'."]
end
desc "Deploys the current version to the server."
task :deploy => :environment do
deploy do
# Put things that will set up an empty directory into a fully set-up
# instance of your project.
invoke :'git:clone'
invoke :'deploy:link_shared_paths'
invoke :'bundle:install'
invoke :'rails:db_migrate'
invoke :'rails:assets_precompile'
invoke :'deploy:cleanup'
to :launch do
invoke :'unicorn:restart'
end
end
end
Pronto, temos nosso setup concluído (não esqueça de alterar acima com dados da sua aplicação). Deixe sua opinião nos comentários.
Até mais.