大规模训练任务与推理服务编排、离线混部及资源调度技术手册
大规模训练任务与推理服务编排、离线混部及资源调度技术手册
目录
4.1 案例一:基于Kubeflow和Volcano的分布式训练任务编排
4.2 案例二:基于KServe和Triton的推理服务部署与自动扩缩
第1章 概述
随着人工智能(AI)和深度学习的飞速发展,训练大规模模型和部署高并发推理服务已成为常态。这些工作负载对底层基础设施提出了独特且严苛的要求,远非传统Web应用可比。在训练任务方面,一个深度学习训练作业往往由成百上千个协同工作的子任务组成,需要同时调度大量计算节点,可能涉及GPU等异构资源,并且训练周期长、资源需求波动大。而推理服务则要求低延迟、高吞吐,需要根据请求流量动态扩缩容,并确保服务级别协议(SLA)。与此同时,为了提高资源利用率,业界日益倾向于在同一套基础设施上混部不同类型的负载,例如将延迟敏感的在线服务与资源消耗型的大数据批处理任务混合部署,以“削峰填谷”提升集群整体利用率。然而,这种多工作负载共存的场景对资源调度和管理提出了更高要求,需要在保证服务质量(QoS)的前提下实现高效调度。
Kubernetes作为云原生时代的事实标准,为容器化应用提供了强大的编排与调度能力。然而,其默认的调度器和资源模型最初主要针对无状态的微服务设计,对于分布式训练作业、推理服务以及离线混部等场景存在不足。例如,默认调度器逐个调度Pod,无法保证一个训练作业的所有Pod同时启动,可能导致部分Pod占用资源却无事可做,造成资源浪费。又如,它缺乏对服务时延敏感型应用与批处理计算型应用混合运行的精细支持,容易在高负载时发生资源争抢,影响在线服务的响应时间。
本技术手册旨在系统讲解如何利用Kubernetes及其生态,实现大规模训练任务和推理服务的高效编排、资源调度优化以及离线混部。我们将遵循“概述+原理+实现+案例”的结构,深入探讨以下主题:
- 大规模训练任务编排:阐述分布式训练任务的调度需求与挑战,介绍Kubeflow等机器学习工作流平台,以及Volcano等高级调度器如何在Kubernetes上实现对训练作业的全生命周期管理。
- 推理服务调度:分析推理服务的部署特点,讲解如何利用KServe、Triton Inference Server等工具,在Kubernetes上部署高性能的模型服务,并结合Prometheus、HPA等实现基于指标的自动扩缩容。
- 离线混部:探讨在同一集群混合部署在线服务与离线批处理任务的技术,重点介绍阿里开源的Koordinator项目如何通过扩展Kubernetes调度器和管理节点资源,实现延迟敏感型应用与批处理任务的高效混跑。
- 资源调度算法与优化:深入剖析Kubernetes调度器的原理,讲解常用调度算法(如轮询、优先级、抢占等)和优化策略,包括负载感知调度、节点亲和性/反亲和性、Pod优先级与抢占等机制,以及它们在训练与推理场景中的应用。
通过本手册的学习,读者将掌握如何在Kubernetes平台上,以声明式、自动化的方式管理AI工作负载的整个生命周期,实现资源的精细化调度与弹性伸缩,从而构建一个既能支撑大规模模型训练又能稳定运行高并发推理服务,并充分利用闲置资源的智能调度系统。
第2章 原理基础
在深入具体实现之前,本章先介绍训练任务、推理服务调度以及离线混部的基本原理和相关概念,为后续章节奠定基础。
2.1 Kubernetes调度基础
Kubernetes中的调度是指将Pod放置到合适节点的决策过程。默认调度器按照预定的策略,将待调度的Pod逐一分配到满足条件的节点上。调度过程主要分为两个阶段:过滤(Predicate)和打分(Priority)。在过滤阶段,调度器会排除掉不符合Pod运行条件的节点,例如资源不足、不满足节点亲和性、存在污点无法容忍等。在打分阶段,调度器会对剩余的候选节点进行评分,根据一系列策略(如尽量将Pod分散在不同节点、尽量将同一服务的Pod调度到同一节点等)计算每个节点的权重,最终选择得分最高的节点来运行Pod。
Kubernetes提供了多种机制让用户影响调度决策:
调度控制机制:
- 节点亲和性/反亲和性:通过节点标签选择器,让Pod只调度到特定节点或避免调度到某些节点。例如,可以要求某个服务只运行在带有
disk=ssd标签的节点上。 - Pod亲和性/反亲和性:基于已运行的其他Pod的位置来调度当前Pod。例如,将前端服务与后端服务调度到同一节点以减少通信延迟,或确保同一个应用的多个实例尽量不在同一节点上以实现冗余。
- 污点(Taint)和容忍(Toleration):节点上的污点可以阻止Pod调度,除非Pod能够容忍该污点。这常用于保留节点供特定工作负载使用,例如GPU节点打上污点,只有标注了相应容忍的Pod才能调度其上。
- 优先级和抢占:为Pod设置优先级,当集群资源不足时,调度器可以抢占(Evict)低优先级的Pod来为高优先级的Pod腾出资源。这确保了关键工作负载能够在资源紧张时仍得到调度。
这些机制为基本的调度策略提供了灵活性,但对于复杂的AI工作负载,仍需更高级的调度框架和算法来满足需求。
2.2 大规模训练任务的调度挑战
训练一个大规模的深度学习模型通常涉及分布式训练,即利用多台机器协同完成训练。常见策略包括数据并行(将训练数据分片,每台机器计算不同数据的梯度,然后通过参数服务器或环状通信汇总梯度)和模型并行(将模型拆分到多台机器上并行计算)。无论哪种方式,训练作业都具有以下调度挑战:
- 原子性调度(Gang Scheduling):一个训练作业由多个相关的子任务(如参数服务器和多个工作进程)组成,它们必须同时启动并协同运行。如果采用逐个Pod调度,可能出现部分Pod调度成功而另一部分因资源不足无法调度的情况,导致已调度的Pod空等,浪费资源。因此,需要一种机制保证要么所有任务都成功调度,要么一个都不调度,这就是原子调度或组调度(Gang Scheduling)的概念。
- 长生命周期与资源占用:训练作业通常运行数小时甚至数天。在运行过程中,其对资源的需求可能随训练阶段不同而变化,例如某些阶段需要大量GPU计算,而另一些阶段可能主要在CPU上预处理数据。调度系统需要考虑如何在长时间内稳定地提供资源,以及在资源紧张时如何处理训练作业(抢占、重新调度等)。
- 异构资源管理:现代训练作业往往需要GPU、高速网络等异构资源。Kubernetes需要识别节点上的GPU设备,并将其作为可调度资源暴露给调度器。同时,GPU资源是稀缺且昂贵的,调度器需要优化GPU资源的分配,避免碎片浪费。
- 作业编排与管理:训练任务可能包含数据准备、模型训练、超参数调优、模型评估等多个步骤,需要工作流编排系统来定义和执行复杂的流程。此外,训练过程中可能需要容错机制,例如当某个工作进程失败时,能够自动恢复或重试。
为了解决上述挑战,社区和工业界发展出了多种方案,例如Volcano项目为Kubernetes引入了高级批调度能力,Kubeflow项目提供了端到端的机器学习工作流平台,这些将在后续章节详细讨论。
2.3 推理服务调度的特殊需求
与训练任务不同,推理服务(Model Serving)是在线服务的一种,其对调度的需求更接近传统微服务,但也有独特之处:
- 低延迟与高吞吐:推理服务要求在接收到请求后尽可能快地响应。调度层面需要确保服务实例(通常是无状态的Pod)运行在高性能的节点上,并且网络延迟尽可能低。例如,可以将推理服务调度到靠近数据源或用户的节点,或使用GPU节点以加速推理计算。
- 弹性伸缩:推理服务的请求流量往往波动明显。调度系统需要根据实时负载自动扩容或缩容服务实例数量。Kubernetes提供了Horizontal Pod Autoscaler(HPA)来实现这一点,但默认HPA基于CPU利用率,对于GPU密集型推理服务或自定义指标(如请求延迟、队列长度)需要扩展支持。
- 模型部署与版本管理:推理服务通常以模型文件的形式存在,需要将其部署为容器服务。这涉及模型文件的加载、服务的启动,以及模型的更新策略(如蓝绿部署、金丝雀发布)。Kubernetes自身并不直接处理模型,因此出现了专门的模型服务框架(如KServe、Triton Inference Server)来简化模型部署和版本管理。
- 资源利用率与成本:推理服务在流量低谷时可能占用着资源却闲置无用。调度系统可以考虑在此时将一些资源密集型任务调度到这些节点上,以提高利用率,但必须保证当流量回升时,推理服务能够迅速获得所需资源。这涉及资源隔离和优先级的问题,与离线混部密切相关。
综上,推理服务调度需要在性能、弹性和资源效率之间取得平衡。后续章节将介绍如何利用Kubernetes原生能力以及社区工具来满足这些需求。
2.4 离线混部的基本原理
离线混部是指在同一个集群中混合部署在线服务(Latency-Sensitive,如Web应用、微服务、推理服务)和离线任务(Batch或Best-Effort,如大数据分析、机器学习训练、离线计算)的技术。其核心思想是:在线服务为了应对突发流量,往往按峰值资源需求申请资源,但实际运行时资源利用率可能很低;而离线任务通常对延迟不敏感,可以弹性利用这些闲置资源。通过混部,可以显著提高集群资源利用率,降低总体成本。
然而,实现混部需要解决两个关键问题:
- 资源隔离与干扰避免:当不同类型的负载运行在同一节点时,如果不对资源进行隔离,离线任务可能占用过多CPU、内存等资源,导致在线服务响应变慢甚至超时。因此,必须引入**服务质量(QoS)**的概念,为不同类型的负载分配不同级别的资源保障。例如,将为延迟敏感型应用分配更高的CPU份额,使其在资源竞争时优先获得调度;而批处理任务则使用“尽力而为”(Best Effort)级别,当资源不足时可以被抑制或驱逐。
- 调度策略:调度器需要感知不同工作负载的优先级和QoS级别,在调度决策时做出合理安排。例如,当一个节点已经运行着高优先级的在线服务时,调度器应避免再调度低优先级的批处理任务到该节点,除非有足够的资源余量。反之,当在线服务处于低谷时,调度器可以大胆地调度批处理任务来利用空闲资源。
为了解决这些问题,业界提出了多种方案。其中,Koordinator是一个基于Kubernetes的混部调度系统,它在Kubernetes原生QoS模型(Guaranteed、Burstable、BestEffort)基础上,定义了更细粒度的QoS模型(如SYSTEM、LSE、LSR、LS、BE等),并通过扩展调度器和节点代理(Koordlet)实现对混部场景下资源的精细化管理。Koordinator能够根据节点的实时负载动态调整资源分配,对BE类任务进行压制(Throttling)或驱逐(Eviction),以保障LS类应用的服务质量。
总而言之,离线混部通过资源隔离和优先级调度,在不影响在线服务性能的前提下,提高了资源利用效率。下一章将深入探讨如何在Kubernetes上实现这些原理。
第3章 核心技术与实现
本章将围绕训练任务编排、推理服务调度和离线混部三个主题,介绍在Kubernetes平台上的实现方案和技术细节。我们将以Kubernetes为核心,结合主流开源项目,讲解如何构建一个支持大规模AI工作负载的调度与编排系统。
3.1 Kubernetes在训练任务编排中的应用
Kubernetes本身并不直接提供分布式训练框架,但可以通过自定义资源定义(CRD)和控制器来扩展其能力,支持机器学习训练工作负载。这里我们重点介绍两个关键组件:Kubeflow和Volcano。
3.1.1 Kubeflow:端到端机器学习平台
Kubeflow项目致力于在Kubernetes上构建一个一站式的机器学习平台,让数据科学家和工程师能够方便地在Kubernetes上部署、运行和管理机器学习工作流。Kubeflow的核心组件包括:
Kubeflow核心组件:
- Kubeflow Pipelines:提供基于Argo Workflows的DAG工作流编排能力,用户可以定义多步的机器学习流水线(如数据准备、模型训练、模型评估、模型部署),并支持定时触发和UI监控。
- Training Operator:提供多种训练框架的作业控制器,如TFJob(TensorFlow训练)、PyTorchJob、MPIJob等,用于在Kubernetes上运行分布式训练任务。这些控制器能够创建和管理训练作业所需的Pod(例如参数服务器和工作节点Pod),并监控作业状态。
- Katib:一个超参数调优系统,支持多种优化算法,可以与Kubeflow Pipelines集成,自动搜索最佳超参数。
- 模型部署(KServe):虽然KServe后来独立发展,但Kubeflow早期提供了模型服务组件,用于将训练好的模型部署为推理服务。
Kubeflow通过这些组件,实现了从数据处理、模型训练、超参调优到模型部署的端到端流程。对于训练任务编排而言,Kubeflow的Training Operator提供了关键能力。例如,TFJob允许用户定义一个TensorFlow训练作业,包括参数服务器和工作节点的副本数、镜像、命令等,然后由Training Operator创建相应的Pod并协调它们的启动。这种方式解决了原子调度的问题:Training Operator会等待所有Pod都准备就绪后再真正启动训练,从而避免部分Pod启动而另一部分无法调度的情况。
下面是一个使用Kubeflow在Kubernetes上运行MNIST分布式训练的简单示例。该示例基于Kubeflow官方教程,展示了如何通过Jupyter Notebook提交一个分布式TensorFlow训练作业:
- 环境准备:首先部署Kubeflow到Kubernetes集群。这可以通过下载kfctl工具并执行配置文件来完成。部署完成后,Kubeflow会安装Istio服务网格、Jupyter Notebook服务器、TensorFlow Operator等组件。
- 启动Notebook:通过Kubeflow UI启动一个Jupyter Notebook服务器。Kubeflow支持多租户,用户可以创建自己的Notebook实例来编写和运行代码。
- 编写训练脚本:在Notebook中编写TensorFlow训练代码。Kubeflow提供了一个Python库
kubeflow.tfjobs,可以方便地创建TFJob。用户需要指定训练脚本的路径、使用的镜像、参数服务器和工作节点的数量等。 - 提交作业:运行Notebook中的代码,创建TFJob资源。TensorFlow Operator会检测到该资源并创建对应的Pod。示例代码中,通过
train_spec定义了TFJob的详细规格,包括参数服务器和工作节点的Pod模板。在Pod模板中,指定了TensorFlow镜像、训练命令(运行/opt/model.py)以及环境变量等。特别地,示例中通过schedulerName: volcano指定了使用Volcano调度器(将在下一节介绍)来调度该作业。 - 监控与管理:用户可以在Kubeflow UI或通过kubectl命令查看TFJob的状态。Kubeflow会为每个作业创建一个UI页面,显示训练进度、日志输出等。当训练完成后,用户可以下载训练模型或直接部署为推理服务。
这个示例展示了Kubeflow如何将复杂的基础设施细节对用户透明化。用户无需关心如何手动调度多个Pod,只需定义训练作业的规格,Kubeflow会自动处理。此外,Kubeflow Pipelines还能将上述训练步骤封装为一个流水线,方便重复运行和协作。
3.1.2 Volcano:高性能批处理调度器
Volcano是华为开源的一个高性能批处理调度系统,它构建在Kubernetes之上,旨在弥补Kubernetes在机器学习、深度学习、HPC(高性能计算)和大数据等场景中的调度能力不足。Volcano通过引入一系列增强特性,为Kubernetes提供了作业(Job)级的调度和管理能力。其主要特性包括:
Volcano主要特性:
- Gang Scheduling:Volcano实现了组调度,确保一个作业的所有Pod同时启动。这对于分布式训练尤为重要,可以避免部分调度导致的资源浪费。
- 任务队列管理:Volcano引入了**队列(Queue)**的概念,支持多租户和公平调度。作业可以被提交到不同的队列,每个队列可以配置权重和资源配额,Volcano会根据队列优先级和公平共享策略来调度作业。
- 任务优先级:支持为作业设置优先级,当资源不足时,高优先级的作业可以抢占低优先级作业的资源。
- 任务拓扑与GPU亲和:Volcano能够感知Pod之间的通信模式,将需要频繁通信的Pod调度到同一节点或同一网络域,以减少通信开销。同时,它支持GPU亲和性调度,将需要GPU的Pod调度到GPU节点,并优化GPU资源的分配。
- 作业生命周期管理:Volcano引入了Job资源(PodGroup),用于统一管理一组相关的Pod。它提供了公平调度、Bin-packing等调度策略,以及对作业失败重试、暂停恢复等生命周期的管理。
Volcano与Kubeflow可以协同工作。在上一节的示例中,通过设置schedulerName: volcano,Kubeflow创建的训练作业Pod将被Volcano调度器接管。Volcano会确保所有Pod作为一个组同时调度,并提供更丰富的调度策略。例如,它可以将多个训练作业按照队列进行排队,根据集群资源情况动态调整作业的并发度,从而提高资源利用率。
Volcano的架构包括一个中心调度器和每个节点上的调度代理。调度器扩展了Kubernetes调度器的框架,增加了新的调度插件,如公平调度插件、作业插件等。当Kubeflow创建TFJob时,Volcano会创建一个对应的PodGroup,然后调度器根据PodGroup的定义来调度所有Pod。Volcano还支持动态调度,即在作业运行过程中,根据资源情况动态调整作业的并行度或挂起/恢复作业。
总之,Volcano为Kubernetes注入了批处理调度的能力,使得大规模训练任务能够在Kubernetes上高效、可靠地运行。它解决了原生Kubernetes调度器在多Pod协同调度和资源公平分配方面的不足,是训练任务编排的重要补充。
3.2 Kubernetes在推理服务调度中的应用
推理服务的调度与部署涉及将训练好的模型以服务的形式运行,并根据请求负载进行弹性伸缩。Kubernetes提供了基础的部署和调度能力,但针对推理服务的特殊需求,需要结合模型服务框架和自动扩缩容机制来实现完整的解决方案。
3.2.1 模型服务框架:KServe与Triton
KServe(原KFServing)是Kubernetes原生的模型服务框架,旨在简化和标准化模型部署。它支持多种机器学习框架(TensorFlow、PyTorch、scikit-learn等)和多种部署方式(如TensorFlow Serving、Triton Inference Server、自定义服务器)。KServe通过Kubernetes Custom Resource Definition (CRD) 来定义模型服务,使得用户可以用声明式的方式部署模型。例如,用户可以创建一个InferenceService资源,指定模型存储位置、使用的推理服务器类型、计算资源需求等,KServe控制器会据此部署相应的Pod,并暴露服务端点。
KServe的一个关键优势是多框架支持和部署简易性。它内置了对TensorFlow Serving和Triton等高性能推理引擎的支持,并提供了统一的API。例如,要部署一个Triton服务器,用户只需在InferenceService中指定predictor为triton并提供模型仓库地址,KServe就会创建一个运行Triton的Deployment。Triton服务器会自动加载模型仓库中的模型,并提供HTTP/gRPC接口供客户端调用。
Triton Inference Server是NVIDIA开源的高性能推理服务器,支持多种模型格式(如TensorRT、ONNX、TensorFlow、PyTorch等)和多模型并发推理。它可以在单个GPU上同时运行多个模型,以最大化GPU利用率,并支持动态批处理、模型流水线等高级特性。Triton通过模型仓库(Model Repository)来管理模型,模型仓库可以是本地文件系统或云存储。当Triton启动时,它会加载模型仓库中的模型,并根据模型配置自动选择最优的推理引擎(如TensorRT加速TensorFlow模型)。Triton还提供了丰富的监控指标,可以通过Prometheus抓取,用于监控每个模型的请求延迟、吞吐量、GPU利用率等。
将KServe与Triton结合,可以实现生产级的模型服务部署。KServe负责模型部署的生命周期管理(如创建Deployment、Service、Ingress等),而Triton负责高性能的模型推理。下面是一个使用KServe部署Triton推理服务的示例:
- 准备模型仓库:将训练好的模型保存为Triton支持的格式,并放置在模型仓库目录中。例如,模型仓库可以放在对象存储或共享存储上。
- 创建InferenceService:编写一个
InferenceServiceYAML文件,指定模型仓库的位置、使用的推理服务器为Triton、暴露的端口等。例如:
1 | apiVersion: serving.kserve.io/v1beta1 |
上述配置会启动一个Triton服务器,加载s3://my-bucket/model-repository下的模型,并设置环境变量OMP_NUM_THREADS=1(用于控制Triton内部的线程数,以避免多线程竞争GPU资源)。KServe会自动创建一个Deployment和Service,将Triton服务暴露为ClusterIP或通过Ingress暴露外部访问。
- 服务调用:一旦服务就绪,客户端可以通过HTTP/gRPC调用模型推理接口。KServe会提供一个统一的API端点,客户端只需发送JSON请求,Triton会根据请求中的模型名称选择相应的模型进行推理。
通过KServe和Triton,我们可以快速在Kubernetes上部署一个高性能、可扩展的推理服务。KServe还支持金丝雀发布、自动模型压缩等高级特性,进一步提高了部署的灵活性和效率。
3.2.2 弹性伸缩与自动扩缩容
推理服务的负载通常具有突发性和波动性,因此需要根据实时负载动态调整服务实例数量。Kubernetes提供了**Horizontal Pod Autoscaler(HPA)**来实现Pod副本数的自动扩缩。默认的HPA基于CPU利用率指标,但对于推理服务,尤其是GPU密集型服务,我们需要更精细的指标来驱动扩缩容。
为了实现基于自定义指标的HPA,可以利用自定义指标适配器(Custom Metrics Adapter)将Prometheus等监控系统的指标注册到Kubernetes Metrics API中。具体步骤如下:
- 部署Prometheus监控:在集群中部署Prometheus,用于收集推理服务的指标数据。可以使用Prometheus Operator简化部署和管理。
- 部署自定义指标适配器:例如部署
k8s-prometheus-adapter,它会从Prometheus抓取指标,并将其暴露为Kubernetes Metrics API的一部分。这样,HPA就可以使用这些指标了。 - 配置HPA:创建一个HPA资源,指定目标推理服务的Deployment,并设置基于自定义指标的扩缩容策略。例如,可以配置当
http_requests_per_second超过阈值时扩容,或当GPU利用率超过阈值时扩容。
下面是一个基于GPU利用率指标进行扩缩容的HPA示例:
1 | apiVersion: autoscaling/v2 |
在这个例子中,HPA会监控每个Pod的GPU利用率(假设通过NVIDIA DCGM Exporter采集并上报给Prometheus,然后通过适配器注册为duty_cycle_current指标)。当所有Pod的平均GPU利用率超过40%时,HPA会触发扩容,增加Pod副本数,直到利用率降至目标值或达到最大副本数。
图1:HPA触发扩容过程GPU利用率变化。图中展示了当平均GPU利用率超过40%阈值(虚线)时,HPA开始扩容,随着新Pod就绪,集群整体利用率逐渐回落至目标值。
通过上述机制,推理服务可以根据实际负载自动调整规模,既能在高负载时及时扩容以保证服务性能,又能在低负载时缩容以节省资源。这种弹性伸缩能力是推理服务调度的重要组成部分。
3.3 离线混部的实现方式
实现离线混部的关键在于区分并隔离不同类型的负载,以及在调度层面给予它们不同的待遇。Kubernetes原生提供了一些机制(如PriorityClass、PodDisruptionBudget等)来支持混部,但为了达到生产级的混部效果,需要更精细的控制。Koordinator项目正是在这方面提供了系统性的解决方案。
3.3.1 Kubernetes原生QoS模型及其局限
Kubernetes通过**服务质量(QoS)**类来为Pod分配资源保障级别。当一个节点资源不足时,Kubernetes会根据QoS级别决定OOM(内存溢出)时首先杀死哪些Pod。QoS类包括:
- Guaranteed:Pod中的每个容器都设置了CPU和内存的请求(request)和限制(limit),且两者相等。这类Pod获得最高资源保障,只有在节点内存不足且无更低QoS的Pod可驱逐时,才会被OOM杀死。
- Burstable:Pod至少有一个容器设置了CPU或内存请求,但没有达到Guaranteed的要求。这类Pod在资源不足时可能被部分限制,但通常不会立即被杀死。
- BestEffort:Pod没有设置任何CPU或内存请求/限制。这类Pod在资源紧张时优先被驱逐或杀死。
原生QoS模型在区分不同重要性的Pod方面有一定作用,但存在局限:
- 它主要针对内存资源,当内存不足时触发OOM,对CPU等其他资源的隔离不够精细。
- 对于在线服务与批处理任务混合场景,简单的三分类不足以表达复杂的服务质量需求。例如,我们可能希望将延迟敏感的服务和CPU密集型的批处理任务区分开,而原生模型将两者都归为Burstable或Guaranteed。
- 没有直接与调度关联。原生QoS更多是影响运行时OOM行为,调度器并不会根据QoS级别做特别的调度决策。
3.3.2 Koordinator:基于QoS的混部调度系统
Koordinator是阿里巴巴开源的云原生混部调度系统,它在Kubernetes原生能力基础上进行了扩展,提供了端到端的混部解决方案。Koordinator的核心思想是引入精细化的QoS模型和资源超卖机制,在保证在线服务性能的同时,充分利用闲置资源运行批处理任务。
Koordinator定义了五种QoS类别,从高到低依次为:
- SYSTEM:系统服务,如DaemonSet。这类服务虽然重要,但需要限制其资源使用,避免占用过多节点资源。
- LSE(Latency Sensitive Exclusive):独占型延迟敏感服务。这类服务需要独占某些资源(如CPU核),不与其他服务共享,以确保极致的性能隔离。
- LSR(Latency Sensitive Reserved):预留型延迟敏感服务。这类服务在节点上预留一定资源,其他服务不能抢占,但可以共享未使用的部分。
- LS(Latency Sensitive):共享型延迟敏感服务。典型的在线服务属于此类,它在节点上与其他服务共享资源,但在资源竞争时拥有较高优先级。
- BE(Best Effort):尽力而为服务。典型的批处理任务属于此类,它在资源竞争时优先级最低,可以被抑制或驱逐。
Koordinator通过在Pod的Annotation中标注QoS类和优先级,让调度器和节点代理识别并采取相应策略。例如,一个批处理任务Pod会被标注为QoS=BE,PriorityClass=koord-batch(Koordinator预定义的批处理优先级类,值为5000)。而一个在线服务Pod会被标注为QoS=LS,PriorityClass=koord-prod(生产级优先级,值为9000)。
资源超卖是Koordinator提高利用率的关键机制。它通过监控节点的实际资源使用情况,动态计算可回收资源。例如,一个节点上运行着多个在线服务Pod,它们的资源请求总量是10核CPU,但实际使用可能只有5核。Koordinator会将这“闲置”的5核以批处理资源的形式对外提供。其他批处理任务Pod可以请求这些超卖资源(通过自定义资源类型如koordinator.sh/batch-cpu),从而在不新增物理资源的情况下运行更多任务。
图2:Koordinator资源超卖示意图。图中对比了节点的CPU资源总量、在线服务请求量与实际使用量,实际使用量以上的区域即为可被Koordinator回收用于批处理任务的超卖资源。
当在线服务的实际使用上升时,Koordinator会压制批处理任务,回收部分超卖资源。这种压制可以通过调整cgroup参数实现,例如限制BE类Pod的CPU配额或将其调度到特定CPU核上,从而让出CPU给LS类Pod使用。如果在线服务持续高负载,Koordinator甚至可以驱逐部分BE Pod,以确保LS服务不受影响。
Koordinator的架构由多个组件协同工作:
Koordinator核心组件:
- Koord-Scheduler:一个扩展的Kubernetes调度器,实现了QoS感知调度和负载感知调度。它会根据Pod的QoS类和节点当前负载来决策调度,避免将BE Pod调度到高负载节点。同时,它支持优先级抢占,当高优先级Pod无法调度时,可以抢占低优先级Pod的资源。
- Koord-Descheduler:一个增强的驱逐器,实现了基于负载的驱逐。它会定期检查节点负载,如果某个节点负载过高,会尝试驱逐部分BE Pod,以降低节点负载。
- Koord-Manager:一个控制器管理器,包含多个控制器,如Colocation Profile Controller(用于自动为批处理Pod注入混部标签和注解)、SLO Controller(用于根据节点负载动态调整超卖比例和水位线)、Recommender Controller(用于分析Pod的资源使用模式并推荐合适的request/limit)。
- Koordlet:一个在每个节点上运行的代理,类似于kubelet。它负责实时监控节点资源使用、调整Pod资源隔离参数、干扰检测等。Koordlet会根据当前节点的负载情况,动态调整BE Pod的CPU配额,或者在检测到LS Pod受到干扰时,采取措施压制或驱逐BE Pod。
- Koord-RuntimeProxy:一个可选组件,用于在容器运行时层面拦截kubelet对容器cgroup的修改,从而实现更精细的资源隔离策略。
通过这些组件的协同,Koordinator构建了一个闭环的混部调度系统:调度器负责初始的资源分配,节点代理负责运行时的资源隔离和调整,监控数据驱动着整个系统的动态优化。
3.3.3 实践:使用Koordinator实现批处理任务混部
要使用Koordinator,首先需要在Kubernetes集群上安装它。Koordinator提供了Helm Chart,可以一键部署所有组件。安装完成后,用户需要为集群中的GPU节点打上特定标签和污点,以便Koordinator识别和管理这些节点。
接下来,用户可以按照以下步骤将一个批处理任务以混部方式运行:
- 创建命名空间并启用混部:创建一个命名空间(如
batch-demo),并为其添加标签koordinator.sh/enable-colocation=true,表示该命名空间下的Pod将参与混部调度。 - 创建ClusterColocationProfile:这是一个全局配置资源,用于定义自动注入规则。例如,可以创建一个Profile,匹配所有带有标签
app-type=batch的Pod,将它们的QoS设置为BE,PriorityClass设置为koord-batch,并指定调度器为koord-scheduler。这样,用户无需手动修改Pod规格,Koordinator会自动将普通Pod转换为混部Pod。 - 提交批处理任务:用户可以像往常一样提交一个Job或Deployment,只需在Pod模板中添加标签
app-type=batch。Koordinator的Webhook会拦截该Pod创建请求,并自动注入必要的注解和调度器名称。例如,它会将Pod的资源请求从cpu: "2"转换为koordinator.sh/batch-cpu: "2000",表示请求2000毫核的批处理CPU资源。 - 监控与验证:Koordinator提供了丰富的监控指标和UI。用户可以通过Prometheus监控节点的超卖资源、BE Pod的资源使用等。也可以通过kubectl查看Pod的注解,确认其QoS和优先级已正确设置。
通过Koordinator,批处理任务能够在不影响在线服务的情况下,利用集群闲置资源运行。当在线服务负载上升时,Koordinator会自动限制批处理任务的资源使用,甚至驱逐部分任务,以确保服务SLA。这种动态调整能力使得混部成为一种安全、高效的资源利用方式。
当然,混部并非没有代价。它增加了调度和资源管理的复杂度,需要对应用的行为有深入理解,并精心配置QoS和优先级策略。但总体而言,Koordinator等混部技术的出现,大幅降低了用户实施混部的门槛,使更多组织能够享受到资源利用率提升带来的成本节约。
第4章 案例与实践
本章通过几个典型案例,将前文介绍的原理和技术付诸实践,帮助读者更直观地理解如何在实际场景中应用这些知识。
4.1 案例一:基于Kubeflow和Volcano的分布式训练任务编排
背景:某研发团队需要在Kubernetes上训练一个大型TensorFlow模型,训练过程包括数据预处理、模型训练、超参调优等多个步骤。他们希望有一个统一的平台来管理整个工作流,并确保分布式训练作业能够可靠、高效地运行。
方案:采用Kubeflow构建端到端的机器学习工作流,并使用Volcano作为底层调度器。
步骤:
- 部署Kubeflow:使用kfctl在Kubernetes集群上部署Kubeflow。这将安装JupyterHub、TensorFlow Operator、Kubeflow Pipelines等组件。
- 定义训练流水线:在Jupyter Notebook中,使用Kubeflow Pipelines SDK定义一个流水线,包括数据准备、模型训练和模型评估三个步骤。数据准备步骤可以使用TensorFlow Transform处理原始数据;模型训练步骤使用TFJob运行分布式TensorFlow训练;模型评估步骤使用一个简单的Python脚本来评估模型准确率。
- 配置Volcano:在Kubeflow部署时,配置TensorFlow Operator使用Volcano调度器。这通常通过在TFJob的Pod模板中设置
schedulerName: volcano来实现。此外,可以为Volcano配置一个队列,将该训练作业关联到该队列,以便控制其资源配额。 - 运行流水线:提交流水线运行。Kubeflow Pipelines会按照定义的DAG顺序执行各步骤。当运行到训练步骤时,TensorFlow Operator会创建TFJob资源,Volcano调度器接管Pod的调度。
- 监控与调优:通过Kubeflow UI监控流水线运行状态和TFJob的训练进度。可以查看每个Pod的日志、TensorBoard等。如果发现训练过程中某些步骤耗时过长,可以考虑使用Katib进行超参调优,或者调整Volcano队列的权重以获得更多资源。
结果:该团队成功在Kubernetes上运行了一个复杂的训练工作流。Volcano确保了训练作业的所有Pod同时启动,避免了资源浪费。Kubeflow Pipelines提供了清晰的流水线视图,方便团队协作和复现。通过将Volcano与Kubeflow结合,他们实现了自动化、可扩展的模型训练流程。
4.2 案例二:基于KServe和Triton的推理服务部署与自动扩缩
背景:某在线服务公司需要将一个深度学习模型部署为推理服务,要求能够处理每秒数千次请求,并在流量高峰时自动扩容以保持低延迟响应。模型存储在对象存储上,团队希望简化部署流程。
方案:使用KServe部署Triton推理服务器,并结合Prometheus和HPA实现自动扩缩。
步骤:
- 部署KServe和Triton:首先在Kubernetes集群上安装KServe(通常通过Helm)。然后,准备模型仓库。模型已经转换为Triton格式,存储在S3存储桶中。创建一个
InferenceServiceYAML,指定Triton服务器和模型仓库路径。KServe会创建一个Deployment和Service,自动加载模型。 - 配置服务暴露:由于需要外部访问,配置一个Ingress或LoadBalancer服务,将Triton服务暴露出来。KServe支持与Istio集成,可以提供更高级的路由和流量管理。
- 设置监控:部署Prometheus监控Triton服务。Triton暴露了丰富的指标(如每秒请求数、请求延迟、GPU利用率等)。配置Prometheus抓取这些指标。
- 配置HPA:创建一个HPA资源,目标为KServe创建的Deployment。使用自定义指标驱动扩缩容。例如,可以使用
nv_inference_request_duration_us(推理请求延迟)或nv_gpu_utilization(GPU利用率)作为扩缩容指标。假设选择GPU利用率,HPA会根据GPU利用率动态调整Pod副本数。 - 压力测试与调优:使用压测工具模拟流量,观察HPA的扩容行为和服务的延迟表现。如果发现扩容不及时,可以调整HPA的冷却时间或扩容策略。如果发现GPU资源成为瓶颈,可以考虑增加GPU节点或使用TensorRT进一步优化模型推理性能。
结果:该推理服务成功部署并表现出良好的弹性。在流量高峰期,HPA自动将Pod副本数从1扩展到10,确保了请求延迟保持在可接受范围。Triton服务器稳定运行,处理了数百万次推理请求而未出现性能瓶颈。通过KServe,团队后续更新模型也非常方便,只需更新模型仓库并重启服务即可。
4.3 案例三:离线混部场景下的资源调度优化
背景:某企业拥有一个运行在线服务的Kubernetes集群,平均CPU利用率仅为20%。为了降低成本,他们希望在不增加硬件的情况下,利用闲置资源运行一些批处理任务。但他们担心批处理任务会干扰在线服务性能。
方案:引入Koordinator实施离线混部,将批处理任务与在线服务混合部署。
步骤:
- 评估与规划:首先评估集群中在线服务的资源需求和实际使用情况,确定哪些资源可以用于混部。例如,发现每个节点平均有30%的CPU资源闲置。
- 安装Koordinator:按照官方文档部署Koordinator组件。为所有节点打上标签
koordinator.sh/colocation=true,并为GPU节点(如果有)打上相应的污点和标签。 - 定义混部策略:创建一个ClusterColocationProfile,将所有带有标签
workload=batch的Pod自动设置为QoS=BE、PriorityClass=koord-batch。同时,将所有在线服务Pod的QoS设置为LS,PriorityClass设置为koord-prod。 - 提交批处理任务:将原本计划在单独集群运行的大数据处理任务,以Deployment形式提交到混部集群。在Pod模板中添加标签
workload=batch。Koordinator会自动将其调度到合适的节点,并限制其资源使用。 - 监控与调整:通过Koordinator提供的监控面板,观察在线服务的性能指标(如响应时间)和批处理任务的进度。如果发现在线服务性能下降,可以调整Koordinator的水位线参数,降低超卖比例,或者更严格地限制批处理任务的资源使用。
结果:在混部初期,批处理任务成功利用了集群闲置资源,将集群平均CPU利用率提升至50%左右,而在线服务的响应时间并未出现明显波动。Koordinator在某次在线服务流量突增时,自动驱逐了部分批处理任务Pod,确保了服务SLA。经过一段时间的运行和调优,该企业成功在不增加硬件投入的情况下,完成了大量批处理计算任务,大幅节省了成本。
图3:离线混部前后集群平均CPU利用率对比。实施混部后,通过利用闲置资源运行批处理任务,集群平均CPU利用率从20%显著提升至50%,有效提高了资源使用效率。
第5章 总结与展望
本手册系统地讲解了在Kubernetes平台上编排大规模训练任务和推理服务,以及实施离线混部和资源调度的相关技术。我们从Kubernetes调度基础出发,深入分析了训练任务、推理服务和混部场景下的特殊需求,并介绍了Kubeflow、Volcano、KServe、Koordinator等主流工具的原理和使用方法。通过案例实践,读者可以看到这些技术如何在真实场景中落地,解决实际问题。
总结而言:
- Kubernetes作为容器编排平台,为AI工作负载提供了统一的基础,但其默认调度器需要通过扩展和增强来满足复杂场景的需求。
- 训练任务编排需要解决原子调度、作业生命周期管理等问题,Kubeflow和Volcano分别从应用层和调度层提供了有力支持。
- 推理服务调度强调性能和弹性,通过模型服务框架(如KServe)简化部署,并结合监控和自动扩缩容技术实现动态调整。
- 离线混部通过精细的QoS和资源超卖机制,在不影响在线服务的前提下,大幅提升资源利用率,Koordinator等项目为此提供了系统级解决方案。
展望未来:
随着云原生技术和AI的进一步融合,我们预计会出现更多创新:
- 统一调度框架:社区正在探索将批处理调度、混部调度等能力纳入Kubernetes核心调度器框架。例如,Kubernetes v1.35引入了工作负载感知调度,通过
SchedulingWorkload等新API,将一组Pod视为一个整体进行调度决策。这标志着Kubernetes调度器正朝着以工作负载为中心的方向演进,有望原生支持类似Gang Scheduling的能力。 - 更强的隔离与公平性:未来的调度器可能会集成更多操作系统级的隔离技术,如CPU拓扑感知调度、内存带宽隔离、网络QoS等,以确保不同工作负载在共享资源时的公平性和性能隔离。
- 自动化与智能化:机器学习技术将更多地应用于调度决策本身,例如根据历史负载模式自动调整资源分配策略,或智能地预测作业完成时间以优化调度顺序。
- 多云与混合云:随着企业采用多云架构,调度系统需要跨云协同调度工作负载。这可能催生新的调度框架,能够统一管理分布在不同云环境中的资源,并根据成本、延迟等因素智能放置工作负载。
总之,大规模AI工作负载的编排与调度是一个快速发展的领域。通过掌握本手册介绍的知识,读者已经站在了这一领域的前沿。在实际工作中,建议持续关注相关开源社区的动态,如Kubernetes sig-scheduling、Kubeflow、Koordinator等,不断学习新的特性和最佳实践。同时,结合自身的业务特点,勇于尝试和创新,才能真正将技术转化为生产力,为AI应用构建一个高效、稳定、智能的运行平台。





