Programador ajudante e aprendiz da comunidade open source.

Ruby, Unicorn e Nginx na Amazon EC2

Se você esta lendo este artigo através do blog, então esta acessando uma estrutura igual a que iremos criar. Irei mostrar como instalar e configurar todos os elementos citados no título deste post, passo-a-passo.

Objetivo

Será mostrado como instalar o Ruby junto ao Rails para ser rodado no Unicorn e servido pelo NGINX na Amazon EC2.

Instância da Amazon EC2

Para criar a instância, siga o artigo Amazon EC2 com Java, MySQL e Tomcat, porém escolha uma instância mais recente do Ubuntu. Enquanto escrevo ou atualizo este artigo, a versão 14.04.

Setup

Após logarmos no servidor com algo do tipo:

ssh -i ~/.ssh/{{app_name}}.pem [email protected]{{amazon_dns}}

Vamos fazer download de um arquivo do projeto Installer:

wget https://raw.githubusercontent.com/wbotelhos/installers/master/amazon/init.sh

E então, para preparar o ambiente com atualização das libs do Ubuntu e afins, vamos executar:

chmod +x init.sh
sudo ./init.sh

Git

Vamos executar a tarefa de instalação e ativação com a seguinte versão:

./git/git.sh activate 1.9.0

Configuração de Path

sudo ./amazon/path.sh

Ruby

Ruby

Se você já pensou no RVM, sem problemas. Também utilizo o mesmo localmente, porém no servidor iremos compilar tudo, deixando o sistema bem enxuto. (:

Vamos executar a tarefa de instalação e ativação com a seguinte versão:

sudo ./ubuntu/ruby/ruby.sh activate 2.1.2

Vá tomar um café...

Ao fim, faça logout e reconecte-se ao servidor:

exit

Verifique se tudo deu certo:

ruby -v
# ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-linux]

RubyGems

RubyGems

O RubyGems é um repositório de gems no qual iremos fazer o download automáticos das nossas gems. Para criar no home do usuário o arquivo de configuração .gemrc, execute:

./rubygems/rubygems.sh install

Bundler

O Bundler é uma ferramenta para garantir a atualização correta das nossas gems, e iremos instalá-lo:

./bundler/bundler.sh install

NGINX

NGINX

Temos boas opção de servidores web para aplicações Ruby como é o caso do Apache, mas por vários motivos iremos utilizar o NGINX.

Como já estamos configurando o servidor, precisamos já decidir onde iremos armazenar o nosso site e qual o nome de usuário que será usado para acessar os arquivos do mesmo. Por padrão, o site é armazenado em /var/www e o nome de usuário é ubuntu por conta do padrão da Amazon. Você pode alterar isso na seção Configurations do arquivo ./nginx/nginx.sh.

Da mesma forma podemos adicionar ou remover módulos do NGINX, para isso, edite o arquivo já citado e altere o método configure com os módulos desejados.

Vamos executar a tarefa de instalação e ativação com a seguinte versão:

./nginx/nginx.sh activate 1.7.4

Verifique se tudo deu certo:

nginx -v
# nginx version: nginx/1.7.4

É possível ver as informações completas incluindo os módulos instalando utilizando o parâmetro V.

nginx -V

Configurations

Contendo as configurações básicas, todos se encontram na ./nginx onde alguns contêm variáveis que são substituidas de acordo com as configurações no arquivo nginx.sh como, por exemplo, pid {{pid_file}};.

Para configurar tudo de forma automática, após você definir suas configurações, execute o seguinte job:

./nginx/nginx.sh configure

Nginx (arquivo do nosso sistema)

Como podemos ter mais de um sistema rodando no mesmo NGINX, devemos criar um arquivo de configuração para cada um. Vamos criar um arquivo para o blog wbotelhos.com:

sudo vim /etc/nginx/sites-enabled/wbotelhos.conf

E colar a seguinte configuração:

upstream app {
  server 127.0.0.1:5000;
  server 127.0.0.1:5001;
  server 127.0.0.1:5002;
}

server {
  # A porta no qual o servidor esta escutando as requisições.
  listen 80;

  # IP ou domínio definido para apontar para o nosso virtual host.
  server_name ec2-x-p-t-o.sa-east-1.compute.amazonaws.com 0.0.0.0;

  # Configurar o root para a pasta "public" é muito importante quando queremos utilizar arquivos
  # estáticos sem passar pelo Rails como é o caso dos meus plugins (wbotelhos.com/raty).
  # Como o root da aplicação esta apontando para "public", logo wbotelhos.com/raty aponta
  # para uma pasta dentro de "public/raty" que é acessada diretamente fora do Rails.
  # E o melhor é que se você acessar apenas wbotelhos.com com barra no final ou não
  # o que você definiu no seu routes.rb será processado: `root to: 'articles#index'`
  root /var/www/wbotelhos/current/public;

  # O "index" é complemento da configuração "root", onde ao acessar wbotelho.com/raty seremos
  # redirecionados automáticamente para a página public/raty/index.html.
  # Precisamos dessa configuração, pois não conseguimos acessar a página html diretamente.
  index index.html;

  # Tamanho máximo permitido para requisção indicado pelo Content-Length. Default: 1M.
  client_max_body_size 5M;

  location / {
    proxy_redirect   off;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP  $remote_addr;

    if ($request_uri ~* "\.(ico|css|js|gif|jpe?g|png)\?[0-9]+$") {
      access_log off;
      add_header Cache-Control public;
      expires    max;
      break;
    }

    if (-f $request_filename/index.html) {
      rewrite (.*) $1/index.html break;
    }

    if (-f $request_filename.html) {
      rewrite (.*) $1.html break;
    }

    if (!-f $request_filename) {
      proxy_pass http://app;
      break;
    }
  }

  error_page  500 502 503 504  /500.html;

  location = /500.html {
    root /var/www/wbotelhos/current/public;
  }
}

Você deve substituir o Public DNS ec2-x-p-t-o.sa-east-1.compute.amazonaws.com pelo DNS da instância que você criou:

server_name ec2-x-p-t-o.sa-east-1.compute.amazonaws.com 0.0.0.0;

E então estamos utilizando o diretório wbotelhos, no qual o root será o diretório público:

root /var/www/wbotelhos/current/public;

Nginx (Upstart)

Vamos criar um arquivo de inicialização do Nginx utilizando o Upstart:

sudo vim /etc/init/nginx.conf

O conteúdo será o seguinte:

description 'nginx webserver'

start on startup
stop on shutdown

respawn
expect fork
exec /opt/local/sbin/nginx

Vamos garantir que o nosso usuário tem acesso aos arquivos de configuração:

sudo chown ubuntu:ubuntu /var/log/nginx/error.log
sudo chown ubuntu:ubuntu /etc/nginx/nginx.conf

E então verificar se tudo esta correto:

nginx -t

Bem provável ser lançado algumas mensagens não positivas.

nginx: [emerg] open() "/var/run/nginx.pid" failed (13: Permission denied)

Ainda não há PID criado, pois não estamos rodando o NGINX, ignore e inicie o serviço:

sudo start nginx
# nginx start/running, process 23757

Certifique-se que o serviço esta rodando:

ps aux | grep nginx

Para parar use o stop:

sudo stop nginx
# nginx stop/waiting

E para reinicar use o restart:

sudo restart nginx
# nginx stop/waiting
# nginx start/running, process 23757

Agora faça o teste acessando o DNS público pelo browser:

open http://ec2-x-p-t-o.sa-east-1.compute.amazonaws.com
# 404 Not Found --- nginx/1.5.8

Unicorn

Unicorn

Configurar o Unicorn é bem simples. Ele é uma gem que declaramos no Gemfile do nosso projeto. Vamos criar o seu arquivo de inicialização:

sudo vim /etc/init/unicorn.conf
description 'unicorn server'

pre-start script
  mkdir -p /var/run/unicorn
  chown ubuntu:ubuntu /var/run/unicorn
  chmod 770 /var/run/unicorn

  mkdir -p /var/log/unicorn
  chown ubuntu:ubuntu /var/log/unicorn
  chmod 770 /var/log/unicorn
end script

start on startup
stop on shutdown

exec sudo -u ubuntu -g ubuntu sh -c "cd /home/ubuntu/www/wbotelhos/current && RAILS_ENV=production GEM_HOME=/opt/local/ruby/gems bundle exec unicorn_rails -c /home/ubuntu/www/wbotelhos/config/unicorn.rb"

respawn

Repare que já apontamos alguns caminhos como a pasta current que manterá a versão corrente do nosso sistema e a pasta config que além de diversas configurações terá um arquivo de configuração do Unicorn.

Ainda não temos a pasta wbotelhos e nem a pasta config, então vamos criá-las:

mkdir -p /var/www/wbotelhos/config

E então podemos criar as configurações:

vim /var/www/wbotelhos/config/unicorn.rb
worker_processes 3

listen 5000
listen 5001
listen 5002

preload_app true

timeout 30

pid               '/var/www/wbotelhos/shared/pids/unicorn.pid'
stderr_path       '/var/www/wbotelhos/shared/log/unicorn.error.log'
stdout_path       '/var/www/wbotelhos/shared/log/unicorn.out.log'
working_directory '/var/www/wbotelhos/current'

Estas configurações basicamente são os caminhos dos arquivos de log e o diretório onde o servidor irá atuar, além da quantidade de processos e portas em que ele ficará escutando.

Deste modo estamos com o Ruby, NGINX e Unicorn prontos para rodar uma aplicação. É claro que iremos precisar de uma tarefa de deploy usando por exemplo, o Capistrano, que será o tema do próximo post. (:

  1. Cândido Sales 10 Fev 2013 13:55

    Washington,

    Havia um erro no meu /etc/init/nginx.conf =.=' ... na hora de copiar e colar esqueci alguns caracteres xD.

  2. Cândido Sales 10 Fev 2013 08:43

    Washington,

    Fiz "sudo nginx -t" e funcionou:

    [email protected]:~$ sudo nginx -t
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: configuration file /etc/nginx/nginx.conf test is successful

    Mas quando uso "sudo nginx stop":

    stop: Unknown job: nginx

    Em seguida usei "sudo nginx -s reload":

    nginx: [error] invalid PID number "" in "/var/run/nginx.pid"

    Depois que fiz o "sudo nginx -t" ele cria o arquivo nginx.pid, mesmo assim o PID é inválido. Fiz "cap deploy:start" e o erro do log do nginx mudou:

    2013/02/10 10:39:14 [notice] 3958#0: signal process started
    2013/02/10 10:39:14 [error] 3958#0: open() "/var/run/nginx.pid" failed (2: No such file or directory)
    2013/02/10 10:39:42 [notice] 3964#0: signal process started
    2013/02/10 10:39:42 [error] 3964#0: invalid PID number "" in "/var/run/nginx.pid"

    É o mesmo que apresenta quando faço "sudo nginx -s reload".

    Você tem alguma ideia do que sejao problema do PID? Eu já apaguei o nginx.pid e recriei de novo com o "sudo nginx -s reload" e continua com o erro.

  3. Cândido Sales 10 Fev 2013 08:23

    Washington,

    Tentei iniciar a aplicação com deploy:start e apresentou o mesmo erro que ocorreu com o Igor e fui ver o error.log do niginx:

    2013/02/08 21:57:10 [warn] 1120#0: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
    2013/02/08 21:57:10 [emerg] 1120#0: open() "/var/run/nginx.pid" failed (13: Permission denied)
    2013/02/08 21:59:25 [warn] 1129#0: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
    2013/02/08 21:59:25 [emerg] 1129#0: open() "/var/run/nginx.pid" failed (13: Permission denied)
    2013/02/08 22:00:17 [warn] 1138#0: the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
    2013/02/08 22:00:17 [emerg] 1138#0: mkdir() "/opt/local/nginx/1.3.9/uwsgi_temp" failed (13: Permission denied)
    ~

    Na parte do tutorial onde você pedi para mudarmos a permissão do caminho "/var/run/nginx.pid" só consegui via:

    chown -R ubuntu:ubuntu /var/run/nginx.pid

    Mesmo mudando as permissões, permanece o erro. O nginx não criou o arquivo PID, mas quando faço "nginx -V" ele apresenta como se estivesse ativo/funcionando.

    Quando faço "nginx -t", apresenta este erro:

    [email protected]:~$ nginx -t
    nginx: [warn] the "user" directive makes sense only if the master process runs with super-user privileges, ignored in /etc/nginx/nginx.conf:2
    nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
    nginx: [emerg] mkdir() "/opt/local/nginx/1.3.9/uwsgi_temp" failed (13: Permission denied)
    nginx: configuration file /etc/nginx/nginx.conf test failed

  4. Cândido Sales 8 Fev 2013 19:56

    Pronto, só precisei dá reboot! =D

  5. Cândido Sales 8 Fev 2013 19:53

    Washington,

    Estou seguindo seus passos na integra, entretanto na parte do "ruby -v", apresenta como se não tivesse instalado:

    [email protected]:~$ ruby -v
    The program 'ruby' can be found in the following packages:

    • ruby1.8
    • ruby1.9.1 Try: sudo apt-get install

    Isso também ocorre com o nginx. O que eu preciso fazer?

  6. Felipe A. Rodrigues 1 Fev 2013 18:29

    Cara, parabéns pelos posts do seu blog.
    Realmente são muito bons e me ajudaram bastante.

  7. Saulo Arruda 25 Out 2012 21:49

    Nós também usamos nginx+unicorn, mas na nossa configuração queríamos ter a possibilidade de usar versões diferentes de ruby e, principalmente, separar os gemsets de cada aplicação para não haver problemas de dependências. Naturalmente usamos uma instância large na amazon com várias aplicações ruby e por isso vem essa necessidade. Em máquina que rodam uma única app o que você fez atende bem!

    Segue o procedimento em um gist: https://gist.github.com/2408305

    Espero ter ajudado, se algo não estiver muito claro ou não funcionar pode me contatar.

    Abraços!

    1. Washington Botelho autor 8 Jan 2013 21:35

      Oi Saulo Arruda,

      Muito legal a sua solução. Onde trabalho não usamos o RVM, mas estamos pensando em migrar para o Unicorn. De qualquer forma é um bom tutorial para necessidades futuras.

      Valeu!

      1. Igor 5 Jan 2013 00:02

        Então olhando a saida do Capistrano , depois do primeiro deploy , teve o seguinte erro:

        • executing "cd /var/www/revisaofiscal/current && RAILS_ENV=production && GEM_HOME=/opt/local/ruby/gems && bundle exec unicorn_rails -c /var/www/revisaofiscal/config/unicorn.rb -D" servers: ["54.232.106.41"] [54.232.106.41] executing command ** [out :: 54.232.106.41] master failed to start, check stderr log for details command finished in 3659ms failed: "sh -c 'cd /var/www/revisaofiscal/current && RAILS_ENV=production && GEM_HOME=/opt/local/ruby/gems && bundle exec unicorn_rails -c /var/www/revisaofiscal/config/unicorn.rb -D'" on 54.232.106.41
        1. Washington Botelho autor 7 Jan 2013 02:26

          Oi Igor,

          Dá uma analisada no log da aplicação (production.log), do Unicorn (unicorn.error.log e unicorn.out.log) e do NGINX (access.log, error.log).

          As vezes precisamos remover o arquivo de PID do Unicorn e dar restart.

Em resposta:
(Cancelar)
Formate seu código utilizando Markdown.