python-39-flask+nginx+Gunicorn的组合应用

flask + nginx + Gunicorn = 王炸

1 flask+nginx+gunicorn+supervisor

1.1 myapp.py

from flask import Flask
app = Flask(__name__)

@app.route("/")
def test_link():
    return "the link is very good"

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

默认是5000端口。
打开虚拟机pip install flask
运行代码python3 myapp.py
访问 curl http://127.0.0.1:5000

1.2 Gunicorn

WSGI (Web Server Gateway Interface) 是 Python 应用程序与 Web 服务器之间的标准接口。Gunicorn 和 uWSGI 是两个常用的 WSGI 服务器。
(1)安装pip install gunicorn。
安装sudo apt install gunicorn。
安装后的路径:/usr/bin/gunicorn。
(2)可以使用Gunicorn来运行Flask应用。 此时Flask应用的入口文件是 myapp.py,可以使用以下命令来运行它:

默认分配给flask应用一个端口8000
gunicorn --workers=2 myapp:app  

也可以使用-b参数显式指定
gunicorn --workers=2 -b 0.0.0.0:5000 myapp:app

在这个命令中,workers=2表示使用2个工作进程,myapp:app表示Flask应用的入口文件是myapp.py,并且Flask应用的实例名是app。

在生产环境中,我们通常会使用进程管理工具(如Supervisor或systemd)来保证Gunicorn服务器的持续运行。

1.3 Supervisor

(1)安装sudo apt install supervisor。
(2)然后需要创建一个配置文件来告诉Supervisor如何运行你的Gunicorn服务器。

[program:myapp]
directory=/home/zb/mydir
command=/usr/bin/gunicorn -w 2 myapp:app
autostart=true
autorestart=true
stderr_logfile=/var/log/myapp.err.log
stdout_logfile=/var/log/myapp.out.log
user=zb

在这个配置中,directory是你的应用程序的目录,command是运行你的Gunicorn服务器的命令,user是运行服务器的用户。
(3)保存并关闭文件,然后使用以下命令来更新Supervisor的配置:

sudo supervisorctl reread
sudo supervisorctl update

此时已经生效了。

(4)最后可以使用以下命令来启动你的应用程序:

sudo supervisorctl start myapp
sudo supervisorctl stop myapp

现在,你的Gunicorn服务器应该会一直运行,即使你的服务器重启。

注意:在生产环境中,可能需要使用Nginx或Apache等Web服务器来代理你的Flask应用。

1.4 Nginx

要在Flask应用程序前使用Nginx作为反向代理,你需要进行以下步骤:
(1)安装sudo apt-get install nginx
(2)配置Nginx
sudo vi /etc/nginx/sites-available/default

server {
    listen 80;
    location / {
        proxy_pass http://localhost:8000;
    }
}

在这个配置中,Nginx会监听80端口,并将所有请求转发到本地的8000端口(你的gunicorn启动的Flask应用)。
(3)启动或重启Nginx

sudo systemctl restart nginx

此时curl http://127.0.0.1:8000/
或者curl http://127.0.0.1/都可以。

2 flask中的日志模块

Python Flask + Gunicorn + Docker 的日志输出设置
flask学习之日志logging

2.1 缺省配置(普通Flask日志设置)

Flask本身使用Python的logging模块来实现日志记录、输出。

Flask中也有自己的日志模块,通过flask的实例(一般叫作app)能够直接调用日志模块,输出或者记录日志。
(1)主程序main.py

import logging
from flask import Flask, jsonify
from flask import current_app
from children import task
app = Flask(__name__)

@app.route('/')
def default_route():
    """Default route"""
    app.logger.debug('this is a DEBUG message')
    app.logger.info('this is an INFO message')
    app.logger.warning('this is a WARNING message')
    app.logger.error('this is an ERROR message')
    app.logger.critical('this is a CRITICAL message')
    task()
    return jsonify('hello world')


@app.route('/current_app')
def default_route_current_app():
    current_app.logger.debug('this is a DEBUG message current app')
    task()
    return jsonify('hello world current app')

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000, debug=True)

(2)子程序children.py

from flask import current_app
def task():
    current_app.logger.info("I am children")

有一个问题就是在蓝图中如何使用flask的日志模块呢?还记得flask中的current_app吗,这个current_app返回的就是该蓝图注册所在的flask实例。在flask中的蓝图要使用app(flask的实例)中的一些方法或者属性就需要用到current_app。
python-39-flask+nginx+Gunicorn的组合应用_第1张图片

我们是在flask应用实例创建之后在添加的handler,因此,在flask应用实例创建的时候已经使用了缺省配置,添加了一个StreamHandler到app.logger了。

2.2 输出到文件方式一

可以使用 logging 模块的不同输出处理器(Handler)来实现标准输出、文件输出或邮件提醒。例如添加日志文件:

import logging
from flask import Flask, jsonify
from flask import current_app
from children import task
app = Flask(__name__)

@app.route('/')
def default_route():
    """Default route"""
    app.logger.debug('this is a DEBUG message')
    app.logger.info('this is an INFO message')
    app.logger.warning('this is a WARNING message')
    app.logger.error('this is an ERROR message')
    app.logger.critical('this is a CRITICAL message')
    task()
    return jsonify('hello world')


@app.route('/current_app')
def default_route_current_app():
    current_app.logger.debug('this is a DEBUG message current app')
    task()
    return jsonify('hello world current app')

if __name__ == '__main__':
    print(app.debug)  # 默认为False
    app.debug = True
    handler = logging.FileHandler('flask.log')
    app.logger.addHandler(handler)
    app.run(host='0.0.0.0', port=8000)

可以看到在当前目录下生成了flask.log日志文件。

2.3 输出到文件方式二

import logging
from flask import Flask, jsonify
from flask import current_app
from children import task
from flask.logging import default_handler
app = Flask(__name__)

@app.route('/')
def default_route():
    """Default route"""
    app.logger.debug('this is a DEBUG message')
    app.logger.info('this is an INFO message')
    app.logger.warning('this is a WARNING message')
    app.logger.error('this is an ERROR message')
    app.logger.critical('this is a CRITICAL message')
    task()
    return jsonify('hello world')


@app.route('/current_app')
def default_route_current_app():
    current_app.logger.debug('this is a DEBUG message current app')
    task()
    return jsonify('hello world current app')

if __name__ == '__main__':
    # 设置日志的记录等级
    logging.basicConfig(level=logging.DEBUG)
    # app.logger.removeHandler(default_handler)  # 是否移除默认配置
    # 创建日志记录器
    handler = logging.FileHandler('flask.log')
    # 定义handler的输出格式
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    # 为全局的日志工具对象(flask app使用的)添加日志记录器
    logging.getLogger().addHandler(handler)
    # app.logger.addHandler(handler)
    app.run(host='0.0.0.0', port=8000)

有两种使用方式

import logging

from flask import current_app
def task():
    current_app.logger.info("I am children, current app")
    logging.info("I am children, logging")

python-39-flask+nginx+Gunicorn的组合应用_第2张图片
在控制台输出时
若用current_app.logger,则标识main1(app的代码所在名称)
若用logging,则标识为root。

至此,Flask 的日志一切都运转良好。然后我们在生产服务器上部署的时候,现在常常会使用 Gunicorn 来运行,这时候的日志输出就有问题了,日志文件没有内容写入。

2.4 按天分割并调整格式

import logging
from flask import Flask, jsonify
from flask import current_app
from logging.handlers import TimedRotatingFileHandler
from children import task
app = Flask(__name__)

@app.route('/')
def default_route():
    """Default route"""
    app.logger.debug('this is a DEBUG message')
    app.logger.info('this is an INFO message')
    app.logger.warning('this is a WARNING message')
    app.logger.error('this is an ERROR message')
    app.logger.critical('this is a CRITICAL message')
    task()
    return jsonify('hello world')


@app.route('/current_app')
def default_route_current_app():
    current_app.logger.debug('this is a DEBUG message current app')
    task()
    return jsonify('hello world current app')

if __name__ == '__main__':
    print(app.debug)  # 默认为False
    app.debug = True
    formatter = logging.Formatter(
        "[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s")

    handler = TimedRotatingFileHandler("flask.log",
                                       when="D",
                                       interval=1,
                                       backupCount=30,
                                       encoding="UTF-8",
                                       delay=False,
                                       utc=False)

    handler.setFormatter(formatter)
    app.logger.addHandler(handler)
    app.run(host='0.0.0.0', port=8000)

3 使用gunicorn部署

Gunicorn有自己的日志记录器,它通过本身的机制控制日志级别。我们只能通过配置Gunicorn的日志设定,来实现我们的需求。同时,需要对上面例子应用的日志处理器设置进行调整。

3.1 方式一(gunicorn指定日志文件名)

import logging
from flask import Flask, jsonify
from flask import current_app
from logging.handlers import TimedRotatingFileHandler
from children import task
app = Flask(__name__)

@app.route('/')
def default_route():
    """Default route"""
    app.logger.debug('this is a DEBUG message')
    app.logger.info('this is an INFO message')
    app.logger.warning('this is a WARNING message')
    app.logger.error('this is an ERROR message')
    app.logger.critical('this is a CRITICAL message')
    task()
    return jsonify('hello world')


@app.route('/current_app')
def default_route_current_app():
    current_app.logger.debug('this is a DEBUG message current app')
    task()
    return jsonify('hello world current app')


# make app to use gunicorn logger handler
if __name__ != '__main__':
    gunicorn_logger = logging.getLogger('gunicorn.error')
    app.logger.handlers = gunicorn_logger.handlers
    app.logger.setLevel(gunicorn_logger.level)


if __name__ == '__main__':
    print(app.debug)  # 默认为False
    app.debug = True
    formatter = logging.Formatter(
        "[%(asctime)s][%(filename)s:%(lineno)d][%(levelname)s][%(thread)d] - %(message)s")

    handler = TimedRotatingFileHandler("flask.log",
                                       when="D",
                                       interval=1,
                                       backupCount=30,
                                       encoding="UTF-8",
                                       delay=False,
                                       utc=False)

    handler.setFormatter(formatter)
    app.logger.addHandler(handler)
    app.run(host='0.0.0.0', port=8000)

启动gunicorn

gunicorn -w 2 -b 0.0.0.0:5000 
--log-level debug 
--log-file /home/zb/mydir/gunicorn.log 
main:app

日志会写到文件gunicorn.log中。

3.2 方式二(自定义日志文件名)

nohup gunicorn -w 2 -b 127.0.0.1:8000 main1:app > /root/gunicorn.log 2>&1 &

为了使用自己设置的日志。
修改 app.py,注意我们添加的 if name != ‘main’ 这部分,就是在 Gunicorn 运行时,让Flask使用全局的日志处理器。

import logging
from flask import Flask, jsonify
from flask import current_app
from children import task
from flask.logging import default_handler
app = Flask(__name__)

@app.route('/')
def default_route():
    """Default route"""
    app.logger.debug('this is a DEBUG message')
    app.logger.info('this is an INFO message')
    app.logger.warning('this is a WARNING message')
    app.logger.error('this is an ERROR message')
    app.logger.critical('this is a CRITICAL message')
    task()
    return jsonify('hello world')


@app.route('/current_app')
def default_route_current_app():
    current_app.logger.debug('this is a DEBUG message current app')
    task()
    return jsonify('hello world current app')

if __name__ != "__main__":
    # 设置日志的记录等级
    logging.basicConfig(level=logging.DEBUG)
    # app.logger.removeHandler(default_handler)  # 是否移除默认配置
    # 创建日志记录器
    handler = logging.FileHandler('flask.log')
    # 定义handler的输出格式
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    # 为全局的日志工具对象(flask app使用的)添加日志记录器
    logging.getLogger().addHandler(handler)

if __name__ == '__main__':
    # 设置日志的记录等级
    logging.basicConfig(level=logging.DEBUG)
    # app.logger.removeHandler(default_handler)  # 是否移除默认配置
    # 创建日志记录器
    handler = logging.FileHandler('flask.log')
    # 定义handler的输出格式
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    handler.setFormatter(formatter)
    # 为全局的日志工具对象(flask app使用的)添加日志记录器
    logging.getLogger().addHandler(handler)
    # app.logger.addHandler(handler)
    app.run(host='0.0.0.0', port=8765)

4 执行flask后继续进行处理

可以解决这个报错的问题:python-“requests.exceptions.ConnectionError: (‘连接中止’, 远程断开连接(‘远程端关闭连接而没有响应’,))”。

需求:flask接口文件启动后,即时返回 ‘访问成功’,之后继续执行,文档中的功能函数。
方法:使用flask自带的一个函数即可解决。
flask_executor 模块。

from flask import Flask
from flask_executor import Executor

import time
app = Flask(__name__)
executor = Executor(app)


@app.route('/fast', methods=["POST", "GET"])
def fast_response():
    def test_function():
        # 需要异步执行的代码
        time.sleep(10)
        print('test_functiony执行了')
    executor.submit(test_function)
    return '异步立即返回'


@app.route('/slow', methods=["POST", "GET"])
def slow_response():
    def test_function():
        # 需要异步执行的代码
        time.sleep(10)
        print('test_functiony执行了')
    test_function()
    return '同步缓慢返回'

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

你可能感兴趣的:(python3,python)