阅读我们的文章

.

本文是系列文章的第三部分,在这一部分中,我们将介绍使用 Mlflow 记录模型、在 Kubernetes 引擎上提供模型并最终根据应用需求对其进行扩展的过程。虽然本文可独立用于测试任何 API 响应,但我们建议您阅读我们之前的两篇文章(第一部分和第二部分),了解如何使用 Mlflow 部署跟踪实例并将模型作为 API 提供。在下文中,我们将关注可扩展性问题,并通过一些实验来了解 k8s 集群的行为,并就如何处理高负载提出建议。.

第 3 部分 - 如何处理高负载并使我们的应用程序具有可扩展性?

导言

在典型的应用场景中,机器学习模型被部署在应用程序或产品的背后,多个用户可以同时与之交互以生成预测。因此,分析我们的基础架构能力并对其进行相应的调整至关重要。就 Kubernetes 而言,这一点变得尤为有趣,因为它可能会影响到是否使用自动扩展、考虑的最大节点数......

在这种情况下,收费测试可以模拟多个同时或递增的请求数量,并监控基础设施的行为(响应时间、CPU 使用率、内存使用率......),以便正确调配资源,避免出现瓶颈。这些测试将使用名为 Locust 的工具进行。.

环境准备

本系列第一篇文章中详细介绍了本次 "实践 "的要求,但作为总结,假设我们的模型已经作为 API 部署在 Kubernetes 集群 (mlflow-k8s) 上,以下是本部分具体需要的主要元素。.

在这部分实践中,我们需要

  • 部署蝗虫的 GKE 集群(此处我们将其命名为 加载测试)
  • 已配置的本地工作站(gcloud、kubectl)
  • 导出以下环境变量

    export GCR_REPO=eu.gcr.io/mlflow-on-k8s/repo
  • 存储库 实践代码所在之处

部署

1.构建 Locust docker 镜像并将 Locust 镜像推送到 GCR

cd mlflow-serving-exampledocker build --tag $/locust-tasks:v1
文件 dockerfile_locust .docker push $/locust-tasks:v1

2.准备测试任务

任务(Tasks)是Locust在负载测试中执行的python函数。 locust-tasks/tasks.py 我们只需向 API 发送 POST 请求,输入 data 行即可获得预测结果。.

Image

在此代码片段中 :

  • on_start: 是 仅在启动线程下载 dataset 时执行一次。.

  • post_metrics这是我们测试任务的核心,这里我们只有一个向 /invocation 端点发送一条记录的函数。.

我们可以创建尽可能多的功能来执行我们想要的测试。例如,我们可以添加一个发送 data 批次的功能。此外,我们还可以使用 @task() 装饰器为不同的任务赋予优先级。.

3.部署到 Kubernetes

现在是部署镜像并在专用集群上运行 Locust 的时候了。首先,确保上下文设置在 加载测试 运行

kubectl config get-contexts
kubectl config use-context NAME

Image

接下来,我们可以更新部署文件 deployments/locust_load_test.yaml 通过指定 GCR 上的图像路径 指向 目标主机 到 API 地址。.

种类复制控制器
apiVersion: v1
元 data:
名称:蝗虫大师
标签
名称:蝗虫
角色:主人
规格:
复制品:1
选择器:
名称:蝗虫
角色:主人
模板:
元 data:
标签
名称:蝗虫
角色:主人
规格:
集装箱
- 名称:蝗虫
图像:GCR_REPO/locust-tasks:v1 # 在此处更改
env:
- 名称: LOCUST_MODE
值:主
- 名称: TARGET_HOST
值:‘http://SERVING_IP:SERVING_PORT’ # 在此处更改
端口:
- 名称: loc-master-web
容器端口:8089
协议:TCP
- 名称: loc-master-p1
容器端口:5557
协议:TCP
- 名称: loc-master-p2
容器端口:5558
协议:TCP
-
种类复制控制器
apiVersion: v1
元 data:
名称:蝗虫工人
标签
名称:蝗虫
角色:工人
规格:
复制品:30
选择器:
名称:蝗虫
角色:工人
模板:
元 data:
标签
名称:蝗虫
角色:工人
规格:
集装箱
- 名称:蝗虫
图像:GCR_REPO/locust-tasks:v1 # 在此处更改
env:
- 名称: LOCUST_MODE
值:工人
- 名称: LOCUST_MASTER
值:蝗虫大师
- 名称: TARGET_HOST
值:‘http://SERVING_IP:SERVING_PORT’ # 在此处更改
-
种类:服务
apiVersion: v1
元 data:
名称:蝗虫大师
标签
名称:蝗虫
角色:主人
规格:
端口:
- 港口8089
targetPort: loc-master-web
协议:TCP
名称: loc-master-web
- 港口5557
targetPort: loc-master-p1
协议:TCP
名称: loc-master-p1
- 港口5558
targetPort: loc-master-p2
协议:TCP
名称: loc-master-p2
选择器:
名称:蝗虫
角色:主人
类型负载平衡器
种类复制控制器 apiVersion: v1 metadata: name: locust-master labels: 名称:蝗虫 角色:主人 spec: replicas:1 选择器 名称:蝗虫 角色:主 模板 metadata: labels: 名称:蝗虫 角色:主人 spec: containers: - 名称:蝗虫 image:GCR_REPO/locust-tasks:v1 # 在此更改 env: - name: LOCUST_MODE 值:master - name: TARGET_HOST 值: 'http://SERVING_IP:SERVING_PORT' # 在此处更改 ports: - 名称: loc-master-web containerPort: 8089 协议:TCP - 名称: loc-master-p1 容器端口: 5557 协议: TCPTCP - 名称: loc-master-p2 容器端口: 5558 协议: TCPTCP --- 类型复制控制器 apiVersion: v1 metadata: name: locust-worker labels: 名称:蝗虫 角色:工人 spec: replicas:30 选择器 名称:蝗虫 角色:工作者 template: metadata: labels: 名称:蝗虫 角色:工人 spec: containers: - 名称: locust image:GCR_REPO/locust-tasks:v1 # 在此更改 env: - name: LOCUST_MODE 值: worker - name: LOCUST_MASTER 值: locust-master - name: TARGET_HOST 值: 'http://SERVING_IP:SERVING_PORT' # 在此更改 --- kind: 服务 apiVersion: v1 metadata: name: locust-master labels: name: locust 角色:主人 spec: ports: - port:8089 targetPort: loc-master-web 协议: TCPTCP 名称: loc-master-web - 端口5557 targetPort: loc-master-p1 协议:TCP 名称: loc-master-p1 - port:5558 targetPort: loc-master-p2 协议:TCP 名称: loc-master-p2 选择器 名称: locust 角色:主控 type:负载平衡器

最后,让我们使用以下命令进行部署。.

kubectl create -f deployments/locust_load_test.yaml

Locust 实例现在应该已经启动,新的负载平衡器应该已经创建。我们可以键入 kubectl get services 并使用负载平衡器 IP:8089 访问接口

Image

实验

我们的想法是使用 Locust 来模拟对服务 API 的并行查询,并分析集群的行为和响应时间(绿色为中位数,橙色为第 95 百分位数)。这样做的目的是为了突出 Kubernetes 提供的两个功能,即水平和垂直(自动)扩展。.

1.手动缩放

在第一个实验中,我们试图了解 多荚 为我们的模型提供服务。我们从一个 pod 开始,并尝试增加请求数量。在下图中,我们可以看到不同配置和费用的 4 个阶段。.

ImageImage

总的来说,我们可以看出,始终监控资源指标(CPU、内存......)以识别瓶颈和配置问题非常重要。在我们的案例中,只有一个 pod 并不能让我们从可用的处理能力中获益。因此,在部署应用程序时,考虑到后台运行的系统服务,必须设置合适的 pod 数量,并为每个 pod 设置足够的资源,以最大限度地提高机器的使用率。因此,我们建议不要将节点的 CPU 使用率提高到 80-90% 以上。.

2.水平自动缩放

幸运的是,Kubernetes 有一个 自动水平缩放功能 来自动监控 CPU 使用情况,并在必要时创建新的 pod 来分配负载。只需执行以下命令即可激活该功能。.

kubectl autoscale deployment mlflow-serving --cpu-percent=80 --min=1 --max=12

然后,我们可以使用 kubectl get hpa mlflow-serving, 分析集群响应时间和资源消耗。.
以下实验的目的是观察 Kubernetes 如何自动添加 pod 以优化资源使用并获得更好的响应时间。如下图所示,我们可以将该实验分为三个阶段。.

ImageImage

在第二个实验中,我们注意到水平自动扩展使我们能够通过创建新 pod 和分配更多集群资源来缩短响应时间。但是,当达到集群容量时(第三阶段),新的 pod 仍处于待处理状态,我们的响应时间再次增加。.

3.垂直自动缩放

在这种情况下,我们可以探索 Kubernetes 的另一个功能,即 垂直自动缩放 该功能包括在需要时分配更多节点。可使用以下命令激活该功能,指定 Kubernetes 可分配的最小和最大节点数。.

gcloud 容器群集更新 mlflow-k8s
--启用自动扩展 --min-nodes 3 --max-nodes 5 --node-pool POOL_NAME

最后,在下图总结的最后一个实验中,启用垂直自动缩放功能后,Kubernetes 可以自动添加两个新节点并创建新 pod 来调度负载,确保更短的响应时间。实际上,Kubernetes 检测到需求并创建资源(第 2 阶段)大约需要 1 分钟。此外,在负载较低的情况下(第 3 阶段),Kubernetes 通过杀死 pod 成功释放了两个新节点,并在大约 15 分钟内将集群缩减到最少 3 个节点。.

Image

4.集群规模估算

既然我们已经了解了 Kubernetes 在使用纵向和横向自动缩放功能响应不同收费水平时的表现,那么最终的步骤就是使用不同的资源执行性能测试,同时考虑到我们应用程序的要求及其用户数量的估计。假设要满足 SLA 要求,第 95 百分位数响应时间应低于 1 秒。在这种情况下,我们可以绘制下图,显示不同内核数下的 API 响应时间,从而了解应用程序在不同条件下的性能。.

特别是,对于使用 Mlflow 服务的 ML 模型,我们可以在 12 个内核的 Kubernetes 集群上同时容纳约 120 个用户,并保证响应时间低于 1 秒。.

Image

结论

在这一系列文章中,我们经历了在 Kubernetes 引擎上部署 Mlflow 跟踪实例并将模型作为 API 服务的整个过程,从而充分利用了其轻松扩展和处理高负载的能力。我们还尝试了 Kubernetes 提供的两个有趣功能,即水平和垂直自动缩放,并展示了监控资源以确保高效使用资源的重要性。最后,我们展示了如何测试我们的应用程序,并根据其对不同测试场景的响应做出有关基础设施的决策。.

中号 Blog by Artefact。.

本文最初发表于 Medium.com.
在我们的 Medium Blog 上关注我们!