linux:rails_with_puma_deploy

Essa é uma revisão anterior do documento!


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

É 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/

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

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
$ 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

$ sudo apt-get install nginx
$ sudo service nginx start
$ sudo service nginx status (Ele deve estar rodando agora!)

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;
}

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

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

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.)

* 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
}

* 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.

  • linux/rails_with_puma_deploy.1504725460.txt.gz
  • Última modificação: 2021/08/25 10:33
  • (edição externa)