跳转至

Supervisor使用笔记及celery生产部署

1 supervisor作用

Linux的后台进程运行有好几种方法,例如nohup,screen等,但是,如果是一个服务程序,要可靠地在后台运行,我们就需要把它做成daemon,最好还能监控进程状态,在意外结束时能自动重启。

supervisor就是用Python开发的一套通用的进程管理程序,能将一个普通的命令行进程变为后台daemon,并监控进程状态,异常退出时能自动重启。

Supervisor用于linux系统中进行进程守护,supervisor3.x版本仅支持在python2环境下运行,但对其管理的子进程运行环境没有要求,在即将发布的4.x版本开始支持python3。

2 supervisor安装

使用easy_install安装(需要默认python环境为python2)

easy_install supervisor
sudo python2.7 -m easy_install supervisor

Git克隆安装

cd /usr/bin
sudo git clone https://github.com/Supervisor/supervisor.git
cd supervisor
sudo python2 setup.py install

3 supervisor配置

生成需要的文件夹

sudo mkdir /etc/supervisord /etc/supervisord/conf.d /var/log/supervisord

由配置模板生成配置文件并赋权

sudo chmod -R 777 /etc/supervisor/ # 文件夹赋权
sudo echo_supervisord_conf > /etc/supervisord/supervisord.conf # 生成默认配置模板
sudo chmod -R 777 /etc/supervisord/supervisord.conf # 对默认配置文件赋权
sudo gedit /etc/supervisord/supervisord.conf # 编辑默认配置文件

需要特殊配置的地方:

[unix_http_server]
file=/tmp/supervisor.sock   ; the path to the socket file

[supervisord]
logfile=/var/log/supervisord/supervisord.log ; main log file; default $CWD/supervisord.log
logfile_maxbytes=50MB        ; max main logfile bytes b4 rotation; default 50MB
logfile_backups=10           ; # of main logfile backups; 0 means none, default 10
loglevel=info                ; log level; default info; others: debug,warn,trace
pidfile=/tmp/supervisord.pid ; supervisord pidfile; default supervisord.pid
nodaemon=false               ; start in foreground if true; default false
minfds=1024                  ; min. avail startup file descriptors; default 1024
minprocs=200                 ; min. avail process descriptors;default 200
childlogdir=/tmp            ; 'AUTO' child log dir, default $TEMP

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock ; use a unix:// URL  for a unix socket
# 此处需要配置 用于保证supervisor停止进程后其对应的UNIX进程被同步关闭
# 不然会出现celery已被关闭但是任务队列仍然可用的情况
stopasgroup=true             ; send stop signal to the UNIX process group (default false)

# 此处需要配置 保证/etc/supervisord/conf.d/文件夹下所有以.conf类型结尾的文件都被当做配置文件被加载
[include]
files = /etc/supervisord/conf.d/*.conf

4 cleley配置

示例使用的项目根目录为/django/JRFamily/,使用的virtualenv路径为/django/JRFamily/environment/,日志存储路径为/django/JRFamily/log/。

生成日志文件,supervisor不会自动生成:

cd /django/JRFamily/log/
sudo touch celery_beat.log
sudo touch celery_worker.log

新增/etc/supervisord/conf.d/JRFamily.conf文件:

[program:jrfamily_celery_beat]
command=/django/JRFamily/environment/bin/python manage.py celery beat -l info
directory=/django/JRFamily
stdout_logfile=/django/JRFamily/log/celery_beat.log
stderr_logfile=/django/JRFamily/log/celery_beat.log
autostart=true
autorestart=true
user=dreamgo
startsecs=10

[program:jrfamily_celery_worker]
numprocs=2
process_name=worker%(process_num)s
command=/django/JRFamily/environment/bin/python manage.py celery worker -E -l info
directory=/django/JRFamily
stdout_logfile=/django/JRFamily/log/celery_worker.log
stderr_logfile=/django/JRFamily/log/celery_worker.log
autostart=true
autorestart=true
user=dreamgo
startsecs=10

[program:jrfamily_celery_beat]
command=/django/JRFamily/environment/bin/python manage.py celerycam
directory=/django/JRFamily
stdout_logfile=/django/JRFamily/log/celery_beat.log
stderr_logfile=/django/JRFamily/log/celery_beat.log
autostart=true
autorestart=true
user=dreamgo
startsecs=10

autostart为是否自动启动,user为当前系统用户名。

上述脚本依次执行的命令为:

python manage.py celery beat -l info # 开启task发送端
python manage.py celery worker -E -l info # -E是为了celerycam能够捕捉到task状态
python manage.py celerycam # 捕捉task执行状态 可在admin->Djcelery->Task查看

5 RabbitMQ配置

在同一台服务器上部署多个Django站点时,若使用同一个RabbitMQ连接,将导致多个Django站点共用同一个任务队列,使得其中某一个站点只能接受到n/m的任务(n为本站点开启的celery wroker数量,m为总站点开启的celery worker数量)。使用RabbitMQ中的vhost功能来隔绝各个站点之间的RabbitMQ连接,从而确保本站点发出的任务(task)只有本站点开启的worker能够接受到并进行处理。

RabbitMQ中的vhost概念相当于Apache中的VirtualHost。

请按照下列命令顺序添加用户并将用户绑定到对应的vhost上:

sudo rabbitmqctl add_user {username} {password} # 添加用户
sudo rabbitmqctl add_vhost {vhost_name} # 添加vhost
sudo rabbitmqctl set_permissions -p {vhost_name} {username} ".*" ".*" ".*" # 为用户设置方位vhost的权限
sudo rabbitmqctl set_user_tags {username} {tag ...}

多个站点可以共用一个用户账号,但是必须使用不同的vhost。 更多rabbitmqctl使用方法请参见rabbitmqctl使用手册

在Django中的settings文件设置:

BROKER_URL = 'amqp://username:password@localhost:5672/vhost_name'
# 请特别注意 BROKER_URL只有在大写时有效,小写时无效。

多站点问题解决时查看的相关资料:

Run Multiple Django Apps With Celery On One Server With Rabbitmq VHosts

Using celeryd as a daemon with multiple django apps?

Running multiple Django Celery websites on same server

Run Multiple Django Apps With Celery On One Server With Rabbitmq VHosts

6 supervisor使用

开启、断开supervisor.sock连接

sudo supervisord -c /etc/supervisord/supervisord.conf # 开启
sudo unlink /tmp/supervisor.sock # 断开连接

状态查看、控制

supervisorctl -c /etc/supervisord/supervisord.conf

> status    # 查看程序状态
> stop [process_alias]   # 关闭 usercenter 程序
> start [process_alias]  # 启动 usercenter 程序
> restart [process_alias]    # 重启 usercenter 程序
> reread    # 读取有更新(增加)的配置文件,不会启动新添加的程序
> update    # 重启配置文件修改过的程序  

sudo supervisorctl -c /etc/supervisord/supervisord.conf stop all 
# 关闭全部进程 unlink前必须使用该命令关闭supervisord 不然celery依旧存在于系统中 导致内存泄露以及有规律的任务丢失!!!
sudo supervisorctl -c /etc/supervisord/supervisord.conf start all # 启动所有配置
sudo supervisorctl -c /etc/supervisord/supervisord.conf reread # 读取有更新(增加)的配置文件,不会启动新添加的程序
sudo supervisorctl -c /etc/supervisord/supervisord.conf update # 重启配置文件修改过的程序

Linux进程查看、清理

ps auxf|grep celery # 树形显示 推荐
ps -efH|grep celery # 查看celery相关进程 必须是大写的H以格式化显示格式
ps aux|grep celery | awk '{system("kill -9 " $2)}' # 杀死celery相关进程

7 supervisor开机自启动

ubuntu开启自启动脚本原始链接

修改后脚本/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 <miquels@cistron.nl>.
#       Modified for Debian
#       by Ian Murdock <imurdock@gnu.ai.mit.edu>.
#               Further changes by Javier Fernandez-Sanguino <jfs@debian.org>
#               Modified by sbilly <superli.1980@gmail.com> Added supervisorctl to status
#
# 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
SUPERVISORCTL=/usr/local/bin/supervisorctl
NAME=supervisord
DESC=supervisor

test -x $DAEMON || exit 0

LOGDIR=/var/log/supervisord
PIDFILE=/tmp/supervisord.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/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 $NAME (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 "$NAME is "
    if running ;  then
        echo "running"
    else
        echo " not running."
        exit 1
    fi
    $SUPERVISORCTL $DAEMON_OPTS status
    ;;
  *)
    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

自启动脚本修改注意事项:

  • 需要修改DAEMON、SUPERVISORCTL两个路径,均位于/usr/local/bin目录下,原始目录为/usr/bin。
  • 务必确保PIDFILE的位置和/etc/supervisord/supervisord.conf中的pidfile对应相同的文件。
  • 确保LOGDIR设置的目录和/etc/supervisord/supervisord.conf中的logfile所在的目录相同。

设置脚本为Ubuntu中的服务

sudo chmod -R 777 /etc/init.d/supervisord
sudo update-rc.d supervisord defaults
sudo service --status-all

移除服务

sudo update-rc.d -f supervisord remove