Skip to content

在虚拟环境中使用 supervisor

大概四个月前搞的东西, 今天可算是记录一下了...

使用 supervisor 来托管 python 程序, 类似于 systemd 的感觉, 不过似乎它是针对 python 程序??(待求证)
但是 supervisor 在虚拟环境中有问题, 特此来记录一下解决过程.

安装 supervisor 并将其作为 systemd 服务

这个网上基本有, 这里照抄了, 在 /etc/systemd/system 中新建 supervisor.service. 内容如下, 其中 $SUPERVISORD_PATH$SUPERVISORCTL_PATH 分别用各自真实的路径替换 (使用which supervisordwhich supervisorctl 查看).

[Unit]
Description=Supervisor process control system for UNIX
Documentation=http://supervisord.org
After=network.target

[Service]
ExecStart=$SUPERVISORD_PATH -n -c /etc/supervisor/supervisord.conf
ExecStop=$SUPERVISORCTL_PATH $OPTIONS shutdown
ExecReload=$SUPERVISORCTL_PATH -c /etc/supervisor/supervisord.conf $OPTIONS reload
KillMode=process
Restart=on-failure
RestartSec=50s

[Install]
WantedBy=multi-user.target

上面涉及到 supervisord.conf, 这个应该安装时默认有, 注意看一下路径是否是上面的路径.

2021.12.23 更新: 如果是 root 用户, 很正常. 但是! 如果是普通的用户, 就会出现问题!!!

一般会出现几个权限的错误
1. IOError: [Errno 13] Permission denied: '/var/log/supervisor/supervisord.log'
2. Error: Cannot open an HTTP server: socket.error reported errno.EACCES (13)

参考: https://binkzhao.github.io/2019/04/30/supervisor-%E5%B8%B8%E8%A7%81%E9%94%99%E8%AF%AF%E9%9B%86%E5%90%88/

但是这个链接是直接把目录的权限变成了 777, 这样很不合理. 因此解决方法方法如下.

1. 使用 root 新建 /var/log/supervisor/var/run/supervisor, 然后把该目录权限转为普通用户.
2. 修改 /etc/supervisor/supervisord.conf, 也就是 supervisor 配置文件, 把里面的 /var/run 变成 /var/run/supervisor, 因为这个目录在上一步中已经变成普通用户的权限了, 所以普通用户可以在这个目录中建文件, 不会报出权限错误.
3. 修改 /etc/systemd/system/supervisor.service, 也就是这篇文章刚开始给出的文件, 在 [Service] 下面新加 User=xxxGroup=xxx.

最后重新启动, 应该是没有问题的.

supervisor 管理虚拟环境下的 Python 程序

如果 Python 程序是在虚拟环境下, 直接 python xx.py 是有问题的, 大概率会报 ImportError 的错误. 折腾了很久很久, 解决方式有两个.

方法一

这个是我比较喜欢的, 利用了 shell 脚本作为跳板来完成.

假设项目是 demo 目录, 想要执行的文件是 python main.py. 需要在项目里新建一个 shell 脚本, 里面的内容是用 Python 的绝对路径来执行文件! 例子:

# init.sh
/root/.pyenv/shims/python $1

然后 supervisor 中的设置去执行这个脚本. 下面是一个示例配置, 注意不要直接复制, 其中的路径需要根据自己的来进行调整.

[program:demo]
# 必须指定 directory 为要执行的项目目录
directory=/root/demo
# 使用 shell 脚本
command = bash /root/demo/init.sh /root/demo/init.py
stdout_logfile=/root/demo/log/demo.log
stderr_logfile=/root/demo/log/demo.log

这个方法好处是, 以后如果换了一个虚拟环境, 那么只需要修改项目里的 init.sh 即可.

方法二

该方法不需要新建一个脚本, 直接在 supervisor 配置文件里指定 python 的绝对路径, 下面是一个示例配置.

[program:demo]
# 仍然必须指定 directory 为要执行的项目目录
directory=/root/demo
# 指定 python 的绝对路径
command = /root/.pyenv/shims/python /root/demo/init.py
stdout_logfile=/root/demo/log/demo.log
stderr_logfile=/root/demo/log/demo.log

但是这个 python 绝对路径改变的话, 要改 supervisor 的配置文件, 而不是像方法一中直接修改项目中的文件.

原因分析

这一块是记录一下我当时是如何排除原因的, 排除方法还是让我比较得意的.

首先在项目建立一个新文件, 来输出 python 是如何找模块的.

# test.py
import site
print(site.getsitepackages())

假如我在 supervisor 配置文件中的 command 就简单的是 python test.py, 我发现输出是: [/usr/lib/python2.7/xxxx], 所以如果在 supervisor 中直接用 python 的话, 默认使用系统的版本, 而不是虚拟版本.

那么我如果使用方法一的脚本, 脚本中为 python test.py, 而不是 python 绝对路径呢? 同样! 输出仍然是系统使用的版本! 所以必须要指定绝对路径, 这样才能得到正确的路径.

此外, 网上的解答大部分说用 PYTHONPATH, 这个我只有在使用 pyenv 并且设置为全局时才成功, 不清楚如果设置为全局变量时是否有用, 但我觉得在启动脚本中添加绝对路径比指定 PYTHONPATH 更好.

结尾

四个月后再写总结, 发现了一个很重要的问题: Why use supervisor? 可以先使用 systemd 来完成, 如果它完成不了再来用 supervisor 来做吧. 我的要求其实就完全可以用 systemd 来完成.

参考: systemd vs supervisord → https://liqiang.io/post/systemd-vs-supervisord

Comments