Настройка сервера и деплоя: rvm, rails, puma, nginx, mina

от автора


Предыстория

На создание этой статьи меня толкнула недавняя публикация о деплое. В приведённой статье описан способ разворачивания проекта на основе rbenv, я же опишу ситуацию с rvm и настройкой upstart.

Задачи и требования

Итак, дано: простейшее RubyOnRails-приложение. В случае разворачивания своего проекта я ставил перед собой следующие задачи:

  • ОС Ubuntu LTS 14.04;
  • Веб-сервер Nginx;
  • Сервер приложения Puma;
  • Использование RVM для установки ruby требуемой версии;
  • Автоматический запуск приложения при запуске VPS-сервера, возможность управлять приложением как службой;
  • Автоматизация процесса деплоя с помощью mina;

Почему Ubuntu?
Просто я к ней привык (точнее к её производной — Linux Mint).

Почему puma, а не unicorn или passenger?
О пуме я слышал неплохие отзывы, а у unicron страшненький сайт. Passenger, на мой взгляд, нарушает принцип единственной обязанности — я хочу иметь веб-сервер и приложение-сервер.

Почему RVM?
Я к нему привык — у меня он установлен на локальной машине, хочу его видеть и на production.

Почему mina?
Она действительно проще чем capistrano и при этом быстрее. Скорость достигается за счёт того, что для каждой задачи capistrano создаёт отдельное ssh-соединение. Mina же формирует shell-скрипт и выполняет его в рамках одного соединения.

В данном случае задача легко разбивается на 3 этапа:

  1. Убедиться, что приложение корректно запускается (пока без автоматизации);
  2. Настроить сервер так, чтобы наше rails-приложение работало как полноценная служба;
  3. На основе этого настроить автоматизированное разворачивание с помощью mina.

Первый запуск

На сервере создан пользователь webapp, от имени которого будет работать наше приложение. Установлен rvm (в данном примере только для пользователя webapp), ruby нужной нам версии, nginx и прочее. В Gemfile присутствует следующая строчка:

# ... gem 'puma', group: :production # ... 

Предварительная настройка mina

Добавим в Gemfile следующую строчку:

gem 'mina', group: :development 

Затем сгенерируем конфигурационный файл командой:
mina init

После чего приведём созданный файл config/deploy.rb к следующему виду:

config/deploy.rb

require 'mina/bundler' require 'mina/rails' require 'mina/git'  set :domain, 'awesome_address' set :user, 'webapp' set :deploy_to, '/home/webapp/awesome' set :repository, 'https://github.com/awesome_user/awesome.git' set :branch, 'master'  set :shared_paths, ['config/database.yml', 'config/secrets.yml', 'config/puma.rb', 'log']  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! %[mkdir -p "#{deploy_to}/#{shared_path}/puma"]   queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/puma"]    queue! %[touch "#{deploy_to}/#{shared_path}/config/database.yml"]   queue! %[touch "#{deploy_to}/#{shared_path}/config/secrets.yml"]   queue! %[touch "#{deploy_to}/#{shared_path}/config/puma.rb"]   queue  %[echo "-----> Be sure to edit '#{deploy_to}/#{shared_path}/config/database.yml', 'secrets.yml' and puma.rb."] end  desc "Deploys the current version to the server." task :deploy => :environment do   deploy do     # Put things that will set up an empty directory into a fully set-up     # instance of your project.     invoke :'git:clone'     invoke :'deploy:link_shared_paths'     invoke :'bundle:install'     #invoke :'rails:db_migrate'     #invoke :'rails:assets_precompile'     #invoke :'deploy:cleanup'   end end 

В данном файле закомментированы строки кода, отвечающие за выполнение рутинных операций по разворачиванию проекта. Пока эти действия мы будем выполнять вручную.

Также хочу отметить, что приведённый файл настройки mina я не добавляю в контроль версий.

После чего запустим команду:
mina setup

И получим необходимую структуру папок на сервере, а также конфигурационные файлы, которые необходимо заполнить корректными данными. Предполагается, что данные файлы вы не будете добавлять в контроль версий, а хранить исключительно на сервере. Ниже приведён пример минимальной конфигурации для puma.

puma.rb

environment "production"  bind  "unix:///home/webapp/awesome/shared/puma/puma.sock" pidfile "/home/webapp/awesome/shared/puma/puma.pid" state_path "/home/webapp/awesome/shared/puma/puma.state"  activate_control_app 

Также я нарочно не храню файл puma.rb в контроле версий, т.к. считаю, что конфигурация запуска сервера может быть индивидуальна как и настройки базы данных.

Для заметки же достаточно создать в приложении файлик config/puma.sample.rb с примером.

Теперь же запустим «разворачивание» нашего приложения:
mina deploy

После выполнения данной команды, на сервере появиться символическая ссылка /home/webapp/awesome/current на папку с кодом нашего проекта.

Настройка nginx
Создадим файл /etc/nginx/sites-available/awesome со следующим содержимым:

awesome

upstream awesome {   server unix:///home/webapp/awesome/shared/puma/puma.sock; }  server {   listen 80 default_server;   listen [::]:80 default_server ipv6only=on;      root /home/webapp/awesome/current/public;      server_name localhost;    location / {     proxy_pass http://awesome;     proxy_set_header Host $host;     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;   }    location ~* ^/assets/ {     # Per RFC2616 - 1 year maximum expiry     expires 1y;     add_header Cache-Control public;      # Some browsers still send conditional-GET requests if there's a     # Last-Modified header or an ETag header even if they haven't     # reached the expiry date sent in the Expires header.     add_header Last-Modified "";     add_header ETag "";     break;   } } 

После чего создадим символическую ссылку:
cd /etc/nginx/sites-enabled/
ln -s ../sites-available/awesome awesome

И перезапустим nginx:
service nginx restart

Ручной запуск приложения
Теперь подготовим и запустим наш проект.

Подготавливаем:
cd /home/webapp/awesome/current
bash —login
rvm use ruby-2.2.1
bundle install —without development:test —path ./vendor/bundle —deployment
RAILS_ENV=production bundle exec rake db:migrate
RAILS_ENV=production bundle exec rake assets:precompile

Запускаем:
bundle exec puma -C config/puma.rb

Открываем проект в браузере — всё должно работать.

Приложение как служба

Для того, чтобы приложение работало как служба и запускалось при старте/перезагрузке системы, нужно создать init.d-скрипт или upstart-конфигурацию.

Я выбрал upstart, потому как получаемый файл выходит намного короче и проще для восприятия.

Для того, чтобы корректно подружить upstart и rvm, создаём обётку для исполняемых файлов наших гемов:

rvm alias create awesome ruby-2.2.1@default

После чего создаём upstart-конфигурацию /etc/init/awesome.conf:

awesome.conf

description "Awesome puma service"  # This starts upon bootup and stops on shutdown start on runlevel [2345] stop on runlevel [06]  setuid webapp setgid webapp  respawn respawn limit 3 30  script   cd /home/webapp/awesome/current   /home/webapp/.rvm/wrappers/awesome/bundle exec puma -C config/puma.rb end script 

И теперь мы можем делать так:
start awesome
stop awesome
restart awesome
status awesome

Итоговая автоматизация

Так как деплой будет происходить от имени пользователя webapp, а для перезапуска службы нам нужны права суперпользователя, добавим соответствующую строчку в sudoers.

Запускаем команду:
visudo
Добавляем:

webapp ALL=(ALL) NOPASSWD: /sbin/start awesome, /sbin/stop awesome, /sbin/restart awesome, /sbin/status awesome 

И сохраняем.

И снова mina
В подобных случаях mina предлагает использовать собственный модуль mina/rvm, однако rvm предоставляет нам возможность создавать псевдонимы для версий ruby и gemset-ов — wrapper-ы.

Для того, чтобы при деплое запускался именно бандлер wrappera, необходимо добавить следующую строку:

set :bundle_bin, '/home/webapp/.rvm/wrappers/awesome/bundle' 

Также добавим задачу для перезапуска сервера:

desc "Restart the puma web server." task :restart do   queue 'sudo restart awesome' end 

Добавив всё это и раскомментировав необходимые строки в задаче deploy, получим следующее:

deploy.rb

require 'mina/bundler' require 'mina/rails' require 'mina/git'  set :domain, 'awesome_address' set :user, 'webapp' set :deploy_to, '/home/webapp/awesome' set :repository, 'https://github.com/awesome_user/awesome.git' set :branch, 'master'  set :bundle_bin, '/home/webapp/.rvm/wrappers/awesome/bundle'  set :shared_paths, ['config/database.yml', 'config/secrets.yml', 'config/puma.rb', 'log']  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! %[mkdir -p "#{deploy_to}/#{shared_path}/puma"]   queue! %[chmod g+rx,u+rwx "#{deploy_to}/#{shared_path}/puma"]    queue! %[touch "#{deploy_to}/#{shared_path}/config/database.yml"]   queue! %[touch "#{deploy_to}/#{shared_path}/config/secrets.yml"]   queue! %[touch "#{deploy_to}/#{shared_path}/config/puma.rb"]   queue  %[echo "-----> Be sure to edit '#{deploy_to}/#{shared_path}/config/database.yml', 'secrets.yml' and puma.rb."] end  desc "Deploys the current version to the server." task :deploy => :environment do   deploy do     # Put things that will set up an empty directory into a fully set-up     # instance of your project.     invoke :'git:clone'     invoke :'deploy:link_shared_paths'     invoke :'bundle:install'     invoke :'rails:db_migrate'     invoke :'rails:assets_precompile'     invoke :'deploy:cleanup'     to :launch do       invoke :'restart'     end   end end   desc "Restart the puma web server." task :restart do   queue 'sudo restart awesome' end 

Всё готово!
Теперь можем ещё раз запустить разворачивание проекта и посмотреть на результат:

mina deploy

Альтернативы

В качестве альтернативы можно рассматривать puma-jungle.
Это шаблоны upstart и init.d скриптов, которые корректно отрабатывают с rbenv, rvm и прочими менеджерами версий.

В случае с rvm нужно сделать так, чтобы корректно определялась версия ruby и gemset, если он используется — для этого можно использовать .ruby-version и .ruby-gemset файлы.

Можно положить их в контроль версий, а можно создавать во время деплоя через mina. Также вам придётся использовать библиотеку mina/rvm и создать задачу для установки корректной версии руби и гемсета.

Всё вместе может выглядеть следующим образом:

deploy.rb

# ...  # Rvm ruby version and gemset rvm = { ruby_version: 'ruby-2.2.1', ruby_gemset: 'default' }  task :environment do   invoke :"rvm:use[#{rvm[:ruby_version]}@#{rvm[:ruby_gemset]}]" end  # ...  desc "Creates appropriate .ruby-version and .ruby-gemset files." task :'rvm:dot_files' do   queue! %[echo "#{rvm[:ruby_version]}" > .ruby-version]   queue! %[echo "#{rvm[:ruby_gemset]}" > .ruby-gemset] end   task :setup => :environment do   # ... end  task :deploy => :environment do   deploy do     invoke :'git:clone'     invoke :'rvm:dot_files'     # ...   end end  # ... 

Конец

На этом моя публикация заканчивается. Надеюсь, было интересно и полезно.

ссылка на оригинал статьи http://habrahabr.ru/post/265599/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *