Marcio Trindade

Nginx + Unicorn

Desde que a Phusion lançou o passenger eu sempre o utilizei em conjunto com o apache que já era familizarizado.

Quando trabalhei na Digital Pages me deparei com o pessoal utilizando o passenger com nginx, pra falar a verdade eu só tinha escutado falar deste servidor web e nem dei muita bola, além de que a aplicação caia de tempos em tempos e o log ficava poluído com algumas mensagens que o passenger adicionava, mostrei para o pessoal como utilizar o apache no lugar do nginx, a aplicação acabou ficando bem estável e optamos por colocar em produção.

De lá pra cá procurei evitar o nginx porém o servidor que está rodando este blog também está rodando outras aplicações como o gettherank e comecei a ter alguns problemas com memória, visto que o passenger não consegue lidar muito bem com os processos que ficam muito tempo ativos aumentando o consumo ao extremo, seguindo alguns conselhos resolvi dar mais uma chance para o nginx, porém não com o passenger e sim com o unicorn criado pelo Chris Wanstrath pra suportar o Github.

Confesso que antes mesmo de usar o unicorn pensei que seria uma tarefa muito complexa, mas hoje depois de configurar este servidor que você está acessando devo dizer que é tão fácil quanto, então resolvi compartilhar a configuração que utilizei tanto pro unicorn como para o nginx e quem sabe isso não o ajude a usar esta combinação também.

config/unicorn.rb
working_directory "/var/www/marciotrindade/current"
pid               "/var/www/marciotrindade/shared/pids/unicorn.pid"
stderr_path       "/var/www/marciotrindade/shared/log/unicorn.log"
stdout_path       "/var/www/marciotrindade/shared/log/unicorn.log"

preload_app true

listen 5002
worker_processes 2
timeout 30

Todas estas e as demais configurações do unicorn você pode encontrar aqui

/etc/nginx/sites-availabel/marciotrindade.com
server {
  listen 80;
  server_name marciotrindade.com www.marciotrindade.com;

  if ($host = 'www.marciotrindade.com' ) {
    rewrite  ^/(.*)$  http://assets.marciotrindade.com/$1  permanent;
  }

  root /var/www/marciotrindade/current/public;

  try_files $uri/index.html $uri.html $uri @unicorn;

  location ~ ^/assets/ {
    expires 1y;
    add_header Cache-Control public;
    add_header Last-Modified "";
    add_header ETag "";
    break;
  }

  location @unicorn {
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_redirect off;
    proxy_pass http://localhost:5002;
  }

  client_max_body_size 4G;
  keepalive_timeout 10;
  error_page 500 502 503 504 /500.html;
  location = /500.html {
    root /var/www/marciotrindade/current/public;
  }
}

Recomendo ler a wiki do nginx onde você irá encontrar mais detalhes destas e outras configurações interessantes pra melhorar a velocidade da sua aplicação.

Estas configurações que utilizei foram retiradas do prórpio código do projeto que mostra estes e outros tantos exemplos, incluindo o arquivo abaixo pra iniciar, reiniciar e para o unicorn.

init.sh
#!/bin/sh
set -e
# Example init script, this can be used with nginx, too,
# since nginx and unicorn accept the same signals

# Feel free to change any of the following variables for your app:
TIMEOUT=${TIMEOUT-60}
APP_ROOT=/var/www/marciotrindade/current
PID=$APP_ROOT/tmp/pids/unicorn.pid
CMD="unicorn -D -c $APP_ROOT/config/unicorn.rb -E production"
action="$1"
set -u

old_pid="$PID.oldbin"

cd $APP_ROOT || exit 1

sig () {
  test -s "$PID" && kill -$1 `cat $PID`
}

oldsig () {
  test -s $old_pid && kill -$1 `cat $old_pid`
}

case $action in
start)
  sig 0 && echo >&2 "Already running" && exit 0
  $CMD
  ;;
stop)
  sig QUIT && exit 0
  echo >&2 "Not running"
  ;;
force-stop)
  sig TERM && exit 0
  echo >&2 "Not running"
  ;;
restart|reload)
  sig HUP && echo reloaded OK && exit 0
  echo >&2 "Couldn't reload, starting '$CMD' instead"
  $CMD
  ;;
upgrade)
  if sig USR2 && sleep 2 && sig 0 && oldsig QUIT
  then
    n=$TIMEOUT
    while test -s $old_pid && test $n -ge 0
    do
      printf '.' && sleep 1 && n=$(( $n - 1 ))
    done
    echo

    if test $n -lt 0 && test -s $old_pid
    then
      echo >&2 "$old_pid still exists after $TIMEOUT seconds"
      exit 1
    fi
    exit 0
  fi
  echo >&2 "Couldn't upgrade, starting '$CMD' instead"
  $CMD
  ;;
reopen-logs)
  sig USR1
  ;;
*)
  echo >&2 "Usage: $0 <start|stop|restart|upgrade|force-stop|reopen-logs>"
  exit 1
  ;;
esac

Aproveitei e mudei o meu arquivo de deploy do capistrano pra utilizar este script pra reiniciar a minha aplicação, mas como eu utilizo os releases o restart não estava funcionando então passei a utilizar o stop seguido pelo start.

config/deploy.rb
...
namespace :deploy do
  task :start do ; end
  task :stop do ; end
  task :restart, :roles => :app, :except => { :no_release => true } do
    run "#{deploy_to}/init.sh stop && sleep 2 && #{deploy_to}/init.sh start"
  end
end
...

PS.: No dia 24 de outubro o Rain Bates publicou um screencast pro mostrando também esta combinação com uma configuração muito parecida com a que mostrei acima.