Если на вашем сервере работает несколько разных приложений (например, Node.js-, Ruby- или Python-приложение или PHP-скрипт, который должен быть всегда запущен (daemon)), то Supervisor — то, что вам нужно. Он позволяет управлять процессами из одного файла конфигурации:
- Указывать файлы логов для каждого приложения;
- управлять политиками запуска и перезапуска;
- указывать от какого пользователя запускать приложения и т.д.
В моем случае есть PHP-скрипт с Websockets-сервером, который должен быть всегда запущен. В начале я запускал его просто через cron, поставив выполнение cron-а на каждый час. Если скрипт падал, то через час он восстанавливался (меньший интервал приводил к багам), это нам не очень подходило. Поэтому я начал искать другие варианты, про Supervisor я слышал уже давно, но в начале он мне показался сложным в настройке, поэтому ушел на дальнюю полку.
Сегодня я постараюсь рассказать как установить и настроить Supervisor на Ubuntu и CentOS.
Установка
Следующие шаги одинаковы для обоих систем. Разница только в пакетном менеджере (у CentOS — yum, у Ubuntu — apt-get), если у вас CentOS, замените в примере apt-get на yum. Сперва установим Python, а потом с помощью него сам Supervisor и создадим стандартный файл конфигурации.
$ sudo apt-get install python-setuptools $ sudo easy_install supervisor $ sudo echo_supervisord_conf > /etc/supervisord.conf
Если при выполнении последней команды выходит ошибка доступа, то выполните сначала echo_supervisord_conf, скопируйте вывод, создайте файл /etc/supervisord.conf и вставьте вывод туда.
Сделаем Supervisor сервисом и включим автозапуск:
CentOS
$ curl -L https://github.com/Supervisor/initscripts/raw/master/redhat-init-mingalevme > /tmp/supervisord $ sudo mv /tmp/supervisord /etc/init.d/ $ sudo chmod +x /etc/init.d/supervisord
Если нет доступа к интернету или скрипт переехал на другой адрес, то вот его содержимое:
Содержимое init-скрпита
#!/bin/bash # # supervisord This scripts turns supervisord on # # Author: Mike McGrath(based off yumupdatesd) # Jason Koppe adjusted to read sysconfig, # use supervisord tools to start/stop, conditionally wait # for child processes to shutdown, and startup later # Mikhail Mingalev Merged # redhat-init-jkoppe and redhat-sysconfig-jkoppe, and # made the script "simple customizable". # # chkconfig: 345 83 04 # # description: supervisor is a process control utility. It has a web based # xmlrpc interface as well as a few other nifty features. # Script was originally written by Jason Koppe . # # source function library . /etc/rc.d/init.d/functions set -a PREFIX=/usr SUPERVISORD=$PREFIX/bin/supervisord SUPERVISORCTL=$PREFIX/bin/supervisorctl PIDFILE=/var/run/supervisord.pid LOCKFILE=/var/lock/subsys/supervisord OPTIONS="-c /etc/supervisord.conf" # unset this variable if you don't care to wait for child processes to shutdown before removing the $LOCKFILE-lock WAIT_FOR_SUBPROCESSES=yes # remove this if you manage number of open files in some other fashion ulimit -n 96000 RETVAL=0 running_pid() { # Check if a given process pid's cmdline matches a given name pid=$1 name=$2 [ -z "$pid" ] && return 1 [ ! -d /proc/$pid ] && return 1 (cat /proc/$pid/cmdline | tr "\000" "\n"|grep -q $name) || return 1 return 0 } running() { # Check if the process is running looking at /proc # (works for all users) # No pidfile, probably no daemon present [ ! -f "$PIDFILE" ] && return 1 # Obtain the pid and check it against the binary name pid=`cat $PIDFILE` running_pid $pid $SUPERVISORD || return 1 return 0 } start() { echo "Starting supervisord: " if [ -e $PIDFILE ]; then echo "ALREADY STARTED" return 1 fi # start supervisord with options from sysconfig (stuff like -c) $SUPERVISORD $OPTIONS # show initial startup status $SUPERVISORCTL $OPTIONS status # only create the subsyslock if we created the PIDFILE [ -e $PIDFILE ] && touch $LOCKFILE } stop() { echo -n "Stopping supervisord: " $SUPERVISORCTL $OPTIONS shutdown if [ -n "$WAIT_FOR_SUBPROCESSES" ]; then echo "Waiting roughly 60 seconds for $PIDFILE to be removed after child processes exit" for sleep in 2 2 2 2 4 4 4 4 8 8 8 8 last; do if [ ! -e $PIDFILE ] ; then echo "Supervisord exited as expected in under $total_sleep seconds" break else if [[ $sleep -eq "last" ]] ; then echo "Supervisord still working on shutting down. We've waited roughly 60 seconds, we'll let it do its thing from here" return 1 else sleep $sleep total_sleep=$(( $total_sleep + $sleep )) fi fi done fi # always remove the subsys. We might have waited a while, but just remove it at this point. rm -f $LOCKFILE } restart() { stop start } case "$1" in start) start RETVAL=$? ;; stop) stop RETVAL=$? ;; restart|force-reload) restart RETVAL=$? ;; reload) $SUPERVISORCTL $OPTIONS reload RETVAL=$? ;; condrestart) [ -f $LOCKFILE ] && restart RETVAL=$? ;; status) $SUPERVISORCTL status if running ; then RETVAL=0 else RETVAL=1 fi ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart}" exit 1 esac exit $RETVAL
$ sudo chkconfig --level 345 supervisord on
Теперь можно стартовать Supervisor командой sudo service supervisord start.
Ubuntu
$ curl -L https://github.com/Supervisor/initscripts/raw/master/ubuntu > /tmp/supervisord $ sudo mv /tmp/supervisord /etc/init.d/ $ sudo chmod +x /etc/init.d/supervisord
Проверьте в какой директории установлен Supervisor командой
$ which supervisord
Если он установлен в /usr/local/bin, то отредактируйте тридцатую строку в /etc/init.d/supervisord (DAEMON=/usr/bin/supervisord), поправьте путь.
Содержимое файла /etc/init.d/supervisord
#! /bin/sh # # Downloaded from: # http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/trusty/supervisor/trusty/view/head:/debian/supervisor.init # # skeleton example file to build /etc/init.d/ scripts. # This file should be used to construct scripts for /etc/init.d. # # Written by Miquel van Smoorenburg. # Modified for Debian # by Ian Murdock . # Further changes by Javier Fernandez-Sanguino # # Version: @(#)skeleton 1.9 26-Feb-2001 miquels@cistron.nl # ### BEGIN INIT INFO # Provides: supervisor # Required-Start: $remote_fs $network $named # Required-Stop: $remote_fs $network $named # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start/stop supervisor # Description: Start/stop supervisor daemon and its configured # subprocesses. ### END INIT INFO . /lib/lsb/init-functions PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin DAEMON=/usr/local/bin/supervisord NAME=supervisord DESC=supervisor test -x $DAEMON || exit 0 LOGDIR=/var/log/supervisor PIDFILE=/var/run/$NAME.pid DODTIME=5 # Time to wait for the server to die, in seconds # If this value is set too low you might not # let some servers to die gracefully and # 'restart' will not work # Include supervisor defaults if available if [ -f /etc/default/supervisor ] ; then . /etc/default/supervisor fi DAEMON_OPTS="-c /etc/supervisord.conf $DAEMON_OPTS" set -e running_pid() { # Check if a given process pid's cmdline matches a given name pid=$1 name=$2 [ -z "$pid" ] && return 1 [ ! -d /proc/$pid ] && return 1 (cat /proc/$pid/cmdline | tr "\000" "\n"|grep -q $name) || return 1 return 0 } running() { # Check if the process is running looking at /proc # (works for all users) # No pidfile, probably no daemon present [ ! -f "$PIDFILE" ] && return 1 # Obtain the pid and check it against the binary name pid=`cat $PIDFILE` running_pid $pid $DAEMON || return 1 return 0 } force_stop() { # Forcefully kill the process [ ! -f "$PIDFILE" ] && return if running ; then kill -15 $pid # Is it really dead? [ -n "$DODTIME" ] && sleep "$DODTIME"s if running ; then kill -9 $pid [ -n "$DODTIME" ] && sleep "$DODTIME"s if running ; then echo "Cannot kill $LABEL (pid=$pid)!" exit 1 fi fi fi rm -f $PIDFILE return 0 } case "$1" in start) echo -n "Starting $DESC: " start-stop-daemon --start --quiet --pidfile $PIDFILE \ --startas $DAEMON -- $DAEMON_OPTS test -f $PIDFILE || sleep 1 if running ; then echo "$NAME." else echo " ERROR." fi ;; stop) echo -n "Stopping $DESC: " start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE echo "$NAME." ;; force-stop) echo -n "Forcefully stopping $DESC: " force_stop if ! running ; then echo "$NAME." else echo " ERROR." fi ;; #reload) # # If the daemon can reload its config files on the fly # for example by sending it SIGHUP, do it here. # # If the daemon responds to changes in its config file # directly anyway, make this a do-nothing entry. # # echo "Reloading $DESC configuration files." # start-stop-daemon --stop --signal 1 --quiet --pidfile \ # /var/run/$NAME.pid --exec $DAEMON #;; force-reload) # # If the "reload" option is implemented, move the "force-reload" # option to the "reload" entry above. If not, "force-reload" is # just the same as "restart" except that it does nothing if the # daemon isn't already running. # check wether $DAEMON is running. If so, restart start-stop-daemon --stop --test --quiet --pidfile $PIDFILE \ --startas $DAEMON \ && $0 restart \ || exit 0 ;; restart) echo -n "Restarting $DESC: " start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE [ -n "$DODTIME" ] && sleep $DODTIME start-stop-daemon --start --quiet --pidfile $PIDFILE \ --startas $DAEMON -- $DAEMON_OPTS echo "$NAME." ;; status) echo -n "$LABEL is " if running ; then echo "running" else echo " not running." exit 1 fi ;; *) N=/etc/init.d/$NAME # echo "Usage: $N {start|stop|restart|reload|force-reload}" >&2 echo "Usage: $N {start|stop|restart|force-reload|status|force-stop}" >&2 exit 1 ;; esac exit 0
Если вы следовали инструкции и файл конфигурации находится в /etc/supervisord.conf, то отредактируйте файл /etc/init.d/supervisord на 47 строке замените DAEMON_OPTS="-c /etc/supervisor/supervisord.conf $DAEMON_OPTS"
на DAEMON_OPTS="-c /etc/supervisord.conf $DAEMON_OPTS"
Выполняем
$ sudo update-rc.d supervisord defaults
и сервис теперь стартует при запуске системы.
Конфигурация
Открываем файл конфигурации:
$ sudo vi /etc/supervisord.conf
Ищем блок [program:theprogramname] — пример конфигурации программы. Воспользуйтесь этим примером для создания своего конфига. Каждая строка там прокомментирована, сложностей, я думаю, не возникнет. Для примера мой блок конфига PHP-скрипта с Websockets:
Details
[program:websocket] command=/usr/bin/php server.php process_name=websocket directory=/var/www/websocket/bin/ autostart=true autorestart=true user=user stdout_logfile=/var/www/logs/websocket_stdout.log stdout_logfile_maxbytes=10MB stderr_logfile=/var/www/logs/websocket_stderr.log stderr_logfile_maxbytes=10MB