本文是对How To Serve Django Applications with uWSGI and Nginx on Ubuntu 16.04(需访问)内容的翻译。
近日,为在Ubuntu16.04上部署Python Django项目,查询了非常多的资料,同时踩过无数坑,后通过Google找到此篇文章,基本顺利的实现了Django项目的部署。
不同于PHP或Java,Python项目的部署显得更加复杂一些,基于Apache或Tomcat的项目部署,常常只需要将项目文件夹放入对应WebApp文件夹即可。而在Python中,需要使用uWSGI软件,来实现HTTP与WSGI协议的适配,同时需要使用Ngnix服务器,启用反向代理来处理静态资源文件。同时,对于两者的管理,想要实现自动化,又需要编写对应的管理脚本。因此,如果开发者没有一定的Linux操作和Web部署的经验,那么这个过程会十分痛苦,踩坑无数。因此翻译此文,为使用Python做Web开发的工程师们提供一个更愉悦的工作体验。
软件 | 版本 |
---|---|
Ubuntu | 16.04 |
uWSGI | 2.0.17.1 |
Nginx | 1.10.3 |
Django是一个功能强大的Web框架,可以帮助你开发Python应用程序或网站。 Django包含一个简化的开发服务器,用于在本地测试你的代码,但对于任何与生产相关的内容,都需要更安全,更强大的Web服务器。
在这篇教程中,我们将阐述如何在Ubuntu16.04的环境中下载和配置uWSGI应用容器,同时能够与我们开发的Web应用进行交互。然后,我们将设置Nginx反向代理到uWSGI服务,这将能使我们获得Nginx提供的安全与高性能的服务。
为了完成本教程,你需要一个全新的Ubuntu16.04的服务器,配置一个非root的用户,同时该用户能使用sudo
操作获得root权限。
我们将在两个不同的虚拟环境中安装Django。 这将允许您的项目及其要求单独处理。 我们将创建两个示例项目,以便我们可以在多项目环境中执行这些步骤。
当我们准备好应用程序后,我们将安装和配置uWSGI应用程序服务器。该服务器将作为我们应用程序的接口,该接口可以使用HTTP协议将客户端的请求发送到我们应用程序的调用上边进行处理。然后,我们将在uWSGI前面设置Nginx,以利用其高性能连接处理机制及其易于实现的安全功能。
接下来,让我们开始本教程吧。
我们将在各个虚拟环境中下载和配置我们的Django项目,以模拟隔离各种需求。为了这么做,我们需要下载virtualenv
,该工具可以创建Python虚拟环境,同时下载virtualenvwrapper
,该工具为virtualenv
工作流程增加了一些可用性改进。
我们将使用Python包管理器pip
安装这两个组件。 我们可以从Ubuntu存储库安装此实用程序。
sudo apt-get update
sudo apt-get install python-pip
sudo apt-get update
sudo apt-get install python3-pip
现在你已经安装了pip,我们可以在全局范围内安装virtualenv
和virtualenvwrapper
。 我们还将使用pip
本身将pip升级到最新版本。
sudo -H pip install --upgrade pip
sudo -H pip install virtualenv virtualenvwrapper
sudo -H pip3 install --upgrade pip
sudo -H pip3 install virtualenv virtualenvwrapper
安装这些组件后,我们现在可以使用virtualenvwrapper脚本所需的信息配置shell。 我们的虚拟环境将全部放在名为Env的主文件夹中的目录中,以便于访问。 这是通过名为WORKON_HOME的环境变量配置的。 我们可以将它添加到我们的shell初始化脚本中,并可以获取虚拟环境包装器脚本。
如果你使用的Python3及pip3
命令,你需要额外增加一条命令在你的初始化脚本中:
echo "export VIRTUALENVWRAPPER_PYTHON=/usr/bin/python3" >> ~/.bashrc
不过你使用哪个Python脚本,你都需要执行以下命令:
echo "export WORKON_HOME=~/Env" >> ~/.bashrc
echo "source /usr/local/bin/virtualenvwrapper.sh" >> ~/.bashrc
现在让你的配置立即生效:
source ~/.bashrc
你现在应该有Env目录出现在你的home目录中,同时其中包含了虚拟环境信息。
现在我们有了虚拟环境工具,我们将创建两个虚拟环境,在每个环境中安装Django,并启动两个项目。
我们可以使用virtualenvwrapper脚本为我们提供的一些命令轻松创建虚拟环境。
通过以下命令,创建你的第一个虚拟环境:
mkvirtualenv firstsite
这将创建一个虚拟环境,在其中安装Python和pip,并激活环境。括号将提醒你现在正在新的虚拟环境中运行。它看起来像这样:(firstsite)user@hostname:〜$
。 括号中的值是虚拟环境的名称。 通过pip安装的任何软件现在都将安装到虚拟环境中,而不是安装在全局系统上。 这允许我们在每个项目的基础上隔离我们的包。
我们的第一步是安装Django本身。 我们可以在没有sudo
的情况下使用pip
,因为我们在虚拟环境中本地安装它:
(firstsite) $ pip install django
Django下载完成后,我们可以开始创建第一个简单的项目:
(firstsite) $ cd ~
(firstsite) $ django-admin startproject firstsite
这将在你的home目录中创建一个名为firstsite的文件夹。 其中包括用于处理项目各个方面的管理脚本,以及用于存放实际项目代码的另一个同名目录。
进入到该项目中,同时进行最小程度的配置,以便本教程进行演示。
(firstsite) $ cd ~/firstsite
首先进行数据库migrate以初始化SQLite数据库(此次使用SQLite进行演示,也可以根据需求使用其他数据库,此处不作具体说明)。
(firstsite) $ ~/firstsite/manage.py migrate
您现在应该在项目目录中有一个名为db.sqlite3的数据库文件。 现在,我们可以通过输入以下内容来创建管理用户:
(firstsite) $ ~/firstsite/manage.py createsuperuser
此刻,你的项目目录(~/firstsite
)应该有以下内容:
接着,打开Django的配置文件:
(firstsite) $ vim ~/firstsite/firstsite/settings.py
首先找到ALLOWED_HOSTS指令。 这定义了可用于连接Django实例的服务器地址或域名列表。 不在此列表中的主机的任何传入请求都将引发异常。 Django要求你设置此参数以防止某类安全漏洞。
在方括号中,列出与Django服务器关联的IP地址或域名。 每个项目都应列在引号中,条目用逗号分隔。 如果你希望请求整个域和任何子域,请在条目的开头添加一个句点。 在下面的代码段中,有一些用于演示的注释掉的示例:
ALLOWED_HOSTS = ['your_server_domain_or_IP', 'second_domain_or_IP', . . .]
由于我们将设置Nginx来为我们的站点提供服务,因此我们需要配置一个目录来保存我们站点的静态资源文件。 这将允许Nginx直接为这些文件提供服务,这将对性能产生积极影响。 我们将告诉Django将它们放在项目基本目录中名为static的目录中。 将此行添加到文件的底部以配置此行为:
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
完成后保存并关闭文件。 现在,收集我们网站的静态元素,并输入以下内容将它们放在该目录中:
(firstsite) $ ~/firstsite/manage.py collectstatic
系统可能会要求您输入“是”以确认操作并收集静态内容。 项目目录中将有一个名为static的新目录。
接下来,我们可以打开一个端口,以便我们可以访问Django开发服务器。 如果您按照初始服务器设置指南进行操作,则应启用UFW防火墙。 键入以下内容允许连接到端口8080:
(firstsite) $ sudo ufw allow 8080
完成所有这些后,我们可以通过临时启动开发服务器来测试我们的项目。
(firstsite) $ ~/firstsite/manage.py runserver 0.0.0.0:8080
这将在端口8080上启动开发服务器。在浏览器中访问服务器的域名或IP地址,然后访问8080:
http://server_domain_or_IP:8080
成功的话将看到:
同样可以访问admin界面:
使用createsuperuser命令选择的管理登录凭据,登录到服务器。 将可以访问管理界面
同理,可以创建第二项目,此处进行省略,其方法同上。
详细代码和说明可参考原链接,由于篇幅过长,此处不再说明。
uWSGI是一个应用程序服务器,可以通过名为WSGI的标准接口与应用程序进行通信。
与之前教程不同,在本节中,我们将全局安装uWSGI。 这将在处理多个Django项目时产生更少的冲突。 在我们安装uWSGI之前,我们需要安装软件所依赖的Python开发文件。 我们可以直接从Ubuntu的存储库安装它。
sudo apt-get install python-dev
sudo apt-get install python3-dev
现在开发环境准备好了,我们可以通过pip全局安装uWSGI。
sudo -H pip install uwsgi
sudo -H pip3 install uwsgi
我们可以通过命令行将站点的基本信息传递给uWSGI服务进行测试。比如,我们可以通过以下命令进行测试:
uwsgi --http :8080 --home /home/sammy/Env/firstsite --chdir /home/sammy/firstsite -w firstsite.wsgi
在这里,我们通过--home
告诉uWSGI使用〜/Env
目录中的虚拟环境,通过--chdir
切换到项目根目录,并使用存储在firstsite
项目中的wsgi.py文件来提供相关服务(该文件自动生成)。 在我们的教程中,我们告诉它在端口8080
上提供HTTP服务。
在浏览器访问你的站点,使用8080端口,你将再次看到你的站点(/admin
界面中的静态元素如CSS将无法使用)。 完成此功能的测试后,在终端中键入CTRL-C退出。
从命令行运行uWSGI对于测试很有用,但对实际部署不是特别有用。 相反,我们将以“Emperor模式”运行uWSGI,它允许主进程在给定一组配置文件的情况下自动管理单独的应用程序。
创建一个用于保存配置文件的目录。 由于这是一个全局过程,我们将创建一个名为/etc/uwsgi/sites
的目录来存储我们的配置文件:
sudo mkdir -p /etc/uwsgi/sites
在此目录中,我们将放置配置文件。 我们需要为我们服务的每个项目配置一个配置文件。 uWSGI进程可以采用各种格式的配置文件,从简单性考虑,我们将使用.ini
文件。
首先,为你的第一个项目创建配置文件:
sudo vi /etc/uwsgi/sites/firstsite.ini
在这个配置文件中,我们必须从[uwsgi]标签开始。 我们所有的信息都将在此标签下方。 我们还将使用变量的方式使我们的配置文件更具可重用性。 在[uwsgi]标签之后,使用你的第一个项目的名称设置一个名为project的变量。 添加一个名为uid的变量,用它保存你的sudo用户名。
[uwsgi]
project = firstsite
uid = sammy
base = /home/%(uid)
接下来,我们需要配置uWSGI,以便它正确处理我们的项目。 我们需要通过设置chdir选项来切换到项目根目录。 我们可以使用相同的变量语法组合主目录和项目名称。
以类似的方式,我们将指出我们项目的虚拟环境。 通过设置模块,我们可以准确地指出如何与我们的项目进行交互(通过从我们内部项目目录中的wsgi.py文件中导入可调用的“application”)。 这些项的配置如下所示:
[uwsgi]
project = firstsite
uid = sammy
base = /home/%(uid)
chdir = %(base)/%(project)
home = %(base)/Env/%(project)
module = %(project).wsgi:application
我们想要创建一个包含5个worker的主进程。 我们可以通过添加以下内容来实现:
[uwsgi]
project = firstsite
uid = sammy
base = /home/%(uid)
chdir = %(base)/%(project)
home = %(base)/Env/%(project)
module = %(project).wsgi:application
master = true
processes = 5
接下来我们需要指定uWSGI应该如何监听连接。 在我们对uWSGI的测试中,我们使用了HTTP和网络端口。 但是,由于我们将使用Nginx作为反向代理,因此我们有更好的选择。
由于所有组件都在单个服务器上运行,因此我们可以使用Unix套接字,而不是使用网络端口。 这样更安全,性能更好。 这个套接字不会使用HTTP,而是实现uWSGI的uwsgi协议,这是一种快速二进制协议,用于与其他服务器通信。 Nginx可以使用uwsgi协议进行原生代理,因此这是我们的最佳选择。
我们还将修改套接字的所有权和权限,因为我们将为Web服务器提供写访问权限。 我们将设置vacuum选项,以便在服务停止时自动清除套接字文件:
[uwsgi]
project = firstsite
uid = sammy
base = /home/%(uid)
chdir = %(base)/%(project)
home = %(base)/Env/%(project)
module = %(project).wsgi:application
master = true
processes = 5
socket = /run/uwsgi/%(project).sock
chown-socket = %(uid):www-data
chmod-socket = 660
vacuum = true
有了这个,我们的第一个项目的uWSGI配置就完成了。 保存并关闭文件。
使用变量设置文件的优点是,它使重用变得异常简单。 复制第一个项目的配置文件,以用作第二个配置文件的基础:
有关第二个项目的操作,就由读者自行完成,有任何问题,可参考原链接或留言。
我们现在有了为Django项目提供服务所需的配置文件,但是我们仍然没有自动化该过程。 接下来,我们将创建一个systemd单元文件来管理uWSGI emperor进程并在启动时自动启动uWSGI。
我们将在/etc/systemd/system
目录中创建单元文件,其中保留了管理员创建的单元文件。 我们将调用我们的文件uwsgi.service:
sudo vi /etc/systemd/system/uwsgi.service
从[Unit]部分开始,该部分用于指定元数据和订购信息。 我们在这里简单介绍一下我们的服务:
[Unit]
Description=uWSGI Emperor service
接下来,我们将配置[Service]部分。 我们将使用ExecStartPre指令来设置运行服务器所需的部分。 这将确保创建/run/uwsgi
目录,并且我们的普通用户拥有它,并将www-data
组作为组所有者。 即使不需要操作,带有-p
标志的mkdir
和chown
命令都会成功返回。 这就是我们想要的。
对于ExecStart
指令指定的实际启动命令,我们将指向uwsgi
可执行文件。 我们将告诉它以“Emperor模式”运行,允许它使用在/etc/uwsgi/sites
中找到的文件管理多个应用程序。 我们还将添加systemd
所需的部分以正确管理该过程。 这些来自uWSGI文档。
[Unit]
Description=uWSGI Emperor service
[Service]
ExecStartPre=/bin/bash -c 'mkdir -p /run/uwsgi; chown sammy:www-data /run/uwsgi'
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/sites
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
现在,我们需要做的就是添加[Install]部分。 这允许我们指定何时应该自动启动服务。 我们将服务绑定到多用户系统状态。 无论何时为多个用户设置系统(正常运行条件),我们的服务都将被激活:
[Unit]
Description=uWSGI Emperor service
[Service]
ExecStartPre=/bin/bash -c 'mkdir -p /run/uwsgi; chown sammy:www-data /run/uwsgi'
ExecStart=/usr/local/bin/uwsgi --emperor /etc/uwsgi/sites
Restart=always
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
[Install]
WantedBy=multi-user.target
我们目前无法成功启动服务,因为它依赖于www-data用户可用。 在安装Nginx之前,我们必须等待启动uWSGI服务。
通过配置uWSGI并准备就绪,我们现在可以安装和配置Nginx作为我们的反向代理。 这可以从Ubuntu的默认存储库下载:
sudo apt-get install nginx
一旦安装了Nginx,我们就可以继续为每个项目创建一个服务器块配置文件。 通过创建服务器块配置文件从第一个项目开始:
sudo vi /etc/nginx/sites-available/firstsite
在配置文件中,我们可以通过指定端口号和域名来访问我们的项目。 server_name块必须与服务器的某个域名或其IP地址匹配,否则可能会使用默认的Nginx页面。 我们假设您拥有域名:
server {
listen 80;
server_name firstsite.com www.firstsite.com;
}
接下来,为Nginx指定图表及相关静态资源的地址:
server {
listen 80;
server_name firstsite.com www.firstsite.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/sammy/firstsite;
}
}
接下来,我们可以创建一个catch-all位置块,将所有其他查询直接传递给我们的应用程序。 我们将包含/etc/nginx/uwsgi_params
中的uwsgi
参数,并将请求倒入到uWSGI服务器设置的套接字:
server {
listen 80;
server_name firstsite.com www.firstsite.com;
}
接下来,为Nginx指定图表及相关静态资源的地址:
server {
listen 80;
server_name firstsite.com www.firstsite.com;
location = /favicon.ico { access_log off; log_not_found off; }
location /static/ {
root /home/sammy/firstsite;
}
location / {
include uwsgi_params;
uwsgi_pass unix:/run/uwsgi/firstsite.sock;
}
}
同理,进行第二个项目的相关配置
接下来,将新配置文件链接到Nginx的启用站点的目录以启用它们:
sudo ln -s /etc/nginx/sites-available/firstsite /etc/nginx/sites-enabled
通过以下方式检查配置情况:
sudo nginx -t
如果未检测到语法错误,则可以重新启动Nginx服务以加载新配置:
sudo systemctl restart nginx
如果你还记得,我们从未真正启动过uWSGI服务器。 现在输入以下命令:
sudo systemctl start uwsgi
让我们将UFW规则删除到端口8080,而是允许访问我们的Nginx服务器:
sudo ufw delete allow 8080
sudo ufw allow 'Nginx Full'
你现在应该可以通过访问各自的域名来访问你的两个项目。 公共和管理界面都应该按预期工作。
如果顺利,你可以通过键入以下命令启用两个服务以在启动时自动启动:
sudo systemctl enable nginx
sudo systemctl enable uwsgi
配置Nginx后,下一步应该是使用SSL / TLS保护服务器的流量。 这很重要,因为没有它,所有信息(包括密码)都以纯文本形式通过网络发送。
如果你有域名,最简单的方法是获取SSL证书来保护您的流量,使用Let’s Encrypt。 按照此教程在Ubuntu 16.04上使用Nginx设置Let’s Encrypt。
如果您没有域名,您仍然可以使用自签名SSL证书保护您的站点以进行测试和学习。
(相关教程需浏览原作者链接)
如果你无法访问你的Web应用程序,则需要对安装进行故障排除。
如果Nginx显示默认页面而不是代理到你的应用程序,则通常意味着你需要调整/etc/nginx/sites-available/firstsite
文件中的server_name以
指向服务器的IP地址或域名。
Nginx使用server_name
来确定用于响应请求的服务器目录。如果看到默认的Nginx页面,则表明Nginx无法正确地将请求与服务器目录相匹配,因此它会回退到/etc /nginx/sites-available/default
中定义的默认块。
项目服务器中的server_name
必须比要选择的默认服务器块中的server_name
更具体。
502错误表示Nginx无法成功代理请求。各种配置问题都表现为502错误,因此需要更多信息才能正确排除故障。
查找更多信息的主要位置是Nginx的错误日志。通常,这将告诉你在代理事件期间导致问题的条件。 键入以下内容,查看Nginx错误日志:
sudo tail -F /var/log/nginx/error.log
现在,在浏览器中发出另一个请求以生成新的错误(尝试刷新页面)。 你应该看到写入日志的新错误消息。 如果你查看该消息,它应该可以帮助您缩小问题范围。
你可能会看到以下消息:
这表明Nginx无法在给定位置找到套接字文件。 您应该将/etc/nginx/sites-available
文件中的firstsite
文件中定义的uwsgi_pass
位置与/run/uwsgi
目录中的firstsite.sock
套接字文件的实际位置进行比较。
键入以下命令检查/run/uwsgi
目录中是否存在套接字文件:
sudo ls /run/uwsgi
如果/run/uwsgi
中没有套接字文件,通常意味着uwsgi
进程无法创建它。检查uwsgi
进程的状态以确定它是否能够启动:
sudo systemctl status uwsgi
如果systemctl status命令指示发生错误或者你未在目录中找到套接字文件,则表明uWSGI无法正确启动。 键入以下命令检查uWSGI进程日志:
sudo journalctl -u uwsgi
查看日志中的消息,找出uWSGI遇到问题的位置。 你可能遇到问题的原因有很多,但通常情况下,如果uWSGI无法创建套接字文件,则出于以下原因之一:
/etc/systemd/system/uwsgi.service
文件中的ExecStartPre
行不包含用于创建目录和分配所有权的正确命令/etc/nginx/sites-available
目录中站点配置文件中的uwsgi_pass
路径不是针对正确的套接字位置/etc/uwsgi/sites
目录中.ini
文件中定义的uWSGI配置不正确。 检查以下项目:
chdir
指令一旦插值,就指向主项目目录。home
指令一旦插值,就指向虚拟环境目录。module
指令使用Python模块导入语法从内部项目目录中加载wsgi.py文件。socket
指令指向/run/uwsgi
文件中的文件(应该由上面提到的服务文件中的ExecStartPre
行创建)。如果对/etc/systemd/system/uwsgi.service
文件进行了更改,请重新加载守护程序以重新读取服务定义并通过键入以下内容重新启动uWSGI进程:
sudo systemctl daemon-reload
sudo systemctl restart uwsgi
修复这些问题应该允许Nginx正确找到套接字文件。
这表明由于权限问题,Nginx无法连接到uWSGI套接字。 通常,在受限制的环境中创建套接字或权限错误时会发生这种情况。 虽然uWSGI进程能够创建套接字文件,但Nginx无法访问它。
如果根目录(/)与套接字文件之间的任何点上的权限有限,则会发生这种情况。 我们可以通过将socket文件的绝对路径传递给namei命令来查看套接字文件及其每个父目录的权限和所有权值:
namei -nom /run/uwsgi/firstsite.sock
output:
f: /run/uwsgi/firstsite.sock
drwxr-xr-x root root /
drwxr-xr-x root root run
drwxr-xr-x sammy www-data uwsgi
srw-rw---- sammy www-data firstsite.sock
输出显示每个目录组件的权限。通过查看权限(第一列),所有者(第二列)和组所有者(第三列),我们可以确定允许哪种类型的访问套接字文件。
在上面的示例中,导致套接字文件的每个目录都具有全局读取和执行权限(目录的权限列以r-x而不是—结尾)。 www-data组对套接字本身具有组所有权。通过这些设置,Nginx进程应该能够成功访问套接字。
如果通向套接字的任何目录不属于www-data组,或者没有世界读取和执行权限,则Nginx将无法访问套接字。通常,这意味着配置文件有错误。
如果目录路径的权限或所有权过于严格,请查看/etc/systemd/system/uwsgi.service
文件。ExecStartPre
指令负责创建/run/uwsgi
目录并将组所有权分配给www-data
组。如果此处的命令不正确,则目录路径可能过于严格。
如果Nginx进程无法访问套接字文件本身,则/etc/uwsgi/sites
中.ini
文件中定义的设置可能不正确。检查chown-socket
和chmod-socket
的值,以确保Web进程具有访问文件的权限。
对于其他故障排除,日志可以帮助缩小根本原因。 依次检查每一个并查找指示问题区域的消息。
以下日志可能会有所帮助:
输入以下命令检查Nginx进程日志:sudo journalctl -u nginx
键入以下命令检查Nginx访问日志:sudo less /var/log/nginx/access.log
键入以下命令检查Nginx错误日志:sudo less /var/log/nginx/error.log
输入以下命令检查uWSGI应用程序日志:sudo journalctl -u uwsgi
在更新配置或应用程序时,可能需要重新启动进程以根据更改进行调整。
如果更新Django应用程序,可以通过键入以下命令重新启动uWSGI进程以获取更改:
sudo systemctl restart uwsgi
如果更改uwsgi systemd服务文件,请重新加载守护程序并键入以下命令重新启动该过程:
sudo systemctl daemon-reload
sudo systemctl restart uwsgi
如果您更改Nginx服务器块配置,请通过键入以下内容来测试配置,然后测试Nginx:
sudo nginx -t && sudo systemctl restart nginx
相信通过此教程,多数的读者都能正确的配置好基于uWSGI和Nginx的Django项目,如果还有什么问题,可以留言大家一同交流。