Essa é uma revisão anterior do documento!
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
Crie o usuário do postgresql da forma que você preferir
Crie o Banco de Dados do Sistema no Postgresql
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 <app_name> pelo nome da sua app:
#!/usr/bin/env puma directory '/mnt/apps/<app_name>/current' rackup "/mnt/apps/<app_name>/current/config.ru" environment 'production' pidfile "/mnt/apps/<app_name>/shared/tmp/pids/puma.pid" state_path "/mnt/apps/<app_name>/shared/tmp/pids/puma.state" stdout_redirect '/mnt/apps/<app_name>/current/log/puma.error.log', '/mnt/apps/<app_name>/current/log/puma.access.log', true threads 4,16 bind 'unix:///mnt/apps/<app_name>/shared/tmp/sockets/<app_name>-puma.sock' workers 4 preload_app! on_restart do puts 'Refreshing Gemfile' ENV["BUNDLE_GEMFILE"] = "/mnt/apps/<app_name>/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 <app_name> pelo nome da sua app e <user_name> 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/<app_name>/current PUMA_CONFIG_FILE=config/puma.rb PUMA_PID_FILE=/mnt/apps/<app_name>/shared/tmp/pids/puma.pid PUMA_SOCKET_FILE=/mnt/apps/<app_name>/shared/tmp/sockets/<app_name>-puma.sock PUMA_USER=<user_name> 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/<app_name>/shared/config, com os arquivos database.yml e secrets.yml. E coloque o conteúdo correto em cada um dos arquivos.
mkdir /mnt/apps/<app_name>/shared/config touch database.yml touch secrets.yml
Crie o diretório tmp e tmp/sockets
mkdir /mnt/apps/<app_name>/shared/tmp mkdir /mnt/apps/<app_name>/shared/tmp/sockets mkdir /mnt/apps/<app_name>/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://<app_name>.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 = <sua_chave_aqui>
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: <Sua chave aqui>
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_<app_name> defaults
* Configurando logrotate para rotacionar os logs da aplicação. Coloque o seguinte código no arquivo: /etc/logrotate.d/<app_name>
/mnt/apps/<app_name>/shared/log/*.log { daily nomissingok rotate 7 compress delaycompress notifempty copytruncate }
Configurando SSL (opcional)
* Crie a o diretório certs dentro da raiz do seu projeto rails:
mkdir certs
* 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 <app_name> pelo nome da sua app:
upstream puma { server unix:///mnt/apps/<app_name>/shared/tmp/sockets/<app_name>-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.<app_name>.ifce.edu.br; rewrite ^(.*) http://<app_name>.ifce.edu.br$1 permanent; } server { listen 443 default ssl; server_name <app_name>.ifce.edu.br; root /mnt/apps/<app_name>/current/public; ssl on; ssl_certificate /mnt/apps/<app_name>/current/certs/www.<app_name>.ifce.edu.br.crt; ssl_certificate_key /mnt/apps/<app_name>/current/certs/www.<app_name>.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
* 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.