flask | uwsgi生产化部署flask服务
0x00 WSGI简介
WSGI是 Web Server Gateway Interface 的缩写。它是 Python应用程序(application)或框架(如 Django)和 Web服务器之间的一种接口,已经被广泛接受。
它是一种协议,一种规范,其是在 PEP 333提出的,并在 PEP 3333 进行补充(主要是为了支持 Python3.x)。这个协议旨在解决众多 web 框架和web server软件的兼容问题。有了WSGI,你不用再因为你使用的web 框架而去选择特定的 web server软件
常见的web应用框架有:Django,Flask等
常用的web服务器软件有:uWSGI,Gunicorn等
WSGI 接口有服务端和应用端两部分,服务端也可以叫网关端,应用端也叫框架端。服务端调用一个由应用端提供的可调用对象。如何提供这个对象,由服务端决定。例如某些服务器或者网关需要应用的部署者写一段脚本,以创建服务器或者网关的实例,并且为这个实例提供一个应用实例。另一些服务器或者网关则可能使用配置文件或其他方法以指定应用实例应该从哪里导入或获取。
0x01 WSGI服务结构
WSGI 对于 application 对象有如下三点要求
- 必须是一个可调用的对象
- 接收两个必选参数environ、start_response。
- 返回值必须是可迭代对象,用来表示http body。
我根据其架构组成的不同将这个过程的实现分为两种:
1、两级结构
在这种结构里,uWSGI作为服务器,它用到了HTTP协议以及wsgi协议,flask应用作为application,实现了wsgi协议。当有客户端发来请求,uWSGI接受请求,调用flask app得到相应,之后相应给客户端。 这里说一点,通常来说,Flask等web框架会自己附带一个wsgi服务器(这就是flask应用可以直接启动的原因),但是这只是在开发阶段用到的,在生产环境是不够用的,所以用到了uwsgi这个性能高的wsgi服务器。
2、三级结构
这种结构里,uWSGI作为中间件,它用到了uwsgi协议(与nginx通信),wsgi协议(调用Flask app)。当有客户端发来请求,nginx先做处理(静态资源是nginx的强项),无法处理的请求(uWSGI),最后的相应也是nginx回复给客户端的。 多了一层反向代理有什么好处?
提高web server性能(uWSGI处理静态资源不如nginx;nginx会在收到一个完整的http请求后再转发给wWSGI)
nginx可以做负载均衡(前提是有多个服务器),保护了实际的web服务器(客户端是和nginx交互而不是uWSGI)
何时需要nginx?
需要返回静态资源时,nginx处理静态资源更高效
当有多个服务器时,nginx可以做负载均衡
需要 TLS/HTTPS(证书集中在前端,后端用纯 HTTP)
需要缓冲慢客户端(Nginx 先接收完整请求体,再迅速把数据交给 uWSGI,释放后端 worker)
需要统一的限流、连接数控制、WAF、安全过滤、IP 黑白名单
需要把不同路径路由到不同后端(/api -> Flask;/static -> 本地目录;/svc2 -> 另一服务)
何时不需要nginx?
- 只有一个服务器
- 不需要返回静态资源,静态资源托管在对象存储 (S3/OSS/CDN),只暴露简单
JSON API
- 不需要 TLS/HTTPS(证书集中在前端,后端用纯 HTTP)
绝大多数对外生产场景,把 TLS 终止(HTTPS)放在 Nginx(或等价前端)是“职责分离 + 性能 + 安全 + 运维”最佳实践;uWSGI/Flask 保持明文本地通信即可。
所以,生成环境下部署 Flask 应用的推荐架构是:Nginx + uWSGI + Flask
0x02 uWSGI简介
uWSGI(官方写法为"uWSGI")是一个Web服务器和应用服务器,用于将Web应用程序和Web服务器之间进行通信。它充当Web服务器和Python Web应用程序之间的桥梁,实现了WSGI协议(Web Server Gateway Interface)的处理。
Nginx本身就是Web服务器,我们为什么还需要uWSGI这个Web服务器呢? Django不是自带runserver服务器?Flask不是自带Werkzeug吗? 答案是Nginx处理静态文件非常优秀,却不能直接与我们的Python Web应用程序进行交互。Django和Flask本身是Web框架,并不是Web服务器,它们自带的runserver和Werkzeug也仅仅用于开发测试环境,生产环境中处理并发的能力太弱。
为了解决Web 服务器与应用程序之间的交互问题,就出现了Web 服务器与应用程序之间交互的规范。最早出现的是CGI,后来又出现了改进 CGI 性能的FasgCGI,Java 专用的 Servlet 规范。在Python领域,最知名的就是WSGI规范了。
0x03 uWSGI常用命令
一个简易的wsgi app的实现如下:
1 | |
可以使用如下命令来启动uwsgi
1 | |
--module 期望“可导入模块路径 + 可调用对象”(默认可调用名为 application)。Django 项目自动生成 myproject/wsgi.py,里面有 application = get_wsgi_application(),正好符合默认名称,所以写 --module myproject.wsgi:application(甚至可省略 :application)。
wsgi-file 直接给文件路径,uWSGI 读取并执行该文件,再从其中拿到指定 --callable(默认仍叫 application;若你用 app 需 --callable app)。
Flask 示例里通常文件名不是一个包路径(或你懒得设 PYTHONPATH),所以示例常用 --wsgi-file app.py --callable app。
两者可互换:如果你的 Flask 项目是包结构,如 mysite/init.py 里有 app,对应也可用 --module mysite:app;反之也可以对 Django 用 --wsgi-file myproject/wsgi.py(因其中也定义了 application)。
Django 必须先加载 settings 和框架初始化逻辑(wsgi.py 已封装),所以最自然是 --module myproject.wsgi。Flask 没这个自动生成的 wsgi.py,最小示例就是一个脚本文件。
常用命令如下 1
2
3
4
5
6
7
8
9
10
11# 启动uWSGI服务器
$ uwsgi --ini uwsgi.ini
# 重启uWSGI服务器
$ sudo service uwsgi restart
# 查看所有uWSGI进程
$ ps aux | grep uwsgi
# 停止所有uWSGI进程
$ sudo pkill -f uwsgi -9
uWSGI常用配置选项如下所示,稍加修改(项目名,项目根目录)即可部署大部分Python Web项目
1 | |
0x04 uWSGI和Nginx之间的通信方式
unix socket,http-socket和http。Nginx的配置必需与uwsgi配置保持一致。
1 | |
如果你的nginx与uwsgi在同一台服务器上,优先使用本地机器的unix socket进行通信,这样速度更快。此时nginx的配置文件如下所示:
1 | |
如果nginx与uwsgi不在同一台服务器上,两者使用TCP socket通信,nginx可以使用如下配置:
1 | |
如果nginx与uwsgi不在同一台服务器上,两者使用http协议进行通信,nginx配置应修改如下:
1 | |
include字段在Nginx配置中的作用是包含外部文件或模块的配置内容。它允许将其他文件中的配置指令包含到当前的配置文件中,以便实现代码的模块化和重用。
具体来说,include指令用于将指定路径下的文件内容包含到当前位置。这些被包含的文件可以包含Nginx的配置指令、变量定义、映射规则、缓存规则等。
include指令可以用于以下场景:
分割配置文件:将大型的Nginx配置文件分割为多个小文件,使得配置更加清晰和易于维护。可以根据不同的功能或服务,将相关的配置指令放在不同的文件中,并使用include指令将它们包含到主配置文件中。
共享配置片段:如果有多个Nginx服务器或虚拟主机需要共享一些相同的配置片段,可以将这些公共配置片段放在一个独立的文件中,并在需要的地方使用include指令进行引用。这样可以避免重复编写相同的配置,提高代码的重用性和可维护性。
引入第三方模块的配置:某些功能可能需要额外的第三方模块或插件来实现。这些模块通常会提供自己的配置文件,我们可以使用include指令将这些配置文件包含到主配置文件中,以启用这些模块的功能。
0x05 docker容器化部署nginx + uwsgi + flask + mysql
flask/uwsgi 镜像 dockerfile.prod
flask镜像可以从alpine或者python官方镜像构建
镜像构建步骤如下所示:
- 修改时区
- 修改apk源
- 安装python和pip
- 通过pip安装依赖包
- 创建一个非root用户避免在root下运行uwsgi
1 | |
uwsgi 配置 uwsgi.ini
1 | |
对于module方式,需要指定wsgi文件位置和application对象名称
创建socket文件夹/var/run/uwsgi,并将其权限设置为660,便于nginx访问
stats.sock用于监控uwsgi状态
nginx 镜像 dockerfile.nginx
1 | |
nginx 配置 nginx.conf
1 | |
主要是对路由进行一个转发保护后端(处理慢客户端),后期可以添加tls等配置。
docker服务编排 docker-compose.yml
整个服务采用flask/uwsgi + nginx + mysql三容器编排方式部署
优点:
- 资源隔离:每个服务运行在独立的容器中,互不影响,便于管理和扩展。
- 便于部署:使用docker-compose可以一键启动和停止所有服务,简化了部署流程。
- 易于扩展:可以根据需要轻松增加或减少服务实例,满足不同的负载需求。
1 | |
如上所示,app镜像使用dockerfile.prod文件构建,nginx镜像使用dockerfile.nginx文件构建
配置文件为了方便开发,使用了两个profile,prod和dev,dev中采用了代码挂载和nodemon热重载方式,抛弃了nginx和uwsgi,直接使用flask自带的run.py启动服务,便于开发调试。
mysql服务挂载了mysql_data数据卷以便数据持久化。
网络通信结构,Compose 会在同一自定义网络内为每个服务注册一个内部 DNS 名(服务名 / 网络别名)。flask访问mysql时,MYSQL_ADDRESS=mysql:3306。uwsgi和nginx的通信方式采用unix socket,uwsgi配置中socket=/run/uwsgi/mysite1.sock,nginx配置中uwsgi_pass unix:/run/uwsgi/mysite1.sock,并将/run/uwsgi目录挂载到数据卷uwsgi-run(对于socket访问权限,需要将nginx和uwsig加入到统同一用户组中)。
1 | |