Предыстория
На создание этой статьи меня толкнула недавняя публикация о деплое. В приведённой статье описан способ разворачивания проекта на основе 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 этапа:
- Убедиться, что приложение корректно запускается (пока без автоматизации);
- Настроить сервер так, чтобы наше rails-приложение работало как полноценная служба;
- На основе этого настроить автоматизированное разворачивание с помощью mina.
Первый запуск
На сервере создан пользователь webapp, от имени которого будет работать наше приложение. Установлен rvm (в данном примере только для пользователя webapp), ruby нужной нам версии, nginx и прочее. В Gemfile присутствует следующая строчка:
# ... gem 'puma', group: :production # ...
Предварительная настройка mina
Добавим в Gemfile следующую строчку:
gem 'mina', group: :development
Затем сгенерируем конфигурационный файл командой:
mina init
После чего приведём созданный файл 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.
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 со следующим содержимым:
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:
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, получим следующее:
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 и создать задачу для установки корректной версии руби и гемсета.
Всё вместе может выглядеть следующим образом:
# ... # 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/
Добавить комментарий