===== Publicando aplicação rails em produção usando Ubuntu + Rails 5 + Nginx + Puma + PostgreSQL + Capistrano 3 =====
Puma é um servidor HTTP 1.1 de aplicações Ruby/Rack, simples, rápido, que utiliza threads e altamente concorrente. Puma é indicado para uso tanto em ambientes de desenvolvimento quanto de produção. Atualmente é preferível fazer deploy de aplicações Rails utilizando o Puma devido a sua performance superior.
Entre na máquina via ssh
$ ssh usuario@app_name.ifce.edu.br
Obs: Caso a chave ssh do servidor tenha sido alterada, por algum motivo específico, remova o arquivo known_hosts:
rm ~/.ssh/known_hosts
==== Criando usuário da aplicação ====
É recomendado criar um usuário na máquina com o mesmo nome da aplicação, caso o mesmo ainda não exista. Não é interessante utilizar um usuário genérico para configurar o deploy. Nesse tutorial iremos utilizar o usuário como exemplo: minha_app. Troque esse nome pelo nome da sua aplicação ou qualquer outro nome que achar mais conveniente.
Crie o usuário
sudo adduser minha_app
Digite a senha e as informações necessárias até finalizar o processo
Adicione o usuário ao arquivo sudoers:
sudo visudo
No final do arquivo adicione:
minha_app ALL=(ALL) NOPASSWD:ALL
Digite Ctrl + O para salvar e depois Ctrl + X para fechar o arquivo
Logue com esse novo usuário
su minha_app
Troque o dono do diretório de deploy que utilizaremos: /mnt
sudo chown -R minha_app:minha_app mnt/
==== Adicione sua chave ssh ao servidor para se logar sem senha: ====
Dê ssh para alguma máquina a fim de criar o diretório ~/.ssh
ssh usuario@app_name.ifce.edu.br
Dê um Ctrl+C ao ser solicitado pela senha.
Adicione sua chave ao arquivo ~/.ssh/authorized_keys
Na sua máquina local pegue a sua chave ssh se existir com o comando (Cuidado para não copiar os espaços):
cat ~/.ssh/id_rsa.pub
No servidor adicione a chave ao arquivo:
vim ~/.ssh/authorized_keys
Cole conteúdo da chave nesse arquivo
Salve e teste
==== Instalando o RVM ====
**Corrigindo possíveis problemas de dns na máquina**
Edite o arquivo /etc/network/interfaces e adicione o conteúdo abaixo após a linha # dns-* options are implemented by the resolvconf package, if installed
dns-nameservers 8.8.8.8
dns-nameservers 8.8.4.4
dns-nameservers 200.17.33.7
dns-search ifce.edu.br
Reinicie a máquina:
sudo reboot
**Instalando o Curl**
$ sudo apt-get update
$ sudo apt-get install curl
Obs: caso tenha algum problema ao baixar o rvm, verifique os seus servidores DNS no arquivo: /etc/resolv.conf. Sugestão: adicione os servidores de DNS do google: 8.8.8.8 e 8.8.4.4
$ curl -L get.rvm.io | bash -s stable
Caso ocorra o seguinte erro, rode o comando sugerido pelo próprio erro:
GPG signature verification failed for '/home/app_name/.rvm/archives/rvm-1.27.0.tgz' - 'https://github.com/rvm/rvm/releases/download/1.27.0/1.27.0.tar.gz.asc'!
try downloading the signatures:
Rode:
curl -sSL https://rvm.io/mpapis.asc | gpg --import -
E depois rode novamente os comandos abaixo para finalizar a instalação do rvm:
$ curl -L get.rvm.io | bash -s stable
Agora é preciso instalar os requisitos para instalar o ruby e instalar também a gem bundles para gerenciar as outras gems do projeto:
$ source ~/.rvm/scripts/rvm
$ rvm requirements
$ rvm install 2.0.0 (Utilize a versão que vc estiver trabalhando no projeto atual)
$ rvm use 2.0.0 --default
$ rvm rubygems current
$ gem install bundler
==== Instale a lib de dependência do PostgreSQL, Imagemagick e Git ====
$ sudo apt-get install libpq-dev imagemagick git-core redis-server
Crie o usuário do postgresql da forma que você preferir
Crie o Banco de Dados do Sistema no Postgresql
Caso queira configura o Postgresql na mesma máquina do sistema, siga esse tutorial clicando
[[https://www.digitalocean.com/community/tutorials/como-instalar-e-utilizar-o-postgresql-no-ubuntu-16-04-pt|aqui]].
==== Instalando e configurando o Nginx ====
$ sudo apt-get install nginx
$ sudo service nginx start
$ sudo service nginx status (Ele deve estar rodando agora!)
==== Adicionando configuração de deploy na aplicação ====
Adicione o código abaixo no Gemfile da sua aplicação:
# Javascript runtime
gem 'therubyracer'
group :development do
gem 'capistrano', require: false
gem 'capistrano-rvm', require: false
gem 'capistrano-rails', require: false
gem 'capistrano-bundler', require: false
gem 'capistrano3-puma', require: false
end
gem 'puma'
Instale as novas gems:
bundle install
Crie os arquivos do capistrano na sua aplicação usando o comando abaixo:
cap install
Substitua o conteúdo do arquivo Capfile pelo código abaixo:
# Load DSL and set up stages
require "capistrano/setup"
# Include default deployment tasks
require "capistrano/deploy"
require "capistrano/scm/git"
install_plugin Capistrano::SCM::Git
# Adittional plugins
require 'capistrano/rails'
require "capistrano/bundler"
require "capistrano/rvm"
require "capistrano/puma"
install_plugin Capistrano::Puma # Default puma tasks
install_plugin Capistrano::Puma::Workers # if you want to control the workers (in cluster mode)
require "capistrano/rails/assets"
require "capistrano/rails/migrations"
require 'capistrano/sidekiq'
require 'capistrano/sidekiq/monit'
# Load custom tasks from `lib/capistrano/tasks` if you have any defined
Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r }
Substitua o conteúdo do arquivo config/deploy.rb, substituindo app_name pelo nome da sua aplicação, pelo código abaixo:
# Change these
server 'app_name.ifce.edu.br', port: 22, roles: [:web, :app, :db], primary: true
set :repo_url, 'git@gitlab.ifce.edu.br:dgti/app_name.git'
set :application, 'app_name'
set :user, 'app_name'
set :puma_threads, [4, 16]
set :puma_workers, 4
# Don't change these unless you know what you're doing
set :pty, true
set :use_sudo, false
set :stage, :production
set :deploy_via, :remote_cache
set :deploy_to, "/mnt/apps/#{fetch(:application)}"
set :puma_bind, "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
set :puma_state, "#{shared_path}/tmp/pids/puma.state"
set :puma_pid, "#{shared_path}/tmp/pids/puma.pid"
set :puma_access_log, "#{release_path}/log/puma.error.log"
set :puma_error_log, "#{release_path}/log/puma.access.log"
set :ssh_options, { forward_agent: true, user: fetch(:user), keys: %w(~/.ssh/id_rsa.pub) }
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true # Change to false when not using ActiveRecord
set :keep_releases, 1
set :linked_dirs, %w{log uploads}
namespace :deploy do
desc "Make sure local git is in sync with remote."
task :check_revision do
on roles(:app) do
unless `git rev-parse HEAD` == `git rev-parse origin/master`
puts "WARNING: HEAD is not the same as origin/master"
puts "Run `git push` to sync changes."
exit
end
end
end
desc 'Creating symlink for config file'
task :config_symlink do
on roles(:app) do
sudo "ln -nfs #{release_path}/config/puma_init.sh /etc/init.d/puma_#{fetch(:application)}"
sudo "chmod +x /etc/init.d/puma_#{fetch(:application)}"
execute "ln -nfs #{release_path}/config/puma.rb.example #{shared_path}/puma.rb"
execute "ln -sf #{shared_path}/config/database.yml #{release_path}/config/database.yml"
execute "ln -sf #{shared_path}/config/secrets.yml #{release_path}/config/secrets.yml"
execute "ln -sf #{shared_path}/puma.rb #{release_path}/config/puma.rb"
end
end
desc 'Creating permissions and running db:seed'
task :populate_db do
on roles(:app) do
execute "cd #{release_path} && ~/.rvm/bin/rvm default do bundle exec rake permissions:create RAILS_ENV=#{fetch(:stage)}"
execute "cd #{release_path} && ~/.rvm/bin/rvm default do bundle exec rake db:seed RAILS_ENV=#{fetch(:stage)}"
end
end
after "deploy:migrating", :populate_db
after "symlink:linked_dirs", :config_symlink
before :starting, :check_revision
after :finishing, :compile_assets
after :finishing, :cleanup
end
Crie o arquivo config/nginx.conf e utilize o conteúdo abaixo (substitua o caminho da aplicação no arquivo abaixo pelo caminho da sua app):
upstream puma {
server unix:///mnt/apps/appname/shared/tmp/sockets/appname-puma.sock;
}
server {
listen 80 default_server deferred;
root /mnt/apps/appname/current/public;
access_log /mnt/apps/appname/current/log/nginx.access.log;
error_log /mnt/apps/appname/current/log/nginx.error.log info;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
try_files $uri/index.html $uri @puma;
location @puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_pass http://puma;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 10M;
keepalive_timeout 10;
}
==== Coloque o puma para ser iniciado automaticamente no boot da máquina ====
Crie o arquivo config/puma.rb.example com o seguinte conteúdo, substituindo pelo nome da sua app:
#!/usr/bin/env puma
directory '/mnt/apps//current'
rackup "/mnt/apps//current/config.ru"
environment 'production'
pidfile "/mnt/apps//shared/tmp/pids/puma.pid"
state_path "/mnt/apps//shared/tmp/pids/puma.state"
stdout_redirect '/mnt/apps//current/log/puma.error.log', '/mnt/apps//current/log/puma.access.log', true
threads 4,16
bind 'unix:///mnt/apps//shared/tmp/sockets/-puma.sock'
workers 4
preload_app!
on_restart do
puts 'Refreshing Gemfile'
ENV["BUNDLE_GEMFILE"] = "/mnt/apps//current/Gemfile"
end
on_worker_boot do
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.establish_connection
end
end
Crie o arquivo config/puma__init.sh com o seguinte conteúdo, substituindo pelo nome da sua app e pelo usuário que será utilizado para fazer o deploy da aplicação (deve ser o mesmo usuário que você faz ssh no servidor):
#! /bin/sh
### BEGIN INIT INFO
# Provides: puma
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description: Controls puma server
# Description: Controls puma server
### END INIT INFO
PUMA_WORKING_DIRECTORY=/mnt/apps//current
PUMA_CONFIG_FILE=config/puma.rb
PUMA_PID_FILE=/mnt/apps//shared/tmp/pids/puma.pid
PUMA_SOCKET_FILE=/mnt/apps//shared/tmp/sockets/-puma.sock
PUMA_USER=
puma_is_running() {
if [ -S $PUMA_SOCKET_FILE ] && [ -e $PUMA_PID_FILE ] && cat $PUMA_PID_FILE | xargs pgrep -P > /dev/null; then
return 0
else
return 1
fi
}
case "$1" in
start)
if puma_is_running; then
echo "Puma is running"
else
su -c "cd $PUMA_WORKING_DIRECTORY && bundle exec puma -C $PUMA_CONFIG_FILE --daemon" - $PUMA_USER
echo "Puma started"
fi
;;
stop)
if puma_is_running; then
su -c "kill -s SIGTERM `cat $PUMA_PID_FILE`" - $PUMA_USER
rm -f $PUMA_PID_FILE
rm -f $PUMA_SOCKET_FILE
echo "Puma stopped"
else
echo "Puma is not running"
fi
;;
restart)
if puma_is_running; then
su -c "kill -s SIGUSR2 `cat $PUMA_PID_FILE`" - $PUMA_USER
echo "Puma restarted"
else
echo "Puma is not running"
fi
;;
status)
if puma_is_running; then
echo "Puma is running"
exit 0
else
echo "Puma is not running"
exit 1
fi
;;
*)
echo "Usage: $0 {start|stop|restart|status}"
exit 1
;;
esac
==== Gere e Adicione a chave ssh do servidor no repositório git remoto (no caso o gitlab do ifce) ====
Entre no diretório do ssh do servidor:
cd ~/.ssh && ls
Verifique se o arquivo **id_rsa.pub** já existe. Caso não existe você deve criá-lo com o seguinte comando:
ssh-keygen -t rsa -C "app_name@ifce.edu.br"
Apenas fique apertando Enter nos prompts que serão exibidos. Não precisa digitar nada.
Agora você precisa copiar o conteúdo da chave pública: **id_rsa.pub** para o repositório git (no caso o gitlab do ifce)
Vá no Projeto > Settings > Repository > Deploy Keys e crie uma nova, colando o conteúdo da chave no campo Key.
Adicione a chave ssh ao seu identity provider
ssh-add ~/.ssh/id_rsa
==== Deploy inicial da estrutura (criação da estrutura de diretórios inicial) ====
Comite todas as mudanças no projeto
git add -A .
git commit -m "Configuração de deploy com capistrano"
git push origin master
Execute o deploy inicial que irá criar a estrutura de diretórios do capistrano
cap production deploy
Crie o diretório /mnt/apps//shared/config, com os arquivos database.yml e secrets.yml. E coloque o conteúdo correto em cada um dos arquivos.
mkdir /mnt/apps//shared/config
cd /mnt/apps//shared/config
touch database.yml
touch secrets.yml
Crie o diretório tmp e tmp/sockets
mkdir /mnt/apps//shared/tmp
mkdir /mnt/apps//shared/tmp/sockets
mkdir /mnt/apps//shared/tmp/pids
Obs: Deverão ocorrer erros no deploy inicial. Caso ocorram, siga os passos abaixo e depois execute um deploy normal, usando: cap production deploy. (Esse comando executado acima, foi apenas para criar a estrutura inicial do deploy, ele dá erro mesmo, é normal.)
==== Configurações finais de deploy ====
* Rode o comando abaixo na máquina remota para configurar o nginx
sudo ln -nfs "/mnt/apps/app_name/current/config/nginx.conf" "/etc/nginx/sites-enabled/app_name"
* Adicione a configuração do ambiente correspondente (production ou staging) ao arquivo:
/mnt/apps/app_name/shared/config/database.yml
* Adicione as configurações abaixo(no projeto local) no arquivo config/enviroments/production.rb:
config.action_mailer.delivery_method = :smtp
config.action_mailer.default_url_options = { :host => 'http://.ifce.edu.br' }
ActionMailer::Base.delivery_method = :smtp
ActionMailer::Base.smtp_settings = {
:address => 'm.ifce.edu.br',
:domain => 'ifce.edu.br',
:port => 25,
:openssl_verify_mode => 'none'
}
* Adicione o token do devise (no seu projeto local) em config/initializers/devise.rb:
config.secret_key =
Você deve gerar essa chave na sua máquina local usando o comando: rake secret
* Crie o arquivo /mnt/apps/app_name/shared/config/secrets.yml com o conteúdo:
production:
secret_key_base:
Você deve gerar essa chave na sua máquina local usando o comando: rake secret
Após realizar as mudanças no seu projeto local, não esqueça de comitá-las e dar um push para o git remoto.
* Remova o site padrão do nginx
sudo rm /etc/nginx/sites-enabled/default
* Reinicie o nginx
sudo service nginx restart
* Se você usa sidekiq no seu projeto instale o monit:
sudo apt-get install monit
Configure o monit para suportar a interface http
Edite o arquivo: /etc/monit/monitrc e procure por: set httpd. Descomente as linhas para ficar igual o código abaixo:
set httpd port 2812 and
use address localhost # only accept connection from localhost
allow localhost # allow localhost to connect to the server and
allow admin:monit
Reinicie o monit
sudo /etc/init.d/monit restart
* Deploy
$ cap production deploy
* Coloque o puma para ser iniciado automaticamente no boot da máquina
sudo update-rc.d puma_ defaults
* Configurando logrotate para rotacionar os logs da aplicação. Coloque o seguinte código no arquivo: /etc/logrotate.d/
/mnt/apps//shared/log/*.log {
daily
nomissingok
rotate 7
compress
delaycompress
notifempty
copytruncate
}
==== Configurando SSL (opcional) ====
* Crie o arquivo .key, trocando pelo domínio da sua aplicação (Pode ser executado da sua máquina local)
openssl genrsa -des3 -out .key 2048
* Crie o arquivo .csr, trocando pelo domínio da sua aplicação (Pode ser executado da sua máquina local)
openssl req -new -key .key -out .csr
* Envie o arquivo .key e .csr para a geração do certificado. Após o certificado ser emitido, crie o arquivo: .crt
* Crie a o diretório certs dentro da raiz do seu projeto rails:
mkdir certs
* Remova a senha da .key, trocando pelo domínio da sua aplicação (Pode ser executado da sua máquina local)
openssl rsa -in .orig.key -out server.key
* Copie os arquivos .key e .crt do certificado para esse diretório recém-criado
cp www.seu-dominio.ifce.edu.br.key certs
cp www.seu-dominio.ifce.edu.br.crt certs
* Troque a configuração do nginx em config/nginx.conf para usar essa nova configuração que suporta ssl, trocando pelo nome da sua app:
upstream puma {
server unix:///mnt/apps//shared/tmp/sockets/-puma.sock;
}
# for redirecting to https version of the site
server {
listen 80;
rewrite ^(.*) https://$host$1 permanent;
}
# for redirecting to non-www version of the site
server {
listen 80;
server_name www..ifce.edu.br;
rewrite ^(.*) http://.ifce.edu.br$1 permanent;
}
server {
listen 443 default ssl;
server_name .ifce.edu.br;
root /mnt/apps//current/public;
ssl on;
ssl_certificate /mnt/apps//current/certs/www..ifce.edu.br.crt;
ssl_certificate_key /mnt/apps//current/certs/www..ifce.edu.br.key;
ssl_session_timeout 5m;
# modern configuration. tweak to your needs.
ssl_protocols TLSv1.2;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
ssl_prefer_server_ciphers on;
location ^~ /assets/ {
gzip_static on;
expires max;
add_header Cache-Control public;
}
try_files $uri/index.html $uri @puma;
location @puma {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_redirect off;
proxy_pass http://puma;
}
error_page 500 502 503 504 /500.html;
client_max_body_size 10M;
keepalive_timeout 10;
}
* Modifique a configuração do config/environments/production.rb e adicione a seguinte linha:
config.force_ssl = true
* Modifique a configuração do enviroment de produção da sua app, provavelmente o config/enviroments/production.rb. Adicione ou modifique as duas linhas conforme abaixo para ficarem com https ao invés de http
config.action_mailer.default_url_options = { host: 'https://' }
config.action_mailer.asset_host = "https://"
* Agora comite tudo e faça um novo deploy
git add -A .
git commit -m "Adicionando suporte a ssl"
git push origin master
cap production deploy
* Após o deploy terminar logue no servidor via ssh e reinicie o nginx:
sudo service nginx restart
* PRONTO! Sua app já deve responder via https://seu-dominio.ifce.edu.br bem como redirecionar o tráfego http para https.