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