使用Flask+nginx+uwsgi+Docker部署python应用

以下内容根据个人理解整理而成,如有错误,欢迎指出,不胜感激。

0. 写在前面

本文记录自己在服务器上部署python应用的实现过程,涉及的内容如下:

  • Flask、nginx和uwsgi之间的关系
  • 从零开始使用Flask+nginx+uwsgi+Docker部署一个python应用

1. Flask、nginx和uwsgi之间的关系

客户端向服务器发送一个http请求,一般要经过下面三个阶段:

  • web服务器:用于接收客户端请求,并向客户端返回响应
  • web框架:用于对客户端的请求进行相应的处理,web框架也直接调用了我们自己编写的处理程序
  • WSGI协议:WSGI全称为Web Server Gateway Interface,它定义了web服务器和web框架之间相互交互的接口规范,只要web服务器和web框架满足WSGI协议,那么不同框架和服务器之间就可以任意搭配。要注意的是WSGI只适用于python语言

理解了上述内容,再来看Flask、nginx和uwsgi就比较简单:

  • nginx就是一个web服务器
  • Flask就是一个web框架,常用的其他web框架还有Django
  • uwsgi与WSGI一样,是一种通信协议。首先要说明,uWSGI是一个web服务器,它实现了WSGI、uwsgi、http等协议,其作用就是把HTTP协议转化成语言支持的网络协议,用于处理客户端请求,并向客户端返回响应。这里的uwsgi则是uWSGI服务器的独占协议(只适用于uWSGI服务器?),与WSGI是两种不同的协议

通过上面的描述中可以得出,uWSGI+Flask就可以实现一个完整的web服务,似乎不需要Nginx服务器。
当一个服务访问量过大时,我们可能会考虑多部署几台web服务器,这几台web服务器都可以处理客户端请求,但问题是如何将客户端请求分发到各个web服务器上?这就是Nginx的作用->反向代理服务器。

正向代理服务器(Proxy Server):如下图所示,用于代理客户端请求来访问外部网络信息,这种方式可以隐藏客户端真实IP,突破访问限制,保护客户端安全,VPN就是这种原理。

反向代理服务器(Reverse Proxy Server):反向代理服务器的功能是代理服务器接收客户端发来的请求,转发到相应的web服务器上进行处理,并将结果返回给客户端,反向代理服务器相当于是代理web服务器而不是客户端。

上图中的web服务器可以视为是uWSGI服务器,而反向代理服务器就相当于是nginx服务器,它具有强大的反向代理和负载均衡功能,可以平衡集群中各个服务器的负载,并且高效稳定。

综上,我们所使用的Flask+nginx+uwsgi部署python应用,客户端的每一次请求都要经过如下流程:

2. Docker部署python应用的整体框架

下图是一个简单的示意图,其中外侧粗线框矩形表示我们的服务器,内部各个小矩形分别代表了Flask Docker和Nginx Docker。
通过配置,我们将服务器的80端口映射到Nginx Docker的80端口,Nginx Docker会处理到达80端口的相关http请求。
通过配置,我们开放Flask Docker的8080端口,并配置Nginx Docker将相关的http请求通过8080端口发送到Flask Docker上,具体示意图如下:

以下内容几乎全部来自Building a Flask app with Docker | Learning Flask Ep. 24,自己按照这篇博客的步骤成功部署了自己的python应用,这里对相关内容进行更加详细的解释。
整个项目的文件结构如下:

app
├── docker-compose.yml    # 用于同时编译flask docker和nginx docker
├── flask
│   ├── Dockerfile        # flask docker配置文件
│   ├── .dockerignore     # 生成docker时需要忽略的文件
│   ├── my_app               # python应用, 可在这里扩展自己的python应用
│   │   ├── __init__.py
│   │   └── views.py
│   ├── uwsgi.ini           # uWSGI服务器配置文件
│   ├── requirements.txt  # docker相关依赖
│   └── run.py
├── nginx
│   ├── Dockerfile
│   └── nginx.conf        # nginx服务器相关配置文件
└── readme.md

2.1 Flask框架下app工作流程

首先以一个简单的例子解释一下Flask框架下app如何工作

# 从flask包中导入Flask类
# Flask用于创建web应用实例
from flask import Flask

# 创建一个web应用实例
# 当我们执行脚本时,__name__变量获得的字符串为'__main__'
app = Flask(__name__)

# 用于响应客户端http请求的函数
# '/'用于响应类似'ip:port'这样的请求
# 还可以定义其他@app.route('/xxx'),用于响应'ip:port/xxx'这样的请求
# route()的作用就是解析http请求中的相关信息
@app.route('/')
def home():
    return "Hey there!"

# app.run()用于启动该web应用实例
# 此时一旦有相关http请求,就会执行相关处理函数
if __name__ == '__main__':
    app.run(debug=True)

2.2 各文件内容详解

首先配置我们的python应用:
我们把自己的应用目录封装成一个模块包,当该目录被首次导入时,会首先执行__init__.py中的代码完成相应的初始化:
my_app/__init__.py

from flask import Flask

# 实例化一个web应用
app = Flask(__name__)

# 导入views及其他相关模块
from my_app import views
xxx

my_app/views.py

# 这里导入的app就是__init__.py文件中初始化的Flask实例
from my_app import app
import os

# 定义相关处理函数
@app.route("/")
def index():

    # Use os.getenv("key") to get environment variables
    # 此处获取环境变量
    # 相关环境变量配置后面会说
    app_name = os.getenv("APP_NAME")

    if app_name:
        return f"Hello from {app_name} running in a Docker container behind Nginx!"

    return "Hello from Flask"

run.py

# 在执行该语句之前,会执行__init__.py,实例化Flask实例,导入自己的相关应用
from my_app import app

if __name__ == "__main__":
    app.run()

以上三个文件中的内容加起来才是2.1节中的内容,这里说一下自己对这么组织程序的理解:

  • views.py: 我们可以为每一种请求定义一个.py文件, 比如还可以添加views1.pyviews2.py等,它们只需要导入实例化的app和相关需要的包即可;
  • __init__.py: 这里面实例化Flask实例,并导入自己的各种python应用
  • run.py: 该文件用于和外部的uWSGI交互
    这种组织方式使得我们只需要在my_app/文件夹下添加自己的应用,并在__init__.py中导入即可,每个应用单独一个文件会使得程序更为清晰。

requirements.txt
可以使用python3-venv来创建虚拟环境,安装需要的依赖来开发应用,最后在虚拟环境中使用pip freeze > requirements.txt来生成依赖列表
自己生成的requirements.txt中有一项是pkg-resources==0.0.0,根据stack overflow,这应该是ubuntu的一个bug,直接删除该行即可。

配置uWSGI服务器:
相关uwsgi.ini文件内容如下:

[uwsgi]
wsgi-file = run.py         # 指定要加载的WSGI文件,也即包含Flask实例(app)的文件名称
callable = app             # 指出uWSGI加载的模块中哪个变量将被调用, 也即Flask实例名称
socket = :8080             # 指定socket文件,也可以指定为127.0.0.1:9000,用于配置监听特定端口的套接字
processes = 4              # 指定开启的工作进程数量(这里是开启4个进程)
threads = 2                # 设置每个工作进程的线程数
master = true              # 启动主进程,来管理其他进程,其它的uwsgi进程都是这个master进程的子进程
chmod-socket = 660         # unix socket是个文件,所以会受到unix系统的权限限制。如果我们的uwsgi客户端没有权限访问uWSGI socket,可以用这个选项设置unix socket的权限
vacuum = true              # 当服务器退出的时候自动删除unix socket文件和pid文件
die-on-term = true         # ?
buffer-size = 65535        # 设置用于uwsgi包解析的内部缓存区大小为128k。默认是4k
limit-post = 104857600     # 限制http请求体大小

更过关于nWSGI相关的内容可参考这里

配置nginx服务器:

server {

    listen 80;                    # 监听80端口
    charset UTF-8;
    client_max_body_size 30M;
    location / {
        include uwsgi_params;
        uwsgi_pass flask:8080;   # flask指容器名字,该配置是指将信息转发至flask容器的8080端口
    }
}

配置Docker:
flask/Dockerfile

# Use the Python3.6 image
# 使用python 3.6作为基础镜像
FROM python:3.6

# Set the working directory to /app
# 设置工作目录,作用是启动容器后直接进入的目录名称
WORKDIR /app

# Copy the current directory contents into the container at /app
# . 表示和Dockerfile同级的目录
# 该句将当前目录下的文件复制到docker镜像的/app目录中
ADD . /app

# Install the dependencies
# 安装相关依赖
RUN pip install -r requirements.txt

# run the command to start uWSGI
# 容器启动后要执行的命令 -> 启动uWSGI服务器
CMD ["uwsgi", "uwsgi.ini"]

nginx/Dockerfile

# Use the Nginx image
# 使用Nginx镜像
FROM nginx

# Remove the default nginx.conf
# 移除官方的配置文件, 并换为自己的
RUN rm /etc/nginx/conf.d/default.conf

# Replace with our own nginx.conf
COPY nginx.conf /etc/nginx/conf.d/

配置docker-compose:
通过配置docker-compose.yml文件,可以同时build和启动多个容器

version: "3.7"

services:

  flask:
    build: ./flask           # 指向相关镜像的Dockerfile所在目录
    container_name: flask
    restart: always
    environment:             # 配置容器的环境变量
      - APP_NAME=MyFlaskApp
    expose:                  # 将该容器的8080端口开放给同一网络下的其他容器和服务
      - 8080

  nginx:
    build: ./nginx
    container_name: nginx
    restart: always
    ports:                   # HOST:CONTAINER 将主机的80端口映射到容器的80端口,相当于将nginx容器的80端口开放给外部网络
      - "80:80"

使用:

# 在docker-compose.yml所在目录执行该命令, 生成镜像文件
docker-compose build

# 启动容器
docker-compose up

# 列出正在运行的镜像
docker-compose images

# 列出正在运行的容器
docker-compose ps

# 停止服务
docker-compose ps

继续添加自己的python应用:
这里给出一个添加自己的python应用的使用例子:

from flask import Flask, request, Response, json
@app.route('/my_python_apps',methods=['POST'])
def my_python_apps():
    try:
        # 获取请求数据
        request_data = json.loads(request.get_data())
        response_data = my_process_code(request_data)
        return Response(json.dumps(response_data))
    except Exception:
        traceback.print_exc()
        Response = {'msg': traceback.format_exc()}
        return Response(json.dumps(Response))

3. 总结

通过以上配置,可以实现在内网127.0.0.1或公网IP上通过http访问自己写的python应用程序。

Reference

谈一下你对 uWSGI 和 nginx 的理解
如何理解Nginx、uWSGI和Flask之间的关系
Building a Flask app with Docker | Learning Flask Ep. 24
Containerizing Python web apps with Docker, Flask, Nginx & uWSGI
How a Flask app works

posted @ 2019-10-24 13:22  vh_pg  阅读(1456)  评论(0编辑  收藏  举报