在Apache服务器上同时运行多个Django程序的方法

背景

由于腾讯云服务器特别便宜(120元/年),禁不住诱惑买了两年。然后前前后后搭建了几个网站,分别是一个科技新闻抓取网站 https://news.stackoverflow.club, 一个书籍分享网站 https://book.stackoverflow.club, 一个网站内容开源api(还没有做前端界面) https://api.stackoverflow.club, 一个机器学习的数据集论坛 https://data.stackoverflow.club.

昨天刚刚找了一个基于Django的开源微型论坛框架Spirit,部署在自己的小服务器上。一开始运行好好的,但是当我试着同时访问上述几个网站时,有一定概率出现Server internal error, 查看error.log发现log如下:

1
2
3
4
5
6
7
[Sun Nov 11 02:38:31.200426 2018] [wsgi:error] [pid 10994:tid 139733405464320] [client 60.207.237.35:59123] mod_wsgi (pid=10994): Target WSGI script '/var/www/data_forum/data_forum/wsgi.py' cannot be loaded as Python module., referer: https://data.stackoverflow.club/
[Sun Nov 11 02:38:31.200483 2018] [wsgi:error] [pid 10994:tid 139733405464320] [client 60.207.237.35:59123] mod_wsgi (pid=10994): Exception occurred processing WSGI script '/var/www/data_forum/data_forum/wsgi.py'., referer: https://data.stackoverflow.club/
...(略去无关log)
[Sun Nov 11 02:38:31.200763 2018] [wsgi:error] [pid 10994:tid 139733405464320] [client 60.207.237.35:59123] ImportError: No module named 'news', referer: https://data.stackoverflow.club/

并且,如果先访问book.stackoverflow.club,该网站正常,但data.stackoverflow.club就会出问题;反之亦然。

初步分析

观察上述log, 本应该是访问data.stackoverflow.club,但是却发现news无法找到,此处的news为新闻网站的网站模块名称。可以判断,是由于多站点并存,导致django环境错乱。

在脚本之家搜索到了一篇名为在Apache服务器上同时运行多个Django程序的方法,该文章声称可以在apache的配置文件中使用SetEnv指令来部署多站点Django, 但是在wsgi.py中已经存在os.environ.setdefault()的情况下,此举似乎没有用。

我还特意试了下,保留wsgi.py中已经存在os.environ.setdefault()不动,单独在apache的配置文件中使用SetEnv,证明确实没有解决问题。

问题定位

搜索到了官方文档How to use Django with Apache and mod_wsgi, 其中明明白白写着

1
2
3
4
5
6
7
8
9
Warning
If multiple Django sites are run in a single mod_wsgi process, all of them will use the settings of whichever one happens to run first. This can be solved by changing:
os.environ.setdefault("DJANGO_SETTINGS_MODULE", " project_name }}.settings")
in wsgi.py, to:
os.environ["DJANGO_SETTINGS_MODULE"] = " project_name }}.settings"
or by using mod_wsgi daemon mode and ensuring that each site runs in its own daemon process.

即如果在单一进程中,django会使用最先运行的那个站点的配置文件,所以我们要么使用os.environ,要么使用mod_wsgi的daemon模式(未尝试)。

我去掉apache的配置文件中的SetEnv,将wsgi.py中的os.environ.setdefault()换为os.environ,重启apache,问题解决。

深入分析

原因呢?为什么使用os.environ.setdefault()会导致使用最先运行站点的配置呢?直到我看了这篇django os.environ慎用setdefault操作环境变量!,里面解释到

1
2
3
4
5
6
7
在绝大多数情况下,如果需要在程序运行过程中设置环境变量,使用os.environ.setdefault函数是没有任何问题的,但是有两种场景下setdefault会造成意外的问题,需要慎用:
如果程序执行前,系统里已经存在了某环境变量(如ENV=VAL1),此时如果在程序中用setdefault函数对该环境变量设置另一个不同的值(如VAL2),会因为setdefault函数的特性导致无法设置为新值
也是因为上述这一点,如果进程A先设置了环境变量(如ENV=VAL1),而A启动了子进程B,子进程B会继承A进程的所有与环境变量,会导致B运行的时候,程序运行环境里已经存在环境变量ENV,导致如果此时用setdefault函数对该环境变量设置另一个不同的值(如VAL2),也会因为同样的原因导致无法设置为新值
因此,在程序运行中设置系统环境变量的最安全方法还是:
os.environ['ENV'] = 'VAL'

os.environ.setdefault无法对子进程、线程设置新值。

那么SetEnv究竟有用没用?我去掉了wsgi.py中的os.environ语句,在apache配置文件中使用SetEnv进行配置文件的选择,奇怪的是不论在SetEnv后面有没有使用引号,该问题都无法解决,有时候报错为模块找不到(与背景中的报错信息相同),有时候报如下错误:

1
2
3
[Sun Nov 11 11:22:53.970319 2018] [wsgi:error] [pid 15279:tid 140525466273536] [client 60.207.237.35:63684] django.core.exceptions.ImproperlyConfigured: Reque
sted setting LOGGING_CONFIG, but settings are not configured. You must either define the environment variable DJANGO_SETTINGS_MODULE or call settings.configur
e() before accessing settings.

所以,SetEnv到底有没有设置环境变量,由于调试困难暂不得而知。

总结

中文文档、博客虽然快,但总时不时进入死胡同。很多时候我们想要的答案明明白白的写在了英文文档最显眼的位置,却因为不是母语不想阅读。

要改要改。

0%