基于K8s的演示用单机ML服务部署
这是仅用一台机器(比如一台MacBook)模拟在k8s上部署一个机器学习服务的演示用实例。
项目地址:https://github.com/HarmoniaLeo/Local-K8s-ML-Demo
该实例分为以下几个部分:
- 使用Keras+Tensorflow搭建并训练神经网络,用于完成KMNIST数据集的分类任务
- 基于该神经网络构建在线机器学习服务并在k8s集群上部署(为了单机运行,以在minikube上部署为例),结合Redis实现可拓展、可并行、高可用
- 提供一个便于演示的UI,完成与系统的图形化交互,并显示系统状态、性能指标等
技术栈:
- 训练:Python、Keras、Tensorflow
- 部署:Docker、Kubernetes、Redis
- UI:NodeJS、React
架构图:
文章目录
- 1 神经网络训练
- 1.1 文件组织
- 1.2 环境配置流程
- 1.3 训练
- 2 服务部署
- 2.1 文件组织
- 2.2 部署流程
- 3 服务演示
- 3.1 文件组织
- 3.2 准备工作
- 3.3 演示
- 3.3.1 UI布局
- 3.3.2 演示流程
- 4 拓展
- 4.1 使用Prometheus+Grafana来监视系统状态
- 4.2 使用Keda来实现自动拓展
1 神经网络训练
1.1 文件组织
train/checkpoints
: 所有由train_model.ipynb
生成的checkpoint都保存在checkpoints
文件夹中。Git没有追踪它,请在此处下载train/data
: KMNIST数据集。Git没有追踪它,请在此处下载train/requirements.txt
、train/requirements_cpu.txt
: 进行神经网络训练所需的Python库train/train_model.ipynb
: 记录所有分析,并提供神经网络的训练过程
1.2 环境配置流程
-
从官方网站下载并安装Anaconda:Anaconda下载
-
使用Python 3.11.5创建新的虚拟环境:
conda create -n KMNIST python=3.11.5
-
激活虚拟环境:
conda activate KMNIST
-
使用
pip
安装所需的库。如果你的电脑支持GPU,则:pip install -r train/requirements.txt
如果你的电脑不支持GPU,则:
pip install -r train/requirements_cpu.txt
1.3 训练
打开train/train_model.ipynb
,选择KMNIST虚拟环境作为kernel,然后逐步运行。
2 服务部署
2.1 文件组织
server/start_server.py
: 运行机器学习服务的Python脚本。读取训练好的神经网络checkpoints,然后不断从Redis的image_stream中读取图片进行识别,将结果放回到Redis的result_streamserver/Dockerfile
: 将服务打包为Docker镜像所需的Dockerfileserver/ml-service
: 配置机器学习服务并部署到k8s集群所需要的Helm chart。其中server/ml-service/values.yaml
提供了配置的基本参数server/requirements.txt
: 运行机器学习服务所需的Python库server/checkpoints
: 训练好的神经网络checkpoints。Git没有追踪它,请在此处下载server/redis
: 配置Redis服务并部署到k8s集群所需要的Helm chart
2.2 部署流程
-
将要使用的神经网络checkpoints从
train/checkpoints
复制到server/checkpoints
(默认已经复制了一组)。更改server/start_server.py
中的CHECKPOINT_HASH为要使用的checkpoints的hash -
安装并启动Docker:Docker文档
-
打开控制台,安装并启动minikube:minikube文档
minikube start
-
安装Helm:Helm文档
-
进入server文件夹
cd server
-
使用Helm部署Redis服务
helm install my-redis ./redis
-
构建Docker镜像
docker build -t ml-service:v1.0 .
-
将Docker镜像加载到minikube
minikube image load ml-service:v1.0
-
使用Helm部署ml-service服务
helm install ml-service ./ml-service
3 服务演示
3.1 文件组织
visualization/imgs
:从KMNIST数据集中抽取并存为jpg格式的100张图片,用于进行测试。Git没有追踪它,请在此处下载visualization/axios_server.js
:用于启动让演示UI能够进行控制台交互所需的Axios服务器visualization/requirements.txt
:为运行visualization/client.py
所需的Python库visualization/client.py
:客户端Python脚本。将演示UI指定的图片上传到Redis,并将识别结果从Redis中取出,打印到控制台供演示UI读取- 其他NodeJS相关文件,其中和项目的定制主要有关的文件是
visualization/src/MLServiceDemo.jsx
和visualization/src/MLServiceDemo.css
3.2 准备工作
-
打开控制台,使用Python 3.11.5创建新的虚拟环境:
conda create -n KMNISTUI python=3.11.5
-
激活虚拟环境:
conda activate KMNISTUI
-
使用
pip
安装所需的库:pip install -r visualization/requirements.txt
-
安装NodeJS: NodeJS文档
-
进入visualization文件夹
cd visualization
-
安装NodeJS项目
npm install .
-
开启Axios服务器用于控制台交互
node axios_server.js
-
打开另一个控制台,开启Redis服务的端口映射,记录此时暴露的端口号(http://127.0.0.1:xxx中的xxx)
minikube service my-redis-master
-
用暴露的端口号替换
visualization/client.py
中的Redis端口号 -
打开另一个控制台,进入visualization文件夹,然后启动演示用网页UI
cd visualization npm start
3.3 演示
3.3.1 UI布局
- UI每次刷新(refresh)会在后台通过Axios服务器执行kubectl的get pods命令,从而获取ml-service关联的所有pod的状态
- pod会被陈列在左上角,其中绿色的pod已经成功连接到Redis,可以正常工作,而红色的pod则还在准备中。通过点击pod可以将其关闭,从而模拟服务故障
- 通过点击Upload Images按钮会进入文件选择视图,可以选择并上传需要识别的图片(例如
visualization/imgs
文件夹下准备好的图片),支持一次上传多张 - 通过更改Min Replicas和Max Replicas并点击Update Replicas,会更新helm的HPA参数从而更改pods的数目。在Redis中没有消息的情况下,pods的数目会减少到Min Replicas;在Redis中有消息的情况下,pods的数目会增加到Max Replicas
- 左下的控制台面板会显示进行图形化交互时,后台通过控制台执行的所有命令。此外,还会显示每次处理的性能指标
- 右侧面板会显示识别结果
3.3.2 演示流程
-
点击upload image,上传
visualization/imgs/sample_0.jpg
。这张图片被识别为”お”,处理时间为2081ms,由目前正在运行的唯一一个pod处理 -
点击upload image,上传sample_0到20这21张图片,处理耗时大概是只上传一张图片时的数倍
为了更好地部署这个服务。我们的目标是完成可拓展、可并行、高可用这三个目标。具体而言,我们现在只有一个用户和一个服务。这样的问题是假设我们一次上传多张图片,它只能串行处理,造成处理速度慢。另一方面,假设这个服务所在的服务器宕机了,服务就无法运行了。我们重点就是要解决这两个问题。
我们希望能够有多个服务,可以自动将服务进行复制,而且每个服务运行在不同的服务器上。然后我们还需要一个负载均衡器,流量一开始只需要发送给它,它会自动把流量分发给不同的服务。
我们利用k8s来自由地实现服务的拓展。而为了实现负载均衡,我们在k8s集群中部署了Redis。Redis提供了一个消息队列。服务从Redis中拉取消息,并在处理完成后向Redis确认,Redis将对应消息在队列中删除。处理结果也会被写入到Redis中,当结果被接收后则会向Redis确认,并在队列中删除。
这样的架构下,多个服务可以同时取消息,实现可并行;且就算一个服务所在的服务器宕机,别的服务也会继续取消息,实现高可用。
-
将min replicas和max replicas增加到2,并点击Update Replicas,从而实际增加一下pod数量
然后,再次点击upload image,上传sample_0到20这21张图片。我们可以看到此时处理时间大幅缩短了,两个服务都被分配到了流量,参与了处理
-
还是点击upload image,上传sample_0到20这21张图片。在处理过程中,我们可以通过点击一个pod将其关闭,以模拟服务宕机。此时由于k8s的HPA中设置了min replicas为2,意味着pod数目不会降低到2以下,因此k8s会自动开启一个新的pod。
此时没有故障的pod还在继续处理图片。新的pod会在服务就绪后开始处理图片。处理结束后,可以看见三个pod此时都参与了处理,且pod宕机期间的流量由最开始未宕机的pod去承担了。
-
我们还可以继续做一些优化,比如我们为了降低成本,可能在没有请求的时候选择关闭一些服务器。首先将Min Replicas和Max Replicas都设置为1,则会关闭一个pod。
然后,保持Min Replicas为1,将Max Replicas设置为2。再次上传图片时,途中会自动追加一个pod来处理消息。
当处理完之后这个新的pod又会被关闭。
在演示中,我们使用了让Helm调整HPA的参数这一非常原始的方式。生产环境中可以用Keda工具来完成自动缩放。这在拓展小节中会介绍。
4 拓展
4.1 使用Prometheus+Grafana来监视系统状态
-
打开控制台,安装Prometheus+Grafana
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm install monitoring prometheus-community/kube-prometheus-stack \ --set prometheus.prometheusSpec.resources.requests.memory=512Mi \ --set grafana.resources.requests.memory=256Mi
-
在ml-service的Helm chart中开启监控功能:将
server/ml-service/values.yaml
中prometheus的enabled选项改为true -
更新ml-service
cd server helm upgrade ml-service ./ml-service
-
暴露Grafana服务。此时会自动打开Grafana网页
minikube service monitoring-grafana
-
登录Grafana。初始用户名为admin,密码为prom-operator
-
选择数据源为Prometheus并添加你所需要的面板。例如,我们添加了一个能够监控ml-service的pod总数和就绪(ready)的pod数的面板。
4.2 使用Keda来实现自动拓展
-
打开控制台,安装Keda
helm repo add kedacore https://kedacore.github.io/charts helm repo update helm install keda kedacore/keda
-
在ml-service的Helm chart中开启Keda:将
server/ml-service/values.yaml
中keda的enabled选项改为true,并将autoscaling的enabled选项改为false -
更新ml-service
cd server helm upgrade ml-service ./ml-service