PaaS ou VPS? Ultimamente tenho preferido serviços PaaS como o Heroku, mas muitos clientes preferem uma VPS na maioria das vezes pelo preço fixo, mas já até mesmo vi um cliente escolher Amazon por que ganhou bônus lá.
Convenções usadas neste post
- Terminal na máquina de desenvolvimento:
% **cd ~
**%
- Terminal no servidor:
ubuntu@ip-172–30–0–58:~$ **sudo su -**
root@ip-172-30-0-58:~#
Ferramentas usadas
Neste post nós vamos usar o Passenger, sua escolha foi feita por causa do zero downtime, i. e., o Passenger vai parar sua app e iniciar novamente, porém ele vai coordenar isso de forma que nenhum usuário perceba essa mudança
O servidor usado foi um Ubuntu Server 16.04 LTS (HVM), SSD Volume Type — ami-e13739f6 na Amazon.
Como o propósito desse post é apenas a instalação de uma app Ruby On Rails eu vou usar o banco de dados RDS da Amazon.
O Projeto
Eu vou usar um projeto Rails gerado usando esses comandos:
% **rails new MyBlog --database postgresql**
% **rails generate scaffold Post title content:text**
Máquina de desenvolvimento
Tudo o que você precisa na máquina de desenvolvedor é instalar o ruby 🙂
Mas primeiro: Atualizar o servidor
Antes de tudo é recomendado atualizar o servidor, faça ssh para o seu servidor com:
% **ssh -i my-key-pair.pem ubuntu@ec2-54-85-37-40.compute-1.amazonaws.com**
Note que como estou usando a Amazon para escrever esse post eu preciso usar o usuário ubuntu, na Linode você tem que se logar diretamente como root.
E depois rode estes comandos:
ubuntu@ip-172-30-0-58:~$ **sudo su -**
root@ip-172-30-0-58:~# **apt-get update && apt-get dist-upgrade --yes && reboot**
Como o último comando mostra depois de atualizar o servidor vai reiniciar.
Instalando e Configurando o Servidor
Vamos usar o Ruby da Hellobits, faça ssh novamente na sua máquina e:
ubuntu@ip-172-30-0-58:~$ **wget -q -O - http://apt.hellobits.com/hellobits.key | sudo apt-key add -
**OK
ubuntu@ip-172-30-0-58:~$ **echo 'deb [arch=amd64] http://apt.hellobits.com/ trusty main' | sudo tee /etc/apt/sources.list.d/hellobits.list
**deb [arch=amd64] [http://apt.hellobits.com/](http://apt.hellobits.com/) trusty main
ubuntu@ip-172-30-0-58:~$ **sudo apt-get update**
ubuntu@ip-172-30-0-58:~$ **sudo apt-get install --yes ruby-2.3 nodejs build-essential libcurl4-openssl-dev libssl-dev zlib1g-dev git-core libpq-dev**
Instale o passenger usando:
ubuntu@ip-172-30-0-58:~$ **sudo gem install passenger bundler**
Por questões de segurança eu sempre recomendo adicionar um usuário para a app:
ubuntu@ip-172-30-0-58:~$ **sudo useradd -m deploy**
Adicione agora as variáveis de ambiente, edite o arquivo /etc/environment, adicionando essas linhas:
RAILS_ENV=production
SECRET_KEY_BASE=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
DATABASE_URL=postgres://myuser:mypass123@myblog.cc189gx1rqes.us-east-1.rds.amazonaws.com:5432/somedatabase
Leia o artigo How To Read and Set Environmental and Shell Variables on a Linux VPS | DigitalOcean para saber outras formas de adicionar as variáveis de ambiente.
Para gerar o seu SECRET_KEY_BASE rode esse comando no projeto:
% **bundle exec rake secret**
0b4c32051205fa77a9035dc31ed7d1769252f0977d4c1bee2befcdee56d4a01f87e41223a9ddf44327e21f81aaa9d8e23979fb65dfdb9b29de1d216809b9b6fb
Instale o passenger com nginx usando esse comando:
ubuntu@ip-172-30-0-58:~$ **sudo passenger-install-nginx-module --auto --auto-download --languages ruby**
Crie um script de inicialização do nginx em /etc/systemd/system/nginx.service:
[Unit]
Description=A high performance web server and a reverse proxy server
After=network.target
[Service]
Type=forking
PIDFile=/run/nginx.pid
PrivateDevices=yes
SyslogLevel=err
EnvironmentFile=/etc/environment
ExecStart=/opt/nginx/sbin/nginx -g 'pid /run/nginx.pid; error_log stderr;'
ExecReload=/usr/bin/kill -HUP $MAINPID
KillSignal=SIGQUIT
KillMode=mixed
[Install]
WantedBy=multi-user.target
Atualize o arquivo de configuração do nginx, /opt/nginx/conf/nginx.conf, com essas configurações:
user deploy;
worker_processes 1;
events {
worker_connections 1024;
}
http {
passenger_root /usr/lib/ruby/gems/2.3.0/gems/passenger-5.1.1/;
passenger_ruby /usr/bin/ruby;
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
passenger_enabled on;
root /home/deploy/myapp/current/public;
}
}
Configure o nginx para iniciar junto com a máquina:
root@ip-172-30-0-58:~# **systemctl daemon-reload**
root@ip-172-30-0-58:~# **systemctl enable nginx**
Created symlink from /etc/systemd/system/multi-user.target.wants/nginx.service to /etc/systemd/system/nginx.service.
Configurando o Deploy na App
Primeiro é preciso instalar a gem mina, a melhor forma de fazer isso é adicionar essa linha no Gemfile:
gem "mina", group: :development
E rodar:
% **bundle install**
Agora vamos adicionar os arquivos do mina no projeto:
% **mina init**
-----> Created ./config/deploy.rb
Edit this file, then run mina setup
after.
Se formos ler os comentários do config/deploy.rb e a documentação da gem mina, para nossa app, nós vamos acabar gerando um arquivo semelhante a este:
require "mina/bundler"
require "mina/rails"
require "mina/git"
set :domain, "ec2-54-91-72-135.compute-1.amazonaws.com"
set :deploy_to, "/home/deploy/myapp"
set :repository, "https://github.com/dmitryrck/MyBlog.git"
set :branch, "master"
set :shared_dirs, fetch(:shared_dirs, []).push("log")
set :user, "deploy"
set :forward_agent, true
desc "Deploys the current version to the server."
task :deploy do
deploy do
invoke :"git:clone"
invoke :"deploy:link_shared_paths"
invoke :"bundle:install"
invoke :"rails:db_migrate"
invoke :"rails:assets_precompile"
invoke :"deploy:cleanup"
on :launch do
in_path(fetch(:current_path)) do
command %{mkdir -p tmp/}
command %{touch tmp/restart.txt}
end
end
end
end
Como estamos usando variáveis de ambiente edite o config/database.yml para que o ambiente de production fique semelhante a isso:
production:
url: <%= ENV['DATABASE_URL'] %>
Copie a sua chave privada para o usuário deploy do servidor, i. e., você tem que copiar o conteúdo do arquivo ~/.ssh/id_rsa.pub da sua máquina para o /home/deploy/.ssh/authorized_keys do servidor.
Com a sua app configurada vamos fazer um “setup” inicial do servidor:
% **mina setup**
-----> Setting up /home/deploy/myapp
total 16
drwxrwxr-x 4 deploy deploy 4096 Dec 4 15:40 .
drwxr-xr-x 5 deploy deploy 4096 Dec 4 15:40 ..
drwxrwxr-x 2 deploy deploy 4096 Dec 4 15:40 releases
drwxrwxr-x 6 deploy deploy 4096 Dec 4 15:40 shared
# github.com:22 SSH-2.0-libssh-0.7.0
Connection to ec2-54-91-72-135.compute-1.amazonaws.com closed.
Elapsed time: 2.82 seconds
Para finalmente fazer o deploy você precisa executar esse comando:
% **mina deploy**
Criando os dados iniciais
Eu sempre prefiro adicionar os dados no db/seed.rb e chamar uma tarefa rake para popular o banco de dados, nesse caso chamar a tarefa db:seed seria:
% **mina "rake[db:seed]"**
Caso precise acessar o rails console do servidor:
% **mina console**
Loading production environment (Rails 5.0.1)
irb(main):001:0>
Agora sim, com seus dados todos prontos só falta iniciar o nginx, eu recomendo que você reinicie a sua máquina, até por que você vai precisar dessa “feature” da app iniciar junto com a máquina.
Caso queira iniciar o nginx sem reiniciar a máquina:
ubuntu@ip-172-30-0-58:~$ **sudo systemctl start nginx**
Agora basta acessar sua app, se você seguiu meu exemplo você tem que entrar em /posts: http://ec2-54-91-72-135.compute-1.amazonaws.com/posts .
Resolução de problemas
Tudo ok, mas minha aplicação não abre no navegador
Verifique o seu Security Group no Console do AWS.
Tudo ok, mas recebo erro 500 ou 404.
Veja o log do nginx com o seguinte comando:
root@ip-172-30-0-126:~# **systemctl status nginx**
Se estiver tudo ok, veja o log da sua própria aplicação:
deploy@ip-172-30-0-126:~$ **tail -F /home/deploy/myapp/shared/log/production.log**
Servidor não tem acesso ao repositório
Se ao executar mina deploy você receber esse erro:
$ mina deploy
-----> Creating a temporary build path
-----> Cloning the Git repository
Cloning into bare repository '/home/deploy/myapp/scm'...
Warning: Permanently added the RSA host key for IP address '192.30.253.113' to the list of known hosts.
Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
! ERROR: Deploy failed.
-----> Cleaning up build
Unlinking current
OK
Connection to ec2-54-91-72-135.compute-1.amazonaws.com closed.
! Run Error
Significa que o servidor não tem acesso ao repositório, faça um teste com o usuário deploy:
ubuntu@ip-172-30-0-58:~$ **sudo su - deploy
**deploy@ip-172-30-0-58:~$ **git clone git@github.com:dmitryrck/MyBlog.git /tmp/clone**
Cloning into '/tmp/clone'...
Permission denied (publickey).
fatal: Could not read from remote repository.
Please make sure you have the correct access rights
and the repository exists.
A solução para isso é permitir que seu usuário deploy tenha acesso ao seu repositório.
Primeiro crie a chave ssh:
ubuntu@ip-172-30-0-58:~$ **sudo su - deploy**
deploy@ip-172-30-0-58:~$ **ssh-keygen -t rsa -N '' -f ~/.ssh/id_rsa**
deploy@ip-172-30-0-58:~$ **exit**
ubuntu@ip-172-30-0-58:~$
Copie o conteúdo do arquivo /home/deploy/.ssh/id_rsa.pub e:
- Se estiver usando o github coloque sua chave em “Settings > Deploy keys”;
No bitbucket seria em “Settings > Deployment Keys”;
Erro “Incomplete response received from application”
Esse é causado por causa da falta de algumas das variáveis de ambiente que sua app precisa, para corrigi-lo basta editar o arquivo /etc/environment corretamente.
Erro “PG::ConnectionBad: could not connect to server: Connection timed out”
Esse erro se deve ao fato de o banco de dados não estar acessível da máquina ruby.
Para testar sua conexão você pode instalar o cliente padrão do PostgreSQL:
root@ip-172-30-0-126:~# **apt-get install postgresql-client**
E tentar se conectar:
deploy@ip-172-30-0-126:~$ **psql -h myblog.cc189gx1rqes.us-east-1.rds.amazonaws.com -p 5432 -U myuser -W somedatabase**
Password for user myuser:
psql (9.5.5, server 9.5.4)
SSL connection (protocol: TLSv1.2, cipher: ECDHE-RSA-AES256-GCM-SHA384, bits: 256, compression: off)
Type "help" for help.
somedatabase=>
No exemplo acima a conexão está certa, apenas precisava atualizar a variável em /etc/environment.
Outro problema bastante comum é nas configurações do Security Group do RDS, no meu caso eu apenas permiti o acesso ao Security Group da app Ruby no Secutiry Group do RDS.
Mais informações
- Ruby installed from APT Hellobits;
Deployment with passenger with info from: Deploying to production — Passenger Library;
Conclusão
Esse post visa cobrir o deploy apenas de uma app Ruby on Rails numa VPS, seja ela AWS, Linode ou qualquer outra.
Se você tiver algum problema comente abaixo o problema, tentarei manter o post o mais atualizado possível, pelo menos na parte de resolução de problemas.
We want to work with you. Check out our "What We Do" section!