加载中

Having spent the majority of my career in the Microsoft stack, lately I’ve decided to step out of my comfort zone and to dive into the world of open source software. The project I’m currently working on at my day job is a RESTful service. The service will be running on a commodity hardware, that should be able to scale horizontally as needed. To do the job I’ve decided to use Flask and Nginx. Flask is a lightweight Python web framework, and nginx is a highly stable web server, that works great on cheap hardware.

In this post I will guide you through the process of installing and configuring nginx server to host Flask based applications. The OS I’ll be using is Ubuntu 13.04.

我职业生涯的大部分都在使用微软的架构,最近我决定走出技术的舒适区,步入开源软件世界。我现在日常工作的项目是一个RESTful服务,这个服务需要在主流硬件上运行,且能够按照需要进行水平拓展。为完成这项工作我决定使用Flask和Nginx。Flask是一个轻量级的Python Web框架,Nginx是一个非常稳定的Web服务器,它们在廉价硬件平台上工作良好。

在这篇文章中我将指导你完成使用Nginx服务器托管Flask应用的安装、配置过程。我所使用的操作系统是Ubuntu 13.04。

Prerequisites

Before we install Nginx and other required software, let’s install some prerequisites. First, we will need PIP and Virtualenv:

sudo apt-get install python-setuptools
sudo easy_install pip
sudo pip install virtualenv
To install nginx from apt-get, we have to add Nginx repositories to apt-get sources:
sudo add-apt-repository ppa:nginx/stable

Note: If the “add-apt-repository” command doesn’t exist on your Ubuntu version, you need to install the “software-properties-common” package: sudo apt-get install software-properties-common (Thanks to get_with_it for mentioning it in the comments)

Upgrade existing packages and make sure you have the required compilers and tools for uWSGI:

sudo apt-get update && sudo apt-get upgrade
sudo apt-get install build-essential python python-dev

前提条件

在我们开始安装Nginx及其他所需软件之前先安装一些前提软件。首先,我们需要PIP与virtualenv:

sudo apt-get install python-setuptools
sudo easy_install pip
sudo pip install virtualenv

使用apt-get安装Nginx的话,我们需要添加Nginx库到apt-get source中:

sudo add-apt-repository ppa:nginx/stable

注意:如果“add-apt-repository”命令在你的Ubuntu版本中不存在的话,你需要安装“software-properties-common”包,使用命令:sudo apt-get software-properties-common(感谢get_with_it在评论中提到)

升级已有的包,确保系统上有uWSGI所需的编译器和工具:

sudo apt-get update && sudo apt-get upgrade
sudo apt-get install build-essential python python-dev

Nginx

Install and start Nginx:

sudo apt-get install nginx
sudo /etc/init.d/nginx start
Nginx is a web server. It serves static files, however it cannot execute and host Python application. uWSGI fills that gap. Let’s install it first, and later we’ll configure nginx and uWSGI to talk to each other.
sudo pip install uwsgi

Milestone #1

Browse to your server and you should get the Nginx greeting page:

nginx

Nginx

安装并运行Nginx:

sudo apt-get install nginx
sudo /etc/init.d/nginx start

Nginx是一个提供静态文件访问的web服务,然而,它不能直接执行托管Python应用程序,而uWSGI解决了这个问题。让我们先安装uWSGI,稍候再配置Nginx和uWSGI之间的交互。

sudo pip install uwsgi

里程碑 #1

打开浏览器访问你的服务器,你应该能看到Nginx欢迎页:

nginx

Sample application

The application we will host is literally a “Hello, world!” application. It will serve only one page, and guess what text it will contain. All the application related files will be stored at the /var/www/demoapp folder. Let’s create this folder, and initialize a virtual environment in it:

sudo mkdir /var/www
sudo mkdir /var/www/demoapp
Since we created the folder under root privileges, it is currently owned by the root user. Let’s change the ownership to the user you are logged in to (“ubuntu” in my case):
sudo chown -R ubuntu:ubuntu /var/www/demoapp/
Create and activate a virtual environment, and install Flask into it:
cd /var/www/demoapp
virtualenv venv
. venv/bin/activate
pip install flask
Create the hello.py file, with the following code:
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)

Milestone #2

Let’s execute the script we’ve just created:

python hello.py

Now you can browse to your server’s port 8080 and see the app in action:

flask

Note: I’ve used port 8080 because port 80 is already in use by nginx

Currently the app is served by Flask’s built in web server. It is a great tool for development and debugging needs, but it is not recommended in production environment. Let’s configure nginx to do the heavy lifting.

示例应用

我们将托管的应用是经典的“Hello, world!”。这个应用只有一个页面,已经猜到页面上将有什么内容了吧。将所有应用相关的文件存放在/var/www/demoapp文件夹中。下面创建这个文件夹并在其中初始化一个虚拟环境:

sudo mkdir /var/www
sudo mkdir /var/www/demoapp

由于我们使用root权限创建了这个文件夹,它目前归root用户所有,让我们更改它的所有权给你登录的用户(我的例子中是ubuntu)

sudo chown -R ubuntu:ubuntu /var/www/demoapp/

创建并激活一个虚拟环境,在其中安装Flask:

cd /var/www/demoapp
virtualenv venv
. venv/bin/activate
pip install flask

使用下面的代码创建hello.py文件:

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8080)

里程碑 #2

让我们执行我们刚创建的脚本:

python hello.py

现在你可以通过浏览器访问你服务器的8080端口,看,应用生效了:

flask

注意:因为80端口已被Nginx使用,这里我使用8080端口。

现在应用是由Flask内置的web服务托管的,对于开发和调试这确实是个不错的工具,但不推荐在生产环境中使用。让我们配置Nginx来挑起这个重担吧。

Configuring Nginx

Let’s start by removing the Nginx’s default site configuration:

sudo rm /etc/nginx/sites-enabled/default

Note: If you installed a version of nginx from other repository, the default configuration file may be located at the /etc/nginx/conf.d folder.

Instead create a new configuration file for our application /var/www/demoapp/demoapp_nginx.conf:

server {
    listen      80;
    server_name localhost;
    charset     utf-8;
    client_max_body_size 75M;

    location / { try_files $uri @yourapplication; }
    location @yourapplication {
        include uwsgi_params;
        uwsgi_pass unix:/var/www/demoapp/demoapp_uwsgi.sock;
    }
}
And symlink the new file to nginx’s configuration files directory and restart nginx:
sudo ln -s /var/www/demoapp/demoapp_nginx.conf /etc/nginx/conf.d/
sudo /etc/init.d/nginx restart

Milestone #3

Browser to server’s public ip address, and you will be greeted by an error:

502 No need to worry, this is a “good” error. It says that nginx uses the configuration file we just created, but it has a trouble connecting to our Python application host, uWSGI. The connection to uWSGI is defined in the configuration file at line #10:

uwsgi_pass unix:/var/www/demoapp/demoapp_uwsgi.sock;

It says that the communication between nginx and uWSGI is done via a socket file, that should be located at /var/www/demoapp/demoapp_uwsgi.sock. Since we still haven’t configured uWSGI yet, the file doesn’t exist, and Nginx returns the “bad gateway” error. Let’s fix this now.

配置Nginx

首先删除掉Nginx的默认配置文件:

sudo rm /etc/nginx/sites-enabled/default

注意:如果你安装了其他版本的Nginx,默认配置文件可能在/etc/nginx/conf.d文件夹下

创建一个我们应用使用的新配置文件/var/www/demoapp/demoapp_nginx.conf

server {
    listen      80;
    server_name localhost;
    charset     utf-8;
    client_max_body_size 75M;

    location / { try_files $uri @yourapplication; }
    location @yourapplication {
        include uwsgi_params;
        uwsgi_pass unix:/var/www/demoapp/demoapp_uwsgi.sock;
    }
}

将刚建立的配置文件使用符号链接到Nginx配置文件文件夹中,重启Nginx:

sudo ln -s /var/www/demoapp/demoapp_nginx.conf /etc/nginx/conf.d/
sudo /etc/init.d/nginx restart

里程碑 #3

访问服务器的公共ip地址,你会看到一个错误:

502

别担心,这个错误是正常的,它代表Nginx已经使用了我们新创建的配置文件,但在链接到我们的Python应用网关uWSGI时遇到了问题。到uWSGI的链接在Nginx配置文件的第10行定义:

uwsgi_pass unix:/var/www/demoapp/demoapp_uwsgi.sock;

这代表Nginx和uWSGI之间的链接是通过一个socket文件,这个文件位于/var/www/demoapp/demoapp_uwsgi.sock。因为我们还没有配置uWSGI,所以这个文件还不存在,因此Nginx返回“bad gateway”错误,让我们马上修正它吧。

Configuring uWSGI

Create a new uWSGI configuration file /var/www/demoapp/demoapp_uwsgi.ini:

[uwsgi]
#application's base folder
base = /var/www/demoapp

#python module to import
app = hello
module = %(app)

home = %(base)/venv
pythonpath = %(base)

#socket file's location
socket = /var/www/demoapp/%n.sock

#permissions for the socket file
chmod-socket    = 666

#the variable that holds a flask application inside the module imported at line #6
callable = app

#location of log files
logto = /var/log/uwsgi/%n.log
Let’s create a new directory for uwsgi log files, and change its owner to your user:
sudo mkdir -p /var/log/uwsgi
sudo chown -R ubuntu:ubuntu /var/log/uwsgi

Milestone #4

Let’s execute uWSGI and pass it the newly created configuration file:

uwsgi --ini /var/www/demoapp/demoapp_uwsgi.ini

Next, browse to your server. Now nginx should be able to connect to uWSGI process:

uwsgi

We are almost finished. The only thing left to do, is to configure uWSGI to run as a background service. That’s the duty of uWSGI Emperor.

配置uWSGI

创建一个新的uWSGI配置文件/var/www/demoapp/demoapp_uwsgi.ini

[uwsgi]
#application's base folder
base = /var/www/demoapp

#python module to import
app = hello
module = %(app)

home = %(base)/venv
pythonpath = %(base)

#socket file's location
socket = /var/www/demoapp/%n.sock

#permissions for the socket file
chmod-socket    = 666

#the variable that holds a flask application inside the module imported at line #6
callable = app

#location of log files
logto = /var/log/uwsgi/%n.log

创建一个新文件夹存放uWSGI日志,更改文件夹的所有权:

sudo mkdir -p /var/log/uwsgi
sudo chown -R ubuntu:ubuntu /var/log/uwsgi

里程碑 #4

执行uWSGI,用新创建的配置文件作为参数:

uwsgi --ini /var/www/demoapp/demoapp_uwsgi.ini

接下来访问你的服务器,现在Nginx可以连接到uWSGI进程了:

uwsgi

我们现在基本完成了,唯一剩下的事情是配置uWSGI在后台运行,这是uWSGI Emperor的职责。

uWSGI Emperor

uWSGI Emperor (quite a name, isn’t it?) is responsible for reading configuration files and spawing uWSGI processes to execute them. Create a new upstart configuration file to execute emperor - /etc/init/uwsgi.conf:

description "uWSGI"
start on runlevel [2345]
stop on runlevel [06]
respawn

env UWSGI=/usr/local/bin/uwsgi
env LOGTO=/var/log/uwsgi/emperor.log

exec $UWSGI --master --emperor /etc/uwsgi/vassals --die-on-term --uid www-data --gid www-data --logto $LOGTO
The last line executes the uWSGI daemon and sets it to look for config files in the /etc/uwsgi/vassals folder. Let’s create this folder and symlink the configuration file we created earlier into it:
sudo mkdir /etc/uwsgi && sudo mkdir /etc/uwsgi/vassals
sudo ln -s /var/www/demoapp/demoapp_uwsgi.ini /etc/uwsgi/vassals
Also, the last line states the the user that will be used to execute the daemon is www-data. For simplicity’s sake, let’s set him as the owner of the application and log folders:
sudo chown -R www-data:www-data /var/www/demoapp/
sudo chown -R www-data:www-data /var/log/uwsgi/

Note: The nginx version we installed earlier uses the “www-data” user for executing nginx. Nginx versions from other repositories may use a user named “nginx” instead.

Since both, nginx and uWSGI, are now being run by the same user, we can make a security improvement to our uWSGI configuration. Open up the uwsgi config file and change the value of chmod-socket from 666 to 644:

...
#permissions for the socket file
chmod-socket    = 644
Now we can start the uWSGI job:
sudo start uwsgi

Finally, both Nginx and uWSGI are configured to correctly serve our application on system start up.

uWSGI Emperor

uWSGI Emperor (很拉风的名字,是不?) 负责读取配置文件并且生成uWSGI进程来执行它们。创建一个初始配置来运行emperor - /etc/init/uwsgi.conf

description "uWSGI"
start on runlevel [2345]
stop on runlevel [06]
respawn

env UWSGI=/usr/local/bin/uwsgi
env LOGTO=/var/log/uwsgi/emperor.log

exec $UWSGI --master --emperor /etc/uwsgi/vassals --die-on-term --uid www-data --gid www-data --logto $LOGTO

最后一行运行uWSGI守护进程并让它到/etc/uwsgi/vassals文件夹查找配置文件。创建这个文件夹,在其中建立一个到链到我们刚创建配置文件的符号链接。

sudo mkdir /etc/uwsgi && sudo mkdir /etc/uwsgi/vassals
sudo ln -s /var/www/demoapp/demoapp_uwsgi.ini /etc/uwsgi/vassals

同时,最后一行说明用来运行守护进程的用户是www-data。为简单起见,将这个用户设置成应用和日志文件夹的所有者。

sudo chown -R www-data:www-data /var/www/demoapp/
sudo chown -R www-data:www-data /var/log/uwsgi/

注意:我们先前安装的Nginx版本使用“www-data”这个用户来运行Nginx,其他Nginx版本的可能使用“Nginx”这个替代用户

由于Nginx和uWSGI都由同一个用户运行,我们可以在uWSGI配置中添加一个安全提升项。打开uWSGI配置文件,将chmod-socket值由666更改为644

...
#permissions for the socket file
chmod-socket    = 644

现在我们可以运行uWSGI了:

sudo start uwsgi

最后,Nginx和uWSGI被配置成启动后立即对外提供我们的应用服务。

Troubleshooting

If something goes wrong, the first place to check is the log files. By default, nginx writes error message to the file /var/log/nginx/errors.log.

We’ve configured uWSGI emperor to write it’s logs to /var/log/uwsgi/emperor.log. Also this folder contains separate log files for each configured application. In our case - /var/log/uwsgi/demoapp_uwsgi.log.

Static Files

If your application has to serve static files, the following rule should be added to the demoapp_nginx.conf file:

location /static {
    root /var/www/demoapp/;
}

As a result, all static files located at /var/www/demoapp/static will be served by nginx. (Bastianh, thanks for pointing this out)

问题解决

如果出现错误的话,第一个检查的地方是日志文件。Nginx默认将错误信息写到/var/log/nginx/errors.log文件。

我们已经配置了uWSGI emperor将日志写到/var/log/uwsgi/emperor.log。这个文件夹还包含着每个配置应用的单独日志。我们的例子是 - /var/log/uwsgi/demoapp_uwsgi.log

静态文件

如果你的应用提供静态文件的话,将下面的规则添加到demoapp_nginx.conf文件:

location /static {
    root /var/www/demoapp/;
}

上面配置的结果就是所有在/var/www/demoapp/static文件夹中的文件将由提供Nginx对外服务(谢谢Bastianh指出)

Hosting Multiple Applications

If you want to host multiple Flask applications on a single server, create a separate folder for each application, as we did earlier, and symlink nginx and uWSGI configuration files to the appropriate folder.

Deploying Applications with Distribute

To deploy Flask apps using distribute, first follow the steps in Flask documentation to convert your application into a package. Next, copy the generated distribute setup package to the server, and use the virtual environment’s Python to install it. E.g.:

python setup.py install
And last but not least, the “app” property of uWSGI configuration’s file should be equal to name of the package that holds the Flask application.

托管多个应用

如果你想在一台服务器上托管多个Flask应用,为每个应用创建一个单独的文件夹,像我们前面所做的一样,创建Nginx及uWSGI配置文件到应用文件夹的符号链接。

使用Distribute部署应用

使用distribute部署Flask应用的话,首先,按照Flask文档里的步骤将应用转化成package,然后复制distribute通用安装包到服务器上,使用虚拟环境中的Python来安装它。如下:

python setup.py install

最后且同样重要的是,uwsgi配置里应用属性的值要设置成包含Flask应用的包的名称。

返回顶部
顶部