В этой небольшой статье я бы хотел показать настройку Rails — приложения работающего на Nginx'е с Puma и как это все деплоить с помощью Mina.

Будем считать, что у Вас на сервере уже установлено все окружение, кроме вышеперечисленных вещей (Я предпочитаю ставить ruby через rbenv, поэтому приведенные конфиги для Mina будут расчитаны именно на него.)

Mina
Поставить Mina очень просто — ставим гем и все (нет нужды помещать установку в Gemfile):
gem install mina

Затем ввести команду:
mina init

Данная команда сгенерирует файл config/deploy.rb, который будет содержать минимально необходимую конфигурацию.
Помните, что git у Вас должен быть сконфигурирован или (на крайний случай) логин и пароль от репозитория можно прописать прямо в файле конфига Mina, как приведено в примере. У Вас должны существовать все настройки, которые касаются баз данных. Также хочу отметить, что если у Вас не создано каких — либо папок/файлов, приведенных в конфиге, необходимо их создать.
Пример полной конфигурации:
deploy.rb
require 'mina/bundler'
require 'mina/rails'
require 'mina/git'
require 'mina/rbenv'

lock '3.2.1'

set :user, 'admin'
set :domain, '100.100.100.100'
set :identity_file, "#{ENV['HOME']}/.ssh/google"
set :deploy_to, '/var/www/admin_app'
set :app_path, lambda { "#{deploy_to}/#{current_path}" }
set :repository, 'https://admin:admin@github.com/Admin_app.git'
set :branch, 'master'
set :forward_agent, true


set :rbenv_path, '/home/admin/.rbenv/'
set :shared_paths, ['config/database.yml', 'log']

task :environment do
   invoke :'rbenv:load'
end

task :setup => :environment do
  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/log"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/log"]

  queue! %[mkdir -p "#{deploy_to}/#{shared_path}/config"]
  queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/config"]

  queue! %[touch "#{deploy_to}/#{shared_path}/config/database.yml"]
  queue  %[echo "-----> Be sure to edit '#{deploy_to}/#{shared_path}/config/database.yml'."]
end

desc "Deploys the current version to the server."
task :deploy => :environment do
  to :before_hook do
    # Put things to run locally before ssh
  end
  deploy do
    invoke :'git:clone'
    invoke :'server:stop_server'
    invoke :'deploy:link_shared_paths'
    invoke :'bundle:install'
    invoke :'rails:db_migrate'
    invoke :'rails:assets_precompile'
    invoke :'deploy:cleanup'

    to :launch do
      queue "mkdir -p #{deploy_to}/#{current_path}/tmp/"
      queue "touch #{deploy_to}/#{current_path}/tmp/restart.txt"
      invoke :'server:start_server'
    end
  end
end

namespace :server do
  desc 'Stop server'
  task :stop_server do
    queue 'echo "-----> Stop Server"'
    queue 'kill -9 $(lsof -i :3000 -t) || true'
    queue '[ -f /var/www/admin/admin_app.pid ] && rm /var/www/admin/admin_app.pid || echo "File admin_app.pid not exist"'
  end

  desc 'Start server'
  task :start_server do
    queue 'echo "-----> Start Server"'
    queue! 'rails s -b 0.0.0.0 -e production -P /var/www/admin/admin_app.pid -d &'
  end
end


После того, как Вы убедились, что Ваш файл deploy.rb похож на правду и вроде должен работать, нужно ввести команду:
mina setup

Данная команда создаст необходимую инфраструктуру папок внутри корневой папки для вашего приложения(в конфиге это /var/www/admin_app).
Убедитесь, что файл database.yml содержит информацию и она верна. Файл находится в /var/www/myapp/shared/config.
Далее вводим:
mina deploy

Если данная операция прошла успешно, Ваше приложение будет ждать Вас по пути: /var/www/admin_app.

Вы можете легко расширять свой деплой, прописывая task'и в deploy.rb и вызывая их внутри :deploy => :environment.
Например, напишем два task'а, для запуска и остановки resque — воркеров.
Task'и для resque
desc 'Start resque-workers'
   task :start_workers do
    queue 'echo "-----> Start resque-workers"'
    queue "cd #{app_path} && RAILS_ENV=#{stage} && touch resque.pid"
    queue "cd #{app_path} &&  bundle exec rake resque:work QUEUE=contacts_adder BACKGROUND=yes TERM_CHILD=1"
    queue "cd #{app_path} &&  bundle exec rake resque:work QUEUE=photo_downloader BACKGROUND=yes TERM_CHILD=1"
    # Запуск по задачи по расписанию
    queue 'echo "-----> Start resque-scheduler"'
    queue "cd #{app_path} && rake resque:scheduler BACKGROUND=yes VERBOSE=yes RAILS_ENV=production "
  end

  desc 'Stop resque-workers'
  task :stop_workers do
    queue 'echo "-----> Stop resque-workers"'
    queue 'kill -9 `ps aux | grep [r]esque | grep -v grep | cut -c 10-16` || true'
  end


Вы можете просматривать список доступных task'ов с помощью команды:
mina tasks

Puma
Для настройки Puma понадобится два файла: файл конфигурации puma.rb и shell — скрипт, предназначенный для запуска/остановки/рестарта Puma.

Создайте файл puma.rb в config/. И заполните его следующей информацией:
puma.rb
#!/usr/bin/env puma
environment ENV['RAILS_ENV'] || 'production'
pidfile "/var/www/admin/current/shared/tmp/pids/puma.pid"
stdout_redirect "/var/www/admin/current/shared/tmp/log/stdout", "/var/www/admin/current/shared/tmp/log/stderr"
threads 2, 16
workers 2
bind "unix:///var/www/admin/current/shared/tmp/sockets/puma.sock"
daemonize true


Внимательно проверяйте пути при заполнении конфигов!

Создайте файл bin/puma.sh. Заполните его:
puma.sh
#! /bin/sh
PUMA_CONFIG_FILE=/var/www/admin/current/config/puma.rb
PUMA_PID_FILE=/var/www/admin/current/shared/tmp/pids/puma.pid
PUMA_SOCKET=/var/www/admin/current/shared/tmp/sockets/puma.sock

# check if puma process is running
puma_is_running() {
  if [ -e $PUMA_SOCKET ] ; then
    if [ -r $PUMA_PID_FILE ] ; then
       if ps -p `cat $PUMA_PID_FILE` > /dev/null; then
        return 0
      else
        echo "No puma process found"
      fi
    else
      echo "No puma pid file found"
    fi
  else
    echo "No puma socket found"
  fi

  return 1
}

case "$1" in
  start)
    echo "Starting puma..."
      if [ -e $PUMA_SOCKET  ] ; then # if socket exists
        rm -f $PUMA_SOCKET
        echo "removed $PUMA_SOCKET"
      fi
      if [ -e $PUMA_CONFIG_FILE ] ; then
        echo "config"
        bundle exec puma -C $PUMA_CONFIG_FILE
      else
        echo "socket"
        bundle exec puma --daemon --bind unix://$PUMA_SOCKET --pidfile $PUMA_PID_FILE
      fi

    echo "done"
    ;;

  stop)
    echo "Stopping puma..."
      kill -s SIGTERM `cat $PUMA_PID_FILE`
      rm -f $PUMA_PID_FILE
      rm -f $PUMA_SOCKET

    echo "done"
    ;;

  restart)
    if puma_is_running ; then
      echo "Hot-restarting puma..."
      kill -s SIGUSR2 `cat $PUMA_PID_FILE`

      echo "Doublechecking the process restart..."
      sleep 5
      if puma_is_running ; then
        echo "done"
        exit 0
      else
        echo "Puma restart failed :/"
        exit 1
      fi
    fi

    echo "Trying cold reboot"
    bin/puma.sh start
    ;;

  *)
    echo "Usage: script/puma.sh {start|stop|restart}" >&2
    ;;
esac


Теперь дополним файл deploy.rb настройками Puma:
Task'и для Puma

namespace :puma do
  desc 'Start the application'
  task :start do
    queue 'echo "-----> Start Puma"'
    queue "cd #{app_path} && RAILS_ENV=#{stage} && bin/puma.sh start" , :pty => false
  end

  desc 'Stop the application'
  task :stop do
    queue 'echo "-----> Stop Puma"'
    queue "cd #{app_path} && RAILS_ENV=#{stage} && bin/puma.sh stop"
  end

  desc 'Restart the application'
  task :restart do
    queue 'echo "-----> Restart Puma"'
    queue "cd #{app_path} && RAILS_ENV=#{stage} && bin/puma.sh restart"
  end
end


Внутри блока «to :launch do»(перед стартом сервера) пропишем:
invoke :'puma:restart'

Не забудьте сделать файл puma.sh исполняемым:
chmod +x bin/puma.sh

И заново производим деплой:
mina deploy

Nginx
Nginx можно ставить прямо из пакетного менеджера:
sudo apt-get install nginx

После того, как nginx установлен нужно удалить default site.
sudo rm /etc/nginx/conf.d/sites-enabled/default

Создайте файл /etc/nginx/sites-available/my_app.conf:
Конфиг nginx'a
upstream my_app {
          server unix:///var/www/admin/current/shared/tmp/sockets/puma.sock;
        }
server {
    listen 80;
    server_name 100.100.100.100; # change to match your URL
    root /var/www/admin/current/public;

    location / {
        root /var/www/admin/current/public;
        try_files $uri @app;
        gzip_static on;
        expires max;
        proxy_read_timeout 150;
        add_header Cache-Control public;
    }

    location @app {
     proxy_pass        http://my_app;
     proxy_set_header  X-Real-IP  $remote_addr;
     proxy_set_header  X-Forwarded-For $proxy_add_x_forwarded_for;
     proxy_set_header  X-Forwarded-Proto http;
     proxy_set_header  Host $http_host;
     proxy_redirect    off;
     proxy_next_upstream error timeout invalid_header http_502;
    }


Если вдруг возникли какие — то проблемы, можно попробовать прописать данные строчки прямо в файле /etc/nginx/nginx.conf. Информация о сервере (server {}), должна находиться внутри блока http(http{}).

Затем создадим ссылку:
sudo ln -sf /etc/nginx/sites-available/my_app.conf /etc/nginx/sites-enabled/my_app.conf

И сделаем перезапуск nginx'a:
sudo service nginx restart

Вот собственно и все. Приятного деплоя :)

Комментарии (14)


  1. ibKpoxa
    26.08.2015 13:00
    -2

    А рецепт под какую ось? Вижу, что не под винду :)


    1. phoenixweiss
      26.08.2015 18:29

      должен подойти под любой *nix по сути. В примере по всей видимости что-то Debian-подобное


    1. rinat_crone
      26.08.2015 18:41

      А вы серьёзно разрабатываете и/или деплоите Rails-приложения на Windows? Или это такой троллинг?


      1. ibKpoxa
        26.08.2015 19:42

        Это рекомендация автору написать под какую ОС его рецепт, не все видят что это под CentOS не заработает, даже если поймут что речь про Linux.


        1. erlyvideo
          27.08.2015 08:21
          +1

          А вы серьёзно разрабатываете и/или деплоите Rails-приложения на CentOS? Или это такой троллинг?


  1. exdee
    26.08.2015 13:34

    Во всех местах, где идет взаимодействие с процессом пумы — почему нельзя использовать pumactl?


  1. mystdeim
    26.08.2015 15:23
    +1

    Почему выбрали mina, а не capistrano?


    1. TheSunwave
      26.08.2015 15:50
      +1

      Мануалов по Capistrano уже десятки, а по mina — нет, мне кажется это правильно.


    1. Vizakenjack
      26.08.2015 16:48

      На моем опыте — mina реально быстрее работает.


      1. phoenixweiss
        26.08.2015 18:30

        где-то можно найти грамотное сравнение?
        Просто реально интересно что кроме капистраны еще интересного есть.


        1. LimeOrange
          31.08.2015 17:27

          Cap выполняет каждую команду отдельно (он даже на каждую команду отдельный туннель открывает, если мне не изменяет память), Mina же генерирует 1 bash-скрипт который и выполняет на сервере одной командой.
          Единственный минус Mina — нет нативной поддержки одновременного деплоя на более чем 1 сервер.


  1. r00t_aka_spin
    26.08.2015 15:29

    Рекомендую взглянуть на eye для мониторинга и запуска пумы или воркеров.


  1. rinat_crone
    26.08.2015 18:39
    +1

    Рекомендую разного рода конфигурационные данные вроде количества тредов или воркеров выносить в ENV. Посмотрите в сторону foreman – он и upstart скрипты автоматом при деплое создаст для всех нужных процессов (puma.sh по факту не нужен будет) и эти самые ENV-переменные правильно вам прокинет из .env-файла.


    1. rinat_crone
      26.08.2015 18:44
      +1

      Ещё важная информация для тех, кому не хочется заморачиваться с конфигурацией сервера – для небольших проектов Heroku теперь предлагает hobby-инстансы по $7 в месяц, что сравнимо как по мощности, так и по цене с тем же Linode или Digital Ocean, но избавляет от геммороя с настройкой/поддержкой всего окружения.