Во-первых, нужно сказать «спасибо» автору этого руководства. Без него я бы долго ещё не сел за написание сего поста: кучу проблем пришлось бы решать самостоятельно. Однако в моём случае ситуация была чуть другая (не 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, чтобы при обновлении не слетали настройки?
Автор: orthanner