如何在systemd服务单元中启用virtualenv?

159

我想在systemd服务文件中“激活”虚拟环境。

我希望避免在systemd进程和Python解释器之间使用shell进程。

我的当前解决方案如下:

[Unit]
Description=fooservice
After=syslog.target network.target

[Service]
Type=simple
User=fooservice
WorkingDirectory={{ venv_home }}
ExecStart={{ venv_home }}/fooservice --serve-in-foreground
Restart=on-abort
EnvironmentFile=/etc/sysconfig/fooservice.env

[Install]
WantedBy=multi-user.target

/etc/sysconfig/fooservice.env

PATH={{ venv_home }}/bin:/usr/local/bin:/usr/bin:/bin
PYTHONIOENCODING=utf-8
PYTHONPATH={{ venv_home }}/...
VIRTUAL_ENV={{ venv_home }}

但我遇到了麻烦。由于sys.path中缺少某些条目,我遇到了导入错误。


请问您能否提供您所遇到的错误信息? - Praveen Yalagandula
@PraveenYalagandula 追踪回溯信息中并没有包含任何有用的信息,因为 ImportError 异常及其上方的所有行都只包含不重要的自定义代码。 - guettli
6个回答

188

virtualenv已经“嵌入到Python解释器中的虚拟环境”中了。这意味着您可以直接在该虚拟环境中启动pythonconsole_scripts,而无需先激活虚拟环境或自行管理PATH

ExecStart={{ venv_home }}/bin/fooservice --serve-in-foreground
或者
ExecStart={{ venv_home }}/bin/python {{ venv_home }}/fooservice.py --serve-in-foreground

并删除 EnvironmentFile 条目。

要验证它确实正确,您可以通过运行以下命令来检查 sys.path

{{ venv_home }}/bin/python -m site

并将输出与比较

python -m site

5
好的,Nils说得不错。顺便说一下,fooservice.py放在venv_home目录里没有意义,我猜这是问题中的一个打字错误。 - stelios
6
请注意,建议使用的打印命令不兼容Python 3。如果您使用的是至少python 2.4版本,则可以选择使用python -m site来获取一个格式良好的输出,其中包括sys.path变量以及其他附加信息。 - Mark Edington
3
不错,我不知道python -m site。我已经调整了我的回答。 - Nils Werner
2
当你启动的Python进程期望虚拟环境被激活时,这种方法就不起作用了。例如,考虑 {{ venv_home }}/bin/python -m newrelic.admin run-program python -m myapp。它会在虚拟环境中启动Python,调用New Relic,然后由于python不在路径中而无法启动应用程序,因为虚拟环境没有被激活。 - Jason R. Coombs
21
对于那些想知道这是否是ninja2的人...并不是,双花括号只是原帖作者发明的占位符:https://superuser.com/questions/1209919/what-do-double-curly-braces-mean-in-systemd-scripts - ankostis
显示剩余12条评论

34

虽然虚拟环境中的Python解释器确实包含了对库的路径,但我曾经遇到过问题,因为某些使用虚拟环境中安装的二进制文件的Python工具无法正常运行。例如,我的Apache Airflow服务无法正常工作,因为它找不到gunicorn二进制文件。为了解决这个问题,下面是我的ExecStart指令,并带有一个Environment指令(为该服务单独设置环境变量)。

ExecStart={{ virtualenv }}/bin/python {{ virtualenv }}/bin/airflow webserver
Environment="PATH={{ virtualenv }}/bin:{{ ansible_env.PATH }}"

ExecStart明确使用虚拟环境的Python解释器。 我还添加了一个PATH变量,将虚拟环境的二进制文件夹添加到系统PATH之前。 这样,我就可以得到所需的Python库和二进制文件。

请注意,我正在使用Ansible构建此服务,因此使用jinja2的花括号。


2
来这里是为了找如何在服务中设置环境,因为Airflow找不到gunicorn。没有失望!谢谢!我已经可以运行了。 - pyFiddler
没错,我需要通过Ansible实现Airflow自动化。非常感谢 :) - Manju N

6

在我的情况下,我只是尝试添加Flask所需的环境变量,例如:

[Service]
Environment="PATH=/xx/yy/zz/venv/bin"
Environment="FLASK_ENV=development"
Environment="APP_SETTINGS=config.DevelopmentConfig"

我正在使用virtualenv,因此/xx/yy/zz/venv/bin是virtualenv文件夹的路径。


4
我没有使用virtualenv,而是使用pyenv:这里只是为了在shebang中使用真正的.pyenv路径,并确保它在PATH中。
例如:对于正在生产环境中运行的用户mortenb,使用pyenv activate flask-prod。
/home/mortenb/.pyenv/versions/flask-prod/bin/python --version
Python 3.6.2

然后,在我的systemd *.service文件中,为我的Flask脚本添加以下shebang:

#!/home/mortenb/.pyenv/versions/flask-prod/bin/python3

0

当你使用Gunicorn在venv中通过systemd运行FlaskApp时,解决一些小问题。假设我创建了一个名为FlaskVenv的venv,位于/home/someuser/z_Venv,我通常会使用source /home/someuser/z_Venv/FlaskVenv/bin/activate来激活它,而我的Flask应用程序位于/home/someuser/FlaskCode/some-ui

所以在交互式环境中,我可能会这样运行:

cd /home/someuser/FlaskCode/someui

然后

source /home/someuser/z_Venv/FlaskVenv/bin/activate

然后

gunicorn -k gevent -w 4 -b :5001 main:appAF

为了将其转换为systemd服务,我在/etc/systemd/system/someui.service创建了一个服务文件,内容如下:

[Unit]
Description=Some UI
After=syslog.target network.target

[Service]
Type=simple
User=someuser
WorkingDirectory=/home/someuser/FlaskCode/some-ui
ExecStart=/home/someuser/z_Venv/FlaskVenv/bin/python -m gunicorn -k gevent -w 4 -b :5001 main:app
Restart=on-abort

[Install]
WantedBy=multi-user.target

以交互方式启动(进行测试): sudo systemctl start someui 并使用以下命令查看状态: sudo systemctl status someui

如果一切正常,可以使用以下命令启用自动启动: sudo systemctl enable someui

如果发生任何奇怪的情况,可以使用journalctl -xe命令检查systemctl日志。


-1
ExecStart=cd /root/app/working-directory && poetry run python my_app.py

1
目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接