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

下一步應連接服務器,并創(chuàng)建具有sudo特權的非根用戶。這個操作背后的邏輯相當簡單:你不想在服務器上把所有東西都作為根運行,因為這樣更容易損壞東西。
- adduser usernameusermod -aG sudousername
最終,切換到新用戶。
- su — username
創(chuàng)建應用容器
整個系統(tǒng)配置分為兩部分:應用程序容器(Flask +Gunicorn)和Web容器(Nginx Web服務器)。
(1) 步驟0——安裝Docker和Docker Compose
Docker和Docker-compose安裝非常簡單,分別在4行和2行內就能完成。
(2) 步驟1——創(chuàng)建FlaskApp和WSGI入口點
在主目錄中創(chuàng)建flask app目錄,并將以下文件放入其中。
- from flask importFlask
- server =Flask(__name__)
- @server.route('/')
- defhello_world():
- return'hello world!'
這是最基礎的Flask應用,幾乎沒有任何功能,不用加載任何模型,不添加任何GET /POST請求和內容(這些將在下文出現)?,F在,我們只有一個主頁上顯示著“ helloworld”的應用程序。
這部分極為簡單——只需為Gunicorn創(chuàng)建一個在端口8000上運行的單獨文件。
(3) 步驟2——為Flask創(chuàng)建一個Docker映像
現在,我們需要創(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的人而言,此腳本的功能如下:
- 導入Python 3.6.7圖像
- 設置所有文件的工作目錄
- 復制包含Flask、Gunicorn和運行Flask應用程序需要的所有其他文件的需求文件。
然后,通過RUN命令安裝所有必要安裝包,最后將所有文件從flaskdir復制到容器內的usrscrflask 應用程序中?,F在只需將此文件放在相同的flask_app目錄中,并添加requirements.txt即可。
- flask
- gunicorn
請記住,如果你對目錄和內容感到困惑,只需在文章結尾處檢查完整的項目結構,或訪問GitHub存儲庫。
(4) 步驟3——創(chuàng)建Nginx文件
為運行Nginx,需要配置一些內容。但在邁出下一步之前,請在主目錄內創(chuàng)建nginx目錄(與flask_app處于同一級別)。之后,我們需要的第一個文件是nginx.conf,該文件幾乎包含所有基本的Nginx信息和變量。
來看一個Nginx基本設置:
- # 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;
- }
第二個文件——特定應用程序的配置。想要做到這一點有兩種比較普遍的方法:
- 在/ 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,該命令指定應用將在哪個端口運行。作為默認端口,我們選擇80。其次,服務器名稱。你可以指定從Linode獲得的IP地址,也可以只使用docker映像名稱。
最后,proxy pass命令,該命令應將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/
導入nginx圖片,復制文件,并將其替換為默認文件。
(6) 步驟5——將Dockerfiles與docker-compose結合
現在已有2個Dockerfile:
- 一個用于Flask + Gunicorn
- 另一個用于Nginx。
現在是時候讓它們交互,并運行整個系統(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個服務):flask_app和nginx。正如從以下幾行中看到的那樣——flask_app容器執(zhí)行運行Flask應用程序的Gunicorn,并用1個工作程序將其轉換為8000端口。
- 第二個容器僅在80端口上運行Nginx。另外,請注意depends_on部分,該部分命令docker-compose首先啟動flask_app容器,然后才是——-nginx,因為這兩個容器相互依賴。
此外,還應添加一件事,以便更輕松運行此Docker設置。那就是run_docker.sh文件。
- echo killing old dockerprocesses
- docker-compose rm -fs
- echo building dockercontainers
- docker-compose up --build -d
該設置只是簡單地運行docker-compose,但首先要確保此時舊的docker進程沒有處于活動狀態(tài)。
(7) 步驟6——將所有設置放在一起
當前的項目結構應如下所示:
- .
- ├── flask_app
- │ ├── app.py
- │ ├── wsgi.py
- │ └── Dockerfile
- ├── nginx
- │ ├── nginx.conf
- │ ├── project.conf
- │ └── Dockerfile
- ├── docker-compose.yml
- └── run_docker.sh
在確保一切就緒之后,就能運行docker了。
- bash run_docker.sh
并通過導航到從Linode獲得的IP,然后在瀏覽器中查看主頁面:

(8) 步驟7——沒有得出任何成果,我該怎么辦?
先在Linode上租用一個服務器,安裝docker和docker-compose,接著克隆git存儲庫,然后運行bash run_docker.sh。
確保成功運行后,開始更改內容。嘗試使用Flask,Dockerfiles或docker-compose,直到出現故障。之后,嘗試找出問題所在,并著手解決。
(9) 步驟8-下一步呢?
接下來要添加的是支持FlaskApp中的POST請求。這樣,可以將請求發(fā)送到模型并獲得響應。
需滿足兩點:一個可以處理請求的模型,以及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個元素。但實際上,運行哪種模型都無關緊要,只需在所有方法上創(chuàng)建模型的實例(prettymuch where you have server = Flask(__name__))就可以了。
現在,如果導航到IP地址,將看到一條消息——“模型已啟動,正在運行。發(fā)送POST請求”,因為只需轉到IP就可將其視為GET請求。
但讓我們試著使用包含模型索引的json文件發(fā)送POST請求。筆者使用Postman,但你可以視個人喜好而定(即Curl)。

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