miércoles, 5 de marzo de 2014

Nginx + Unicorn + Capistrano 3 + Rails 3

Como ya expliqué en el último post, conseguí configurar una instancia de EC2 con Ubuntu 13.10 para tener una aplicación de Ruby on Rails, con Ruby en su versiíon 2.0.0 y rails en la 3.2 (en breve haré el upgrade).

https://github.com/blog/517-unicorn
Esquema de la arquitectura
Ahora explicaré la mejor forma, al menos para mi, de montar todo el tinglado para hacer despliegues más o menos cómodos y rápidos. Comenzaré desde fuera para dentro, donde fuera es el servidor web de aplicaciones, para el que elegí Nginx, que es más ligero (una microinstancia de EC2 con 512MB no está para derrochar!). Tras Nginx tenemos Unicorn, el servidor HTTP, que será el que se comunique directamente con nuestra App Rails.

La comunicación entre Unicorn y Nginx se realizará por medio de un socket compartido.



Nginx

La instalación de Nginx es bastante sencilla:
add-apt-repository ppa:nginx/$nginx
apt-get update 
apt-get install nginx

Una vez instalado, pasamos a la parte de configuración. En mi caso sólo he modificado el fichero /etc/nginx/sites-available/default, aunque se podría haber hecho lo mismo en el ngingx.conf. El fichero ha quedado tal que así:


upstream app {
    server unix:/var/www/myapp/tmp/sockets/unicorn.sock fail_timeout=0;
} 
server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;


    root /var/www/myapp/current/public;
    #index index.html index.htm;

    # Make site accessible from http://localhost/
    server_name localhost;

    if ($request_method !~ ^(GET|HEAD|PUT|POST|DELETE|OPTIONS)$ ){
            return 405;
    }

    location ~ ^/assets/  {  
            gzip_static on;
            expires     max;
            add_header  Cache-Control public;
    } 

    location / {
            # First attempt to serve request as file, then
            # as directory, then fall back to displaying a 404.
            try_files $uri/index.html $uri.html $uri @app; 
            # Uncomment to enable naxsi on this location
            # include /etc/nginx/naxsi.rules
    }
    location @app {
            proxy_pass http://app;
    }

    location /doc/ {
            alias /usr/share/doc/;
            autoindex on;
            allow 127.0.0.1;
            allow ::1;
            deny all;
    }
}

Explicado brevemente: definimos un upstream (app) que leerá el socket donde lo cree el unicorn. Definimos el public de la aplicación rails que será de donde coja los assets, y para lo demás irá a nuestro upstream.

Unicorn

La configuración de Unicorn para dar el servicio es bastante sencilla:


root = "/var/www/myapp"
working_directory "#{root}/current"
pid "#{root}/tmp/pids/unicorn.pid"
stderr_path "#{root}/shared/logs/unicorn-err.log"
stdout_path "#{root}/shared/logs/unicorn-out.log"

worker_processes Integer(ENV["WEB_CONCURRENCY"] || 3)
timeout 15
preload_app true

listen "#{root}/tmp/sockets/unicorn.sock", :backlog => 64
listen 8080, :tcp_nopush => true

GC.respond_to?(:copy_on_write_friendly=) and
    GC.copy_on_write_friendly = true

before_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
    Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
      ActiveRecord::Base.connection.disconnect!
end

after_fork do |server, worker|
  Signal.trap 'TERM' do
    puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
      ActiveRecord::Base.establish_connection

Como vemos, la configuración es bastante similar a la que tendríamos con Heroku, con la salvedad de que le decimos a Unicorn que debe crear un socket en la ruta dada.

Capistrano 3

El despliegue de la aplicación, al igual que los arranques y paradas de Unicorn, se harán por medio de Capistrano. La configuración la mostraré en el próximo post ;)