Deploy model lên k8s sử dụng FastAPI
2022-05-19 17:19:09

Train model

Ở bài blog này mình tiếp tục sử dụng tập dữ liệu Iris huyền thoại để train model Random Forest.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score

# load dataset
iris = load_iris()
X = iris.data # we only take the first two features.
y = iris.target

# train-test split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.33, random_state=42
)

# train with RF
clf = RandomForestClassifier()
clf.fit(X_train, y_train)

# evaluate
X_pred = clf.predict(X_test)
acc = accuracy_score(y_test, X_pred)

Sau khi train xong model, chúng ta sẽ lưu model sử dụng joblib như sau

1
2
import joblib
joblib.dump(clf, "../api/models/model.joblib")

Viết API sử dụng FastAPI

Đầu tiên chúng ta sẽ import những thư viện cần thiết

1
2
3
4
from fastapi import FastAPI
from pydantic import BaseModel
import joblib
import os

Tiếp theo chúng ta sẽ khởi tạo instance FastAPI,

1
app = FastAPI()

và sử dụng pydantic để validate request body [1]. Ở đây chúng ta expect request body sẽ có dạng 1 dict, với 4 attribute có type float.

1
2
3
4
5
6
7
from pydantic import BaseModel

class request_body(BaseModel):
sepal_length : float
sepal_width : float
petal_length : float
petal_width : float

Bây giờ chúng ta sẽ định nghĩa route /predict như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@app.post('/predict')
def predict(data : request_body):
# Making the data in a form suitable for prediction
test_data = [[
data.sepal_length,
data.sepal_width,
data.petal_length,
data.petal_width
]]

# Predicting the class
class_idx = clf.predict(test_data)[0]

# Return the result
return { 'class' : target_names[class_idx]}

Hàm predict sẽ validate request nhận được, predict, và trả về kết quả. target_names ở đây là 1 list chứa tên các class để map từ class_idx target_names = ['setosa', 'versicolor', 'virginica']

Deploy API trên k8s

Có nhiều cách config (viết YAML file) để deploy API trên k8s, mình sẽ nói chi tiết hơn trong các bài blog sau. Ở đây mình sẽ sử dụng một cách khá phổ biến đó là deployment kết hợp với service object.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: apps/v1
kind: Deployment
metadata:
name: iris
labels:
app: iris
namespace: models
spec:
replicas: 2
selector:
matchLabels:
app: iris
template:
metadata:
labels:
app: iris
spec:
containers:
- name: iris
image: docker.io/dangvanquan25/iris-fastapi:0.0.1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: iris
labels:
app: iris
namespace: models
spec:
selector:
app: iris
ports:
- port: 80
protocol: TCP
targetPort: 80
type: ClusterIP

Một cách dễ hiểu: deployment dùng để thông báo với k8s control plane rằng: xin hãy khởi tạo và đảm bảo giúp tôi số lượng pod bằng số lượng replicas. Trong khi đó, service sẽ giống như một lớp abstract để front-end/client giao tiếp với các pod, bởi lẽ IP của các pod có thể thay đổi bất cứ lúc nào. service sẽ sử dụng selector app: iris để xác định các pod của mình. Khi gọi service iris, thì các requests sẽ được phân bố tới các pod theo cơ chế mặc định là round-robin (nôm na là request 1 tới ông pod 1 rồi thì request thứ 2 sẽ tới ông pod thứ 2).

Note: Cho các bác chưa quen với k8s, thì pod đơn giản chỉ là một nhóm các container chung storage và network.

Ơ nhưng mà tôi gọi service iris như thế nào nhể?
Nếu các bác có một service trong cùng cluster, thì chỉ việc gọi thông qua qua DNS name của từng service theo dạng my-svc.my-namespace.svc.cluster.local. Ví dụ service iris ở trên sẽ là iris.models.svc.cluster.local.

Thế nếu service của tôi ở cluster khác thì gọi sao?
Mời các bác xem tiếp phần dưới nhé.

Quản lý traffic trong cluster

Các bác có thể sử dụng một trong những cách expose service sau để external service/client có thể gọi tới:

Nginx Ingress Controller

Đầu tiên, các bác cài đặt Nginx Ingress Controller theo link này.
Tiếp đó chỉ cần viết Ingress object với spec ingressClassName: nginx hoặc annotation kubernetes.io/ingress.class: nginx như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: iris-ingress
spec:
rules:
- host: '*'
http:
paths:
- path: /seldon/models/iris/api/v1.0/
pathType: Prefix
backend:
service:
name: iris
port:
number: 80
ingressClassName: nginx

iris-ingress định nghĩa rule cho request tới cluster như sau: khi gọi tới Nginx Ingress Controller theo route /seldon/models/iris/api/v1.0/, traffic sẽ được điều hướng sang service iris.

NGINX Ingress Controller
Nguồn: https://www.nginx.com/products/nginx-ingress-controller/

Istio Ingress Controller

Để sử dụng cách này, trước tiên cluster phải được cài đặt Istio Service Mesh (nếu chưa có các bác có thể cài theo link này). Bộ cài đặt Kubeflow mặc định có cái này, nên khả năng cao nếu bác nào đang dùng Kubeflow thì đã có sẵn cái này luôn rồi :>. Tiếp theo chỉ cần viết bộ đôi GatewayVirtualService tương tự như sau:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: iris-gateway
namespace: models
spec:
selector:
istio: ingressgateway # use Istio default gateway implementation
servers:
- hosts:
- '*'
port:
name: http
number: 80
protocol: HTTP
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: iris
namespace: models
spec:
gateways:
- models/iris-gateway
hosts:
- '*'
http:
- match:
- uri:
prefix: /seldon/models/iris/api/v1.0/
rewrite:
uri: /
route:
- destination:
host: iris
port:
number: 80

Trong đó: Gateway đóng vai trò như người gác cửa giúp accept/reject external requests. Còn virtual service sẽ điều hướng các request đã được accept đi tới service iris dựa vào route /seldon/models/iris/api/v1.0/.

Note 1: Service mesh thông thường có các tính năng như service discovery, load balancing, encryption và failure recovery. Service mesh cũng hơn API Gateway ở chỗ mỗi lần thêm/bớt 1 microservice thì không cần config lại ở đầu API Gateway nữa.

Note 2: Nếu các bác dùng Seldon Core, thì mỗi lần khởi tạo 1 SeldonDeployment object, thì Deployment, Service, VirtualServiceGateway sẽ tự động được tạo ra luôn mà không cần làm gì thêm.

Note 3: Ở cách expose này, Nginx Ingress Controller ở hình trên sẽ được thay bằng Istio Ingress Gateway

Source code

https://github.com/quan-dang/fastapi-k8s

References

[1] https://pydantic-docs.helpmanual.io/
[2] https://kubernetes.github.io/ingress-nginx/user-guide/basic-usage/
[3] https://www.journaldev.com/54888/gateway-and-virtual-service-in-istio

Prev
2022-05-19 17:19:09
Next