Back-End

24 mai, 2017

Subindo uma aplicação Ruby on Rails 5 em um servidor Linux

Publicidade

Este é um processo que se faz necessário de vez em quando na vida de um desenvolvedor Ruby. Muitos dirão: “ah, usa Heroku”; mas acredite jovem padawan, existe um caminho melhor a seguir.

Este é o caminho mais sujeito a falhas, afinal, você ter o controle sobre tudo lhe permite que deixe falhas críticas na segurança. Mas é só você estudar um pouco e tomar bastante cuidado que a sua segurança não estará tão comprometida.

Eu resolvi escrever este artigo também para documentar passo-a-passo qual o procedimento que eu costumo fazer neste tipo de situação. Alguns amigos sempre me questionam como fazê-lo e esta será a forma mais prazerosa, tanto para mim, quanto para vocês, de aprender.

Resumão do que faremos hoje:

  • Criando uma máquina virtual
  • Instalando Rbenv
  • Instalando Ruby
  • Instalando Node.JS
  • Instalando Rails
  • Instalando PostgreSQL
  • Instalando Nginx
  • Instalando Passenger
  • Considerações finais

Criando uma máquina virtual

Esta etapa é padrão independente do ambiente que você irá trabalhar – seja Amazon Web Services ou Digital Ocean, Linode etc. O que muda é a forma como você estrutura seu servidor. Na AWS, você tem uma espécie de marketplace com imagens Linux já pré-configuradas. Arrisco dizer que é a forma mais fácil (mas não necessariamente a melhor). Vale a sugestão para quem já está acostumado a fazer este processo e gostaria de automatizar um pouco mais.

Hoje, eu vou usar a DigitalOcean.

As configurações serão:

  • S.O CentOS 7.2 x64;
  • Plano US$ 10 -> 1 GB, 1 CPU, 30 GB SSD, 2 TB transferência;
  • Datacenter NYC 3.

Por que CentOS?

Sou a favor de quebrar esta homogenidade que o Ubuntu criou no Linux. O Linux vai muito além do Ubuntu, tem muita coisa bacana para se usar. Eu digo isso, pois durante muito tempo fui a favor desta ditadura Ubuntuniana. Hoje, eu uso Arch Linux na minha máquina pessoal e abstrai as possibilidades que vão além do apt-get. O Pacman com o AUR é superior a qualquer coisa que já vi nesta vida. Eu consigo instalar praticamente qualquer software relevante de hoje em dia só pelo terminal sem qualquer esforço. Slack, Skype, Sublime Text, VS Code… Qualquer coisa.

Por que este plano de US$ 10?

O ideal é que se tenha pelo menos 1 GB de memória para ambientes de produção. O plano inicial de US$ 5 só tem 512 mb e isso sempre estoura facilmente. Arrisco dizer que memória é o recurso mais “chato” (leia-se caro) de se trabalhar.

Por que o NYC 3?

É o que tem a menor latência para o Brasil. É nesse ponto que, com certeza, a Amazon ganha para nós, brasileiros. Eles têm um datacenter em São Paulo e isso contribui muito para um bom desempenho das suas aplicações.

Aproveitei para adicionar a minha chave SSH na minha instância e sugiro fortemente que você faça o mesmo (caso não saiba o que é isso).

Acessando nosso servidor

Agora que já criamos nossa instância e ela está rodando, basta rodar o comando abaixo para acessarmos nossa máquina via SSH:

ssh root@IP.DA.MAQUINA

Agora que já estamos dentro, a primeira coisa que devemos fazer é…

Criar um novo usuário

É uma boa prática evitar usar o usuário root. Mas por enquanto, vamos trabalhar com ele.

Para adicionar um novo usuário, rode:

adduser lhas

Eu coloquei lhas, mas você deve colocar o username que lhe for conveniente. Ele vem sem senha por padrão, então, vamos colocar uma senha:

passwd lhas

Agora, você deverá digitar a nova senha e confirmar.

Vamos agora colocar ele como super usuário. Isto dará a ele as permissões de sudo (superusuário).

gpasswd -a lhas wheel

wheel é o nome do grupo que vamos adicionar o usuário. Este grupo, no CentOS, é um grupo que tem as permissões de sudo.

Desabilitar acesso root

Segurança, lembra? Tão importante no mundo digital quanto no real. Para isto, vamos ter que editar o arquivo /etc/ssh/sshd_config.

Podemos usar o vi que vem por padrão. Eu não gosto muito, prefiro o nano, acho bem mais intuitivo. Para quem não gosta do vi, sugestão: sudo yum install -y nano.

E agora podemos editar nosso arquivo:

nano /etc/ssh/sshd_config

Aperte Ctrl+W para pesquisar por uma string; digite PermitRootLogin e dê enter. Ele vai mudar o ponteiro diretamente para a linha. Descomente-a, e mude para no.

PermitRootLogin no

Ctrl+X para sair, Y para confirmar, enter para confirmar o overwrite e FOI!

Reinicie o serviço de SSH (opcional, porém interessante fazê-lo):

systemctl reload sshd

Agora, podemos sair do usuário root, e finalmente logarmos com nosso novo usuário.

exit
ssh lhas@ip.aqui

sudo yum install -y git-core zlib zlib-devel gcc-c++ patch readline readline-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf automake libtool bison curl sqlite-devel

Se você ver algo parecido com isto, deu tudo certo.

Instalando Rbenv

Mais uma sopa de letrinhas explicada em comentários:

# Vai para a pasta do nosso usuário
cd ~/
# Clona o rbenv em uma pasta .rbenv
git clone git://github.com/sstephenson/rbenv.git .rbenv
# Adiciona o executável do rbenv na nossa variável PATH
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
exec $SHELL
# Aqui ele vai instalar as dependencias necessárias para compilar o Ruby
git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> ~/.bash_profile
exec $SHELL

Após rodar estes comandos, nós teremos o executável rbenv disponível para nosso usuário. Caso você não consiga executar o comando rbenv, experimente fazer logout e acessar de novo. Costuma resolver, pois ele recarrega os perfis de terminal.

Instalando (de fato) o Ruby

A versão atual na escrita deste post do Ruby é 2.3.1. Então, para instalá-la, vamos rodar:

# Vai instalar a versão que nós desejamos
rbenv install -v 2.3.1
# Vai definir ela como versão global da nossa máquina
rbenv global 2.3.1

Vai tomar um café que esse momento é lerdo mesmo…

Ele vai compilar tudo e isso é um processo demorado.

O resultado deverá ser o seguinte:

Você já pode rodar ruby -v para confirmar que está funcionando.

Passo opcional: caso você não queira que o RubyGems fique gerando documentação na instalação de cada gem, rode o seguinte comando para que isto não ocorra:

echo "gem: --no-document" > ~/.gemrc

Agora podemos instalar o Bundler:

gem install bundler

Agora, devemos remover o Ruby velho que tem na sua máquina provavelmente, e forçar o binário do Ruby instalado pelo Rbenv. Para isto, rode:

# Remove o binário antigo
sudo rm -rf /usr/bin/ruby
# Descobre o caminho do binário atual - copie o retorno deste comando (para mim foi /home/lhas/.rbenv/versions/2.3.1/bin/ruby)
rbenv which ruby
# Isto criará um symlink (atalho de binário) entre o Ruby do rbenv e a pasta de binários da máquina
sudo ln -s /home/lhas/.rbenv/versions/2.3.1/bin/ruby /usr/bin/ruby

Instalando o Rails

Esta é a etapa mais fácil. Como vamos instalar a versão mais recente, isto basta;

gem install rails

Caso, após este comando, você não consiga rodar o Rails, experimente rodar isto aqui:

rbenv rehash

Ele recarrega todos os executáveis reconhecidos pelo Ruby.

Agora podemos confirmar que o Rails está instalado com rails -v.

Instalando o Node.JS

Esta etapa pode ser necessária para rodar o Asset Pipeline disponível no Rails.

Como este não é o foco do artigo, eu vou colocar os comandos necessários para instalar o nvm e a versão mais estável.

# Instalar nvm
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.32.1/install.sh | bash
# Rode isto para poder enxergar o binário do nvm nesta sessão (se não só fazer logout e logar de novo)
export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
# Instala a versão mais estável
nvm install node --default

O resultado será este:

Próximo passo…

Instalando Postgres

Aqui poderia ser o MySQL por exemplo. Mas é uma convenção na comunidade rubista utilizar o PostgreSQL para tudo ao invés do MySQL.

Para instalar o Postgres, basta rodar:

sudo yum install -y postgresql-server postgresql-contrib

É importante instalar dois pacotes que permitirão que nós instalemos nossa gem pg:

sudo yum install -y postgresql-libs postgresql-devel

Agora vamos inicializar a configuração padrão dele:

sudo postgresql-setup initdb

Configurando o Postgres

Por padrão, o Postgres não permite autenticação com senha. Vamos mudar esta configuração editando um arquivo chamado HBA (Host-based Authentication):

sudo nano /var/lib/pgsql/data/pg_hba.conf

Pesquise pela linha “# IPv4 local connections:”.

Abaixo dela, você verá algo assim:

host  all  all  127.0.0.1/32  ident
host  all  all  ::1/128            ident

Mude-o para o seguinte:

host   all   all   127.0.0.1/32   md5
host   all   all   0.0.0.0/0        md5
host   all   all   ::1/128             md5

A linha extra, do meio, é para permitir que possamos fazer acesso remoto no nosso banco de dados. A mudança de ident para md5 é necessária para que ele leve o campo de senha em consideração na autenticação.

Criando nosso usuário para acessar o banco

Acesse com o usuário Postgres para poder rodar comandos no ambiente dele:

sudo -i -u postgres

Agora, vamos rodar o comando para criar o usuário:

createuser --interactive

Ele vai pedir o nome, e depois se deve ser um superusuário. Confirme para superusuário com “y”.

É um padrão do Postgres pesquisar por uma base de dados com o mesmo nome do usuário a logar. Mesmo que esta base de dados fique vazia, é apenas um comportamento padrão dele. Para evitar futuros problemas, podemos criar uma base para nosso usuário:

createdb lhas

Agora, podemos sair do usuário postgres e voltar para nosso usuário lhas:

exit

Após fazer isto, precisamos fazer mais duas etapas:

  1. Permitir o Postgres que IPs remotos o acessem;
  2. Alterar a senha do usuário (role) que nós criamos.

Permitindo o Postgres que IPs remotos o acessem

Acesse com o usuário postgres para poder editar os arquivos dele:

sudo -i -u postgres

Agora, edite o arquivo de configuração dele:

nano /var/lib/pgsql/data/postgresql.conf

Aperte Ctrl+W, pesquise por “listen_addresses”. Descomente esta linha, e modifique de “localhost” para ““”. Ficando assim:

psql

Agora, basta rodar este comando SQL para mudar a senha:

ALTER USER lhas PASSWORD 'nova_senha_aqui';

Pronto, isto basta.

Acessando nosso banco de dados

Se você usar um programa como DBeaver, você já poderá acessar o nosso banco usando o nosso IP como Host, o usuário que nós criamos, e a senha que criamos acima.

Iniciando Postgres

Rode os seguintes comandos:

# Inicializa o serviço do Postgres que está desligado por padrão
sudo systemctl start postgresql
# Habilita o serviço do postgres ao ligar a máquina
sudo systemctl enable postgresql

Próximo passo:

Instalando Passenger e Nginx

O primeiro passo é habilitar o EPEL. Para isto:

sudo yum install -y epel-release yum-utils
sudo yum-config-manager --enable epel

Agora, vamos instalar o Passenger junto com o Nginx em si:

# Adicionando repositório do Passenger
sudo curl --fail -sSLo /etc/yum.repos.d/passenger.repo https://oss-binaries.phusionpassenger.com/yum/definitions/el-passenger.repo
# Instalando o Passenger e Nginx
sudo yum install -y passenger nginx

Obs.: Caso você já tenha o Nginx instalado, você tem que desinstalá-lo completamente com sudo yum remove -y nginx* antes de instalar o Passenger e o Nginx juntos.

Isto é necessário, pois o Nginx que vem do repositório do Passenger é diferente do Nginx que vem do repositório oficial do CentOS. Esta diferença inclui os módulos necessários para o Passenger funcionar. Caso ele insista em instalar o Nginx do CentOS, dê uma olhada no Yum Priorities.

Agora, se abrirmos no nosso browser http://nosso.ip.aqui/, poderemos ver uma tela semelhante a esta:

Após instalar o Nginx, vamos rodar dois comandos:

# Inicializa o serviço do nginx
sudo systemctl start nginx

# Configura ele para ser bootável
sudo systemctl enable nginx

Configurando o Nginx e Passenger

Esta é uma etapa necessária para indicarmos ao Passenger qual binário do Ruby usar e quais configurações ele deve trabalhar também.

Para isto, precisamos saber onde está o arquivo de configuração do Passenger. Basta rodar:

passenger-config --root

Ele vai retornar um caminho. Aqui, para mim, foi:

/usr/share/ruby/vendor_ruby/phusion_passenger/locations.ini

Anote este caminho. Ele será nosso passenger_root. Agora, vamos descobrir onde está o nosso binário do Ruby:

rbenv which ruby

Para mim, retornou:

/home/lhas/.rbenv/versions/2.3.1/bin/ruby

Anote este caminho também. Ele será nosso passenger_ruby.

Agora vamos editar o arquivo que o Passenger incluiu na nossa configuração do Nginx:

sudo nano /etc/nginx/conf.d/passenger.conf

Você verá um conteúdo semelhante a este:

#passenger_root /usr/share/ruby/vendor_ruby/phusion_passenger/locations.ini;
#passenger_ruby /usr/bin/ruby;
#passenger_instance_registry_dir /var/run/passenger-instreg;

Vamos descomentar estas 3 linhas, e mudar o passenger_root e o passenger_ruby para os caminhos que nós anotamos ali em cima:

passenger_root /usr/share/ruby/vendor_ruby/phusion_passenger/locations.ini;
passenger_ruby ~/.rbenv/shims/ruby;
passenger_instance_registry_dir /var/run/passenger-instreg;

Após finalizar estas alterações, salve e feche o arquivo.

Agora, vamos reiniciar o Nginx e conferir se está tudo:

sudo systemctl stop nginx
sudo systemctl start nginx
# Validando instalação do Passenger
/usr/bin/passenger-config validate-install

Se rodarmos o comando a seguir, deveremos visualizar alguns processos tanto na aba de Nginx, quanto do Passenger. Caso você não veja nenhum processo, algo na sua instalação deu errado:

/usr/sbin/passenger-memory-stats

Agora que já estamos com nossa dobradinha Nginx + Passenger instalado e configurado, vamos seguir para nossa aplicação de exemplo.

Rodando uma aplicação

O primeiro passo aqui é clonar um projeto em Rails existente. Eu tenho um e vou disponibilizar o caminho no código abaixo; vocês podem usá-lo de exemplo. Ele contém apenas dois endpoints e alguns dados de seed, apenas para validar umas ideias.

É importante definirmos uma pasta que irá armazenar nossos projetos. Fica a gosto do freguês.

Eu vou usar a pasta /home/lhas/apps, pois estou familiarizado com ela já. Outras pessoas usam /var/www/apps ou algo parecido.

Clonando o projeto

# Cria nossa pasta que armazenará nossas aplicações em Rails
mkdir ~/apps
cd ~/apps
# Clona o projeto
git clone https://github.com/lhas/piratenstrasse-api.git

Configurando o projeto

# Abra a pasta do projeto
cd piratenstrasse-api/
# Instale as dependências do projeto
bundle install

Configurando database.yml e secrets.yml

# Edita a base de dados de exemplo
# Você deve levar o atributo "production" em consideração
nano config/database.yml
# Gera uma nova secret key - copie ela
bundle exec rake secret
# Edita o arquivo de secrets keys - cole aqui
# Você deve levar o atributo "production" em consideração
nano config/secrets.yml

Rodando as migrações

# Cria a base de dados
RAILS_ENV=production bundle exec rake db:create
# Roda as migrações
RAILS_ENV=production bundle exec rake db:migrate
# Alimenta a base com os seeds
RAILS_ENV=production bundle exec rake db:seed

Segurança em dados sensíveis

chmod 700 config db
chmod 600 config/database.yml config/secrets.yml

Configurando permissões

Configurações necessárias para que o usuário e grupo nginx possam ler o conteúdo da pasta do app que pertence ao usuário e grupo lhas.

# Na pasta do app
sudo chmod g+x,o+x /home/lhas/apps/piratenstrasse-api
# Na pasta dos apps
sudo chmod g+x,o+x /home/lhas/apps
# Na pasta do usuário
sudo chmod g+x,o+x /home/lhas
# Na pasta de usuários
sudo chmod g+x,o+x /home/

Testando se a app roda

RAILS_ENV=production rails s

Se você abrir no seu navegador http://159.203.163.124:3000/students.json, verá a nossa app rodando. Não se esqueça de substituir pelo IP da sua máquina, é claro!

Se estiver funcionando corretamente, agora vamos fazer ela rodar através do Nginx.

Rodando app através do Nginx

Vamos criar um arquivo de configuração para ele:

sudo nano /etc/nginx/conf.d/nome_do_app.conf

Agora coloque as seguintes informações neste arquivo:

server {
    listen 80;
    server_name ip.da.maquina.aqui;
    # Tell Nginx and Passenger where your app's 'public' directory is
    root /home/lhas/apps/piratenstrasse-api/public;
    # Turn on Passenger
    passenger_enabled on;
    # Você deve substituir o CAMINHO DO RUBY AQUI pelo caminho correto do binário do seu ruby através de "rbenv which ruby"
    passenger_ruby /home/lhas/.rbenv/versions/2.3.1/bin/ruby;
}

Salve o arquivo e saia dele.

Agora, vamos reiniciar o Nginx:

sudo systemctl stop nginx
sudo systemctl start nginx

Se tentarmos acessar nossa aplicação (http://159.203.163.124/students.json), você deverá ver algo parecido com isto:

Se estiver visualizando isto (ou a sua aplicação) significa que deu tudo certo.

Parabéns!

Dúvidas?

Postem comentários com suas dúvidas e farei questão de ajudá-los a resolver problemas no processo deste artigo.

Problemas comuns

Na primeira vez que abro minha aplicação rola um delay de alguns segundos. Isso é normal?

Sim, é normal. O Passenger está “ligando” a sua aplicação na primeira requisição, caso ele fique muito tempo ocioso, ele hiberna novamente.

Você pode fazer com que ele fique sempre rodando através da diretiva passenger_pre_start.

Apareceu um erro 500. Como posso ver o log?

Nginx

/var/log/nginx/error.log

Aplicação

/home/lhas/apps/piratenstrasse-api/log/production.log

Como posso reiniciar minha aplicação?

Rode o comando abaixo:

passenger-config restart-app

Ele irá pedir para selecionar qual app deseja reiniciar:

Aperte enter e tudo se resolverá.

Fiz modificações no meu app. Como subir?

O ideal para trabalhar profissionalmente com o processo de deploy é utilizar um automatizador para isto, como o Capistrano. Mas não irei abordar este assunto aqui para não fugir do tema.

Da forma que nós fizemos, o processo é bem simples de qualquer forma:

  • Envie as modificações para o seu repositório Git;
  • Acesse via SSH seu servidor;
ssh root@192.168.0.1
  • Abra a pasta do nosso projeto;
cd ~/apps/piratenstrasse-api/
  • Receba as modificações do repositório remoto;
git pull --force
  • Instale novas gems;
bundle install
  • Rode migrações;
RAILS_ENV=production bundle exec rake db:migrate
  • Reinicie o Passenger.
passenger-config restart-app

Conclusão

Espero que este artigo torne-se referência quanto a discussão repetitiva de novatos de qual é a melhor solução para subir nossas aplicações no dia-a-dia.

Muitos sugerem Heroku, mas existe vida depois do Heroku; acredite! E esta vida é mais divertida e econômica!