作者:王磊
更多精彩分享,欢迎访问和关注:https://www.zhihu.com/people/wldandan
在上一篇【MindSpore易点通机器人-03】迭代0的准备工作,我们从整体上概述了MindSpore易点通机器人项目开始前需要在迭代0的准备工作,本篇将会为大家讲述迭代0中具体的MLOps 环境搭建过程。整体的MLOps流水线设计如下图,包含持续训练流水线和CI/CD流水线。相关代码请参考MindSpore易点通机器人代码仓。
实际的技术选型上,我们基于Jenkins构建CI/CD流水线,基于Argo构建持续训练流水线。同时,我们把Jenkins和Argo都运行在K8S上来保证可用性和弹性。下文将为大家介绍如何在本地使用Minikube完成机器人项目的MLOps流水线的搭建,并在本地运行起来。
具体的过程如下:
因为团队大部分人的开发环境都是Windows,所以需要选择WSL+Linux+Docker的方式。这里我们没有采用Docker Desktop+WSL Backend,而是利用Distord让Docker在Ubuntu上直接运行,相关的安装配置文档可以参考如何不安装Docker Desktop在WSL下运行Docker这篇文章。
Minikube是一个单机安装配置K8S集群的工具,它支持多平台(Mac/Linux/Windows)。Minikube可以将K8S集群安装配置在单个Docker容器或者VM(hyper-v/VMWare等)中,过程也非常简单。首先在Ubuntu中安装Minikube:
- curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
- sudo install minikube-linux-amd64 /usr/local/bin/minikube
然后执行minikube start
,因为在第一步中我们已经配置好了Docker,Minikube在启动时会默认使用Docker作为VM,然后在容器中启动K8S集群。启动完成后在Ubuntu上使用docker ps
只能看到一个容器:
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- 699a71fee349 kicbase/stable:v0.0.30 "/usr/local/bin/entr…" 2 weeks ago Up 2 hours 127.0.0.1:49157->22/tcp, 127.0.0.1:49156->2376/tcp, 127.0.0.1:49155->5000/tcp, 127.0.0.1:49154->8443/tcp, 127.0.0.1:49153->32443/tcp minikube
如果我们执行docker exec -it 699a71fee349
进入容器后再执行docker ps
,就能发现K8S集群的服务了:
- CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
- af45f3caa0d9 99a3486be4f2 "kube-scheduler --au…" 2 hours ago Up 2 hours k8s_kube-scheduler_kube-scheduler-minikube_kube-system_be132fe5c6572cb34d93f5e05ce2a540_1
- e648e7d30a7d Error 404 (Not Found)!!1 "/pause" 2 hours ago Up 2 hours k8s_POD_kube-apiserver-minikube_kube-system_cd6e47233d36a9715b0ab9632f871843_1
- e26d9e92c4e3 k8s.gcr.io/pause:3.6 "/pause" 2 hours ago Up 2 hours k8s_POD_kube-scheduler-minikube_kube-system_be132fe5c6572cb34d93f5e05ce2a540_1
- e658bf17922d Error 404 (Not Found)!!1 "/pause" 2 hours ago Up 2 hours k8s_POD_kube-controller-manager-minikube_kube-system_b965983ec05322d0973594a01d5e8245_1
- 1f85a9bae877 Error 404 (Not Found)!!1 "/pause" 2 hours ago Up 2 hours k8s_POD_etcd-minikube_kube-system_9d3d310935e5fabe942511eec3e2cd0c_1
- ....
从容器中退出,安装kubectl之后就在Ubuntu上使用Kubectl管理集群了:
- curl -LO "https://dl.k8s.io/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl.sha256"
- sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
执行kubectl get nodes
,查询K8S管理的节点。
- ~$ kubectl get nodes
- NAME STATUS ROLES AGE VERSION
- minikube Ready control-plane,master 19d v1.23.3
如果关注Kubernetes的界面,使用minikube dashboard
就可以启动K8S的管理界面,并在Windows上通过浏览器访问 http://127.0.0.1:44185/api/v1/namespaces/kubernetes-dashboard/services/http:kubernetes-dashboard:/proxy/
。
使用Minikube可以让我们在本地拥有一个和生产环境一样功能的K8S集群,但这种方式同样带来了网络的复杂性,如下图,Ubuntu运行在Hyper-v的虚机中,在K8S部署的服务运行在Ubuntu的Docker容器的容器中(Docker in Docker)。所以,如果要在Windows的浏览器上访问K8S中运行的服务,需要先通过kubectl port-forward
完成Ubuntu VM和Minikube容器的端口映射,然后再使用minikube tunnel
完成VM到Windows的端口映射。
Jenkins是一个经久不衰的持续集成工具,它的插件生态比较强大。我们构建基于Jenkins+Kubernetes的CI/CD流水线要达到的目的如下:
MindSpore易点通机器人的代码仓已经给出了在K8S上部署Jenkins的配置文件,这里要设置成LoadBalancer
类型。
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: jenkins
- spec:
- type: LoadBalancer
- selector:
- name: jenkins
- ports:
- -
- name: http
- port: 8080
- targetPort: 8080
- protocol: TCP
再用kubectl create -n jenkins
创建Jenkins的namespace,通过kubectl apply -f jenkins.yaml -n jenkins
完成部署,然后用kubectl apply -f service-account.yaml -n jenkins
完成API调用的授权。
在配置文件中我们指定了对外暴露的端口是8080,所以可以用kubectl port-forward svc jenkins/jenkins 8080:8080 -n jenkins
完成端口映射,再执行minikube tunnel
就可以在浏览器中使用127.0.0.1:8080
打开Jenkins界面了,初始密码可以在pod启动的日志中获得。
完成Jenkins在K8S上的部署后,需要在Jenkins中安装kubernetes
插件,配置节点类型为K8S集群。从插件管理中先安装kubernetes
插件,然后在节点管理
中选择配置集群
。
首先配置K8S集群的信息,因为在一个K8S集群,服务间都可以通过主机名的方式相互访问,所以“Kubernetes地址”配置只需要输入https://kubernetes.defaults
,同时补充配置“Kubernetes命名空间”为jenkins
,如下图所示。
同理,对于“Jenkins地址”,只需要填入http://jenkins.jenkins:8080
。
另一个要配置是pod模板,既任务运行的pod的基础镜像及相关信息配置。要同时运行Java和Python,所以在dockerhub找了一个Java和Python都有的镜像,如下图所示,保存后即可完成配置。
我们的最后一步是基于Jenkins的流水线即代码功能完成CI/CD流水线搭建,按照设计,它应该包含如下任务:
使用Jenkins流水线即代码功能,对应的配置如下:
- pipeline {
- agent {
- kubernetes {
- containerTemplate {
- name 'python'
- image 'bitnami/java:1.8'
- command 'sleep'
- args 'infinity'
- }
- defaultContainer 'python'
- }
- }
- stages {
- stage('Code Check ') {
- steps("Code Check") {
- echo 'checking python code.'
- }
- }
- stage('Unit Testing') {
- steps("Unit Testing") {
- echo "running unit tests"
- }
- }
- stage('API Testing') {
- steps {
- echo 'running inference API Testing'
- }
- }
- stage('Training Trigger') {
- when {
- changeset "src/train/*.py"
- }
- steps("trigger training") {
- echo 'trigger new round of training'
- }
- }
- }
- }
部署流水线的pipeline脚本如下:
- pipeline {
- agent any
- stages {
- stage('Packaging') {
- steps("Packaging") {
- echo 'packaging with model and inference code'
- }
- }
- stage('Continuous Deployment') {
- steps("deploying") {
- echo 'deploy new version of model'
- }
- }
- }
- }
配置文件中每个step先置空,是为了方便调试。在Jenkins基于上面的配置文件创建一个流水线后的测试结果如下:
Argo是一个基于K8S的开源的工作流管理工具,也支持机器学习的工作流。MindSpore DX Sig已经在先前的社区机器人项目中使用了该工具,所以这里我们复用了工具和配置。Argo Workflow的安装配置如下:
首先,我们执行kubectl create -n argo
为Argo创建新的命名空间。然后,基于配置文件,执行kubect apply -f install.yml -n argo
完成安装。最后,执行kubect apply -f manifests/create_serviceaccount.yaml -n argo
完成权限配置。
和前面的Jenkins配置类似,Argo Server需要设置为LoadBalancer
类型。
- apiVersion: v1
- kind: Service
- metadata:
- name: argo-server
- spec:
- ports:
- - name: web
- port: 2746
- targetPort: 2746
- type: LoadBalancer
- sessionAffinity: None
- externalTrafficPolicy: Cluster
- selector:
- app: argo-server
执行kubectl get svc -n argo
可以看到:
- ~$ kubectl get svc -n argo
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- argo-server LoadBalancer 10.97.145.232 127.0.0.1 2746:30001/TCP 19d
- workflow-controller-metrics ClusterIP 10.99.196.47 <none> 9090/TCP 19d
如果要在Windows的浏览器上访问Argo的Web界面,则还需要把argo-server的端口暴露出来,同上使用kubectl port-forward svc/argo-server 2746:2746 -n argo
完成端口映射。然后浏览器上访问https://127.0.0.1:2746
,通过下面的脚本获得密码,登录后就可以正常使用Argo的Web管理界面了。
- #!/bin/bash
- SECRET=$(kubectl get sa argo-server -n argo -o=jsonpath='{.secrets[0].name}')
- ARGO_TOKEN="Bearer $(kubectl get secret $SECRET -n argo -o=jsonpath='{.data.token}' | base64 --decode)"
- echo $ARGO_TOKEN
我们期望训练的工作流可以完成以下任务:
基于工作流的设计以及Argo工作流语法,可以得出基础的配置:
- apiVersion: Page Not Found
- kind: Workflow
- metadata:
- generateName: robot-train-eval-
- spec:
- serviceAccountName: robot-sa
- entrypoint: robot-controller
- onExit: summary
- templates:
- - name: robot-controller
- steps:
- - - name: data-process
- template: process
- - - name: robot-train
- template: train
- - - name: robot-eval
- template: eval
- - name: robot-interpretability
- template: interpretability
- - name: robot-reliability
- template: reliability
- - - name: summary
- template: summary
而后,展开每个任务需要的配置,如训练的配置代码:
- - name: train
- container:
- image: ubuntu
- imagePullPolicy: Always
- env:
- - name: IS_TRAIN
- value: "True"
- - name: NUM_STEPS
- value: "10"
- command: ['echo']
- args: ["trainning"]
- ...
把每个阶段的任务汇总在一起就完成了整个的工作流。接下来,我们尝试使用Argo运行下训练流水线。
可以使用Argo CLI的客户端完成工作流任务的提交,首先安装客户端:
- #!/bin/bash
- # Download the binary
- curl -sLO https://github.com/argoproj/argo-workflows/releases/download/v3.3.5/argo-linux-amd64.gz
- # Unzip
- gunzip argo-linux-amd64.gz
- # Make binary executable
- chmod +x argo-linux-amd64
- # Move binary to path
- mv ./argo-linux-amd64 /usr/local/bin/argo
然后通过argo cli提交工作流argo submit -n robot --watch robot-train-eval.yaml
,执行结果如下,和我们期望的流水线步骤一致。
本篇文章总结了如何在本地完成MLOps环境的搭建,基于Minikube、Argo等工具可以让我们很好的在本地展开开发验证工作,不用依赖复杂的基础设施,也不用有额外的开销。在配置文件中和脚本中,我们没有加真实的实现,是为了先打通流程,然后再将调试好内容逐步补充进去,始终都可以从端到端的角度来完成验证。