当前位置: 首页 > 科技观察

机器学习平台在Kubernetes上的实践

时间:2023-03-18 21:31:02 科技观察

背景以往音乐算法的模型训练任务都是在物理机上进行开发、调试和调度。每个算法团队都使用自己独立的物理机,这会带来一些问题。比如物理机分布比较分散,缺乏统一管理。主要依靠doc文档表来记录机器的使用情况和所有权;各业务间的机器资源配置,有时需要将机器迁移到不同的机房,费时费力。另外,由于多人共享,开发调度任务共享等,会造成环境的相互影响和资源的竞争。针对目前的情况,总结存在的问题如下:资源利用率低:部分机器资源利用率低;无法根据各个业务的不同阶段,在全球范围内进行快速动态的扩容和缩容,从而实现资源的合理配置,提高资源的整体利用率;环境交互:多人共享、测试和调度混合使用同一台开发机器,没有任何隔离,可能导致环境和共享资源的相互影响和竞争;缺少监控告警:物理机模式,task缺少监控告警功能,导致task无法运维,或者效率低下。如果资源不全局统一分配,就会造成负载不均衡,资源得不到最大程度的利用。Kubernetes尝试快速扩缩容、环境隔离、资源监控等,Kubernetes及其相关扩展可以很好地解决问题。现在收集物理机器并构建一个Kubernetes集群。机器学习平台(GoblinLab)通过分析算法同事以往的工作方式,决定尝试提供两种基于Kubernetes的解决方案:在线开发调试容器环境和任务容器化调度,分别针对任务开发的两种场景和任务调度。任务开发为了尽量减少算法同事从物理机迁移到容器化环境的学习成本,GoblinLab系统基本采用Kubernetes容器作为云主机。容器镜像基于Tensoflow各版本镜像(底层为Ubuntu),集成大数据开发环境(Hadoop、Hive、Spark等Client),安装常用软件。另外,为了方便使用,容器环境提供了三种使用方式:JupyterLab、SSH登录、CodeServer(VSCode)。在GoblinLab中创建一个新的容器化开发环境相对简单。你只需要选择镜像,填写需要的资源,以及需要挂载的外部存储(任务开发环境以下简称开发实例)。创建新的环境配置后,单击启动实例。容器初始化时,Jupyterlab、SSH、CodeServer会自动启动。JupyterLab:代码服务器:SSH登录:算法可以选择以上任意一种方式进行任务开发或调试。由于提供了代码服务器(VSCode),可以获得更好的体验。用于任务开发的容器化环境由底层Kubernetes上的StatefulSet类型实现。对应的资源布局文件如下(细节已经简化):kind:StatefulSetapiVersion:apps/v1metadata:name:${name}namespace:"${namespace}"spec:replicas:1selector:matchLabels:statefulset:${name}system/app:${name}template:spec:<#if(gpu>0)>tolerations:-effect:NoSchedulekey:nvidia.com/gpuvalue:"true"<#ifusePrivateRepository=="true">imagePullSecrets:-name:registrykey-myhubvolumes:-name:localtimehostPath:path:/etc/localtime<#ifMountPVCs??&&(MountPVCs?size>0)><#listMountPVCs?keysaskey>-name:"${key}"persistentVolumeClaim:claimName:"${key}"containers:-name:notebookimage:${image}imagePullPolicy:IfNotPresentvolumeMounts:-name:localtimemountPath:/etc/localtime<#ifreadMountPVCs??&&(readMountPVCs?size>0)><#listreadMountPVCs?keysaskey>-name:"${key}"mountPath:"${readMountPVCs[key]}"只读:true<#ifwriteMountPVCs??&&(writeMountPVCs?size>0)><#listwriteMountPVCs?keysaskey>-name:"${key}"mountPath:"${writeMountPVCs[key]}"env:-name:NOTEBOOK_TAGvalue:"${name}"-name:HADOOP_USERvalue:"${hadoopUser}"-name:PASSWORDvalue:"${password}"resources:requests:cpu:${cpu}memory:${memory}Gi<#if(gpu>0)>nvidia.com/gpu:${gpu}limits:cpu:${cpu}memory:${memory}Gi<#if(gpu>0)>nvidia.com/gpu:${gpu}目前GolbinLab已经提供了11张基于Tensoflow各个版本的CPU和GPU的通用图片,以及多张定制化图片imagetasks调度算法同事在使用容器化环境之前,任务的开发和调度都是在GPU物理机上完成的。调度通常是通过定时器或crontab命令完成的。任务没有失败、超时、不重试等告警机制,基本没有相关的任务运维工具。在介绍容器内开发的任务如何在线调度之前,先简单介绍一下GoblinLab的系统架构。上图为GoblinLab的简化系统架构,主要分为四层,从上到下:Application-应用层:提供直接面向用户的机器学习开发平台(GoblinLab)Middle-middlelayer:中间层,主要接入统一调度、告警、配置服务向导执行服务:提供统一执行服务,包括Kubernetes、Spark、Jar等各种任务的提交和执行。Plug-in,支持快速扩展Infrastructure-Infrastructure:底层基础设施主要包括Kubernetes集群、Spark集群、普通服务器。为了保证调度任务的稳定性,GolbinLab将任务开发和调度进行了拆分。在物理机上开发任务后,通过定时器或crontab来调度任务。如上图所示,开发完成后,通过任务流中容器化的任务调度组件实现任务调度。调度函数实现任务调度。不同于任务开发,每个调度任务都在一个独立的容器中执行,保证任务之间相互隔离。同时,通过后面介绍的资源隔离方案,可以优先保证在线调度任务所需的资源。任务调度执行的大致流程如下:任务调度执行时Kubernetes上的资源编排文件(细节已经简化):apiVersion:batch/v1kind:Jobmetadata:name:${name}namespace:${namespace}spec:template:spec:containers:-name:jupyter-jobimage:${image}env:-name:ENV_TESTvalue:${envTest}command:["/bin/bash","-ic","cd${workDir}&&\${execCommand}/root/${entryPath}${runArgs}"]volumeMounts:-mountPath:"/root"name:"root-dir"resources:requests:cpu:${cpu}memory:${memory}Gi<#if(gpu>0)>nvidia.com/gpu:${gpu}限制:cpu:${cpu}内存:${memory}Gi<#if(gpu>0)>nvidia。com/gpu:${gpu}volumes:-name:"root-dir"persistentVolumeClaim:claimName:"${pvc}"backoffLimit:0权限控制容器化开发环境配置启动后,用户可以通过SSH、CodeServer或使用JupyterLab等其中一种方式登录。为了防止容器化开发环境被他人利用,GoblinLab为每个方法设置了统一的key,每次启动时随机生成key。1.随机生成密码2.设置账号密码(SSH登录密码)echo"root:${password}"|chpasswd3.设置代码服务器密码(VSCode)#设置环境变量PASSWORD为env:-name:密码值:“${密码}”4。设置JupyterLab密码jupyternotebook--generate-config,在~/.jupyter目录下生成jupyter_notebook_config.py,添加代码importosfromIPython.libimportpasswdc=c#pylint:disable=undefined-variablec.NotebookApp.ip='0.0.0.0'#https://github.com/jupyter/notebook/issues/3946c.NotebookApp.port=int(os.getenv('PORT',8888))c.NotebookApp.open_browser=FalsesetsapasswordifPASSWORDDissetintheenvironmentif'PASSWORD'inos。environ:password=os.environ['PASSWORD']ifpassword:c.NotebookApp.password=passwd(密码)else:c.NotebookApp.password=''c.NotebookApp.token=''delos.environ['PASSWORD']数据持久化在Kubernetes容器中,如果没有特殊配置,容器中的数据是不持久化的,也就是说当容器被删除或者重启时,数据会丢失。对应的解决方案比较简单,将需要持久化的目录挂载外存即可。在GoblinLab中,会自动为每个用户创建一个默认的外部存储PVC,并挂载到容器的/root目录下。此外,用户还可以自定义外接存储的挂载。除了自动创建PVC,用户还可以自己创建PVC,并支持将创建的PVC以只读或读写方式共享给他人。另外,PVC中的数据也可以在Goblinlab上进行管理。该服务暴露了在Kubernetes集群中创建的服务,无法在集群外直接访问。GoblinLab使用NginxIngress+Gateway接入,将集群内的服务对外暴露。容器化开发环境的服务资源编排文件如下(细节已简化):apiVersion:v1kind:Servicemetadata:name:${name}namespace:${namespace}spec:clusterIP:Noneports:-name:port-notebookport:8888protocol:TCPtargetPort:8888-name:port-sshdport:22protocol:TCPtargetPort:22-name:port-vscodeport:8080protocol:TCPtargetPort:8080-name:port-tensofboardport:6006protocol:TCPtargetPort:6006<#ifports??&&(端口?size>0)><#listportsasport>-name:port-${port}port:${port}targetPort:${port}选择器:statefulset:${name}type:ClusterIP每当用户启动容器化开发环境时,GoblinLab会自动通过接口修改NginxIngress配置,将服务暴露出来供用户使用。Ingress转发配置如下:apiVersion:v1kind:ConfigMapmetadata:name:tcp-servicesnamespace:kube-systemdata:"20000":ns/notebook-test:8888"20001":ns/notebook-test:8080"20002":ns/notebook-test:22资源控制为了提高资源利用率,GoblinLab底层Kubernetes中的资源基本上都是共享使用的,并进行一定比例的超卖。但是,当多个团队共享一个资源总量固定的集群时,为了保证每个团队公平共享资源,此时就需要对资源进行管理和控制。在Kubernetes中,资源配额是解决这个问题的利器。目前GoblinLab需要控制的资源主要是CPU、内存、GPU、存储。平台在考虑到各个团队的实际需求后,将资源划分为多个队列(Kubernetes中的概念是命名空间),提供给各个团队。apiVersion:v1kind:ResourceQuotametadata:name:skiff-quotanamespace:testspec:hard:limits.cpu:"2"limits.memory:5Girequests.cpu:"2"requests.memory:5Girequests.nvidia.com/gpu:"1"requests.storage:10Gi在集群中,最常见的资源是CPU和内存。因为它们可以被过度使用(overcommit),所以有两个配额限制:limits和requests。此外,其他资源都是扩展类型。由于不允许过度使用,因此只有请求配额限制。参数说明:limits.cpu:跨所有非终端状态的pod,CPUlimits之和不能超过这个值。limits.memory:在所有处于非终端状态的pod中,内存限制总和不能超过这个值。requests.cpu:跨所有非终端状态的pod,CPU请求总和不能超过这个值。requests.memory:跨所有处于非终端状态的pod,内存请求的总和不能超过这个值。http://requests.nvidia.com/gpu:跨所有处于非终端状态的pod,gpu请求的总和不能超过这个值。requests.storage:在所有persistentvolumeclaims中,存储请求的总和不能超过这个值。可以受配额控制的资源包括CPU、内存、存储和GPU。其他类型请参考官方文档:https://kubernetes.io/docs/con...otas/resourceisolationGoblinLab的资源隔离指的是资源隔离在同一个Kubernetes集群中,资源相对隔离在调度层面,包括GPU机器资源的隔离,以及线上和测试任务的隔离。GPU机器资源隔离在Kubernetes集群中,GPU机器资源比CPU机器更珍贵。因此,为了提高GPU利用率,禁止在GPU机器上调度CPU任务。GPU节点设置污点(Taint):禁用通用任务调度。对GPU节点键的影响的可选配置:nvidia.com/gpuvalue:trueeffect:NoScheduleTaint:NoSchedule:Pod不会被调度到标记为污点的节点。PreferNoSchedule:NoSchedule的软策略版本。尽量避免在不能容忍污点的节点上调度Pod。NoExecute:该选项表示一旦Taint生效,如果节点中运行的Pod不符合Tolerate设置,将直接被驱逐。GPU任务设置容忍度(Toleration):允许GPU任务在GPU节点<#if(gpu>0)>tolerations:-effect:NoSchedulekey:nvidia.com/gpuvalue:"true"在线和测试任务隔离在线和测试任务(GolbinLab中的在线任务和测试任务定义在业务层面,指的是周期性调度任务和开发测试任务)使用同一个Kubernetes集群,但是为了保证在线任务的资源,将进行一些特殊设置。机器节点是在线任务的专用资源池。在线任务执行时,先调度到在线节点上,当在线资源池中没有资源时,也可以调度到离线节点上。在线资源池节点设置污点(Taint):禁止一般任务调度在线资源池key:node.netease.com/node-pool值:onlineeffect:NoSchedule在线任务添加容忍度(Toleration):允许在线任务调度在资源池上,但不一定在在线资源池中调度tolerances:-effect:NoSchedulekey:node.netease.com/node-poolvalue:"online"operator:Equal在在线资源池中,设置机器节点标签+在线任务设置节点亲和度(nodeAffinity):优先调度在线资源池中的在线任务,但是如果在线资源池中没有现成的资源,此时也可以调度到其他节点上。方便,标签与污点同名:node.netease.com/node-pool:online在线任务设置节点亲和力(nodeAffinity):在线资源池中在线任务优先级调度affinity:nodeAffinity:preferredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:-matchExpressions:-key:node.netease.com/node-poooloperator:Invalues:-online目前Nodeaffinity有以下几种策略,官方文档affinity-and-anti-affinity:requiredDuringSchedulingIgnoredDuringExecution表示Pod必须部署在一个node上满足条件,如果没有满足条件的节点,则继续重试。其中,IgnoreDuringExecution表示部署后Pod运行时,如果节点标签发生变化,不再满足Pod指定的条件,则Pod继续运行。requiredDuringSchedulingRequiredDuringExecution表示Pod必须部署在满足条件的节点上。如果没有满足条件的节点,会一直重试。其中,RequiredDuringExecution表示部署后Pod运行时,如果节点标签发生变化,不再满足Pod指定的条件,则重新选择满足要求的节点。preferredDuringSchedulingIgnoredDuringExecution表示先部署在满足条件的节点上。如果没有满足条件的节点,忽略这些条件,按正常逻辑部署。preferredDuringSchedulingRequiredDuringExecution表示优先部署在满足条件的节点上。如果没有满足条件的节点,则忽略这些条件,按正常逻辑部署。其中,RequiredDuringExecution表示如果后续节点的标签发生变化,满足条件,则重新调度到满足条件的节点。策略生效后的效果如下图所示。在线任务先在在线资源池节点执行,但是当在线资源池没有空闲资源时,在线任务Job5也可以使用普通节点的资源。阶段性成果截至目前,音乐机器学习平台(GoblinLab)已经在容器化方面进行了一段时间的实验,并取得了阶段性成果。集群建设已经尝试了一段时间。目前音乐数据平台的Kubernetes,随着承载的业务越来越多,以及基于Kubernetes的大数据计算平台(Flink等)的落地,未来会增加大量的CPU资源。它的稳定性将成为一个比较大的挑战。用户使用任务迁移:目前协助算法同事完成80%的任务迁移任务开发用户情况:60%的算法同学使用过开发实例的容器化环境;用户来源包括音乐推荐算法、社交视频推荐算法、搜索算法、音视频团队开发实例、数据应用、实时计算算法等:平台鼓励组内分享开发实例,并限制每人最多创建3个开发实例。任务调度:涵盖云音乐音乐推荐、社交视频推荐算法、搜索算法、音频视频、数据应用、实时计算算法等多个团队容器化的好处对于算法同仁来说,从物理机迁移到机器学习平台提供容器化环境可以带来以下好处:更多资源:由于资源利用率的提高,你将有机会获得比单独使用物理机更多的资源;另外,资源扩缩容的周期可以从以前的几天缩短到秒级完成,更好的体验:通过打通大数据,Git环境提供多种使用方式(SSH和在线IDE),机器学习平台统一维护环境形象,避免了每个团队都要自己搭建和维护环境的苦恼。更全面的任务调度:GoblinLab调度提供了更完善的告警、重试、依赖检查等功能,可以与已有的PS、Ironbaby任务集成,实现一个任务流中的统一调度。更好的隔离:环境隔离是容器化的天然优势。另外,调度层面的资源隔离,可以更好的保证在线任务。统一入口:统一开发入口后,可以有更大的操作空间。例如,将通用服务抽象出来直接由平台提供(依赖检查、调度、告警监控等)、数据共享、镜像更新、后续规划等持续支持服务。目前音乐机器学习平台可以提供完整的容器。为了进一步提高集群的资源利用率,提高运维效率,后续计划将从资源调度策略优化(抢占等)、更丰富的资源监控等入手,进一步优化。作者:王俊政,网易云音乐数据智能部数据平台组