一步步带你完成Flask应用的生产环境部署

一步步带你完成Flask应用的生产环境部署

image-20230305152722914

一. 项目需求

使用服务器部署openai web应用, 应用的使用范围人群不多, 这次部署也是一次试炼过程, 期望部署完成后可以使用openai的最新API访问., 后续还会继续在这个服务器上包装Restful API为其他应用提供服务.

初步计划使用gunicorn在虚拟环境中部署. 目标:

1- 虚拟环境pipenv 中部署gunicorn

2- 虚拟环境pipenv 中的gunicorn能够自动启动运行Flask应用

3- 可以使用git 拉取最新的代码进行更新程序

二. 系统环境

2.1 生产环境

二级域名: im.chenlp.cc

腾讯服务器: Centos8 64bit + Python3.8.5(64bit) + Git 1.23 + Nginx 1.20 + gunicorn20.1 + pipenv + Flask 2.2

2.2 开发调试环境

Windows 10 64bit + Python3.8.5(64bit) + pipenv + Flask 2.2

有些特殊包要特别注意生产环境和开发环境的区别, 尤其是在Socket 方面 和 多线程的应用模块

2.3 应用依赖包

应用系统服务器端依赖包: 这里列出的是最关键的依赖包

# 包如果指定了版本, 则必须使用该版本
flask = "==2.2.2"
jinja2 = "==3.1.2"
requests = "==2.28.2"
flask-login = "*"
flask-moment = "*"
flask-wtf = "*"
flask-socketio = "*"
pillow = "*"
python-dotenv = "==0.21.1"
flask-sqlalchemy = "==3.0.2"
mistune = "==3.0.0rc4"
flask-restful = "*"
openai = "==0.27.0"
moment = "*"
greenlet = "*"
eventlet = "==0.30.2"
gunicorn = "==20.1.0"
pyopenssl = "*"

2.4 开发程序发布

开发程序在开发环境中调试完成后, 上传代码到git 库中, 在生产环境中通过git pull拉取.

一般在执行Git Pull 操作前, 先将gunicorn应用退出, 更新代码后, 再启动gunicorn

三. 安装过程

安装步骤清单:

1- 在pipenv虚拟环境中安装 gunicorn

2- 使用 gunicorn 运行 flask应用

3- 使用systemd 配置 flask应用自动启动 (这一步坑太多, 已改为.sh 命令方式启动)

4- 配置Nginx WebSocket

5- 配置 域名解析

参考:

python - CentOS 下使用 Pipenv + Gunicorn + Supervisor 部署 Flask 程序 - 个人文章 - SegmentFault 思否

Nginx配置WebSocket (包含nginx-ingress-controller) - 流年晕开时光 - 博客园 (cnblogs.com)

四. 操作步骤

描述

4.1. 部署环境目录

Centos8 服务启动配置目录: /usr/lib/systemd/system

flask 项目目录: /home/app/proj/mychat

cd /home/app/proj
git clone git@gitee.com:hoeking/mychat.git

flask 项目虚拟环境目录: /home/venv/mychat-5MD6h6y4;

在进行实际开发过程中, 要特别注意:

1- 先在开发环境下进行程序调试, 注意配置相关内容

2- 关于安装包的Pipfile文件更新, 一般情况包管理在开发环境已经确定下来, 生产环境中只是进行该文件拉取, 不要在生产环境中进行pipenv install ***的操作; 每次如果有新安装的包, 请先在开发环境中安装, 然后到生产环境中通过git pull拉取; 如果想将pipfile文件排除不更新, 可使用下面的脚本

# pull时候忽略Pipfile Pipfile.lock
git update-index --assume-unchanged Pipfile
git update-index --assume-unchanged Pipfile.lock

# pull时候取消忽略xxx这个文件
git update-index --no-assume-unchanged Pipfile 
git update-index --no-assume-unchanged Pipfile.lock 

# 忽略Output 这个文件夹,注意后面带着 / 斜杠
git update-index --assume-unchanged Output/

4.2 安装 Gunicorn

进入flask 项目目录, 激活虚拟环境, 安装Gunicorn ; Gunicorn 不支持Windows哦

# 激活虚拟环境
cd /home/app/proj/mychat
pipenv shell
pipenv install greenlet
pipenv install eventlet==0.30.2
pipenv install gunicorn==20.1.0
# pipenv install uvicorn

# 验证启动
pipenv run gunicorn --workers=4 --bind=0.0.0.0:5188 hello:app

# 启动方式2: 在项目目录下面创建一个 gunicorn.conf.py; 文件内容如下:
workers=4
bind='0.0.0.0:5188'
worker_class='eventlet'
worker_connections=20
pidfile='/var/run/gunicorn.pid'
accesslog='/home/app/proj/mychat/logs/gunicorn.access.log'
accesslog='-'
errorlog='/home/app/proj/mychat/logs/gunicorn.error.log'
errorlog='-'
loglevel='error'

# 使用配置文件启动方式
pipenv run gunicorn -c gunicorn.conf.py wsgi:app


gunicorn和eventlet的版本匹配问题 如果gunicorn版本是19.9或者20.1 使用 eventlet的版本要是0.30.2

4.3 使用systemd 配置自启动服务

(* 该方法一直没有成功, 但是我还是先记录一下)

在本地电脑编辑好 gunicorn.service 文件, 上传到 /usr/lib/systemd/system

python - nginx+gunicorn+fastapi 部署自动后台启动 - 沉迷学习 - SegmentFault 思否

[Unit]
Description='mychat'
After=network.target

[Service]
Type=forking
User=root
PrivateTmp=true
Restart=on-failure
PIDFile=/var/run/gunicorn.pid
RuntimeDirectory=/home/venv/mychat-5MD6h6y4/bin
WorkingDirectory=/home/app/proj/mychat
ExecStart=/usr/bin/pipenv run gunicorn -c gunicorn.conf.py wsgi:app
ExecReload=/bin/kill -s HUP $MAINPID
ExecStop=/bin/kill -s TERM $MAINPID

[Install]
WantedBy=multi-user.target

[Install]
WantedBy=multi-user.target

# 复制文件到 /usr/lib/systemd/system
cp gunicorn.conf.py /home/venv/mychat-5MD6h6y4/bin
cp gunicorn.service /usr/lib/systemd/system/
cp gunicorn.socket /usr/lib/systemd/system/

## 验证服务是否正常
systemctl enable gunicorn.socket --now
systemctl enable gunicorn.service
systemctl daemon-reload
systemctl start gunicorn.socket
systemctl start gunicorn.service
systemctl status gunicorn.service
systemctl stop gunicorn

# 取消自动启动
systemctl disable gunicorn.socket
systemctl disable gunicorn.service
systemctl daemon-reload

4.4. 配置Nginx WebSocket

从其他站点的 .conf 复制一个文件进行修改, 配置WebScoket ; (这里有个很大担心 云服务器是否支持WebSocket )

我使用的是socket.io.js ; 这个客户端JS默认会请求 /socket.io 这个地址;

一直报错400 : 说明从客户端请求 /socket.io/这个地址一直不正确. (这种错误, 我是通过设置gunicorn 的工作进程为1解决的 ) ;

image-20230309111047272

# 请先切换的root
sudo -i

# 新增配置文件 im_chenlp_cc.conf
git pull
systemctl stop nginx
cp im_chenlp_cc.conf /etc/nginx/conf.d/
nginx -t
systemctl restart nginx
pipenv run gunicorn -c gunicorn.conf.py wsgi:app

# 创建命令执行文件start.sh, 授权后, 后台启动服务,并记录输入
chmod +x start.sh
nohup ./start.sh > logs/nohup.txt 2>&1 &

# 查看服务 # 查看服务状态
journalctl -e -u gunicorn 
netstat -ntlp
# 强制杀进程
ps -ef | grep gunicorn
killall gunicorn

# 优雅重启法
pstree -ap|grep gunicorn
kill -HUP pid

# 访问:
http://im.chenlp.cc

附录1: 阿里云的WebScoket是计费的:

附录2: 腾讯云的是暂时免费的哦

4.5. 配置域名解析

阿里云上配置域名解析, 我就先不说了哦!!!! 这次先暂时不使用HTTPS了, 直接http吧

4.6. 配置HTTPS

https的问题已经困扰好多天了啊, Socket.io在http时状态非常正常, 没有任何问题, 在Nginx上配置https后,就报错了

大部分都是400 错误;

今天偶尔bing了一下后,修改了一个配置. 同时在服务器端实例化socket对象时增加了跨域的配置, 尽然OK了啊, 哈哈哈

老怀甚慰. 当浮一大白.

image-20230312160736122

另外: 增加了跨域设置, 允许跨域的 cors_allowed_origins="*"

socketio = SocketIO(logger=True, engineio_logger=True, cors_allowed_origins="*")

其实这会想想, 很有可能是我的生产环境中启用了 Flask 的配置了全局的跨域检查, 但是我的sockeio服务没有启用跨域. socket客户端发送的请求变成了https , 导致NGinx代理转发时, 匹配不到https的请求, 所以在nginx的localtion上增加一个的前面加上 规则匹配, 转发就成功了.

附录: 生产环境 部署 N多坑 记录

生产环境部署推荐结构:

推荐部署

坑一: flask-sockeio 本身与客户端 sokert.io.js 版本兼容问题

这个问题是一个 flask-sockeio 这个包的本身问题, 请千万在开发环境/测试环境中就解决掉哦; 不要带到生产环境在解决啊

image-20230309153908862

坑二: 使用systemd配置 gunicorn 自启动,

设置自启动没有问题, 但是结合pipenv 和 Nginx一起时, 就没有成功过, 最后解决方法是: 改用 .sh 执行文件的方式

坑三: gunicorn 生产环境部署 带socketio 应用时, 不要使用多个worker 的工作模式

如果是多个worker的启动配置,则会出现HTTP 请求 400Bad Request的报错

gunicorn 的官方部署文档上有一段话, 已经描述的这个要求, 对于大并发的应用时, 官方建议是采用 Nginx 的负载均衡 + 多进程之间的消息队列解决(这个已经超出我目前的需求范围).

img


作者: CLP ; 日期: 2023-3-10 ; 地点: 临沂; 天气: 晴

QQ: 53258372; Mail : 53258372@QQ.com

微信: image-20220903114619121