深度学习模型服务端部署——flask+gunicorn+supervisor+nginx+docker

前言:深度学习模型经过前期的训练调优评估,最终得到一个精度速度满足要求的模型(.pth, .ckpt,或者.onnx等等格式),但模型要实际用起来,还得部署起来,部署分为在移动端芯片上和服务器上。在移动端芯片部署通常还需要对模型进行格式转换,转换为芯片指定格式才能在芯片上进行推理,比如瑞芯微芯片的.rknn格式,海思芯片的.wk格式,推理代码通常都是c++代码编写。而在服务器上进行部署由于可以利用强大的GPU算力,对模型加速要求不是很高,为了适当提速,通常将模型转换为TensorRT格式,利用Nvidia对算子做的加速实现减少模型推理耗时;服务器上部署也有C++代码实现和Python代码实现,但都是基于后端支撑,提供api服务供使用者调用,本文是基于服务器上部署深度学习模型,利用python代码实现,提供flask接口供使用者调用。


在部署之前,需要对代码做功能性调试,这里不细细展开,这是前提,调试大概分为以下几个步骤,根据实际需要做调整:
1、整个算法工程用python加载正常模型能跑通,且已经改写成flask接口形式,运行能正常监听端口,用postman软件测试能正常返回结果;
2、若需要对模型进行加密,需要先将模型进行加密并删除原来未加密的.pt模型;代码中在加载模型部分需加入对模型进行解密的代码,这就做完了对模型进行加解密的步骤;
3、若需要对整个工程启动加入license验证(用于匹配物理机,防止将算法拷贝到其他物理机上运行),则在这一步改写代码进行license验证;
4、若需要对每个接口进行权限验证,则需改写接口函数的代码进行验证;
5、若需对整个工程代码进行加密,则放在最后一步做,保留加密后的代码;
6、运行加密后的代码,用postman软件测试能正常返回结果即可。


一、制作Docker镜像

深度学习模型部署基本都需要gpu推理,所以需要cuda环境,深度学习框架我这里选择的是pytorch;宿主机上,我的工程路径是 /mvdata/centos199/lishanlu/projects/test_project

1、下载nvidia/cuda的Ubuntu镜像

可到 https://hub.docker.com/r/nvidia/cuda 页面上直接下载,也可用docker pull命令在终端上直接拉取对应版本镜像;

docker pull nvidia/cuda:11.2.0-cudnn8-devel-ubuntu18.04(这里选的是Ubuntu18.04的cuda11.2带cudnn加速的开发环境,有完整的cuda编译文件)

2、运行容器,进入容器后安装必要的工具,及运行工程必要的环境

docker run -d --name dnn_inference -v /mvdata/centos199/lishanlu/projects/test_project:/home/work/project -p 8200:8203 nvidia/cuda:11.2.0-cudnn8-devel-ubuntu18.04 (以代码挂载的方式启动容器,并将容器的8203端口映射到宿主机的8200端口,容器名字为dnn_inference,这个命令是后台启动的容器)
注意:docker run启动可以指定gpu,但只是在docker19.0版本以后才支持 --gpus

进入容器:docker exec -it dnn_inference /bin/bash

进入容器后就可以进行调试环境了:

# 安装必备软件
apt-get update
apt-get upgrade -y -q
apt-get install vim
apt-get install net-tools
apt-get install inetutils-ping
apt-get install curl

# 安装anaconda,将宿主机上的anaconda安装文件拷贝到容器中(新开一个终端)
sudo docker cp ./Anaconda3-2022.10-Linux-x86_64.sh  dnn_inference:/home/work/(将宿主机当前目录下的anconda3安装文件复制到容器/home/work/目录下)
# 切换到容器中的终端,进行安装
cd /home/work
bash Anaconda3-2022.10-Linux-x86_64.sh   (安装同宿主机上一样,一路yes)

# 然后就可以创建虚拟环境了
conda create -n inference_env python=3.8
# 激活虚拟环境
source activate inference_env
# 安装运行my_testproject工程需要的库,深度学习模型一般需要pytorch,tensorflow,opencv,PIL,numpy,后端部署需要flask,gunicorn等等;
conda install pytorch==1.12.1 torchvision==0.13.1 torchaudio==0.12.1 cudatoolkit=11.3 -c pytorch
python -m pip install flask -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m pip install opencv-python -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m pip install gunicorn -i https://pypi.tuna.tsinghua.edu.cn/simple
python -m pip install numpy -i https://pypi.tuna.tsinghua.edu.cn/simple

# 进入到挂载代码路径,运行程序,若报错缺少哪些包就继续安装
cd /home/work/project
python server.py

3、在宿主机上写test_api.py测试能否正常调用flask接口,并且能收到正常响应

url = "http://192.168.103.xxx:8200/person_detetion"
files = {'file': open('./TestDataImage/6.jpg', 'rb')}
r = requests.post(url, files=files)
#print('========== res:', r.text)
#msg = r.json()['msg']
response = r.json()['result']
#print(msg)
print(response)

4、退出docker容器,用docker commit将容器提交成工程环境镜像

docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
              -a,--author="" Author
              -m,--message="" Commit message
              -p,--pause=true Pause container during commit

docker commit -a "lishanlu" -m "Deeplearning model deploy environment." -p dnn_inference dnn_deploy/inference_env:v1.0

运行docker commit命令后,通过docker images就可以看到 dnn_deploy/inference_env:v1.0这个镜像了


二、了解web服务框架

下图为web服务大致部署架构图,web应用服务采用Nginx+supervisor+(u)WSGI+flask/django部署;客户端指移动设备App,Web浏览器或者第三方应用,它们通过Nginx映射后提供的ip地址和端口实现对web应用服务的调用,这里nginx充当反向代理,负载均衡的作用,把请求分发到后面的flask/django服务,提高响应速度。uWSGI搭配Nginx实现响应速度快、内存占用率低、高可用定制、自带详尽日志等功能,支持平滑重启。Flask完全兼容WSGI (WebServer Gateway Interface )标准,便于搭建微服务框架,完全基于Unicode编码,无须处理编码问题。
在这里插入图片描述


三、gunicorn启动服务

为什么需要gunicorn启动应用?
从上图可以看出完整的web服务框架在nginx和web应用直接是存在一个uWSGI的,这里的gunicorn就属于WSGI,虽然flask自身也能提供web服务,但当请求数量增加时还是不稳定的,从下图启动flask服务弹出的警告就可以看出:
在这里插入图片描述
可以看到,flask自身应用后弹出了一条警告,提示当前处理开发模式下的服务器,不要在生产环境使用它,而是要使用一个生成环境下的WSGI服务器。WSGI(Web Server Gateway Interface)- Web服务器网关接口。是为 Python 语言定义的Web服务器和Web应用程序或框架之间的一种简单而通用的接口。通过查看flask文档可以发现,推荐采用的WSGI就有如下几种:
在这里插入图片描述
gunicorn是基于unix系统,被广泛应用的高性能的Python WSGI HTTP Server。用来解析HTTP请求的网关服务。它通常是在进行反向代理、负载均衡(如 nginx)和一个web 应用(比如 Django 或者 Flask)之间。

在使用gunicorn启动web应用时,gunicorn 会启动一组 worker进程,所有worker进程公用一组listener,在每个worker中为每个listener建立一个wsgi server。使用命令行启动gunicorn有两种方式获取配置项,一种是在命令行配置,一种是在配置文件中获取。

命令行配置如下:

gunicorn -w 1 -b 0.0.0.0:8203 --access-logfile logs/gunicorn_access.log --error-logfile logs/gunicorn_error.log server:app -D
# -w 代表启用几个进程
# -b 代表监听端口号
# --access-logfile 指定access日志目录
# --error-logfile 指定error日志目录
# server:app 其中server是web应用服务的文件名,app代表Flask程序实例名
# -D 代表后台启动程序

启动后可通过命令 ps -ef | grep gunicorn 查看gunicorn进程信息

对于工作模式,默认是sync,即同步模式。这种模式就是说在调用的时候,必须等待调用返回结果后,决定后续的行为。而要切换成异步模型,使用gevent,此时还需要pip来安装gevent。

python -m pip install gevent -i https://pypi.tuna.tsinghua.edu.cn/simple
gunicorn -w 1 -b 0.0.0.0:8203 -k 'gevent' --access-logfile logs/gunicorn_access.log --error-logfile logs/gunicorn_error.log server:app -D

gunicorn以配置文件方式启动,通过 -c 参数指定配置文件,配置文件必须是.py格式
cat guni_conf.py查看配置文件如下:

import os
import gevent.monkey
gevent.monkey.patch_all()
import multiprocessing

bind = '0.0.0.0:8200'
workers = 1
timeout = 100
backlog = 1024
worker_class = 'gevent'
worker_connections = 1000
daemon = True
debug = False
proc_name = 'gunicorn_openmv'
pidfile = '/root/openmv/logs/gunicorn.pid'
errorlog = '/root/openmv/logs/gunicorn_error.log'
accesslog = '/root/openmv/logs/gunicorn_access.log'
access_log_format = '%(t)s %(p)s %(h)s "%(r)s" %(s)s %(L)s %(b)s "%(f)s" "%(a)s"'
loglevel = 'warning'
x_forwarded_for_header = 'X-FORWARDED-FOR'

gunicorn -c guni_conf.py server:app -D
至此基本的gunicorn+flask异步服务部署就实现了。


四、nginx配置

在web服务中,nginx这里充当反向代理服务器和负载均衡的作用,用nginx做反向代理和负载均衡非常简单,支持两个用法,1个proxy,1个upstream,分别用来做反向代理和负载均衡。
示例配置如下:cat nginx_conf.txt

user  root;                      #设置nginx服务的系统使用用户
worker_processes  8;             #工作进程数(和cpu核心数保持一致)

error_log  /home/lishanlu/openmv/logs/nginx_error.log info;    #nginx的错误日志
pid       /home/lishanlu/openmv/logs/nginx.pid;                #nginx服务启动时候pid



events {
    worker_connections  1024;        #每个进程允许最大连接数
}



http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    client_body_buffer_size 10M;
    client_max_body_size 100M;  

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /home/lishanlu/openmv/logs/nginx_access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    #include /etc/nginx/conf.d/*.conf;
    
    upstream city_server{
    server 192.168.103.221:8200;
    server 192.168.103.223:8200;
    }     # 负载均衡这两个服务

    server {
    listen       9602;
    server_name  localhost;

    #charset koi8-r;
 
    location / {
        proxy_pass http://city_server;
    }
    location ^~ /new/ {
    proxy_pass http://192.168.103.221:8201/;
    }
    location ^~ /ocr_ {
    proxy_pass http://192.168.103.221:5000;
    }
}
}

上述配置文件,对于city_server服务,部署在了局域网221和223两天宿主机上,nginx配置对他们做了负载均衡,nginx对外暴露的端口是9602;当nginx接收到请求时,会分析请求的接口地址形式,像类似于/person_detect这种直接转发到city_server,走负载均衡分发到两台服务的其中一台,类似于/new/face_restore这种会转发到221服务器上8201端口的应用服务,类似于/ocr_开头的请求,会转发到221服务器上5000端口的应用服务。

启动nginx服务可以通过命令nginx -c ./nginx_conf.txt
这一步做完就完全打通了web服务的链路,浏览器——>nginx——>uWSGI(采用gunicorn)——>flask应用,接下来就是要保证服务的高可用了。


五、supervisor

supervisor 是一个用 python 语言编写的进程管理工具,它可以很方便的监听、启动、停止、重启一个或多个进程。当一个进程意外被杀死,supervisor 监听到进程死后,可以很方便的让进程自动恢复,不再需要程序员或系统管理员自己编写代码来控制。

安装supervisor:

# 进入inference_env虚拟环境
source activate inference_env
pip install supervisor
# 可以看到supervisord可执行文件在anaconda3/envs/inference_env/bin目录下(同时还有supervisorctl,echo_supervisord_conf)

验证是否安装成功,执行命令:echo_supervisord_conf,会打印出supervisor的样例配置信息。(证明supervisord,supervisorctl,echo_supervisord_conf都加入了环境变量)
然后就是配置文件:

[program:gunicorn_processing]
command=/home/jovyan/anaconda3/envs/inference_env/bin/gunicorn -w 1 -b :8200 server:app
directory=/home/jovyan/project/test_project
autostart=true
autorestart=true
user=root
redirect_stderr=true

详细supervisor请参考:https://xugaoxiang.com/2019/12/04/supervisor/


六、其他让程序故障重启的方式

1、通过docker --restart=always命令启动容器

2、写一个sh文件,检测程序是否存在,若不存在则重启;将运行该sh的命令加入到crontab中

3、docker启动容器的时候就指定一个sh文件,sh文件循环查询应用服务是否存在,不存在则拉起

具体详细过程请参考:怎样使程序开机自启动和程序挂掉自动重启

我这里是采用的第3种,sh文件如下,cat run.sh

#!/bin/bash
while true
do
    exist_id=`ps -ef|grep "gunicorn"|grep -v grep|wc -l`
    if [ $exist_id -eq 0 ]
    then
        echo "gunicorn not running, now run it..."
        cd /home/work/project/test_project
        PYTHONIOENCODING=utf-8 gunicorn -c guni_conf.py service:app
    fi
    usleep 30000000
done

启动容器命令如下:sudo docker run -d --restart=always --name dnn_inference --gpus "all" --privileged -v /mvdata/centos199/lishanlu/projects/test_project:/home/work/project -p 8200:8203 dnn_deploy/inference_env:v1.0 /bin/bash /home/work/project/test_project/run.sh

综上所述,我这里采用的架构如下图所示:
在这里插入图片描述

参考:
https://xugaoxiang.com/2020/07/21/flask-12-deployment/
https://www.cnblogs.com/mokundong/p/deploy-deeplearning-model-on-flask.html
https://www.zmrenwu.com/tutorials/hellodjango-blog-tutorial/materials/74/
https://zhuanlan.zhihu.com/p/488458470

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值