機器學習模型持續部署(基於Flask, Docker, Jenkins 和 Kubernets )

本文主要介紹部署機器學習模型的一種自動化方式,如題所示,通過 FlaskDockerJenkins 和 Kubernets 實現。基本原理就是通過 Flask 提供 RESTful API 接收客戶端的 predict 請求,然後將這個服務打包成一個 docker image 便於部署和遷移,當代碼或模型更新時通過 Jenkins 觸發自動構建新的 docker image,而通過 kubernets 管理容器則讓整個服務具備伸縮性和可靠性。本文主要參考了 Deploy a machine learning model in 10 minutes with Flask, Docker, and Jenkins,並在其基礎上進行了完善和拓展,如通過一個簡單的 shell script 實現 jenkins 的觸發功能,並添加了 kubernets 部分的介紹等。本文的對應的所有代碼可從 DeployMachineLearningModel 獲取。

下文基本可以依樣畫葫蘆走一遍,爲了避免不必要的麻煩,儘量不要在 windows 下配置,雖然上述這些工具也提供了 windows 的版本,但是使用起來總是出現各種問題;也不要在win10 的 wsl 中配置,因爲 docker 涉及到了 linux 底層的 cgroup,在 wsl 中並不能直接安裝 docker。本文的實驗時最開始爲了方便在上面提到的兩個環境中進行了實驗,結果是折騰了好久,最後通過在 virtual box 中的 ubuntu 16.04 進行以下的實驗。

下圖摘自文章前面提到的 Deploy a machine learning model in 10 minutes with Flask, Docker, and Jenkins,從中可以看到清晰看到整個部署和訪問的流程。

 

Flask 提供 RESTful api

Flask 的作用主要是提供 RESTful api 供客戶端進行 predict,像 Google、Microsoft、Face++ 這些公司提供的 AI 服務(即人臉識別,表情識別等),基本都是通過 RESTful api 提供的,其基本原理是客戶端將通過 POST 請求將需要預測的樣本發送到服務器,然後服務器提取樣本進行預測並返回結果;且通常還需要附帶 id 判別身份,從而進行相應的扣費,這裏爲了簡單起見不會去考慮這些問題。

通過 Flask 能夠非常簡單地在搭建一個 HTTP Server 並在指定端口監聽,如果接收到 POST 請求便調用模型進行預測並返回,因此首先需要訓練模型並將訓練好的模型 load 進內存,爲了簡單起見,這裏的任務是 sklearn 內置的 iris 分類。

訓練並保存模型

訓練並持久化模型的代碼如下所示,對應 train_model.py 文件

# coding: utf-8
import pickle
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn import tree

# simple demo for traing and saving model
iris=datasets.load_iris()
x=iris.data
y=iris.target

#labels for iris dataset
labels ={
  0: "setosa",
  1: "versicolor",
  2: "virginica"
}

x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=.25)
classifier=tree.DecisionTreeClassifier()
classifier.fit(x_train,y_train)
predictions=classifier.predict(x_test)

#export the model
model_name = 'model.pkl'
print("finished training and dump the model as {0}".format(model_name))
pickle.dump(classifier, open(model_name,'wb'))

​

加載模型並提供調用 api

通過 Flask 能夠快速啓動一個 http server 並在不同的訪問路徑設置不同的處理函數,詳細語法可參考官網教程

本文的例子很簡單,如下代碼所示(對應源文件 server.py),首先把模型 load 進內存,然後設置了訪問路徑爲 /api 時調用模型進行 predict,爲了簡單起見這裏沒做輸入數據的檢查和異常處理;最後 app.run 啓動了一個 server 並默認監聽在 5000 端口。

​# coding: utf-8
import pickle

from flask import Flask, request, jsonify

app = Flask(__name__)

# Load the model
model = pickle.load(open('model.pkl', 'rb'))
labels = {
  0: "versicolor",   
  1: "setosa",
  2: "virginica"
}

@app.route('/api', methods=['POST'])
def predict():
    # Get the data from the POST request.
    data = request.get_json(force = True)
    predict = model.predict(data['feature'])
    return jsonify(predict[0].tolist())

if __name__ == '__main__':
    app.run(debug = True, host = '0.0.0.0')

​

客戶端要進行預測時可通過如下代碼(見源文件 client.py), 這裏的 192.168.31.78 是我的實驗環境裏面啓動 httpserver 的機器ip(client.py 裏面使用的是 8000 端口,因爲利用了 docker 進行了端口映射,後文會對這一點進行講解)利用以上兩個文件,通過命令 python train_model.py && python server.py 便可訓練出一個模型並通過 http server 提供訪問 api。

​# coding: utf-8
import requests
# Change the value of experience that you want to test
url = 'http://192.168.31.78:5000/api'
feature = [[5.8, 4.0, 1.2, 0.2]]
labels ={
  0: "setosa",
  1: "versicolor",
  2: "virginica"
}

r = requests.post(url,json={'feature': feature})
print(labels[r.json()])

Docker 打包和運行程序在同一局域網的機器運行上面的代碼便能輸出 setosa 這個預測結果

Docker 的安裝參考 Get Docker CE for Ubuntu, 這裏不再贅述

打包

利用 Docker 可以將上述部署的環境打包成一個 image,便於部署、遷移和彈性擴展(配合 Kubernets 使用),因此下文主要描述如何通過 Dockerfile 構建 image,關於 Dockerfile 的詳細語法可參考 文檔,這裏只列出本文用到的一些語法。

類似 shell 腳本,Dockerfile 裏面是一系列的指令,作用是讓 Docker 通過 Dockerfile 和 docker build 命令自動構建出目標 image。

在執行 docker build 命令時通過 -t 指定生成的 image 的 tag,能夠保存生成的 image,如 docker build -t shykes/myapp .,最後的 . 表示 Dockerfile 的目錄,即這條命令是在 Dockerfile 所在目錄下執行

Dockerfile 的基本原理是首先通過 FROM 命令獲取一個基本的 image,然後在這個 image 基礎上通過各種命令配置好我們運行程序需要的環境,接着把我們的源文件複製到 image 裏,進行構建和運行。

Dockerfile 中值得注意事項如下,爲了保持原意這裏不進行翻譯

  • each instruction is run independently, so RUN cd /tmp will not have any effect on the next instructions
  • basic syntax is INSTRUCTION arguments, the instruction is not case-sensitive. However, convention is for them to be UPPERCASE to distinguish them from arguments more easily.
  • A Dockerfile must start with a FROM instruction. The FROM instruction specifies the Base Image from which you are building
  • FROM can appear multiple times within a single Dockerfile to create multiple images or use one build stage as a dependency for another
  • Docker treats lines that begin with # as a comment
  • RUN <command> (the command is run in a shell, which by default is /bin/sh -c on Linux or cmd /S /C on Windows
  • There can only be one CMD instruction in a Dockerfile. If you list more than one CMD then only the last CMD will take effect.
  • RUN v.s CMDRUN actually runs a command and commits the result; CMD does not execute anything at build time, but specifies the intended command for the image.
  • The WORKDIR instruction sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile. If the WORKDIR doesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction
  • COPY <src>... <dest>The COPY instruction copies new files or directories from <src> and adds them to the filesystem of the container at the path <dest>;The <dest> is an absolute path, or a path relative to WORKDIR, If <dest> doesn’t exist, it is created along with all missing directories in its path.
  • ADD <src> <dest>; The ADD instruction copies new files, directories or remote file URLs from <src> and adds them to the filesystem of the image at the path <dest>
  • COPY v.s ADDCOPY only lets you copy in a local file or directory from your host (the machine building the Docker image) into the Docker image itself. ADD lets you do that too, but it also supports 2 other sources. First, with ADD you can use a remote URL instead of a local file / directory. Secondly, you can extract a tar file** from the source directly into the destination.
  • Environment variables (declared with the ENV statement) can also be used in certain instructions as variables to be interpreted by the Dockerfile; Environment variables are notated in the Dockerfile either with $variable_name or ${variable_name}

因此,構建上述的環境的 Dockerfile 如下所示, 參考鏈接中的 Dockerfile 中有兩個 FROM 語句,分別表示 ubuntu 環境和 python 環境,且需要安裝 pip 等工具,這裏直接通過 nitincypher/docker-ubuntu-python-pip 提供這些功能

​# train and run the model with RESTful api
FROM nitincypher/docker-ubuntu-python-pip

COPY ./requirements.txt /app/requirements.txt

WORKDIR /app

RUN pip install -r requirements.txt

COPY . /app

CMD python /app/train_model.py && python /app/server.py

​

1)構建前系統的 docker images 情況,由於之前已經運行過這條命令,因此依賴的 nitincypher/docker-ubuntu-python-pip 也已經 pull 到本地了。如果是第一次運行,則下載 nitincypher/docker-ubuntu-python-pip 需要一定的時間實驗的項目路徑爲 /opt/src/DeployMachineLearningModel, 則構建 image 的命令爲 docker build -t deploy_ml_model ., 其過程如下所示,可以看到

2) Dockerfile 中每條命令都是運行時的一個 step,在構建時不會運行 CMD 的命令,而是通過 docker run 時才執行

構建完成後可以看到系統中的多了 deploy_ml_model 這個 image

 

運行

接着需要運行這個 image,運行的 container 內部 Flask 在監聽 5000 端口,因此需要通過端口映射爲外部機器可見的端口,通過命令 docker run -p 8000:5000 deploy_ml_model 可通過運行 docker 的機器的 8000 端口訪問 container 內部提供的 api,如下所示

將上面的客戶端的代碼的端口改成 8000 便是 client.py 源文件了,運行 client.py 結果如下所示,

此時的 server 接收到一個 POST 請求,輸出的日誌如下

如果需要停止運行的 container,通過 docker stop 並指定 container 的 id 即可, container id 並不需要全輸入,只需要輸入能系統能區分不同 container 的程度即可。該過程如下所示

 

Jenkins或自定義腳本觸發自動構建

上面的構建流程中,只要每次代碼或模型有更新便需要重新手動執行 docker build 和 docker run, 而通過 jenkins 或自定義的腳本便能讓這個流程自動化,這個過程需要結合 Github 實現,即當代碼倉庫有更新時,便自動構建新的 image。

其基本原理是 Github 在 repository 發生變化時,會向指定的 url 發送一個 POST 請求告知 repository 有更新,只要我們監聽這個 url 並在收到這個 POST 請求時進行更新即可,這個機制在 Github 中被稱爲 WebHooks。Github 提供的 WebHooks 中涵蓋了多種更新情況,不同的更新對應於不同的 event,可以在 Github 中自定義需要觸發的事件,默認觸發的是 PUSH 事件(如commit、PR 等)。

Jenkins 自動構建

Jenkins 在 Ubuntu 下的安裝參考 Installing Jenkins,這裏不再贅述

Jenkins 是一個功能齊全的自動化構建工具,類似 Docker 通過 Dockerfile 定義 image 的構建過程,jenkins 也能通過 Jenkinsfile 定義工程的構建過程。

但是本文只用到其接收到 Github 發送的 POST 請求並觸發其重新構建的功能,其配置流程如下,首先新建一個自由風格的項目,並配置其爲 Github 項目,管理源碼的方式爲 git,如下所示

然後配置觸發方式和構建的命令如下圖所示

配置並保存後便可直接 “立即構建” 進行項目的構建,jenkins 會自動下載倉庫並進行構建,通過控制檯輸出可以看到構建過程,該過程如下所示

點擊控制檯輸出後顯示的日誌

上面提到了觸發 jenkins 自動構建的原理,即當代碼倉庫有更新時,github 會發送 POST 請求給 jenkins,然後 jenkins 會進行自動構建,這種情況下 jenkins 首先需要有一個能夠接受 github 的 POST 請求的 url,但是 jenkins 當前是部署在局域網內部的,這時便需要藉助 ngrok 這個工具來生成一個 github 能夠訪問的 url 了

ngrok 的作用就是爲局域網內部的機器生成一個 public url,從而使得內部的服務能夠被其他機器訪問,其基本原理就是 ngrok 在這個訪問過程中提供了中轉。ngrok 的下載和安裝都很簡單,可參考上面上面的 ngrok 的官網,這裏不再贅述。

由於 jenkins 在本地的端口是8080,因此通過 ngrok 爲 jenkins 生成 public url 如下所示,可以看到生成了 http 和 https 兩個類型的地址;最下面顯示的是最近的請求情況,可以看到 github 發送了3個更新的 POST 請求

得到 public url 後,需要將其配置到 github 項目的 webhook 中,打開 github 項目的地址,點擊 setting 進行設置,設置如下所示,Payload URL 爲通過 ngrok 得到的 public url 加上 /github-webhook/ 路徑,注意不能省略最後的斜杆,否則會出現 403 No valid crumb was included in the request 的錯誤

點擊 update webhook 後(第一次是 save)後,github 便會向 payload url 發送一個 POST 請求,就是在上上一張圖最下方顯示的 POST 請求。

這樣當 github 的倉庫有更新時就會自動觸發 jenkins 進行自動構建,但是由於前一個構建任務會一直運行 http server 接受,因此會出現如下圖的 already in progress 的問題,新的 build 會被掛起,直到前一個build 被終止(通過 docker stop) 關掉服務

針對這個問題,這個 issue Pushing new commit to existing PR does not stop previous build 給出了通過配置 jenkins 的解決方法,但是我在我配置的環境中找不到這個設置選項,試了幾遍後卻依然找不到這個配置選項,所以就有了下面的自定義腳本進行自動構建。

而針對這個問題,令一種解決方法是在構建命令時只寫 docker build, 每次都只是生成最新的 image;而 docker run 留給人工去啓動,但是這樣可能就顯得不那麼自動化了。

自定義腳本進行自動構建

細想一下上面的觸發構建過程,本地需要做的是 jenkins 接受 github 發過來的 POST 請求然後啓動 docker build 和 docker run, 然後由於已經有 container 在跑了,因此無法決定啓動新的構建過程。

那其實我們也可以自己建立一個 http server 接受 github 的 POST 請求,在接受到請求後通過 docker stop 停掉當前正在運行的 container 並開始新的構建過程,而藉助前文描述的 Flask,我們可以很容易建立一個接受 POST 請求的 http server,代碼如下所示(見源文件 hook_server.py)

​# -*- coding: utf-8 -*-
from flask import Flask, jsonify
import subprocess

app = Flask(__name__)

@app.route('/github_webhook', methods=['POST'])
def rebuild():
    print('new commits to github repository')
    ## subprocess.run can just deal with the first change
    ## since it stuck in it, use popen instead
    # subprocess.run(['sh', 'build_and_run.sh'])
    subprocess.Popen(['sh', 'build_and_run.sh'])
    return jsonify('got it')

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0', port=8081)

​

上面執行的腳本 build_and_run.sh 的具體內容如下, 首先通過 git pull 更新代碼,這裏項目的代碼的本地路徑爲 “/opt/src/DeployMachineLearningModel/“,然後判斷當前是否有正在運行的 container,如果有則先 stop,然後再執行構建過程,在構建和運行之間通過 docker image rm(等價於 docker rmi)刪除 docker 的 <none>:<none> images, 這些 images 也被稱爲 dangling images, 是被覆蓋的原來的 image,會佔用額外的磁盤空間,詳細信息可參考 What are Docker <none>:<none> images?。爲了保持一致性,這裏的路徑也選擇爲 /github_webhook, 爲了簡單起見,處理的函數只是接受請求,沒有對 POST 請求做進一步的解析,接收到命令後通過 subprocess 新創建一個進程執行重新構建並運行 docker image 的腳本 build_and_run.sh, 注意這裏要使用 subprocess.Popen 而不是 subprocess.run, 因爲 subprocess.run 要等命令執行返回才能繼續往下執行,而我們啓動的服務也是一個 http server。如果使用 subprocess.run 只能在第一次的更新時觸發自動構建,之後會一直保持在新創建的進程中而無法處理 github 發過來的新的請求,因此要使用 subprocess.Popen 避免這個問題,兩者更詳細的區別可參考 What is the difference between subprocess.popen and subprocess.run

​#!/bin/bash
# update code
project_dir="/opt/src/DeployMachineLearningModel/"
cd $project_dir && git pull

# build and run with new code
running_container=$(docker ps | grep deploy_ml_model | awk  -F ' ' '{print $1}')
if [ -n "$running_container" ]; then
    echo "container id not empty, stop it firstly"
    docker stop $running_container
else
    echo "empty container id"
fi
docker build -t deploy_ml_model .
docker image rm -f $(docker images -f "dangling=true" -q)
docker run -p 8000:5000 deploy_ml_model

​

同樣需要通過 ngrok 映射本地的 http server 到一個 public url 並將 public url 添加到 github 項目的 webhook 中,如下圖所示

通過 python hook_server.py 運行腳本監聽指定的 repository 是否有新的 commit,如果有,則觸發運行 build_and_run.sh 腳本,其過程如下所示

 

Kubernets

通過上面的三個步驟,已經基本能夠形成一個自動化的部署方案了,個人的自娛自樂基本也夠了,但是上面還只是單點的服務,缺乏高可用性和伸縮性。

針對這一點,Docker 會經常與 Kubernets 配合使用,Kubernets 是專門爲容器化應用的自動部署、拓展和管理的一個分佈式系統。Kubernets 的前身是 Google 內部的系統 Brog,而 google 也參與了 Kubernets 的設計,Kubernets + 容器的部署方式應該會是未來的發展趨勢,這裏主要根據 Learn Kubernetes Basics 總結 Kubernets 的一些經典的使用方式。包括應用的部署,拓展,滾動更新等。

由於實驗環境需要多臺機器,雖然 Minikube 能夠在單機上實現 Kubernets sigle-node 集羣,但是根據 Install Minikube ,virtual box 中的虛擬機似乎不支持 VT-x or AMD-v virtualization,因此,這裏直接使用 Learn Kubernetes Basics 提供的 shell 環境。

基本架構

Kubernets cluster 是經典主從架構(master-nodes),主(master)負責管理集羣,如調度、拓展、更新等,從(nodes)則負責計算或提供服務,每個 node 通過 Kubelet 這個 agent 與 master 通信,除此之外,node 中還要有容器運行環境如 Docker 或 rkt。基本架構如下圖所示

Kubernets 提供的命令行程序 Kubectl(注意與node的 Kubelet 區分)能夠獲取與集羣通信,獲取集羣信息,部署應用等,如下圖是通過 kubectl 獲取通過 Minikube 啓動的 Kubernets 集羣的一些信息

  • kubectl cluster-info:提供 web 界面查看應用的具體信息
  • kubectl nodes:顯示所有的 nodes 的信息

 

部署(deployment)

部署應用到 Kubernets 集羣時,需要構建好要運行的 docker image 的路徑,部署使用的也是命令行程序 kubectl,命令是 kubectl run NAME --image=image_url, NAME 是指定的應用的名稱,–image 則是指定的 image 的 url,通過 kubectl get deployments 可以看到當前部署的應用,如下圖所示

在 Kubernets cluser 中啓動了應用後,外部網絡是無法直接訪問這個應用的,這點跟 Docker 有點相似,需要做映射,但是爲了調試的便利性,kubectl 提供了 kubectl proxy 這個命令,相當於把Cluster內部的地址映射到本地機器,啓動之後可通過本機訪問 Kubernets cluser 內部 的應用。如下圖所示是訪問上面啓動的應用

Pods

上面通過 kubectl 進行部署後,Kubernets 會在 node 中創建了 Pod 來容納 container,一個 node 中可能有多個 pod,Kubernetes 的 master 會根據 node 的資源情況在不同 node 中分配 pod;pod 是 container 和 其所包含的資源的機器,其定義如下,

A Pod is a Kubernetes abstraction that represents a group of one or more application containers (such as Docker or rkt), and some shared resources for those containers. Those resources include:

  • Shared storage, as Volumes
  • Networking, as a unique cluster IP address
  • Information about how to run each container, such as the container image version or specific ports to use

Pod 相當於應用的“邏輯主機”,而 a group of containers 值得是一個應用中有若干個聯繫緊密的 container 協作,這些 containers 具有相同的IP。

除了 kubectl run, kubectl 常用的命令一下這些

  • kubectl get:列出當前系統的資源(pods、nodes等),後面跟着
  • kubectl describe:列出資源的詳細信息

如下是通過這兩條命令獲取前面部署的應用的 pod 信息

下面的命令則是查看 pod 的日誌信息在 pod 中的 container 執行命令,通過命令 export POD_NAME=$(kubectl get pods -o go-template --template '{ {range .items} }{ {.metadata.name}}{ {"\n"}}{ {end}}') 能夠獲取當前的 pod name

  • kubectl logs $POD_NAME:打印 pod 中的 container 的日誌信息
  • kubectl exec $POD_NAME: 在 pod 中的 container 執行命令

下面首先通過命令獲取了 pod 的名稱,然後通過 pod 的名稱查看其日誌並執行命令,執行效果如下所示

Service

Service 可以說是比 Pod 更高一級的概念,假設部署某個應用時指定其 replicas 的數量是 3,那麼就會有 3 個相互獨立的 pods,每個 pod 都有自己的 ip,,而 service 就是這些 pods 的集合。Service 管理着這些 pod 的失敗重啓等,從而向上提供 Pod 的抽象;service 的概念如下圖所示

關於 service 的定義如下

A Service in Kubernetes is an abstraction which defines a logical set of Pods and a policy by which to access them

除了 pods,service 中還有一項是 policy,指的是讓 cluster 內部的 pod 供外界進行訪問的方式,service 可設置的訪問方式有下面四種

  1. ClusterIP (default) - Exposes the Service on an internal IP in the cluster. This type makes the Service only reachable from within the cluster.
  2. NodePort - Exposes the Service on the same port of each selected Node in the cluster using NAT. Makes a Service accessible from outside the cluster using <NodeIP>:<NodePort>. Superset of ClusterIP.
  3. LoadBalancer - Creates an external load balancer in the current cloud (if supported) and assigns a fixed, external IP to the Service. Superset of NodePort.
  4. ExternalName - Exposes the Service using an arbitrary name (specified by externalName in the spec) by returning a CNAME record with the name. No proxy is used. This type requires v1.7 or higher of kube-dns.

通過 kubectl expose 能夠讓集羣內部的 service 供外界訪問,如下指定的訪問方式是 NodePort, kubernets 默認會啓動一個 keubernets 服務,就是第一條 kubectl get services 所顯示的內容, 而經過 kubectl expose 的服務也會出現在其中,內部端口 8080 被映射爲了外部的 32066 端口,通過外部ip(命令中的 minikube ip) 和 32066 端口便能訪問內部的服務。

Service 通過 Labels 和 Selectors 來區分同一個 service 中的不同 pod,label 就是一系列的 key-value 對,label 可結合具體的應用場景進行使用,如區分開發、測試和生產環境的 pod;區分同一個 pod 的不同版本等。

部署時每個 pod 會被自動分配一個 label;通過 kubectl describe deployment 查看其對應的 label,也可以在 kubectl get 查看 pod 或 services 的信息時通過 -l 參數指定具體的 pod 或 service,如下圖所示

通過 kubectl label 可更改 pod 的 label,如下圖所示

可以根據 label 刪除 service,此時雖然外部無法訪問 pod,但是集羣內部的 pod 仍然在運行,如下圖所示

伸縮性(scaling)

伸縮性就是改變運行同一個 image 的 pods 的數量,如下圖所示

可以通過 kubectl scale 命令指定 replica 的數量,也可以自動伸縮,如下圖所示是將原來只有一個 pod 的 deployment 拓展到 4 個 pod, 從 kubectl get deployments 可以看到當前 deployment 可用的 pod 的數量

而有了多個 pod, service 就要決定如何分配訪問這些 pods 的流量,上面提到的 service 設置的訪問方式 LoadBalancer 就是在這裏使用(需要注意的是 NodePort 和 LoadBalancer 是可以共存),通過下面訪問多次的結果,可以看到每次訪問的 pod 都不一樣,從而實現了負載均衡

滾動更新(rolling updates)

有了多個 pods,在更新 images 時便可以進行 rolling update,即不是一次性地 stop 所有 pods 然後同時進行更新,而是先停掉部分的 pods,然後進行更新,並根據這個方法更新所有的 pods。如下圖所示

這樣的好處是在更新時不會讓服務停止,如下圖所示是更新前 pod 的一些信息,可以看到此時 image 的版本均爲 v1

下面通過 kubectl set 更新上圖所示的 deployment,使用了 v2 版本的 image,在 kubectl set 後,可以看到原來的 pod 處於 terminating 的狀態,且多了四個新的 pod(可通過 AGE 區分),隨着 update 完成,只有新的 pods 在運行,image 版本均變爲了 v2,通過 kubectl rollout status 可以查看更新的情況。

除此之外, Kubernets中的每次更新都有版本記錄,可進行回滾,如下圖更新了一個不存在的 image,從 kubectl get pods 可以看到新的 pod 的狀態是 ErrImagePull,通過 kubectl rollout undo 即可進行版本的回滾,最後所有 pods 的狀態均恢復正常,image 版本均爲 v2,如果再進行一次 kubectl rollout undo,那麼 image 版本就變爲 v1 了。

總結

本文主要介紹了部署機器學習模型的一種方式,通過 Flask,Docker,Jenkins 和 Kubernets 共同構建。Flask 負責加載模型並提供 RESTful api,Docker 負責把程序及其依賴的環境打包成鏡像,Jenkins 則可以在代碼倉庫有更新時觸發自動構建,生成最新的 image,本文也通過自定義腳本的方式來實現了這一簡單功能,但是 Jenkins 是一個功能非常豐富的工具,在項目更大更復雜時,採用 Jenkins 會更加方便。

通過 Flask,Docker 和 Jenkins 可以實現基本的自動化部署,但是此時的服務是單點的,不具備容災性和伸縮性,通過 Kubernets 則可以較好地解決這個問題,只需要提供打包好的鏡像,Kubernets 便能夠提供伸縮性服務,滾動更新,回滾等操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章