為ML模型注入靈魂:基于MVP的超簡單部署方案
本文轉(zhuǎn)載自公眾號“讀芯術(shù)”(ID:AI_Discovery)
開發(fā)一個出色的機器學(xué)習(xí)模型是一件棘手的事,但即使開發(fā)完成也不意味著工作的結(jié)束。在部署之前,它仍然毫無用處,他人可以輕易訪問。
部署模型的方法有很多,筆者想談一種適用于基本MVP的非常簡單的解決方案——使用Flask為模型編寫API,將Gunicorn用于軟件服務(wù)器,將Nginx用于網(wǎng)站服務(wù)器,并將其包裝在Docker中,以便更輕松地在其他機器(特別是AWS和GCP)上進(jìn)行部署。
設(shè)置服務(wù)器
筆者更喜歡用專門為此租用的服務(wù)器,而不是使用私人或工作硬件對新配置進(jìn)行實驗。這樣,即使某些東西被嚴(yán)重?fù)p毀,也無關(guān)緊要。
因此筆者建議使用Linode,筆者本人就是用其來進(jìn)行實驗的,它們的硬件使用感不錯。只要在Ubuntu18.04 LTS上,就可以隨心所欲使用其他任何服務(wù)。
這一部分適合使用Linides的人借鑒。導(dǎo)航到Linodes,然后單擊“添加Linode”。有些東西需要填寫。在發(fā)行版中,筆者建議選擇Ubuntu18.04 LTS映像:
- 該區(qū)域——靠近你的區(qū)域(筆者用的是法蘭克福,德國)
- Linode計劃——Nanode(每月僅需5美元,就需求而言足夠了)
- 根密碼——你的密碼
然后單擊“創(chuàng)建”。大約幾分鐘后,可以轉(zhuǎn)到“網(wǎng)絡(luò)”,在這里能找到有關(guān)通過SSH訪問服務(wù)器的信息。

下一步應(yīng)連接服務(wù)器,并創(chuàng)建具有sudo特權(quán)的非根用戶。這個操作背后的邏輯相當(dāng)簡單:你不想在服務(wù)器上把所有東西都作為根運行,因為這樣更容易損壞東西。
- adduser usernameusermod -aG sudousername
最終,切換到新用戶。
- su — username
創(chuàng)建應(yīng)用容器
整個系統(tǒng)配置分為兩部分:應(yīng)用程序容器(Flask +Gunicorn)和Web容器(Nginx Web服務(wù)器)。
(1) 步驟0——安裝Docker和Docker Compose
Docker和Docker-compose安裝非常簡單,分別在4行和2行內(nèi)就能完成。
(2) 步驟1——創(chuàng)建FlaskApp和WSGI入口點
在主目錄中創(chuàng)建flask app目錄,并將以下文件放入其中。
- from flask importFlask
- server =Flask(__name__)
- @server.route('/')
- defhello_world():
- return'hello world!'
這是最基礎(chǔ)的Flask應(yīng)用,幾乎沒有任何功能,不用加載任何模型,不添加任何GET /POST請求和內(nèi)容(這些將在下文出現(xiàn))?,F(xiàn)在,我們只有一個主頁上顯示著“ helloworld”的應(yīng)用程序。
這部分極為簡單——只需為Gunicorn創(chuàng)建一個在端口8000上運行的單獨文件。
(3) 步驟2——為Flask創(chuàng)建一個Docker映像
現(xiàn)在,我們需要創(chuàng)建一個將運用這些文件的Dockerfile,并創(chuàng)建一個稍后能運行的映像。
- FROM python:3.6.7
- WORKDIRusr/src/flask_app
- COPYrequirements.txt .
- RUN pip install--no-cache-dir -r requirements.txt
- COPY . .
對于不熟悉Docker的人而言,此腳本的功能如下:
- 導(dǎo)入Python 3.6.7圖像
- 設(shè)置所有文件的工作目錄
- 復(fù)制包含F(xiàn)lask、Gunicorn和運行Flask應(yīng)用程序需要的所有其他文件的需求文件。
然后,通過RUN命令安裝所有必要安裝包,最后將所有文件從flaskdir復(fù)制到容器內(nèi)的usrscrflask 應(yīng)用程序中?,F(xiàn)在只需將此文件放在相同的flask_app目錄中,并添加requirements.txt即可。
- flask
- gunicorn
請記住,如果你對目錄和內(nèi)容感到困惑,只需在文章結(jié)尾處檢查完整的項目結(jié)構(gòu),或訪問GitHub存儲庫。
(4) 步驟3——創(chuàng)建Nginx文件
為運行Nginx,需要配置一些內(nèi)容。但在邁出下一步之前,請在主目錄內(nèi)創(chuàng)建nginx目錄(與flask_app處于同一級別)。之后,我們需要的第一個文件是nginx.conf,該文件幾乎包含所有基本的Nginx信息和變量。
來看一個Nginx基本設(shè)置:
- # Define the user that will own and run theNginx server
- user nginx;
- # Define the number of worker processes;recommended value is the number of
- # cores that are being used by yourserver
- worker_processes 1;
- # Define the location on the file systemof the error log, plus the minimum
- # severity to log messages for
- error_log /var/log/nginx/error.log warn;
- # Define the file that will store theprocess ID of the main NGINX process
- pid /var/run/nginx.pid;
- # events blockdefines the parameters that affect connection processing.
- events {
- # Define themaximum number of simultaneous connections that can be opened by a workerproce$
- worker_connections 1024;
- }
- # http blockdefines the parameters for how NGINX should handle HTTP web traffic
- http {
- # Include thefile defining the list of file types that are supported by NGINX
- include /etc/nginx/mime.types;
- # Define thedefault file type that is returned to the user
- default_type text/html;
- # Define theformat of log messages.
- log_format main '$remote_addr - $remote_user [$time_local] "$request" '
- '$status$body_bytes_sent "$http_referer" '
- '"$http_user_agent" "$http_x_forwarded_for"';
- # Define thelocation of the log of access attempts to NGINX
- access_log /var/log/nginx/access.log main;
- # Define theparameters to optimize the delivery of static content
- sendfile on;
- tcp_nopush on;
- tcp_nodelay on;
- # Define thetimeout value for keep-alive connections with the client
- keepalive_timeout 65;
- # Define the usageof the gzip compression algorithm to reduce the amount of data to transmit
- #gzip on;
- # Includeadditional parameters for virtual host(s)/server(s)
- include/etc/nginx/conf.d/*.conf;
- }
第二個文件——特定應(yīng)用程序的配置。想要做到這一點有兩種比較普遍的方法:
- 在/ etc /nginx / sites-available / your_project中創(chuàng)建一個配置文件,然后創(chuàng)建到/ etc /nginx / sites-enabled / your_project的符號鏈接。
- 只需在主要Nginx目錄中創(chuàng)建一個project.conf。下述為第二種方法。
- server {
- listen 80;
- server_name docker_flask_gunicorn_nginx;
- location / {
- proxy_pass http://flask_app:8000;
- # Do not change this
- proxy_set_header Host $host;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For$proxy_add_x_forwarded_for;
- }
- location /static {
- rewrite ^/static(.*) /$1 break;
- root /static;
- }
- }
有幾點需要注意:首先,請看一下listen80,該命令指定應(yīng)用將在哪個端口運行。作為默認(rèn)端口,我們選擇80。其次,服務(wù)器名稱。你可以指定從Linode獲得的IP地址,也可以只使用docker映像名稱。
最后,proxy pass命令,該命令應(yīng)將Nginx配置指向flask項目。由于flask容器的名稱為flask_app(將在后面介紹),因此只須使用容器的名稱以及Flask項目中指定的端口。
(5) 步驟4——為Nginx創(chuàng)建一個Docker映像
該特定Docker映像非常簡單。與Flask一樣,它僅包含5行,且僅執(zhí)行2件事:
- FROM nginx:1.15.8
- RUN rm/etc/nginx/nginx.conf
- COPY nginx.conf/etc/nginx/
- RUN rm/etc/nginx/conf.d/default.conf
- COPY project.conf/etc/nginx/conf.d/
導(dǎo)入nginx圖片,復(fù)制文件,并將其替換為默認(rèn)文件。
(6) 步驟5——將Dockerfiles與docker-compose結(jié)合
現(xiàn)在已有2個Dockerfile:
- 一個用于Flask + Gunicorn
- 另一個用于Nginx。
現(xiàn)在是時候讓它們交互,并運行整個系統(tǒng)了。為完成這一點,要用到docker-compose。我們只需要在主目錄中創(chuàng)建docker-compose.yml文件。
- version: '3'
- services:
- flask_app:
- container_name: flask_app
- restart: always
- build: ./flask_app
- ports:
- - "8000:8000"
- command: gunicorn -w 1 -b 0.0.0.0:8000 wsgi:server
- nginx:
- container_name: nginx
- restart: always
- build: ./nginx
- ports:
- - "80:80"
- depends_on:
- - flask_app
為了解其工作原理,我們來處理幾個重要問題:
- 首先,docker-compose分為2部分(2個服務(wù)):flask_app和nginx。正如從以下幾行中看到的那樣——flask_app容器執(zhí)行運行Flask應(yīng)用程序的Gunicorn,并用1個工作程序?qū)⑵滢D(zhuǎn)換為8000端口。
- 第二個容器僅在80端口上運行Nginx。另外,請注意depends_on部分,該部分命令docker-compose首先啟動flask_app容器,然后才是——-nginx,因為這兩個容器相互依賴。
此外,還應(yīng)添加一件事,以便更輕松運行此Docker設(shè)置。那就是run_docker.sh文件。
- echo killing old dockerprocesses
- docker-compose rm -fs
- echo building dockercontainers
- docker-compose up --build -d
該設(shè)置只是簡單地運行docker-compose,但首先要確保此時舊的docker進(jìn)程沒有處于活動狀態(tài)。
(7) 步驟6——將所有設(shè)置放在一起
當(dāng)前的項目結(jié)構(gòu)應(yīng)如下所示:
- .
- ├── flask_app
- │ ├── app.py
- │ ├── wsgi.py
- │ └── Dockerfile
- ├── nginx
- │ ├── nginx.conf
- │ ├── project.conf
- │ └── Dockerfile
- ├── docker-compose.yml
- └── run_docker.sh
在確保一切就緒之后,就能運行docker了。
- bash run_docker.sh
并通過導(dǎo)航到從Linode獲得的IP,然后在瀏覽器中查看主頁面:

(8) 步驟7——沒有得出任何成果,我該怎么辦?
先在Linode上租用一個服務(wù)器,安裝docker和docker-compose,接著克隆git存儲庫,然后運行bash run_docker.sh。
確保成功運行后,開始更改內(nèi)容。嘗試使用Flask,Dockerfiles或docker-compose,直到出現(xiàn)故障。之后,嘗試找出問題所在,并著手解決。
(9) 步驟8-下一步呢?
接下來要添加的是支持FlaskApp中的POST請求。這樣,可以將請求發(fā)送到模型并獲得響應(yīng)。
需滿足兩點:一個可以處理請求的模型,以及POST請求能夠自我支持。
- from flask importFlask, request,jsonify
- server =Flask(__name__)
- defrun_request():
- index =int(request.json['index'])
- list = ['red', 'green', 'blue', 'yellow', 'black']
- return list[index]
- @server.route('/', methods=['GET', 'POST'])
- defhello_world():
- if request.method =='GET':
- return'The model is up and running. Send a POST request'
- else:
- returnrun_request()
為方便起見,在這種情況下,該模型僅返回顏色列表的第i個元素。但實際上,運行哪種模型都無關(guān)緊要,只需在所有方法上創(chuàng)建模型的實例(prettymuch where you have server = Flask(__name__))就可以了。
現(xiàn)在,如果導(dǎo)航到IP地址,將看到一條消息——“模型已啟動,正在運行。發(fā)送POST請求”,因為只需轉(zhuǎn)到IP就可將其視為GET請求。
但讓我們試著使用包含模型索引的json文件發(fā)送POST請求。筆者使用Postman,但你可以視個人喜好而定(即Curl)。

行得通!現(xiàn)在,可以添加其它路徑,以接收GET /POST請求。該想法背后的原因是可以通過加載多個模型,根據(jù)URL將請求發(fā)送到特定模型。
(10) 步驟9——進(jìn)一步發(fā)展如何?
實際上,還有一個重要的步驟要做。為快速部署此Docker設(shè)置,將其部署在云空間是個不錯的想法。這種方法的一個主要優(yōu)勢是:AWS將為群集管理基礎(chǔ)架構(gòu)提供保障。
你學(xué)會了嗎?