Автозапуск rails+rvm+unicorn+nginx на FreeBSD

от автора

Во-первых, нужно сказать «спасибо» автору этого руководства. Без него я бы долго ещё не сел за написание сего поста: кучу проблем пришлось бы решать самостоятельно. Однако в моём случае ситуация была чуть другая (не Debian, а FreeBSD), да и вопрос с автозапуском unicorn остался открытым. Встречавшиеся мне на просторах интернета решения на изящество тоже не претендовали: делать по службе на веб-приложение — моветон. Во FreeBSD эта проблема решается на первый взгляд просто — созданием «метаслужб», позволяющих запускать более одного экземпляра (в качестве примера — FreeBSD jails). Однако, как это часто бывает, есть нюансы…

rc.d

Так как все rc.d-конфиги являются shell-скриптами, можно не ограничиваться файлом /etc/rc.conf и его собратом /etc/rc.conf.local, а поместить фрагменты в каталоги /etc/rc.conf.d и /usr/local/etc/rc.conf.d. Единственная рекомендация — файлы должны называться так же, как использующие их службы. Это позволяет избежать превращения конфигурационных файлов в нечитабельных монстров из сотни с лишним строк, в которых без grep’а не разберёшься. Проблема в другом: если у службы есть несколько экземпляров, ВСЕ их настройки формально должны находиться в одном файле (имя которого, напомню, совпадает с именем службы). Чтобы решить и эту проблему, нужно разобраться, как работают скрипты запуска служб.
Обычно этот процесс описывают примерно следующим образом: " При старте системы выполняется скрипт /etc/rc. Он считывает настройки из файла /etc/rc.conf (а также /etc/rc.conf.local — при наличии такового)…" А вот и нет! Вместо этого он включает в себя файл /etc/rc.subr, определяющий кое-какие вспомогательные функции и точно так же включающий в себя файлы системных настроек:

... if ${_rc_conf_loaded:-false}; then         : else         if [ -r /etc/defaults/rc.conf ]; then                 debug "Sourcing /etc/defaults/rc.conf"                 . /etc/defaults/rc.conf                 source_rc_confs         elif [ -r /etc/rc.conf ]; then                 debug "Sourcing /etc/rc.conf (/etc/defaults/rc.conf doesn't exist)."                 . /etc/rc.conf         fi         _rc_conf_loaded=true fi if [ -f /etc/rc.conf.d/"$_name" ]; then         debug "Sourcing /etc/rc.conf.d/${_name}"         . /etc/rc.conf.d/"$_name" fi ... 

В свою очередь, файл /etc/defaults/rc.conf содержит следующий код:

... rc_conf_files="/etc/rc.conf /etc/rc.conf.local" ... ############################################################## ### Define source_rc_confs, the mechanism used by /etc/rc.* ## ### scripts to source rc_conf_files overrides safely.       ## ##############################################################  if [ -z "${source_rc_confs_defined}" ]; then         source_rc_confs_defined=yes         source_rc_confs () {                 local i sourced_files                 for i in ${rc_conf_files}; do                         case ${sourced_files} in                         *:$i:*)                                 ;;                         *)                                 sourced_files="${sourced_files}:$i:"                                 if [ -r $i ]; then                                         . $i                                 fi                                 ;;                         esac                 done         } fi 

После загрузки всех настроек составляется список системных служб, и для каждой службы вызывается скрипт запуска — вне зависимости от того, включена она или нет. Поэтому настоятельно не рекомендуется помещать файлы пользовательских служб в каталог /etc/rc.d — это увеличит время загрузки системы. Для пользовательских служб есть каталог /usr/local/etc/rc.d — находящиеся в нём скрипты вызываются в последнюю очередь, когда основная система уже сконфигурирована и готова к работе. Но настоящая чёрная магия начинается дальше: каждый из этих скриптов снова включает скрипт /etc/rc.subr, загружающий относящиеся к нему переменные. И вот это-то даёт возможность делать ещё более модульные файлы настроек: достаточно включить в основной файл в каталоге /etc/rc.conf.d код вроде следующего (сразу на примере unicorn):

unicorn_enable="YES" unicorn_profiles=""  for p in $(grep -rlE '^unicorn_[0-9a-zA-Z]+_enabled="[Yy][Ee][Ss]"$' /usr/local/etc/unicorn.d); do 	bn=$(basename $p) 	if [ -n "$unicorn_profiles" ]; then 		unicorn_profiles="$unicorn_profiles $bn" 	else 		unicorn_profiles="$bn" 	fi 	. $p done 
Unicorn

Первым делом поставим sudo — это намного удобнее, чем «лепить» команду для запуска через su, напрашиваясь на shell injection.

cd /usr/ports/security/sudo && make install clean 

Ещё мы поставим утилиту под названием portmaster — это облегчит дальнейший процесс:

cd /usr/ports/ports-mgmt/portmaster && make install clean 

Весь остальной софт будем ставить с её помощью:

LN='ln -f' portmaster devel/bison textproc/libxml2 textproc/libxslt textproc/diffutils devel/gmake security/openssl devel/automake devel/git devel/subversion shells/bash 

Для ленивых — установка окружения по умолчанию:

portmaster lang/ruby19 sysutils/rubygem-bundler www/rubygem-unicorn 

Описание дальнейших шагов можно найти в оригинальной статье, а мы переходим к следующей части — установке всего этого добра в качестве службы.

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

is_unicorn_profile() {     local profile      for profile in $unicorn_profiles; do         if [ "$profile" = "$1" ]; then             return 0         fi     done      return 1 }  if [ -n "${unicorn_profiles}" ]; then         if [ -n "$2" ]; then                 profile="$2"                 if ! is_unicorn_profile $profile; then                         echo "$0: no such profile defined in unicorn_profiles."                     exit 1                 fi                 eval unicorn_socket=\${unicorn_${profile}_socket:-"/tmp/${name}-${profile}.sock"}                 eval unicorn_config=\${unicorn_${profile}_config}                 eval unicorn_dir=\${unicorn_${profile}_dir}                 eval unicorn_flags=\${unicorn_${profile}_flags:-"${unicorn_flags}"}                 eval unicorn_environment=\${unicorn_${profile}_environment:-"${unicorn_environment}"}                 eval unicorn_rails=\${unicorn_${profile}_rails:-"${unicorn_rails}"}                 eval unicorn_user=\${unicorn_${profile}_user:-"${unicorn_user}"}                 eval unicorn_procname=\${unicorn_${profile}_procname:-"${unicorn_procname}"}                 eval unicorn_bundler=\${unicorn_${profile}_bundler:-"${unicorn_bundler}"}                 eval unicorn_rvm=\${unicorn_${profile}_rvm:-"${unicorn_rvm}"}                 eval unicorn_ruby=\${unicorn_${profile}_ruby:-"${unicorn_ruby}"}                 if checkyesno unicorn_rvm; then                         unicorn_procname="~${unicorn_user}/.rvm/rubies/ruby-${unicorn_ruby}/bin/ruby"                 fi         elif [ -n "$1" ]; then                 for profile in ${unicorn_profiles}; do                 echo "Processing ${name} profile: ${profile}"                 $0 $1 ${profile}             done             exit 0         fi fi 

Использование RVM создаёт проблему: для разных приложений необходимо подстраивать окружение. Можно, конечно, после запуска каждого экземпляра переинициализировать окружение, но проще воспользоваться вспомогательным скриптом:

if checkyesno unicorn_rvm; then         if [ -d "~${unicorn_user}/.rvm/${unicorn_ruby}" ]; then                 /usr/local/bin/sudo -u $unicorn_user /usr/local/bin/unicorn-wrapper ${unicorn_ruby} $(basename ${command}) ${command_args}                 rc=$?         else                 echo "Ruby version ${unicorn_ruby} not found by RVM for user ${unicorn_user}"                 rc=1         fi else         /usr/local/bin/sudo -u $unicorn_user ${command} ${command_args}         rc=$? fi 

Файл /usr/local/bin/unicorn-wrapper — простая обёртка, устанавливающая нужную версию Ruby и выполняющая заданную команду с аргументами. Полную версию скрипта запуска можно взять рядом. Пользоваться очень просто:

service unicorn <action> [<app>] 

Пример конфига:

unicorn_redmine_enabled="YES" unicorn_redmine_dir="/var/www/sites/mycoolsite.tld/redmine" unicorn_redmine_rails="NO" 

По-хорошему, надо бы оформить Port Request, но пока руки не дошли.

P. S. Остался открытым только один вопрос: как это добро интегрировать с тем же capistrano, чтобы при обновлении не слетали настройки?

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


Комментарии

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

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