在虚拟环境中使用 supervisor
大概四个月前搞的东西, 今天可算是记录一下了...
使用 supervisor 来托管 python 程序, 类似于 systemd 的感觉, 不过似乎它是针对 python 程序??(待求证)
但是 supervisor 在虚拟环境中有问题, 特此来记录一下解决过程.
安装 supervisor 并将其作为 systemd 服务
这个网上基本有, 这里照抄了, 在 /etc/systemd/system
中新建 supervisor.service
. 内容如下, 其中 $SUPERVISORD_PATH
和 $SUPERVISORCTL_PATH
分别用各自真实的路径替换 (使用which supervisord
和 which 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)
但是这个链接是直接把目录的权限变成了 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=xxx
和 Group=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