Демонизация по-быстрому или сага о правильном велосипеде.

CORPSE аватар

Что такое демон, человеку, знакомому с UNIX средой, думаю объяснять не нужно. Сегодня я расскажу, как быстро и просто запускать процессы в SysV-style системе в качестве демонов с pid, lock файлами и т.п., в общем, по-феншую. Думаю, новичкам пригодится. А может быть и не только новичкам.

Всё ниже описанное было реализовано и проверено на моём домашнем сервере, работающем под Debian Lenny 5.0.3.

Итак, я против изобретения велосипеда, если этот самый велосипед имеется в наличии, не требует топлива из обеднённого урана экзотических зависимостей и не натирает попу при езде (читай - довольно удобен в использовании). Встречайте - daemonize! Эту небольшую, простую в использовании и удобную утилиту написал некто Брайан Клаппер (Brian Clapper), за что ему моё большое нечеловеческое спасибо.

Подбираем тут:
Оффсайт: http://www.clapper.org/software/daemonize/

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

SYNOPSIS
       daemonize [-a] [-c directory] [-e stderr] [-o stdout] [-p pidfile] [-l lockfile] [-u user] [-v] path [arg] ...

Специально для танкистов ниже в мане подробно описано значение каждого параметра. То, что неочевидно, приведу ниже, дабы облегчить Вам жизнь:

-a     Append to the output files, rather than overwriting them (which is the default). Only applicable if -e and/or -o are specified.

Иными словами, при использовании с ключом -a вместо перезаписи лог файлов будет происходить дописывание в уже существующие.
-v     Cause daemonize to write verbose messages to standard error, telling what it’s doing as it daemonizes the program.

Обычный "вербоуз" - чуть больше информации в еррорлоге о работе программы.

path - это полный путь к запускаемой программе. Например, если я хочу запустить своего джаббер бота, я должен в качестве path указать /opt/bot/bot, а не просто bot, даже если я использовал параметр -c /opt/bot.

arg - в данном случае - аргументы запускаемой программы.

Рекомендации по сборке велосипеда поражают своей подробностью, дотошностью и вниманием к деталям:

INSTALLATION

Normal installation:

	$ sh configure
	$ make
	$ su 
	# make install

For a detailed report of the available "configure" options:

	$ sh configure --help

Что радует, вышеописанного вполне достаточно. На упомянутой lenny 5.0.3 велосипед самособрался в мгновение ока, я даже не успел сказать "вотнифигасебебрайанклаппермолодецкакой"! Чего и Вам желаю.

Итак, с устройством велосипеда разобрались, теперь учимся на нём ездить. :) Что необходимо для феншуистой SysV системы? Правильно! Скрипт запуска/остановки в /etc/init.d/ и соответствующие симлинки в rc*.d. Не буду особо мучать вас теорией, а лучше сразу перейду к практике. Все, кто ещё не знают принципов, по которым должен работать скрипт из /etc/init.d/, могут пообщаться с гуглом на предмет "SysV-style".

Рассмотрим создание подобного файла на примере моей задачи. Это всего лишь пример, который я набросал на коленке. Он всем меня устраивает и даже (о, чудо!) работает.

Бот состоит из двух частей. :)
"Внутре у ей неонка и думатель" (с) Стругацкие
Внутре у него действительно "думатель" и транспорт для общения думателя с джаббером (есть ещё транспорт для работы с ICQ, реализованный на licq, но код совершенно идентичен, так что не за чем перегружать блог). Как они общаются между собой в данном контексте не важно. Нам необходимо, чтобы в качестве демонов были запущены два процесса (/opt/bot/bot и /opt/bot/js) от имени некоего пользователя (опять таки bot), при этом в отдельной директории были созданы для каждого из процессоров лог, еррор лог, лок файл и пид файл. А при останове - пид и лок файлы удалились. Ну что-ж, у нас теперь есть велосипед, поэтому можно с места в карьер:

#!/bin/bash

# Переменная содержит путь к рабочей директории процесса
BPATH="/opt/bot"
# А здесь хранится путь к некоей директории var, в которой будут содержаться наши логи, пид и лок файлы
VP="/opt/bot/var"
# Имя пользователя, от которого должны запускаться процессы. Помни! Сильно кастрированный в правах пользователь - залог твоей безопасности!
USER="bot"

# Функция проверяет, запущен ли процесс с именем, переданным параметром. Если запущен, возвращает 0, в противном случае 1. Не логично? Конечно! Мы же используем stderr!
# Usage: chk 
function chk() {
        if [ -f "$VP/$1.pid" ]; then   # Смотрим, существует ли пид файл
                TPID="$( ps -A | grep "^ *$( cat $VP/$1.pid) " )"  # Если существует, проверяем, запущен ли процесс с пидом, хранящемся в этом файле
                fi
        if [ -n "$TPID" ]; then  # Если в этой переменной что-то есть, т.е. процесс запущен, то возвращаем 0
                return 0
                else # А если не запущен, то 1.
                return 1
                fi
        }

# Функция для запуска. 1-й параметр - имя процесса, второй - его описание для красоты в stdout :)
# Usage: fstop  [long name]
function fstart() {
        if [ -z "$2" ]; then # Если описания нет, то берём в качестве описания имя процесса
                LNAME="$1"
                else
                LNAME="$2"
                fi
        if [ -n "$1" ]; then # Нам нужен хотя бы один параметр - имя процесса
                SNAME="$1"
                echo "Starting $LNAME module..."
                if chk $SNAME; then # Если процесс уже запущен, грязно ругаемся на пользователя
                        echo Another $LNAME process is already running...
                        else # Если нет, то запускаем по-феншую, как и обещали
                        /usr/local/sbin/daemonize -c "$BPATH" -e "$VP/err_$SNAME.log" -o "$VP/$SNAME.log" -p "$VP/$SNAME.pid" -l "$VP/$SNAME.lock" -u "$USER" -v "$BPATH"/$SNAME
                        if chk $SNAME; then # Проверяем снова. Процесс таки запустился?
                                echo $LNAME started successfully... # Всё хорошо, юзер, спи спокойно. :)
                                else 
                                echo $LNAME start failed! See $VP/err_$SNAME.log for more information. # Шеф, всё пропало! Надо послать юзера на в лог.
                                exit 1 # Выходим тоже по-феншую, указывая, что завершились фейлом
                                fi
                        fi
                fi
       }

# Тут аналогичная функция для останова. Хорошая, пушистая. Умеет подтирать локи и пиды за собой.
# Usage: fstop  [long name]
function fstop() {
        if [ -z "$2" ]; then
                LNAME="$1"
                else
                LNAME="$2"
                fi
        if [ -n "$1" ]; then
                SNAME="$1"
                echo "Stopping "$LNAME" process..."
                if chk $SNAME; then
                        kill "$( cat "$VP/$SNAME.pid" )"
                        if chk $SNAME; then
                                echo "$LNAME stopping failed!"
                                else
                                echo Removing lock file...
                                rm -f $VP/$SNAME.lock
                                echo Removing pid file...
                                rm -f $VP/$SNAME.pid
                                echo Main bot stopped successfully...
                                fi
                        else
                        echo The "$LNAME" process not running!
                        fi
                else
                echo Error stopping some process. Not enough parameters.
                exit 1
                fi
        }

# Функция, проверяющая статус запущенных процессов. С функцией chk это очень просто.
# Usage: fstatus  [long name]
function fstatus() {
	if [ -z "$2" ]; then
		LNAME="$1"
		else
		LNAME="$2"
		fi
	if [ -n "$1" ]; then
		SNAME="$1"
 		if chk $SNAME; then
			echo "$LNAME" is working...
			else
			echo "$LNAME" is done...
			fi
		else
		echo Not enough parameters!
		exit 1
		fi
	}

# С вот этого места начинаем наслаждаться результатом. Научились ездить на велосипеде и теперь это нам даётся легко и просто. 
#Если мне надо добавить ещё транспорт аськи например, то для этого вполне достаточно двух строчек в start и stop. Да и переправить скрипт для другого приложения тоже довольно просто.

case "$1" in
        start) 
                fstart bot "Main bot"
                fstart jb "Jabber transport"
                ;;
        stop)
                fstop bot "Main bot"
                fstop jb "Jabber transport"
                ;;
        stat*)
                fstatus bot "Main bot"
                fstatus jb "Jabber transport"
                ;;
        restart)
                $0 stop && $0 start
                ;;
        *)
                echo "Usage: /etc/init.d/$NAME {start|stop|status|restart}" >&2
                exit 1
                ;;
        esac

exit 0

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

Возможно также вам захочется запускать из одного срипта различные процессы от имени различных пользователей, тогда необходимо будет допилить функцию fstart, чтобы передавать туда имя пользователя ещё одним параметром. На данный момент синтаксис вызова всех функций сделан одинаковым для всеобщего вселенского блага. А можно например ещё сделать force-reload через kill -9, если вам это принесёт счастье. У меня просто сегодня не дошли до этого руки. Скрипт писался минут 20-30, а статья 2-3 часа. Но чего только не сделаешь, когда задалбывают по телефону и в аське однотипными вопросами хочется помочь начинающим собратьям по разуму! :)

Осталось только поместить полученный скрипт в /etc/init.d и наделать кучу нужных симлинков. Но это совсем просто, поэтому я подло оставляю Вас с этим один на один и вероломно иду спать, ибо за бортом -3 и половина третьего ночи, а за спиной рабочая неделя. До новых встреч!

*Мну открыл пасть, засунул в неё лапу и упал в спячку*

Хрррррр.... Хррррр..... Хрррррр....

CORPSE аватар

Вдогонку из берлоги...

black:/opt/bot# /etc/init.d/botd start
Starting Main bot module...
Creating PID file "/opt/bot/var/bot.pid".
Changing ownership of PID file to "bot" (1001)
Appending to /opt/bot/var/bot.log
Appending to /opt/bot/var/err_bot.log
Main bot started successfully...
Starting Jabber transport module...
Creating PID file "/opt/bot/var/jb.pid".
Changing ownership of PID file to "bot" (1001)
Appending to /opt/bot/var/jb.log
Appending to /opt/bot/var/err_jb.log
Jabber transport started successfully...

black:/opt/bot# ls var
bot_err.log  bot.lock  bot.log  bot.pid  err_bot.log  err_jb.log  jb_err.log  jb.lock  jb.log  jb.pid

black:/opt/bot# cat var/bot.pid var/jb.pid
4704
4735
black:/opt/bot# 

black:/opt/bot# ps -A | grep -E "4704|4735"
 4704 ?        00:00:00 bot
 4735 ?        00:02:27 jb

black:/opt/bot# /etc/init.d/botd start
Starting Main bot module...
Another Main bot process is already running...
Starting Jabber transport module...
Another Jabber transport process is already running...

black:/opt/bot# /etc/init.d/botd restart
Stopping Main bot process...
Removing lock file...
Removing pid file...
Main bot stopped successfully...
Stopping Jabber transport process...
Jabber transport stopping failed!
Starting Main bot module...
Creating PID file "/opt/bot/var/bot.pid".
Changing ownership of PID file to "bot" (1001)
Appending to /opt/bot/var/bot.log
Appending to /opt/bot/var/err_bot.log
Main bot started successfully...
Starting Jabber transport module...
Creating PID file "/opt/bot/var/jb.pid".
Changing ownership of PID file to "bot" (1001)
Appending to /opt/bot/var/jb.log
Appending to /opt/bot/var/err_jb.log
Jabber transport started successfully...

black:/opt/bot# cat var/bot.pid var/jb.pid
13836
13869

black:/opt/bot# /etc/init.d/botd stop
Stopping Main bot process...
Removing lock file...
Removing pid file...
Main bot stopped successfully...
Stopping Jabber transport process...
Removing lock file...
Removing pid file...
Main bot stopped successfully...

black:/opt/bot# ls var/
bot_err.log  bot.log  err_bot.log  err_jb.log  jb_err.log  jb.log

Настройки просмотра комментариев

Выберите нужный метод показа комментариев и нажмите "Сохранить установки".