• 使用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

  • 相关阅读:
    观察者模式(Observer)
    怎样解决Java Web项目更改项目名后报错
    MAVEN最佳实践:模块划分
    java.lang.OutOfMemoryError: PermGen space及其解决方法
    以Windows服务方式启动MySQL,并将其默认编码设置为UTF-8
    ubuntu 12.04和Windows 7双系统的安装方法
    允许ubuntu下mysql远程连接
    Linux 系统目录介绍
    SVN中图标符号的含义
    简单介绍Linux下安装Tomcat的步骤
  • 原文地址:https://www.cnblogs.com/vh-pg/p/11731637.html
Copyright © 2020-2023  润新知