Kubernetes-生成式人工智能解决方案-全-
Kubernetes 生成式人工智能解决方案(全)
原文:
annas-archive.org/md5/d9e3760bfd4fdc2675fe5ee5a2f00a1b
译者:飞龙
前言
生成式 AI(GenAI)正在革新组织构建智能系统的方式,使机器能够创建内容、代码、文本、图像等。随着大规模 AI 应用需求的不断增长,Kubernetes(K8s)已成为管理这些工作负载的事实标准平台,具有可扩展性、韧性和高效性。
本书提供了一本全面的实用指南,介绍如何在 Kubernetes 上使用 Amazon EKS 及其他开源和云原生工具来构建、部署、监控和扩展 GenAI 应用程序。从基础概念到高级 GPU 优化,本书通过实践示例、部署模式和行业最佳实践,全面讲解了 GenAI 项目的生命周期。
本书适合哪些人群
本书适合以下人群:
-
解决方案架构师
-
工程领导者
-
DevOps 工程师
-
GenAI 开发者
-
产品经理
-
探索 GenAI 和 Kubernetes 的学生和研究人员
你应该对云计算和 AI/ML 概念有基本的了解。不需要具备 Kubernetes 的前期经验——本书通过现实世界的示例提供了渐进式的学习曲线。
本书内容涵盖了什么
第一章,生成式 AI 基础知识,概述了生成式 AI(GenAI)的基础知识,涵盖了与传统 AI 的区别、机器学习从 CNNs/RNNs 到变换器(transformers)的演变,概述了 GenAI 项目的生命周期,并探索了各行各业的应用。
第二章,Kubernetes 与 GenAI 的介绍与集成,探讨了大规模运行 GenAI 工作负载的挑战,以及为什么容器化和 Kubernetes 是理想的解决方案。你将构建并在本地运行你的第一个 GenAI 容器镜像。
第三章,在云中开始使用 Kubernetes,介绍了基于云的 Kubernetes 部署选项,并提供了逐步指导,教你如何使用 Terraform 设置 Amazon EKS 并部署一个 LLM 模型。
第四章,针对特定领域应用的 GenAI 模型优化,深入探讨了如何优化通用 GenAI 模型以适应领域特定的应用,重点介绍了 检索增强生成(RAG)、微调以及 LangChain 的使用,以提高聊天机器人和个性化推荐等任务的性能和效率。
第五章,在 K8s 上使用 GenAI——以聊天机器人为例,展示了如何在 Kubernetes 上使用 Amazon EKS 部署和微调 GenAI 模型,具体示例包括设置 Jupyter Notebook 进行实验、微调 Llama 3 模型,以及部署一个基于 RAG 的聊天机器人,用于个性化电商推荐。
第六章,在 Kubernetes 上扩展 GenAI 应用程序,探讨了 Kubernetes 应用程序的各种扩展策略和最佳实践,重点关注使用 HPA、VPA、KEDA、Cluster Autoscaler 和 Karpenter 实现高效的资源利用和最佳性能。
第七章,在 Kubernetes 上优化 GenAI 应用程序的成本,探讨了在 Kubernetes 上优化 GenAI 应用程序成本的策略,重点关注计算、存储和网络。强调资源的合理配置、高效的存储管理和网络最佳实践,并介绍了如 Kubecost 和 Goldilocks 等工具,用于监控和优化资源利用。
第八章,在 K8s 上部署 GenAI 的网络最佳实践,涵盖了 CNI、Kubelet 和 CRI 等关键网络组件,叠加和原生网络方法,并深入探讨了如 Service Mesh 和 NetworkPolicy 等高级功能,确保网络性能的安全、高效和可扩展性。
第九章,在 Kubernetes 上部署 GenAI 的安全最佳实践,提供了一个全面的框架,通过深度防御方法保护 Kubernetes 上的 GenAI 应用程序,涵盖了包括供应链、主机、网络和运行时安全等关键安全领域,同时还涉及了机密管理最佳实践和 IAM。
第十章,在 Kubernetes 中优化 GenAI 应用程序的 GPU 资源,探讨了在 Kubernetes 中最大化 GPU 效率的策略,讨论了 GPU 资源管理、MIG 和 MPS 等分区技术、GPU 监控以及自动扩展解决方案,以优化性能和成本。
第十一章,GenAIOps——数据管理和 GenAI 自动化流水线,介绍了 GenAIOps,详细说明了部署、监控和优化 GenAI 模型的工具和工作流程,重点关注数据管理、隐私保护、偏见缓解和持续的模型监控。
第十二章,可观察性——在 K8s 上查看 GenAI,解释了在 Kubernetes 上监控 GenAI 应用程序时可观察性的重要性,详细说明了如何使用 Prometheus、Grafana 和 NVIDIA DCGM 构建一个强大的监控框架,以提供实时洞察和调试功能。
第十三章,GenAI 应用程序的高可用性和灾难恢复,探讨了 Kubernetes 上 GenAI 应用程序的高可用性和灾难恢复策略,详细说明了能够在故障期间实现自动扩展和持续服务的架构模式。它涵盖了冗余方法、关键指标(RPO、RTO 和 MTD)以及从备份恢复到多区域部署的实施策略。
第十四章,总结:GenAI 编程助手与进一步阅读,重点介绍了像 Amazon Q Developer、GitHub Copilot 和 Google Gemini Code Assist 等 GenAI 编程助手,在自动化 IaC、优化工作负载和管理 Kubernetes 集群方面的变革性影响。它还讨论了 AI 驱动的安全性、成本效率和可扩展性改进,并提供了进一步阅读资源,帮助掌握 Kubernetes 和 GenAI 工具。
为了最大限度地利用本书内容
如果你熟悉基础的编程(优先选择 Python)、云计算基础以及机器学习的概念,你将从本书中获益最多。不需要深入的 Kubernetes 经验,因为本书会详细介绍设置和配置步骤。
本书中涉及的 软件/硬件 | 操作系统要求 |
---|---|
操作系统 | Linux、macOS、Windows(通过 WSL) |
Kubernetes | Amazon EKS、kind(用于本地测试) |
AI/ML 框架 | Hugging Face Transformers、PyTorch、TensorFlow |
加速器 | NVIDIA GPUs、AWS Trainium/Inferentia |
可观测性 | Prometheus、Grafana、OpenTelemetry、Loki |
自动化 | Kubeflow、MLflow、Ray、Argo Workflows |
安全工具 | OPA、Kyverno |
你将需要以下内容:
-
一个具有足够配额的 AWS 账户用于 EC2 实例(尤其是 GPU 节点)
-
基本的 CLI 工具(kubectl、eksctl、Terraform 和 Helm)
-
一个 Hugging Face 账户用于访问模型
如果你正在使用本书的数字版,我们建议你自己输入代码,或从本书的 GitHub 仓库访问代码(下一个部分将提供链接)。这样做将帮助你避免与复制和粘贴代码相关的潜在错误。
下载示例代码文件
你可以从 GitHub 下载本书的示例代码文件,网址是github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions
。如果代码有更新,GitHub 仓库中会更新相应内容。
我们还有其他代码包,来自我们丰富的图书和视频目录,您可以在github.com/PacktPublishing/
找到。快来看看吧!
使用的约定
本书中使用了许多文本约定。
代码文本中的代码
:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入以及 X/Twitter 账号。例如:“可以通过向 K8s 服务添加service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
注解来实现,如下所示:”
代码块的设置如下:
...
metadata {
name = "gp2"
...
当我们希望将注意力引向代码块的特定部分时,相关的行或项会以粗体显示:
metadata {
name = "gp3"
annotations = {
"storageclass.kubernetes.io/is-default-class": "true"
...
所有命令行输入或输出如下所示:
$ terraform init
$ terraform plan
$ terraform apply -auto-approve
粗体:表示新术语、重要单词或屏幕上出现的词汇。例如,菜单或对话框中的词汇通常以粗体显示。以下是一个示例:“在 Kubecost UI 控制台中,选择左侧菜单的Savings,查看成本节省建议。”
提示或重要说明
以此方式显示。
与我们联系
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何内容有疑问,请通过 customercare@packtpub.com 给我们发邮件,并在邮件主题中注明书名。
勘误:尽管我们已尽力确保内容的准确性,但错误仍可能发生。如果您在本书中发现任何错误,敬请向我们报告。请访问 www.packtpub.com/support/errata 并填写表格。
盗版:如果您在互联网上发现任何我们作品的非法复制品,恳请您提供该地址或网站名称。请通过 copyright@packt.com 联系我们,并附上该资料的链接。
如果您有兴趣成为作者:如果您在某个领域拥有专业知识,且有兴趣撰写或参与书籍的编写,请访问 authors.packtpub.com。
分享您的想法
在阅读完 Kubernetes for Generative AI Solutions 后,我们希望听到您的反馈!请点击这里直接访问亚马逊评价页面,分享您的想法。
您的评价对我们以及技术社区非常重要,能够帮助我们确保提供高质量的内容。
在云计算和 DevOps 领域保持敏锐——加入超过 44,000 名 CloudPro 订阅者
CloudPro 是面向云计算专业人员的每周新闻通讯,帮助他们跟上快速发展的云计算、DevOps 和基础设施工程领域的最新动态。
每一期都提供针对性强的高价值内容,涵盖以下主题:
-
AWS、GCP 和多云架构
-
容器、Kubernetes 和编排
-
使用 Terraform、Pulumi 等实现基础设施即代码(IaC)
-
平台工程与自动化工作流
-
可观测性、性能调优和可靠性最佳实践
无论您是云工程师、SRE、DevOps 从业者,还是平台负责人,CloudPro 帮助您在不被杂音干扰的情况下,专注于重要内容。
扫描二维码免费加入并每周获取直达您收件箱的洞察内容:
下载本书的免费 PDF 版本
感谢您购买本书!
您喜欢随时阅读,但又无法随身携带纸质书籍吗?
您购买的电子书与您选择的设备不兼容吗?
不用担心,现在购买每本 Packt 图书时,您都可以免费获得该书的无 DRM PDF 版本。
在任何地方、任何设备上阅读。直接从你最喜欢的技术书籍中搜索、复制和粘贴代码到你的应用程序中。
优惠不仅如此,你还可以获得独家折扣、新闻简报和每日送到邮箱的精彩免费内容。
按照以下简单步骤获取福利:
- 扫描二维码或访问以下链接
packt.link/free-ebook/9781836209935
-
提交你的购买凭证
-
就是这样!我们将把你的免费 PDF 和其他福利直接发送到你的邮箱。
第一部分:GenAI 和 Kubernetes 基础
本节介绍了生成性 AI(GenAI)的基础知识,追溯其从传统神经网络到变换器(transformers)的发展历程,并概述了完整的 GenAI 项目生命周期。它探讨了容器和 Kubernetes 如何解决 GenAI 工作负载的挑战,并提供了在云端开始使用 Kubernetes 的指南。
本部分包含以下章节:
-
第一章, 生成性 AI 基础
-
第二章, Kubernetes – 与 GenAI 的介绍与集成
-
第三章, 在云端开始使用 Kubernetes
第一章:生成式人工智能基础
生成式人工智能 (GenAI) 自从 2022 年 11 月 OpenAI 推出 ChatGPT 后,已经彻底改变了我们的世界,并吸引了每个人的关注(openai.com/index/chatgpt/
)。然而,这项技术的基础概念已经存在了相当长一段时间。在本章中,我们将介绍 GenAI 的关键概念及其如何随着时间的推移而发展。然后,我们将讨论如何思考一个 GenAI 项目,并将其与业务目标对齐,涵盖整个开发和部署 GenAI 工作负载的过程,以及在不同行业中的潜在应用场景。
在本章中,我们将涵盖以下主要主题:
-
人工智能与 GenAI 的区别
-
机器学习的演进
-
Transformer 架构
-
GenAI 项目生命周期
-
GenAI 部署堆栈
-
GenAI 项目应用场景
人工智能与 GenAI 的区别
在深入了解 GenAI 概念之前,让我们讨论一下 人工智能 (AI)、机器学习 (ML)、深度学习 (DL)、和 GenAI 之间的区别,因为这些术语经常被交替使用。
图 1.1 显示了这些概念之间的关系。
图 1.1 – AI、ML、DL 与 GenAI 之间的关系
让我们更多地了解这些关系:
-
AI:AI 是指能够执行通常需要人类智能的任务的系统或算法。这些任务包括推理、学习、问题解决、感知和语言理解。AI 是一个广泛的类别,可能包括基于规则的系统、专家系统、神经网络和 GenAI 算法。AI 算法的发展使机器具备了类人感官和能力,如通过视觉分析周围世界,通过听觉和语言理解与人类交流,并利用传感器数据理解外部环境并作出响应。
-
ML:ML 是 AI 的一个子集,涉及使机器能够从数据中学习并进行预测的算法和模型,而不需要显式编程。在传统编程中,开发人员为计算机编写明确的指令来执行任务,而在 ML 中,算法从数据中的模式和关系中学习并进行预测。ML 还可以进一步细分为以下子类别:
-
监督学习:这是使用带标签的数据集来训练模型。它可以进一步细分为分类问题和回归问题:
-
分类问题 使用带标签的数据,例如带标签的狗猫图片,用来训练模型。模型训练完成后,可以使用其训练过的类别来分类用户提供的图片。
-
回归问题,另一方面,使用数值数据来理解依赖变量和自变量之间的关系,例如根据不同属性预测房价。一旦模型建立了这种关系,它就可以预测不同属性集的价格,即使该模型没有在这些特定属性上进行过训练。一些流行的回归算法包括线性回归、逻辑回归和多项式回归。
-
-
无监督学习:这类算法利用机器学习分析和聚类未标注的数据集,以发现数据中的潜在模式。无监督学习可以进一步细分为以下两类:
-
聚类算法根据相似性或差异性对数据进行分组。一种流行的聚类算法是k 均值聚类算法,该算法使用数据点之间的欧几里得距离来衡量数据点之间的相似性,并将它们分配到k个不同且不重叠的聚类中。它通过迭代来精细化聚类,最小化每个聚类内部的方差。一个典型的应用场景是根据购买行为、人口统计特征或偏好对客户进行细分,从而有效地制定营销策略。
-
降维是另一种无监督学习方法,用于减少给定数据集中的特征/维度数量。它旨在简化模型,降低计算成本,并提高整体模型性能。主成分分析(PCA)(
towardsdatascience.com/a-one-stop-shop-for-principal-component-analysis-5582fb7e0a9c
) 是一种常用的降维算法。它通过寻找一组新的特征,称为主成分,这些主成分是原始特征的组合,并且相互之间不相关,从而实现降维。
-
-
半监督学习:这是一种结合监督学习和无监督学习的机器学习方法,通过利用标注数据和未标注数据进行训练。当获取标注数据既费时又昂贵时,这种方法尤为有用,因为你可以使用少量标注数据进行训练,然后反复将其应用于大量未标注数据。这可以应用于分类和回归问题,如垃圾邮件/图像/物体检测、语音识别和预测等。
-
强化学习:在强化学习中,有一个代理和奖励系统,算法通过反复试验来学习最大化代理的奖励。代理是一个自主系统,比如计算机程序或机器人,能够做出决策并根据其环境采取行动,而无需直接的人工指令。奖励是当代理的行为带来积极结果时,环境给予的反馈。例如,如果我们想训练一个机器人走路而不摔倒,当机器人做出有助于保持直立的动作时,会给予正向奖励,而做出导致摔倒的动作时,会给予负向奖励。机器人首先随机尝试不同的动作,比如向前倾、移动腿或改变体重。随着它执行这些动作,它观察到其状态的变化。机器人利用反馈(奖励)来更新其对哪些动作有益的理解,从而随着时间推移学会走路。
我们在图 1.2中总结了机器学习的不同类别:
-
图 1.2 – 机器学习的不同类别
-
深度学习(DL):深度学习是机器学习(ML)的一个子集,涉及具有多层的深度神经网络。从概念上讲,它的灵感来源于人类大脑,大脑拥有数十亿个深度连接的神经元,赋予人类非常先进的认知能力。一些流行的深度神经网络例子包括卷积神经网络(CNNs),用于图像处理,以及递归神经网络(RNNs),用于分析时间序列数据或自然语言处理。
-
生成式人工智能(GenAI):生成式人工智能是深度学习(DL)的一个进一步子集,专注于创建新数据,如文本、图像、音乐和其他形式的内容。许多生成应用基于基础模型(FMs),即在大量多样化数据上训练的大规模 AI 模型,作为广泛下游任务的基础。它们在广泛的数据集上预训练,并可以针对特定应用进行微调。大规模语言模型(LLMs)是 FMs 的一个子集,专门设计用于理解和生成自然语言。生成式人工智能是本书的主要重点;我们将在本书后续部分深入探讨其细节。
现在我们已经理解了 AI、ML、DL 和生成式人工智能之间的关键区别,让我们来探讨一下机器学习的演变,以及变压器架构如何彻底改变了机器学习的格局,特别是在自然语言处理(NLP)领域。
机器学习的演变
由于本书是关于生成式人工智能的,那开始时请 ChatGPT 总结过去十年 AI 和机器学习(ML)发展的演变岂不更好?
"为什么鸡要过马路?"
描述过去十年间,AI/ML 是如何演化出该问题的答案的。
ChatGPT 回应(ChatGPT-4o,2024 年 6 月 16 日):
The evolution of AI/ML responses to the question "Why did the chicken cross the road?" over the past decade reflects significant advancements in language processing and understanding. Here's a detailed description of how these responses have evolved:
Early 2010s: Rule-Based Systems
Response Type: Traditional, rule-based
Example Answer: "To get to the other side."
Description: In the early 2010s, AI systems were primarily rule-based. These systems relied on pre-defined patterns and rules coded by developers. When asked "Why did the chicken cross the road?" the AI would output the standard punchline "To get to the other side" because it was programmed to recognize this classic joke and respond accordingly. The response lacked creativity and was limited to what was explicitly coded into the system.
Mid 2010s: Statistical and Early Machine Learning Models
Response Type: Basic machine learning-generated variations
Example Answer: "To find some food on the other side."
Description: As machine learning techniques began to develop, early models could generate responses based on statistical patterns in the data they were trained on. These models, such as simple neural networks or early natural language processing (NLP) algorithms, could create variations of the joke. However, the answers were still relatively straightforward and sometimes nonsensical, as these models had limited understanding of context and nuance.
Late 2010s: Advanced Neural Networks and NLP
Response Type: Context-aware and more sophisticated
Example Answer: "Because it wanted to explore the other side of the road."
Description: The late 2010s saw the rise of advanced neural networks and NLP (natural language processing) techniques, such as LSTM (Long Short-Term Memory) networks and early transformer models. These models had a better grasp of context and could generate more sophisticated responses. They could recognize the structure of the joke but also provide contextually appropriate variations that made sense and were more engaging.
Early 2020s: Transformer-Based Models
Response Type: Highly fluent and creative
Example Answer: "To escape from the boredom of farm life and seek adventure!"
Description: With the advent of transformer-based models like GPT-3 and BERT, AI's ability to generate human-like text improved dramatically. These models, trained on vast amounts of internet text, could produce responses that were not only fluent but also creative and contextually rich. They could understand humor better and generate answers that went beyond the traditional punchline, incorporating more complex wordplay or humor styles.
Present Day: LLMs and Personalization
Response Type: Personalized and contextually relevant
Example Answer: "Because its AI model predicted a 95% chance of finding a better grain on the other side."
Description: Today's LLMs, such as GPT-4, are capable of highly personalized and contextually aware responses. They can take into account previous conversational history, the user's preferences, and cultural context. The AI might generate responses that are tailored to the individual user, offering a wide range of humorous and inventive answers that reflect a deep understanding of language and context. This level of personalization and creativity was unimaginable a decade ago.
如前面的例子所示,transformers 通过实现并行处理和提高可扩展性与效率,彻底改变了自然语言处理(NLP);然而,在 transformers 之前,深度神经网络(如 CNN 和 RNN)自 1980 年代被引入以来,主导了深度学习(DL)领域。以下是这些神经网络的简要描述:
-
CNN 的功能类似于我们视觉皮层的工作原理。我们的脑部通过使用处理特定类型信息或特征的专门神经元来处理来自视网膜的图像。同样,CNN 中的不同滤波器能够检测图像或数据集中各种特征。欲了解更多信息,请参考 Yann LeCun 等人于 1989 年发布的论文 《反向传播应用于手写邮政编码识别》(
ieeexplore.ieee.org/document/6795724
)。CNN 目前仍广泛用于图像分析。 -
RNN 常用于数据序列或时间序列数据,以分析模式并可能预测未来事件,如分析历史股市数据以预测未来交易选项。RNN 也常用于 NLP,因为自然语言是一个单词序列,其中单词顺序很重要,并且可能对意义产生重大影响。
RNN 的概念最早出现在 David Rumelhart, Geoffrey Hinton 等人于 1986 年发表的论文《通过反向传播误差学习表示》(*
www.nature.com/articles/323533a0
*)中。这篇具有开创性的论文提出了反向传播算法的概念,该算法基于梯度下降原理,是训练神经网络的核心技术,已经彻底改变了整个 AI 领域。在 附录 1A 中,我们包括了 RNN 及其流行变体(如长短期记忆(LSTM)网络和门控循环单元(GRU))的简要数学介绍。
2007 年,时任斯坦福大学计算机科学教授的李飞飞启动了 ImageNet 竞赛(www.image-net.org/
),该竞赛包括一个庞大的图像数据集,这些图像可以在互联网上获得,并且已标注用于训练和测试。每年,不同的 AI/ML 团队都会尝试使用这个训练数据集自动化预测,并提高其模型的准确性。
直到 2011 年,ImageNet 竞赛中的最先进技术仍然基于经典的机器学习方法,如 支持向量机 (SVM),该方法试图在两个不同类别之间创建一个最大间隔的隔离平面。2012 年,AlexNet 的推出突破了这一局限,Alex Krizhevsky、Ilya Sutskever 和 Geoffrey Hinton 开发了这一模型。这篇论文 (papers.nips.cc/paper_files/paper/2012/hash/c399862d3b9d6b76c8436e924a68c45b-Abstract.html
) 使用深度卷积神经网络赢得了 ImageNet 竞赛,并使 GPU 编程成为人工智能与机器学习发展的重要前沿。
- Transformer:2017 年,Vaswani 等人在他们的开创性论文 Attention Is All You Need (
arxiv.org/abs/1706.03762
) 中介绍了 Transformer 架构。它通过实现并行处理并提升了 NLP 的可扩展性和效率,彻底改变了自然语言处理(NLP)。Google 开发的 双向编码器表示的 Transformer (BERT) 大大改善了语言模型中上下文理解的能力。从那时起,不同公司陆续推出了大量的 LLM,例如 OpenAI 的 GPT 系列和 Anthropic 的 Claude。
在过去二十年中,机器学习从依赖手动挑选特征并基于规则的基本算法模型发展到了使用深度学习框架(如神经网络和 Transformer)的高级上下文感知模型。现在让我们更详细地了解 Transformer 架构。
Transformer 架构
Transformer 模型采用 编码器-解码器 架构,其中编码器通过自注意力机制将输入序列/标记映射。解码器使用这些映射数据生成输出序列。输入标记的映射不仅保留了它们的内在值,还保留了它们在原始序列中的上下文和权重。接下来,我们将通过下图介绍编码器架构的一些关键方面:
图 1.3 – 来自《Attention Is All You Need》论文的 Transformer 架构
以下是 图 1.3 中突出显示的概念:
- 输入嵌入:如图中标记为 1,这是 transformer 模型的关键部分,它将输入序列/标记转换为高维向量嵌入。在实际应用中,训练模型的输出嵌入可能会存储在高维向量数据库中,如 Elasticsearch、Milvus 或 PineCone。向量数据库帮助通过欧几里得距离或余弦相似度在高维空间中找到相似的搜索,类似的对象会被分配到该高维向量空间中更接近的位置,如下图所示。
图 1.4 – 高维空间中相似对象的分配
- 位置编码:位置编码,在图 1.2中标记为2,提供了输入序列中标记顺序的信息。与 RNN(循环神经网络)不同,RNN 具有时间步长 t 或序列的概念,而 Transformer 模型依赖自注意力机制,并且缺乏对内在标记顺序的感知。位置编码将顺序信息注入标记嵌入中。
例如,如果输入序列是 The Brown hat
,以下是机制的工作原理:
-
The
-> [0.1, 0.2] -
Brown
-> [0.3, 0.4] -
hat
-> [0.5, 0.6] -
位置编码向量:我们为句子中的每个位置生成位置编码。Transformer 模型通常使用正弦函数进行位置编码,但为了说明,我们采用以下示例:
-
位置 0: [0.01, 0.02]
-
位置 1: [0.03, 0.04]
-
位置 2: [0.05, 0.06]*
The
+ 位置 0: [0.1, 0.2] + [0.01, 0.02] = [0.11, 0.22]*Brown
+ 位置 1: [0.3, 0.4] + [0.03, 0.04] = [0.33, 0.44]*hat
+ 位置 2: [0.5, 0.6] + [0.05, 0.06] = [0.55, 0.66]
-
现在,每个单词都有一个独特的向量,其中包括该单词的意义和它在句子中的位置。
-
多头注意力机制:在多头注意力机制中,在图 1.2中标记为3,模型为每个输入序列和每个注意力头计算查询、键和值向量。一个可能的类比是将多头注意力机制比作一组侦探解决一个非常复杂的案件,其中每个侦探就是一个注意力头。每个注意力头都有自己的查询向量,专注于案件的某一方面,比如犯罪动机、使用的武器等。最后,键是与该动机相关的线索或证据。所以,如果查询是使用的武器,侦探会调查犯罪现场,寻找任何可以作为武器的物品,而值则是从每一条证据中获得的见解(例如,武器上的指纹)。通过拥有多个注意力头,模型可以同时关注输入的不同部分,捕捉数据中的各种模式或依赖关系,如单词的意义、单词的相对位置和句子结构,类似于我们的类比,不同的侦探关注案件的不同方面。此外,多头注意力机制还允许并行处理输入,使其高效,能够加速训练和推理过程,将任务分配到不同的设备上。
在附录 1B中,我们涵盖了键值对的数学模型和复杂性,以及前馈网络和 Transformer 模型中的温度概念。
既然我们已经理解了变换器架构如何通过使用依赖自注意力机制的编码器-解码器框架,使深度学习实现了革命性的突破,进而支持输入数据的并行处理,接下来我们将探索一个典型的生成式人工智能(GenAI)项目生命周期。
GenAI 项目生命周期
自 2023 年以来,企业在 GenAI 项目上的支出呈指数级增长,C-suite 高管计划在 GenAI 项目上投入更多资金(www.gartner.com/en/newsroom/press-releases/2023-10-11-gartner-says-more-than-80-percent-of-enterprises-will-have-used-generative-ai-apis-or-deployed-generative-ai-enabled-applications-by-2026
)。然而,如何量化这些努力的投资回报率(ROI)正成为日益关注的问题,比如收入影响、效率和准确性的提升。未来,ROI 将成为讨论的关键部分,因为企业在寻找新的 GenAI 项目时将重点考虑。因此,在启动新 GenAI 项目之前,建议全面思考项目的整个生命周期。本节将详细介绍项目生命周期。
让我们首先看一下以下图表,该图展示了端到端的 GenAI 项目生命周期,从定义业务目标或 KPI 开始。接下来是选择或训练 FM,并通过微调和提示调优等技术进行优化。然后,模型被评估、部署并持续监控,以确保业务目标得以实现。
图 1.5 – GenAI 项目生命周期
让我们仔细看看 GenAI 项目生命周期的每个阶段:
-
业务目标和 FM 选择:GenAI 项目生命周期从我们试图通过 GenAI 解决的业务目标/问题陈述开始。业务目标可能包括提高客户转化率或留存率、创建个性化的营销活动,或者利用企业内部数据创建客户聊天机器人:
-
关键 KPI:在确定使用案例后,下一个需要考虑的是业务关键绩效指标(KPI),例如每次推理的成本。例如,如果我们在创建个性化营销活动,我们应确保每次定制的成本低于客户生命周期价值(LTV)与客户转化概率的乘积。如果推理成本高于项目预计带来的业务价值,项目可能无法保持长期的可持续性。其他需要考虑的 KPI 可能包括延迟,因为某些使用案例可能需要亚秒级的响应时间,或者吞吐量,如果期望每秒处理一定数量的令牌。
-
选择和训练 FM:下一步是选择现有的 FM 或训练一个新的 FM。训练一个新的 FM 可能非常消耗资源,成本高达数十亿美元且需要大量时间,因此在大多数应用中,选择现有的 FM 并进行特定领域的优化更为可取。选择现有模型时,可以选择开源模型或通过网页接口或 API 访问的专有模型,如 Claude 或 ChatGPT。检查这些模型的许可条款,以确保它们符合应用需求,并能随着企业需求的增长进行扩展,始终是一个好主意。
-
-
模型优化:一旦选择了 FM,下一步是使用微调、提示调优、人类反馈强化学习(RLHF)和 DPO 等技术来优化模型,以适应业务用例。
-
微调:在微调中,使用一个包含提示和完成数据的特定领域标注数据集来训练模型,以适应特定领域或一组领域。由于在微调过程中所有模型权重都可以更新,因此微调的计算需求与完整训练类似。然而,训练时间较短,因为我们是在一个较小的数据集上训练模型。为了减少训练资源,可以选择例如性能高效微调(PEFT)的方法,它只更新少量的权重,从而降低计算需求。低秩适配(LoRA)是 PEFT 中一种非常流行的方法,其中原始模型矩阵通过低秩表示重新参数化,从而显著减少需要更新的模型参数数量。例如,在 Vaswani 的论文中,每个注意力头的维度是[512, 64],也就是每个头需要训练 32,768 个参数。通过 LoRA,我们训练更小的权重矩阵,从而实现 80%或更高的权重减少。我们将在第四章中详细讨论这一主题。LoRA 的另一版本是QLoRA(量化低秩适配)。在 QLoRA 中,我们使用量化技术将模型权重从 32 位精度压缩到 8 位或 4 位精度,这大大减少了模型大小,并使其在内存较小的 GPU 上运行更加高效。
-
提示调优是一种低成本的技术,其中向输入查询中添加软提示令牌,以优化模型在特定领域任务中的表现。随着模型在标注的领域特定数据上重新训练,这些令牌会被优化。与传统的微调不同,后者会在多个训练迭代中调整模型的参数,提示调优则专注于精炼提示,以更有效地引导模型。
-
在RLHF (
huggingface.co/blog/rlhf
) 中,使用人类反馈训练 LLM,使其与人类偏好对齐,并使用强化学习(RL)技术。在这种方法中,LLM 生成对各种提示的多个响应,人类根据准确性、相关性和道德标准等标准对其进行评估和排名。使用排名后的响应,训练一个奖励模型来预测任何给定 LLM 响应的人类偏好得分,并通过 RL 算法对 LLM 进行微调,奖励模型为其指导。 -
直接偏好优化 (DPO) (
huggingface.co/papers/2305.18290
) 中,使用简单的分类目标训练一个策略,以最佳方式与人类偏好对齐,而无需使用强化学习(RL)。与 RLHF 类似,LLM 会为一组输入提示生成多个输出。人类评估者将这些输出成对比较,并指出他们偏好的输出。这将生成一组偏好对数据集,例如(Ap, Anp),其中 Ap 表示偏好的答案,Anp 表示不偏好的答案。然后设计一个损失函数,最大化偏好输出在 LLM 输出中高于不偏好输出的概率。模型参数经过优化,以最小化所有收集的偏好对上的损失函数。这直接调优模型,以生成更符合人类偏好的输出。 -
评估:在模型训练后,需要使用如ROUGE (
huggingface.co/spaces/evaluate-metric/rouge
) 或 BLEU (huggingface.co/spaces/evaluate-metric/bleu
) 等指标根据使用场景评估模型的准确性:-
双语评估替代 (BLEU) 衡量机器生成文本与一组参考文本的匹配程度。此指标最初是为机器翻译设计的,也常用于评估文本生成任务,例如生成文本中与参考文本的n-grams(连续的n个单词)对比。
-
召回导向的摘要评估 (ROUGE) 是一套用于评估模型生成的摘要和翻译质量的指标。它比较生成文本与一组参考文本之间的重叠程度。ROUGE 指标在评估摘要系统的性能时尤其受欢迎。
-
-
除了这些指标外,开发人员还应根据诚实性、无害性和有用性 (3H) 指标评估生成的响应,并确保训练数据没有偏见和有害内容。
-
部署优化:一旦模型训练/微调完成,下一步是探索减少模型大小并优化其延迟和成本的选项。一些可能的选项包括量化、蒸馏和剪枝:
-
量化:在量化过程中,我们可以探索模型精度的权衡,例如将模型权重从 FP32(浮动点,32 位)转换为 FP16 或 Bfloat16(每个参数需要 2 字节内存),甚至是 Int 8(每个参数只需要 1 字节内存)。因此,从 FP32 转换到 Int8,我们可以将内存需求减少 4 倍;然而,这可能会影响模型的准确性。因此,我们需要评估模型的性能/准确性,以判断这些权衡是否可以接受。
-
蒸馏:在蒸馏过程中,我们通过最小化蒸馏损失来训练一个比原始模型更小的学生模型。蒸馏的目标是创建一个在计算资源(如内存和推理时间)上更高效的学生模型,同时尽可能接近教师模型的性能。
-
剪枝:在剪枝过程中,我们通过去除不太重要的参数(如接近零的权重)来减少模型的大小和复杂性,同时保持或提高模型的性能。剪枝的主要目标是创建一个更加高效的模型,在推理和训练时需要更少的计算资源,同时不显著影响准确性。
-
-
部署选项:一旦模型准备好,接下来的选择是选择部署选项,如云部署、本地部署或混合部署。选择取决于硬件可用性、成本、资本支出与运营支出的对比以及数据驻留要求等标准。通常,云部署因其托管服务和可扩展性而提供最简便的选项;然而,可能存在数据驻留要求,迫使我们选择本地或混合部署。
-
summarize this text
。在少样本学习中,用户在输入提示中包含几个示例,帮助模型学习期望的输出格式和风格。
一旦模型部署完成,我们需要持续监控它,以确保模型的结果不会随着时间的推移发生漂移或过时。我们将在第十一章中详细探讨模型监控、性能和漂移。
在这一节中,我们回顾了 GenAI 项目生命周期的各个阶段。它从定义业务目标和关键绩效指标(KPI)开始,然后是模型选择和各种微调技术。我们使用 BLEU 和 ROGUE 等指标评估模型的准确性,最后部署并持续优化模型。接下来,让我们来看一下用于部署模型的 GenAI 部署堆栈的各个层次。
GenAI 部署堆栈
在我们讨论基于 Kubernetes 的 GenAI 应用开发和部署时,了解整个部署堆栈是个好主意,这可以帮助我们思考合适的基础设施、编排平台和库。下图展示了 GenAI 部署堆栈的各个层次,从包括计算、存储和网络的基础设施层,到编排、工具和部署层。
图 1.6 – GenAI 应用的部署堆栈
让我们更仔细地看看这些层次:
-
基础设施层:我们将从堆栈的基础层开始,向上移动。这个堆栈的基础是基础设施层,涵盖了计算、网络和存储选项:
-
计算:对于计算,我们可以选择 CPU、GPU、定制加速器,或这些的组合。如前所述,LLM 非常计算密集型。GPU 提供大规模的并行矩阵乘法能力,通常在训练工作负载中受到青睐。对于推理,CPU 和 GPU 都可以使用,但对于具有数十亿参数的 LLM,推理时通常也需要 GPU。除了 CPU 和 GPU,还有定制加速器,如 AWS Inferentia 和 Trainium,它们是专为 ML 设计的定制硅芯片,并且对数学运算进行了高度优化。
-
网络:网络是下一个关键的基础设施组件。对于非常大的语言模型,训练和推理可能会成为分布式系统问题。为了说明这一点,我们来看一下近期 LLM 模型的趋势:
-
年份 | 模型 | 模型大小(以 十亿参数为单位) |
---|---|---|
2018 | BERT-L | 0.34 |
2019 | T5-L | 0.77 |
2019 | GPT2 | 1.5 |
2020 | GPT3 | 175 |
2023 | GPT4 | 万亿 |
表 1.1 – GenAI 模型规模的演变
显然,GenAI 模型正在呈指数级增长,更多的参数通常意味着更复杂的模型,能够捕捉数据中更复杂的模式,因此在训练和推理过程中需要更多的计算资源。
为了澄清,如果我们说一个模型有 10 亿个参数,通常是指训练完成后模型的权重。然而,训练这些模型时,我们需要模型权重、梯度、Adam 优化器、激活项和一些在训练时期使用的临时变量(huggingface.co/docs/transformers/v4.33.3/perf_train_gpu_one#batch-size-choice
)。
因此,在训练过程中,我们可能需要为每个训练权重存储最多六个参数,这可能需要 24 字节(FP32 精度)、12 字节(FP16 或 Bfloat16 精度)或 6 字节(FP8 或 Int8)。实际的精度取决于准确性要求与训练成本之间的权衡。
假设我们正在训练或微调一个 70B 参数的模型,比如 Llama3。为了存储所有的模型权重和临时变量,使用 Bfloat16 或 FP16 进行精度存储,可能需要 840GB 的内存(70B*12 字节)。如果我们使用的是 2024 年推出的最新 NVIDIA H200 GPU,最大支持 141GB 内存,那么我们仍然需要大约六个 GPU 来存储这些模型参数,在训练或完全微调期间,假设模型是完全分片的。
实际上,如果我们希望在合理的时间内训练这些模型,实际的 GPU 数量可能会更高。这就解释了东西向流量,也就是在数据中心节点或 GPU 之间流动的流量,可能会成为大规模模型训练或微调的性能瓶颈。因此,像内存一致性和远程直接内存访问(RDMA)这样的非阻塞网络技术,可以帮助在节点间扩展性能。RDMA 是一项技术,允许分布式系统中的节点在不涉及核心处理器或操作系统的情况下,访问其他节点的内存。同样,内存一致性技术确保系统中所有的缓存都拥有最新的内存信息,并且一个节点对内存的写操作能够被所有连接一致的节点/缓存看到。这两项技术能够降低延迟并提高分布式训练和微调任务的吞吐量。
-
存储:在网络之后,下一步的基础设施选择是存储,选项包括块存储、文件存储或 Lustre。在块存储系统中,如亚马逊 S3,数据存储在数据块中。这些数据块的大小范围从 512 字节到 64KB,多个块可以并行访问,从而提供更高的 I/O 带宽。在文件存储系统中,数据存储在文件和目录中,这使得数据的管理和结构化变得更加容易。然而,文件存储系统增加了管理文件层次结构和元数据的额外开销。Lustre是一种流行的存储系统,近年来在生成式 AI(GenAI)应用中获得了越来越多的关注,并且在高性能计算中已使用了相当长的时间。Lustre 提供了一个大规模并行的文件存储系统,可以通过增加更多资源进行水平扩展。
如果你计划将数据存储在数据库中,可能会选择 SQL 用于固定模式的数据,或者选择 NoSQL 数据库来存储非结构化数据,如图片和视频。
-
计算单元:在选择基础设施之后,下一步是选择计算单元。可能的选项包括裸金属机器、虚拟机(VMs)或容器。容器将所有软件依赖项,如语言运行时和库,打包成镜像,多个容器可以共享同一个节点/内核。与虚拟机相比,这允许更紧密的资源利用或资源打包。对于虚拟机,每个虚拟机在部署应用程序之前都需要单独的操作系统和运行时。裸金属机器是专门为单一租户或客户提供的物理服务器,不像虚拟机那样通过虚拟机监控程序在共享物理服务器上运行。
-
编排平台:在选择计算单元后,我们选择编排平台来管理底层基础设施的生命周期。这个编排平台需要能够根据工作负载需求进行扩展或缩减,并且应该能够承受网络中断或硬件故障。Kubernetes(K8s)已成为容器编排的领先平台,本书将重点讨论它,因为许多领先公司,如 OpenAI (
openai.com/research/scaling-kubernetes-to-7500-nodes
) 和 Anthropic,都在使用它来进行 GenAI 工作负载的编排。OpenStack 是一个开源虚拟机编排平台。 -
框架:在选择编排平台之后,接下来是选择 AI 框架,例如 TensorFlow 或 PyTorch。PyTorch 的完全分片数据并行(FSDP)和 TensorFlow 分布式库允许将模型参数和训练数据分布到多个 GPU 上,并帮助系统扩展以适应大型模型。
-
集成开发环境(IDE):在完成基础设施选择后,接下来的选择是使用什么集成开发环境(IDE),例如 JupyterHub,以及选择哪些库,如 cuDNN、NumPy 或 pandas,这些都取决于之前做出的基础设施选择。
-
终端节点:最后,用户可以选择最终的部署终端节点,例如将模型提供为 API、平台或工作负载。考虑到可扩展性、成本、延迟和灾难恢复等因素,可以帮助用户在应用开始扩展时避免昂贵的重构。根据我们的经验,数据科学团队通常会为概念验证选择简单的架构,但这些实现有时无法很好地扩展,而且在部署过程中非常昂贵。
本节讨论了 GenAI 部署堆栈的各个层次,从基础设施层到高级抽象层。我们还考察了随着模型规模增长,这些模型所带来的挑战及其不同的解决方案。接下来,让我们看看各行业中的 GenAI 使用案例。
GenAI 使用案例
GenAI 正在改变所有行业。根据麦肯锡的研究(www.mckinsey.com/capabilities/mckinsey-digital/our-insights/the-economic-potential-of-generative-ai-the-next-productivity-frontier#key-insights
),预计到 2030 年,GenAI 将为经济增加数万亿美元。以下是一些受影响的行业领域及其应用案例。这不是一个全面的列表,因为应用场景的数量正在迅速增长。
然而,了解其中一些将帮助你了解 GenAI 的潜力:
-
最好的跑步鞋,价格低于 100 美元,带红色条纹
。GenAI 能理解用户的需求并相应地提供推荐。 -
评论总结:GenAI 可以帮助总结用户评论和整体情感,使用户无需浏览所有不同的评论。
-
金融:
-
财务报告/分析:GenAI 可以根据数据和趋势分析,帮助创建财务报告和总结。
-
客户服务:GenAI 可以帮助进行个性化营销,并使用聊天机器人根据用户数据回答问题。* 医疗保健:
-
药物发现:GenAI 模型可以通过分析现有的相互作用数据,预测不同药物如何与各种生物靶点(蛋白质、酶等)相互作用。这有助于更高效地识别有前景的药物候选物。它们还可以提出可能有效的药物的新分子结构。这些模型还可以进一步优化,例如在结合亲和力、生物利用度和毒性等属性方面。
-
个性化医疗:大型语言模型(LLM)可以整合和分析多种患者数据,如病历、实验室结果、影像数据和遗传信息,以创建全面的患者档案,并识别预测患者对特定治疗反应的遗传标记物。* 教育:
-
个性化学习:GenAI 可以根据用户的旅程和反馈,帮助开发个性化的学习路径。
-
新语言学习:GenAI 可以帮助创建个性化的内容和对话示例。* 法律:
-
文档审阅和总结:GenAI 可以帮助自动化审阅和总结法律文件以及类似案件的前例。
-
合同生成:GenAI 可以根据提供的参数帮助生成法律合同。
-
客户互动:GenAI 可以帮助开发聊天机器人,处理客户查询和支持。* 娱乐:
-
内容创作:GenAI 可以帮助创作剧本、音乐、艺术作品和角色。
-
虚拟现实:GenAI 可以帮助创建沉浸式的 VR 环境和体验。
-
个性化内容:GenAI 可以根据个人偏好帮助定制娱乐内容和推荐。
-
在这一部分,我们考察了各个行业中 GenAI 的不同应用案例,从零售行业提升客户体验到医疗保健中的药物发现和个性化医疗。这只是一个非详尽的列表,随着技术和模型的进化,应用场景还在不断增长。
总结
本章中,我们讨论了 AI 和 GenAI 之间的区别。AI 是一个非常广泛的术语,指的是使机器能够模拟人类智能的技术,涵盖了广泛的应用领域,包括 GenAI,而 GenAI 特别专注于创建新内容,如文本、图像和视频。
接着,我们探讨了机器学习(ML)的演变,理解了从卷积神经网络(CNN)/循环神经网络(RNN)到 2017 年推出的 Transformer 架构的进展。Transformer 以其高效处理数据序列的能力,革新了 AI,成为许多 GenAI 应用的基础,尤其是在自然语言处理(NLP)领域。
本章还概述了一个 GenAI 项目的生命周期,包括商业目标和关键绩效指标(KPI)、基础模型选择、模型训练、评估和部署。每个阶段都至关重要,并且会根据性能反馈进行持续迭代。
最后,本章讨论了 GenAI 在不同行业中的各种应用案例,包括零售/电子商务、金融、医疗保健和法律行业,这些行业可以利用 GenAI 进行摘要、推荐和个性化。本次探索强调了 GenAI 在增强人类创造力和转变我们日常生活中的多样性和变革潜力。在下一章中,我们将介绍容器、K8s 的概念,并讨论 K8s 如何管理容器化工作负载的部署、扩展和运维。我们还将探讨在 GenAI 项目中使用 K8s 的具体优势,以及它为何对 GenAI 应用具有吸引力。
附录 1A – 循环神经网络(RNN)
在这一部分,我们将提供 RNN 工作原理的基本概述,包括其功能的数学解释。RNN 通过保持一个隐藏状态(或记忆单元)来处理序列数据,能够捕捉来自前一个时间步的信息。以下是 RNN 的一个非常简单的数学表示。
图 1.7 – RNN 的简单表示
在此图中,ht 表示给定时间步 t 的隐藏状态,可以表示为:
ht = f(wh * ht-1 + wx ***** Xt )
其中,wh 是隐藏阶段的权重,ht-1 是在时间步 t-1 上此隐藏阶段的输出。Xt 是输入,wx 是输入阶段的权重,f 是激活函数。
输出 Yt = wy * ht + by
在 RNN 中,时间 t 的输出依赖于隐藏状态,其中包括先前步骤的加权输出,如 ht-1、ht-2 等等。该架构可以处理任意长度的输入,并且随着输入的增加,模型大小不会增加。然而,这种实现是顺序的,且通过并行处理无法进一步加速。
有四种不同类型的 RNN 拓扑结构:
-
序列到序列 RNN:对于这种 RNN 拓扑结构,输入和输出都是序列,例如股票市场分析。
-
序列到向量 RNN:这包括通过分析语句或文本进行情感分析等示例。
-
向量到序列分析:这包括实际场景,例如从图像生成说明文字。
-
编码器到解码器:这可以用于将一种语言翻译成另一种语言。
RNN 的显著进展源于 LSTM 网络的引入。LSTM 网络解决了标准 RNN 中梯度消失和爆炸的问题,使得学习序列中的长距离依赖关系变得更加有效。LSTM 单元保持独立的短期和长期状态。GRU 是对 LSTM 的另一种优化,并且在训练性能上表现更好。
附录 1B – Transformer 自注意力机制的数学模型
在本节中,我们将提供 Transformer 模型如何工作的基本概述,包括其功能的数学解释。我们在本章早些时候讨论了队列、键和值的概念,作为 Transformer 分析的一部分。对于给定的注意力头 i,以下是查询、键和值向量:
Q= X Wi*Q
K= X Wi*K
V= X Wi*V
其中,WiQ、WiK 和 WiV 是注意力头 i 对查询、键和值的权重向量。这些权重是我们在训练模型时优化的参数。
为了理解这些计算的计算复杂度,我们来看看这些向量的维度:
-
X= [n, dmodel],其中 n 是输入序列中标记的数量,dmodel 是多维空间的维度。
-
权重向量:WiQ Wik Wiv = [dmodel ,dk],其中 dk = dmodel / 注意力头的数量
在 Attention Is All You Need 论文中,dmodel 是 512,注意力头的数量是 8,因此 dk = 512/8 = 64。
在前向传播过程中,每个 Q、K 和 V 向量的计算大约需要 262,144 次乘法运算(864512)和 261,632 次加法运算(864511)。在训练过程中,模型会对每个头部在多组训练数据上进行多次前向和反向传播。这解释了这些 Transformer 模型的复杂性和计算资源需求。
对于每个注意力头,注意力得分是通过以下公式计算的:
计算每个头部的注意力输出后,它们会被连接起来形成多头注意力,其中 Wo 是输出的权重矩阵:
最终输出会在后续的 Transformer 模型层中被使用,用于执行各种任务,如翻译、文本生成或分类。
理解 GenAI 用例中的温度参数
在作为注意力得分一部分的softmax函数中,温度指的是一个控制 softmax 函数生成的概率分布平滑度或尖锐度的参数。调整温度可以影响模型对输出预测的自信度:
较低的温度设置使得 softmax 分布更加尖锐(预测更有信心),而较高的温度则使分布更平滑(预测信心较低),并可能导致更具创意的响应。
以下是 ChatGPT 在两种温度设置下的示例响应,输入提示为 生命的目的是什么?
温度 = 2.0:
The purpose of life is like an ever-changing kaleidoscope, bursting with colors and patterns that shift with every moment. For some, it's about weaving tales of adventure and discovery, chasing after the mysteries that lie just beyond the horizon. For others, it's the serene pursuit of inner peace, finding harmony in the gentle ebb and flow of existence.
温度 = 0.75:
The purpose of life is a question that has intrigued humanity for centuries, and it can be seen from various perspectives. For many, the purpose of life is to seek happiness and fulfillment, to find and pursue passions that bring joy and meaning. This can involve forming deep connections with family and friends, contributing to the well-being of others, and making a positive impact on the world.
第二章:Kubernetes – 简介及与 GenAI 的集成
在大规模部署和管理 GenAI 工作负载时,会面临诸多挑战,包括构建模型、打包模型进行分发,以及确保有效的部署和扩展。在本章中,我们将讨论容器和Kubernetes(K8s)的概念,以及它们为什么成为应对这些复杂性强大解决方案的趋势。它们正成为 OpenAI(openai.com/index/scaling-kubernetes-to-7500-nodes/
)和 Anthropic(youtu.be/c9NJ6GSeNDM?si=xjei4T9VfZvejD5o&t=2412
)等公司的事实标准,用于部署 GenAI 工作负载。我们将涵盖以下主要内容:
-
理解容器
-
为什么选择容器来运行 GenAI 模型
-
什么是 Kubernetes(K8s)?
理解容器
容器通过标准化打包格式,彻底改变了我们管理应用程序的方式。借助容器的可移植性,应用程序被打包成一个标准的软件单元,包含所有代码和依赖项,以便在各种环境中(如本地、公共云和私有云)一致且可靠地部署。容器还被认为是虚拟机(VM)技术的演进,其中多个容器在同一个操作系统上运行,共享底层内核以提高整体服务器利用率。这是容器的巨大优势,因为没有多个操作系统(OSes)和其他操作系统级组件的开销。因此,容器可以更快地启动和停止,同时提供隔离性。
以下图示展示了计算环境的演变,突出了向更高层次抽象的转变,以及对业务逻辑的关注日益增强。
图 2.1 – 容器技术的演变
物理服务器提供最少的抽象,需大量手动配置,并且存在资源低效问题。虚拟机通过使用虚拟化技术来抽象底层硬件资源,提供一种折中的方案。这使得你可以在同一台物理服务器上运行多个虚拟机,从而提高资源利用率和安全性。然而,它们体积庞大,启动速度较慢。容器通过将应用程序和依赖项封装在便于移植的单元中,提供了最高级别的抽象,让我们可以更多地专注于开发和优化业务逻辑,而不是管理基础设施。图 2.2 展示了虚拟机和容器之间的高层次差异。
图 2.2 – 虚拟机与容器的对比
容器技术的实现得益于 Linux 内核中的命名空间(namespaces)和控制组(cgroups)。它们构成了提供隔离和资源限制的基础构件。Linux 命名空间 (man7.org/linux/man-pages/man7/namespaces.7.html
) 是操作系统资源的一种抽象。它将操作系统级别的资源进行划分,使得不同的进程集合即使在同一操作系统内核上运行,也能看到不同的资源集(如网络、文件系统等)。控制组 (man7.org/linux/man-pages/man7/cgroups.7.html
) 用于管理一组进程的系统资源的隔离和使用,如 CPU、内存、网络等,并可以选择性地强制执行限制和约束。这些能力使容器能够为现代应用程序抽象操作系统组件。
容器术语
以下是一些与容器相关的术语,它们在阅读本书时至关重要:
-
容器运行时:这是一个主机级别的进程,负责创建、停止和启动容器。它与低级容器运行时(如 runc)交互,为容器设置命名空间和控制组。常见的容器运行时包括 containerd、CRI-O 等。
-
容器镜像:这是一个轻量级、独立的可执行包,包含了运行软件所需的一切,包括代码、运行时、库、环境变量和配置文件。它是通过 Dockerfile 创建的,Dockerfile 是一个包含一组指令的纯文本定义文件,用来安装依赖、应用程序等。容器镜像的典型特征包括以下几点:
-
自包含:它封装了运行软件应用所需的一切。
-
不可变:它是只读的;任何更改都需要创建新的镜像。
-
分层:镜像是分层构建的,每一层都代表一个文件系统。这使得镜像具有高度的效率,因为相同的层可以在多个镜像之间共享。
-
可移植:由于镜像打包了应用程序及其所有依赖项,因此可以在任何支持容器运行时的系统上运行,使其具有高度的可移植性。
-
-
容器注册表:这是一个用于管理和分发容器镜像的工具。常见的注册表包括 Docker Hub (
hub.docker.com/
)、亚马逊弹性容器注册表 (aws.amazon.com/ecr/
)、谷歌 Artifact Registry (cloud.google.com/artifact-registry
) 等。工具如 Artifactory 和 Harbor 可以用来自托管注册表。 -
容器:这是一个通过容器运行时从容器镜像创建的正在运行的实例或进程。
现在,让我们通过 Docker 来了解高级容器工作流,Docker 是一个旨在帮助开发人员构建、分享和运行容器应用程序的软件平台。Docker 遵循传统的客户端-服务器架构。当你在主机上安装 Docker 时,它会运行一个名为 Docker 守护进程的服务器组件和一个客户端组件——Docker CLI。守护进程负责创建和管理镜像,使用这些镜像运行容器,并设置网络、存储等。如图 2.3所示,Docker CLI 用于与 Docker 守护进程交互,以构建和运行容器。一旦镜像构建完成,Docker 守护进程可以将这些镜像推送到容器注册表,或从容器注册表拉取镜像。将镜像存储在容器注册表中可以使它们具有可移植性,并且可以在任何有容器运行时的地方运行。
图 2.3 – Docker 架构概述
创建容器镜像
让我们创建第一个 hello world 容器镜像并在本地运行它。请使用 docs.docker.com/engine/install/
上的 Docker 文档页面,在你的机器上安装 Docker 引擎。以下是不同操作系统安装 Docker Desktop 的链接:
-
Docker Desktop for Linux:
docs.docker.com/desktop/install/linux-install/
-
Docker Desktop for Mac (macOS):
docs.docker.com/desktop/install/mac-install/
-
Docker Desktop for Windows:
docs.docker.com/desktop/install/windows-install/
在以下 Dockerfile 中,我们使用 nginx
服务器创建一个简单的 hello world 应用程序。我们将使用 nginx
作为父镜像,并将 index.html
自定义为我们的 Hello World! 消息。让我们按照以下步骤开始:
-
创建一个具有以下内容的 Dockerfile:
FROM nginx RUN echo "Hello World!" > /usr/share/nginx/html/index.html
-
使用
v1
标签构建容器镜像:$ docker build -t hello-world:v1 .
你可以使用以下命令列出本地的容器镜像:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE hello-world v1 7a5469eb898f 2 mins ago 273MB
-
使用
hello-world
镜像运行容器,并将 nginx 的端口80
映射到主机的8080
端口:$ docker run -p 8080:80 hello-world:v1
-
现在我们已经启动了镜像,我们可以通过访问本地的
8080
来测试容器:$ curl http://localhost:8080/ Hello World!
作为可选步骤,你也可以将容器镜像推送到 xyz
容器注册表,以便你可以在任何地方运行它。将 xyz
替换为你的容器仓库名称。例如,按照 www.docker.com/blog/how-to-use-your-own-registry-2/
上的说明创建 Docker Hub 中的注册表:
$ docker tag hello-world:v1 xyz/hello-world:v1
$ docker push xyz/hello-world:v1
在本节中,我们学习了计算环境的演变,使用容器相比传统物理服务器和虚拟机的优势。我们了解了整体的 Docker 架构以及各种关键的容器术语,并构建和运行了我们的第一个 hello-world 容器应用。接下来,让我们探讨为什么容器是 GenAI 模型的理想选择。
为什么为 GenAI 模型选择容器?
开发 ML 或 GenAI 应用的一个典型挑战是使用复杂且不断发展的开源 ML 框架,如 PyTorch 和 TensorFlow,ML 工具包如 Hugging Face Transformers,以及来自 NVIDIA 的不断变化的 GPU 硬件生态系统和来自亚马逊、谷歌等的定制加速器。
以下图示展示了创建和运行 ML 或 GenAI 容器所涉及的各种组件。
图 2.4 – 典型的 GenAI 容器镜像
在容器的最上层,封装了各种软件库、深度学习框架和用户提供的代码。下一组层包含硬件层特定的库,用于与主机上的 GPU 或定制加速器交互。可以使用容器运行时从容器镜像启动容器。让我们深入探讨使用容器处理 GenAI 工作负载的显著优势:
-
依赖管理:由于框架和版本间的相互依赖,依赖管理可能变得至关重要。通过容器,我们可以将 GenAI 应用程序代码及其依赖项封装在容器镜像中,并在开发者机器或测试/生产环境中始终如一地使用。
-
资源访问:GenAI/ML 应用计算密集型,需访问单个或多个 GPU 或定制加速器,并根据工作负载需求动态调整资源分配。容器允许对资源分配进行细粒度控制,能够高效利用可用资源,并避免邻居噪声问题。容器还可以水平或垂直扩展,以应对应用程序需求的增加。
-
模型版本控制和更新:管理模型的不同版本并保持相关依赖项更新而不干扰应用程序可能是一个挑战。使用容器,可以创建和版本化不同的镜像,便于跟踪更改、管理不同的模型版本,并在需要时无缝地执行回滚。我们还将探讨如何使用容器编排引擎来自动化这些更新,稍后将在 第十一章中进行详细介绍。
-
安全性:在开发 GenAI 应用时,保护训练和推理阶段的数据至关重要。通过使用容器,我们可以为数据访问实施严格的访问控制和策略,减少攻击面,只在容器镜像中包含必要的组件,还可以在容器与底层主机之间提供一层隔离。
通过提供隔离、一致和可重现的环境,容器可以简化依赖管理、优化资源效率、简化模型部署并提高整体系统安全性。容器技术全面解决了 GenAI 应用开发中的所有挑战,因此成为事实上的首选。
构建 GenAI 容器镜像
让我们亲身体验构建第一个 GenAI 容器镜像并将其部署到本地。首先,我们将从Hugging Face(huggingface.co/
)下载模型文件,这是一个帮助用户构建、部署和测试机器学习模型的 AI/ML 平台。Hugging Face 运营着模型中心,开发者和研究人员可以在这里共享成千上万的预训练模型。它还支持多种框架,包括 TensorFlow、PyTorch 和 ONNX。
在本教程中,我们将使用Meta的一个流行开源模型Llama 2(llama.meta.com/llama2/
)。不过,您需要阅读并遵守模型的条款和条件。请访问 Hugging Face 的 Llama 模型页面(huggingface.co/meta-llama/Llama-2-7b
)以请求访问该模型。Llama 2 模型有多个版本:7B、13B 和 70B,其中 B 代表十亿参数。模型的大小越大,所需的资源就越多。考虑到并非所有个人笔记本电脑都配备了专门的硬件(如 GPU),我们将使用 Llama 2 模型的 CPU 版本。这得益于llama.cpp(github.com/ggerganov/llama.cpp
),这是一个开源项目,旨在提供 Llama 模型的高效且可移植的实现。它使我们能够在各种平台上(包括个人电脑)部署这些模型,而无需 GPU。llama.cpp 采用定制量化方法,将模型压缩为 GGUF 格式,从而减少模型的大小和资源需求。
通过5000
创建推理端点,并接受包含输入提示、系统消息等的 JSON 请求。输入参数将传递给 Llama 模型,并以 JSON 格式返回模型输出。让我们开始这个过程:
-
安装先决条件:
-
从
huggingface.co/docs/huggingface_hub/en/installation
安装huggingface-cli
-
来自
docs.docker.com/engine/install/
的Docker Engine
-
-
按照
huggingface.co/docs/huggingface_hub/en/guides/cli
上的说明进行 Hugging Face 平台的身份验证。 -
使用
huggingface-cli
下载 Llama 2 模型:$ huggingface-cli download TheBloke/Llama-2-7B-Chat-GGUF llama-2-7b-chat.Q2_K.gguf --local-dir . --local-dir-use-symlinks False
-
模型下载完成后,将显示以下消息:
$ Download complete. Moving file to llama-2-7b-chat.Q2_K.gguf llama-2-7b-chat.Q2_K.gguf
-
创建一个包含 Flask 代码的
app.py
。此代码块设置了一个 Python Flask 应用程序,具有一个单一路由/predict
,它接受 HTTP POST 请求。它使用llama_cpp
库加载 Llama 2 模型,基于输入提示和请求中的系统消息生成响应,并将模型的响应作为 JSON 对象返回。你可以从 GitHub 下载app.py
代码:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch2/app.py
:from flask import Flask, request, jsonify import llama_cpp app = Flask(__name__) model = llama_cpp.Llama("llama-2-7b-chat.Q2_K.gguf") @app.route('/predict', methods=['POST']) def predict(): data = request.json prompt = f"""<s>[INST] <<SYS>>{data.get('sys_msg', '')}<</SYS>>{data.get('prompt', '')} [/INST]""" response = model(prompt, max_tokens=1000) return jsonify({'response': response}) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)
-
创建一个 Dockerfile,将 Python 源代码、Llama 2 模型和其他依赖项打包在一起。你可以从 GitHub 下载 Dockerfile 代码:
github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch2/Dockerfile
:# Use an official Python runtime as a parent image FROM python # Set the working directory WORKDIR /app # Expose the application on port 5000 EXPOSE 5000 # Define environment variable ENV FLASK_APP=app.py # Install any needed packages specified in requirements.txt RUN pip install --no-cache-dir flask llama-cpp-python # Copy the current dir contents into the container at /app COPY . /app # Run app.py when the container launches CMD ["python", "app.py"]
-
构建容器镜像:
8000:
curl:
$ curl -X POST http://localhost:8000/predict -H "Content-Type: application/json" -d '{"prompt":"Create a poem about humanity?","sys_msg":"You are a helpful, respectful, and honest assistant. Always provide safe, unbiased, and positive responses. Avoid harmful, unethical, or illegal content. If a question is unclear or incorrect, explain why. If unsure, do not provide false information."}' | jq .
这将对我们的本地my-llama
容器进行推理,并返回以下 LLM 响应:
……
Of course, I'm here to assist you with a safe and positive response! Here's a poem about humanity:
Humanity, oh humanity, so diverse and wide,
A mix of cultures, beliefs, and ideals inside.
We may come from different places, with different views,
But beneath our differences, we share a common brew.
……
So here's to humanity, in all its grace and might,
A masterpiece of diversity, a work of art in sight.
Let us cherish each other, with kindness and respect,
And create a world where love is the only text.
输入请求包含两个关键属性:
-
提示:输入给 LLM 的内容
-
系统消息:在请求过程中设置上下文并引导 LLM 的行为
可选地,我们可以传递额外的属性,如max_tokens
,以限制生成输出的长度。
如果你注意到我们构建的镜像大小,它大约是 7+ GB。这主要归因于模型文件和其他依赖项。我们将在后续的第九章中探索减少镜像大小和优化容器启动时间的技术。
在我们本地计算机上创建并运行一个单一容器实例是很容易的。试想一下,在数百或数千个虚拟机上运行这些容器,并确保这些容器高度可用、可扩展、负载均衡,管理资源分配,并实现自动化部署和回滚。这就是容器编排器派上用场的地方。
容器编排器在现代软件开发和部署中发挥着至关重要的作用,解决了所有前述问题,并大规模管理容器化应用程序。它们的一些关键优势如下:
-
高可用性和容错性:它们会定期监控容器的健康状态,自动重启/替换失败的容器,从而确保所需数量的容器始终处于运行状态。
-
扩展性:它们可以根据用户负载/需求自动扩展应用程序。容器实例会根据应用程序负载自动创建和删除。当应用程序进行扩展时,底层计算资源也会进行扩展,以容纳新的容器。
-
自动化部署:它们自动化容器化应用程序在多个主机上的部署,并在发生故障时进行回滚。我们还可以实现高级流量路由模式,如金丝雀发布和蓝绿部署,以安全地推出新的更改。
-
负载均衡:它们提供内置的负载均衡,将传入的流量分配到多个容器实例,提升性能和可靠性。它们还可以与外部负载均衡解决方案集成,以进行流量的负载均衡。
-
服务发现:由于容器本质上是短暂的,编排工具提供服务发现功能,以动态发现容器端点,促进服务间通信。它们还管理容器网络,包括 IP 地址管理、DNS 解析和网络分段。
-
可观察性:它们可以与监控和日志工具集成,提供对容器化应用程序健康状况和性能的可视化。
-
资源管理和高级调度:它们还可以管理容器的资源分配,包括 CPU、内存、GPU 等。它们强制执行资源限制和预留,防止“吵闹邻居”问题。可以使用高级调度策略,将具有特殊硬件需求(如 GPU)的应用程序调度到特定主机上。
现在我们已经理解了容器编排器的必要性,下一个问题是我们应该选择哪个编排器?市场上有许多不同的开源和专有编排器。一些值得注意的编排器如下:
-
Amazon Elastic Container Service (
aws.amazon.com/ecs/
) -
Azure Container Apps (
azure.microsoft.com/en-us/products/container-apps
) -
Docker Swarm (
docs.docker.com/engine/swarm/
) -
Apache Mesos (
mesos.apache.org/
) -
Kubernetes (
kubernetes.io/
),以及像Amazon Elastic Kubernetes Service (aws.amazon.com/eks/
)这样的托管 Kubernetes 服务 -
Google Kubernetes Engine (GKE) (
cloud.google.com/kubernetes-engine
) -
Azure Kubernetes Service (****AKS) (
azure.microsoft.com/en-us/products/kubernetes-service
) -
Red Hat OpenShift (
www.redhat.com/en/technologies/cloud-computing/openshift
)
在本节中,我们了解了构建 GenAI 模型的典型挑战,以及如何利用容器技术将 GenAI 代码/模型、AI/ML 框架、硬件特定库和其他依赖项打包成镜像,以确保一致性、可重用性和可移植性。我们还使用开源 Llama 2 模型构建了我们的第一个 GenAI 容器镜像,并通过在本地部署运行推理。最后,我们讨论了大规模管理容器化应用程序的挑战,以及 K8s 等容器编排工具如何解决这些问题。在下一节中,让我们深入了解 K8s 及其架构,看看它为什么非常适合运行 GenAI 模型。
什么是 Kubernetes(K8s)?
Kubernetes,通常称为 K8s,是一个开源的容器编排平台,能够自动化容器化应用程序的部署、扩展和管理。它是最广泛使用的容器编排平台 (www.cncf.io/reports/kubernetes-project-journey-report/
),并且已成为许多企业运行各种工作负载的事实标准。
Kubernetes 最初由 Google 工程师 Joe Beda、Brendan Burns 和 Craig McLuckie 于 2014 年开发,现在由云原生计算基金会(CNCF)维护,其名称来源于古希腊语,意为“领航员”或“舵手”(负责掌舵的人)。它已经成为全球第二大开源项目,仅次于 Linux,并且是 71%财富 100 强公司使用的主要编排工具 (www.cncf.io/reports/kubernetes-project-journey-report/
)。根据 Gartner 的《CTO 的容器与 Kubernetes 指南》 (www.gartner.com/en/documents/5128231
),到 2027 年,全球 90%以上的组织将在生产环境中运行容器化应用程序。
Kubernetes(K8s)变得如此受欢迎的几个显著因素如下:
-
丰富的社区和生态系统:K8s 拥有来自 44 个不同国家的 77K+贡献者,并由 8K+公司贡献,是最具活力和活跃的社区 (
www.cncf.io/reports/kubernetes-project-journey-report/
)。CNCF 的调查表明,与 Kubernetes 相关的项目(如 Helm、Prometheus 和 Istio)也得到了广泛采用,进一步加强了其生态系统。 -
全面的功能:K8s 提供了一系列丰富的功能,包括自动化部署与回滚、自愈、横向扩展、服务发现和负载均衡等。这些功能使其成为管理容器化应用程序的多功能和强大工具。
-
可移植性:K8s 抽象化了底层基础设施,使得应用程序可以在本地、公共云、私有云、混合云或边缘位置一致地进行部署。
-
托管 K8s 服务:主要云服务提供商如 Amazon EKS、GKE 和 AKS 提供的托管 K8s 服务大大降低了入门门槛。这些服务去除了运行和操作 K8s 集群的操作复杂性,使企业能够专注于核心业务目标。
-
强大的治理:自 2016 年以来,K8s 由 CNCF 进行治理,这促进了协作开发和社区参与,使来自不同开发者、组织和最终用户的贡献得以实现。它遵循开源模式,具有明确的治理结构,包括关注特定开发和操作领域的特别兴趣小组(SIGs)。
-
声明式配置:K8s 采用声明式的方法进行配置管理,允许用户使用 YAML 或 JSON 文件定义应用程序和基础设施的期望状态。如图 2.5所示,K8s 控制器持续监控资源的当前状态,并自动进行调整,以匹配期望状态,从而简化操作并确保一致性。
图 2.5 – K8s 控制器的工作原理
- 可扩展性:这可能是 K8 如此受欢迎的最重要原因。K8s 设计采用模块化架构,支持通过明确定义的 API 进行自定义插件和扩展。这使得开发者和公司能够扩展或定制 K8s 功能,而无需修改上游代码,促进了创新和适应性。
接下来,让我们来看一下 Kubernetes 的架构。
Kubernetes 架构
K8s 架构基于运行集群,使您的应用程序/容器能够跨多个主机运行。每个集群由两种类型的节点组成:
-
控制平面:K8s 控制平面是集群操作的“大脑”。它由关键组件组成,如 kube-apiserver、etcd、调度管理器、控制器管理器和云控制器管理器。
-
数据平面/工作节点:K8s 数据平面运行在工作节点上,由多个关键组件组成,如 kube-proxy、kubelet 和容器网络接口(CNI)插件。
Figure 2**.6 描述了高级别的 K8s 集群架构。您会注意到 kube-apiserver 是 K8s 控制平面的前端,并与其他控制平面组件(如控制器管理器、etcd 等)交互,以满足请求。K8s 工作节点托管多个关键组件,负责从 kube-apiserver 接收指令,在工作节点上执行它们,并报告状态。
图 2.6 – Kubernetes 集群架构
控制平面组件
控制平面的主要组件如下所示:
-
kube-apiserver:这充当 K8s 集群的入口点或前端,并且是暴露 K8s API 的中央管理组件。K8s 用户、管理员和其他组件使用此 API 与集群通信。它还与 etcd 组件通信以保存 K8s 对象的状态。它还处理认证和授权、验证和请求处理,并与其他控制平面和数据平面组件通信,以管理集群状态。
-
etcd:这是分布式键值存储,用作所有集群状态的 K8s 后端存储。它存储所有 K8s 对象的配置数据和对它们进行的任何更新,并确保集群状态始终可靠和可访问。定期备份 etcd 数据库至关重要,以便在任何中断情况下恢复集群。
-
kube-controller-manager:负责管理集群中的各种控制器。这包括默认的上游控制器和任何定制构建的控制器。一些示例包括以下内容:
-
Deployment controller:监视 K8s 部署对象,并管理对 K8s Pod 的更新。
-
kube-scheduler:负责在工作节点上调度 K8s Pod。它监视 API 服务器以获取新创建的 Pod,并根据资源可用性、Pod 配置中定义的调度要求(如 nodeSelectors、Pod/node 亲和性、拓扑传播等)分配工作节点。当由于资源耗尽等原因无法调度 Pod 时,它将标记 Pod 为Pending,以便其他操作附加组件(如集群自动缩放器)可以介入并向集群添加/删除计算容量(工作节点)。
-
cloud-controller-manager:管理云特定控制器,处理云提供商 API 调用以进行资源管理。它是从 K8s 核心到云提供商 API 的网关,负责根据 K8s 对象的更改(如节点、服务等)创建和管理云提供商特定的资源(如节点、负载均衡器等)。一些示例包括以下内容:
-
Node controller:负责监控工作节点的健康状况,并处理集群中节点的添加或删除。
-
服务控制器:它监视服务和节点对象的变化,并相应地创建、更新和删除云提供商负载均衡器。
-
数据平面组件
数据平面的主要组件如下:
-
kubelet:这是一个代理程序,运行在集群中的每个工作节点上。它负责从 kube-apiserver 接收指令,在相应的工作节点上执行这些指令,并将节点组件的更新报告回集群控制平面。它与其他节点组件互动,如容器运行时以启动容器进程,CNI 插件以设置容器网络,以及 CSI 插件以管理持久化存储卷等。
-
kube-proxy:这是一个网络代理,运行在集群中的每个工作节点上,实现了 K8s 服务概念。它维护工作节点上的网络路由规则,允许集群内外的网络通信到达或离开你的 Pods。它使用操作系统的包过滤层,如 IP tables、IPVS 等,将流量路由到集群中的其他端点。
-
容器运行时:containerd 是事实上的容器运行时,负责在工作节点上启动容器。它负责管理 K8s 环境中容器的生命周期。K8s 还支持其他容器运行时,如 CRI-O。
除了这些组件外,在 K8s 集群中部署额外的附加软件对于生产操作是至关重要的。这些附加组件提供监控、安全和网络等功能。
附加软件组件
下面是一些附加软件的示例:
-
CNI 插件:这是一个实现容器网络规范的软件附加组件。它们遵循 K8s 网络原则,并负责为 K8s Pods 分配 IP 地址(
kubernetes.io/docs/concepts/workloads/pods/
),并使它们能够在集群内相互通信。这个领域的流行附加组件有 Cilium(github.com/cilium/cilium
)、Calico(github.com/projectcalico/calico
)和 Amazon VPC CNI(github.com/aws/amazon-vpc-cni-k8s
)。 -
CSI 插件:这是一个实现容器存储接口(CSI)规范的软件附加组件。它们负责为 K8s Pods 提供持久存储卷,并管理这些卷的生命周期。一些著名的附加组件包括 Amazon EBS CSI 驱动程序(
github.com/kubernetes-sigs/aws-ebs-csi-driver
)和 Portworx CSI 驱动程序(docs.portworx.com/portworx-enterprise/operations/operate-kubernetes/storage-operations/csi
)。 -
CoreDNS:这是一个必不可少的软件附加组件,用于提供集群内的 DNS 解析。在 K8s 工作节点中启动的容器会自动将此 DNS 服务器包含在其 DNS 查询中。
-
监控插件:这是一个软件附加组件,提供对集群基础设施和工作负载的可观察性。它们提取关键的可观察性细节,如日志、指标和跟踪,并将其写入监控平台,如 Prometheus、Amazon CloudWatch、Splunk、Datadog 和 New Relic。
-
设备插件:现代 AI/ML 应用程序使用诸如 NVIDIA、Intel 和 AMD 的 GPU,以及 Amazon、Google 和 Meta 的定制加速器等专用硬件设备。K8s 提供了一个设备插件框架,您可以使用它向 kubelet 和控制平面广播系统硬件资源,以便根据这些资源的可用性做出调度决策。
这并不是 K8s 所有组件和附加组件的详尽列表。我们将在本书的后续部分深入探讨与 AI/ML 相关的附加组件。
在本节中,我们深入探讨了 K8s 架构,了解了各种控制平面和数据平面组件,探索了 K8s 平台的优势以及为什么它成为社区中的事实标准。接下来,我们将了解为什么 K8s 非常适合运行 GenAI 模型。
为什么 K8s 非常适合运行 GenAI 模型
既然我们已经理解了 K8s 架构、其组件以及平台的优势,那么接下来让我们讨论如何应用这些优势来解决运行 GenAI 模型时常见的挑战。
运行 GenAI 模型的挑战
运行 GenAI 模型时的一些常见挑战如下:
-
计算要求:GenAI 模型越来越大且复杂,因此需要大量的计算资源,包括 GPU、TPU 和用于训练和推理的定制加速器。有效管理这些资源对于确保性能和成本效率至关重要。
-
可扩展性:随着 AI/ML 服务需求的增加,扩展 GenAI 模型以应对需求至关重要。这要求能够无缝扩展计算资源,同时不牺牲模型的性能和成本。
-
可观察性:随着 GenAI 模型的普及,了解它们的性能变得至关重要,这需要通过日志和指标监控业务级别的关键绩效指标(KPI)以及整个系统的健康状况。
-
数据管理:GenAI 模型依赖于大量的数据进行训练和推理。数据的准备、安全性和管理对提高模型的准确性和性能至关重要。
-
部署复杂性:正如我们在本章前面所学,所有 GenAI 模型都需要自定义框架、插件库和其他依赖项来进行部署。这种复杂性可能导致部署问题、延迟和错误增多。
K8s 优势
K8s 提供了若干优势,能够帮助应对运行 GenAI 模型的挑战,具体如下:
-
高效的资源管理:K8s 具有强大的资源管理系统,内置于 kube-scheduler 中。它自动将 K8s Pod 分配到工作节点,同时满足不同的调度要求/约束条件。调度器可以配置为以最低成本、随机或按 bin-pack 模式进行操作,以提供灵活性。凭借 K8s 的扩展性,您可以开发自定义调度器并用于工作负载的调度。一个常见的应用场景是将训练或推理工作负载调度到具有自定义设备的设备上,例如 AWS Trainium 和 Inferentia。通过使用 K8s,我们可以根据模型要求实现动态资源分配,从而优化成本并提高性能。
-
无缝的可扩展性:训练或微调 GenAI 模型需要大量的计算资源。这些模型的推理端点也需要根据工作负载需求进行水平扩展。这将通过使用 K8s 自动扩展机制无缝实现,如 水平 Pod 自动扩展 (HPA) (
kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
),垂直 Pod 自动扩展 (VPA) (github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler
),和 集群自动扩展 (kubernetes.io/docs/concepts/cluster-administration/cluster-autoscaling/
)。HPA 会自动扩展工作负载资源(如 Deployment 或 StatefulSet),以匹配工作负载需求。它通过创建和部署新的 K8s Pods 来响应需求。VPA 会自动调整 Pod 的资源限制(如 CPU、内存等),以匹配其实际使用情况。它有助于优化资源分配,确保工作负载的高效运行。集群自动扩展负责确保集群始终附加了正确数量的资源。我们将在第六章中深入探讨这些机制。 -
可扩展性:可扩展性在 K8s 上运行 GenAI 工作负载中起着至关重要的作用。它允许你在不修改上游代码的情况下,以可扩展的方式扩展 K8s 的功能。我们可以使用自定义构建的附加组件,如 Kubeflow(
www.kubeflow.org/
),一个提供管理 ML 管道、模型训练和部署的自定义资源的 AI/ML 平台。硬件公司也可以通过开发设备插件来利用这一点,以管理 K8s 中的 GPU 资源,从而确保 GenAI 的训练和推理工作负载能够按需调度。GenAI 工作负载通常需要特定的框架,如 PyTorch、TensorFlow 和 Jupyter Notebook。我们可以使用自定义构建的操作符,将这些框架和工具集成在一起,以实现无缝开发和部署。 -
安全性:K8s 有许多内置的安全机制来保护 GenAI 工作负载的安全。可以使用 基于角色的访问控制(RBAC)(
kubernetes.io/docs/reference/access-authn-authz/rbac/
)来限制对资源的访问,确保只有授权的用户或应用程序可以访问敏感数据。K8s Secrets 或外部秘密管理解决方案可以用于保护敏感信息。K8s 网络策略可以用于实施网络分段,以便只有授权的 Pod 能够访问数据存储。除此之外,我们还可以启用安全控制,如加密、审计日志、安全扫描以及 Pod 安全标准(PSS)(kubernetes.io/docs/concepts/security/pod-security-standards/
),以确保强大的安全态势。我们将在第九章中详细探讨这些功能。 -
高可用性(HA)和容错性:这些在高效、可靠地运行 GenAI 工作负载中起着至关重要的作用。基础模型训练通常需要数周或数月。如果节点或 Pod 发生故障,K8s 内置的自愈机制可以自动将任务调度到另一个节点,从而最小化中断。AI 框架可以与此一起使用,实施检查点策略,定期保存训练状态。对于模型推理,K8s 可以根据需求自动扩展推理 Pod,并通过启动替代 Pod 来恢复失败的 Pod。K8s 还可以执行滚动蓝绿更新,以部署新版本的模型,并在出现故障时无缝回滚。我们将在第十三章中深入探讨这个话题。
-
丰富的生态系统和附加组件:《云原生人工智能白皮书》(
www.cncf.io/wp-content/uploads/2024/03/cloud_native_ai24_031424a-2.pdf
) 强调了采用基于 Kubernetes 的工具和框架来简化 AI 模型开发、训练和部署的增长趋势。值得注意的例子包括 Kubeflow 和 MLflow 用于在 K8s 上操作端到端的 ML 平台;KServe、Seldon 和 RayServe 用于模型服务和扩展;以及 OpenLLMetry、TruLens 和 Deepchecks 用于模型可观察性。随着行业围绕 GenAI 用例的成熟,这个列表将继续增长。
在本节中,我们了解了操作 GenAI 模型的典型挑战,并查看了使用 K8s 解决这些挑战的优势。K8s 的可扩展性、高效的资源管理、安全性、高可用性和容错能力使其非常适合大规模运行 GenAI 模型。
摘要
在本章中,我们从计算技术的演变和容器作为标准封装和交付应用程序以及为开发人员抽象基础设施复杂性的方式开始。我们讨论了在 GenAI 模型中使用容器的好处,并构建并运行了我们的第一个 hello-world,GenAI 容器映像。然后,我们看了运行和管理大规模容器的挑战,以及像 K8s 这样的容器编排引擎如何帮助简化这一过程。
我们深入探讨了 K8s 的高级架构和构成控制平面和数据平面的各个组件。我们还了解到 K8s 的可扩展性、可移植性、声明性质和强大的社区使其在市场上受欢迎,并成为事实上的容器编排器。
最后,我们讨论了在大规模运行 GenAI 工作负载的典型挑战,以及 K8s 如何通过其高效的资源管理、无缝扩展、可扩展性和安全能力来很好地解决这些挑战。在下一章中,我们将探讨如何在云环境中构建一个 K8s 集群,利用流行的开源工具来管理 GenAI 工作负载,并在其中部署我们的my-llama容器。
附录
以下是深入学习 Kubernetes 的一些优秀资源:
-
Kubernetes 培训网站:
kubernetes.io/training/
-
Linux 基金会的 Kubernetes 课程:
training.linuxfoundation.org/full-catalog/?_sft_product_type=training&_sft_technology=kubernetes
-
KodeKloud 的 Kubernetes 学习路径:
kodekloud.com/learning-path/kubernetes/
第三章:在云中使用 Kubernetes 入门
云计算彻底改变了组织访问可扩展 IT 资源的方式,使得计算、存储和网络服务的快速部署成为可能。对于采用容器化应用程序的团队,Kubernetes(K8s)已经成为事实上的平台。云服务提供商提供托管的 K8s 服务,如Amazon Elastic Kubernetes Service(EKS)、Google Kubernetes Engine(GKE)和Azure Kubernetes Service(AKS),使得在云中运行和部署 GenAI 模型变得更加容易。
在本章中,我们将讨论云计算如何通过减轻一些复杂性,简化生产级 K8s 集群的管理。然后,我们将指导你通过基础设施自动化创建你的第一个集群。
让我们探索以下关键主题:
-
在云中运行 K8s 的优势
-
在云中设置 K8s 集群
-
在 K8s 集群中部署我们的第一个 GenAI 模型
在云中运行 K8s 的优势
一份 2023 年发布的报告,《Kubernetes in the wild report 2023》(www.dynatrace.com/news/blog/kubernetes-in-the-wild-2023/
)指出,云中 K8s 集群的数量增长速度大约是同时期本地托管集群的五倍。这主要归因于以下几个因素:
-
托管服务:运营一个生产级的 K8s 集群通常需要确保控制平面高可用,集群管理活动(如创建、升级和修补所有 K8s 控制平面和数据平面组件)、资源管理、安全性、监控等。许多这些活动增加了显著的运营开销,而这些开销是没有差异化的,且会占用核心业务运营的时间和资源。因此,许多 K8s 用户选择通过选择现有的托管服务之一来将管理 K8s 集群的繁重任务外包。一些著名的托管 K8s 服务如下:
-
Amazon EKS:这是一项托管服务,用于在 AWS 云和本地数据中心运行 K8s。使用 Amazon EKS,你可以利用 AWS 基础设施的所有性能、规模、可靠性和安全性,并且能够与其他 AWS 托管服务进行更深入的集成。欲了解更多信息,请访问
aws.amazon.com/eks/
。 -
GKE:这是一个来自谷歌云平台(GCP)的托管 K8s 服务,你可以利用它在谷歌的基础设施上部署和操作容器化应用程序。它提供标准版和企业版,其中标准版提供完全自动化的集群生命周期管理,企业版则提供强大的功能来治理、管理和操作大规模企业容器化工作负载。如需了解更多信息,请访问
cloud.google.com/kubernetes-engine
。 -
AKS:这是 Azure 提供的一个托管服务,用于简化 K8s 集群的部署、管理和扩展。AKS 自动化了关键任务,如监控、升级和扩展,同时与其他 Azure 服务(如 Active Directory、负载均衡器和虚拟网络)集成。如需了解更多信息,请访问
azure.microsoft.com/en-us/products/kubernetes-service
。
-
除此之外,还有许多其他由公司提供的托管 K8s 服务,如 Red Hat、Oracle Cloud Infrastructure (OCI)、阿里云等。这些服务的目标是简化 K8s 集群操作,并提供与相应云服务提供商基础设施的更深层次集成。
-
可扩展性与效率:云服务提供商通过提供按需基础设施和按量计费的定价模式,提供无缝的 K8s 集群可扩展性。随着集群规模的增长,它们会自动扩展 K8s 控制平面组件,以适应其使用需求。
-
可用性与全球扩展:所有托管的 K8s 服务都提供严格的正常运行时间服务级别协议(SLA)—例如,Amazon EKS 提供 99.95% 的正常运行时间。为了实现这一目标,它们通常会部署多个 API 服务器实例,且其 etcd 数据库组件分布在多个可用区(AZ)之间,自动监控这些组件的健康状况,并恢复或替换任何不健康的组件。云服务提供商还在多个地理区域运营,这些区域被称为区域;注意,不同的云服务提供商对区域的定义可能有所不同。例如,AWS 的一个区域由多个物理上分离且相互隔离的 AZ 组成,这些 AZ 通过低延迟、高吞吐量、高冗余的网络连接。我们可以利用这些区域为全球用户部署工作负载或实施灾难恢复机制。
-
安全性和合规性:云服务提供商和消费者之间始终存在共享责任。提供商负责云的安全性和合规性,而作为消费者,你负责云中的安全性和合规性。这意味着云服务提供商确保 K8s 产品的托管组件(如控制平面)是安全的,并符合各种合规标准,如PCI DSS、HIPPA、GDPR、SOC等。作为消费者,你需要负责保障集群中应用程序和自管 K8s 插件的安全。此外,云服务提供商还负责自动修补控制平面组件,以确保其安全。
-
原生集成:要操作一个生产就绪的 K8s 集群,我们需要与许多外部组件进行集成,如存储系统、数据库、负载均衡器以及监控和安全工具。云服务提供商通常为这些组件提供托管服务,并与他们的托管 K8s 产品创建无缝集成。这使得构建端到端解决方案变得更容易,并消除了对各种组件进行兼容性测试的痛苦。它还提供与各种第三方(3P)工具的无缝集成,如Splunk(
www.splunk.com/
)、Datadog(www.datadoghq.com/
)、New Relic(newrelic.com/
)、Aqua Security(www.aquasec.com/
)、Sysdig(sysdig.com/
)、Kubecost(www.kubecost.com/
)等,用于监控和安全目的,并为 K8s 工作负载分配和优化成本。 -
扩展支持:截至目前,K8s 社区大约每年发布三个新的 K8s 版本。如图 3.1所示,每个版本的支持期限为 12 个月。在此期间,社区会提供补丁发布,包括错误修复、安全补丁等。执行多个 K8s 版本的升级通常会为平台工程团队带来较大负担,因为每次版本升级都涉及验证并修复任何已弃用的 API 的使用,以及升级控制平面、数据平面和操作插件,同时确保应用程序的可用性。云服务提供商提供最长 26 个月的扩展支持,帮助客户规划和执行集群升级。始终建议使用最新的 K8s 版本,以便能够利用社区的最新创新,因此,自动化集群生命周期操作使用基础设施即代码(IaC)是至关重要的。
图 3.1 – K8s 发布周期
在本节中,我们了解了使用各大云服务提供商的托管 K8s 服务的优势。这些服务使我们能够利用云的无缝扩展性、简便的管理和全球扩展,以便满足不同地域客户的需求,同时在运营中高效控制成本并达到甚至超越安全性和合规性要求。接下来,我们将使用基础设施即代码(IaC)在 AWS 云中设置我们的第一个 K8s 集群,并将 my-llama
模型部署到其中。
在云中设置 K8s 集群
托管 K8s 服务通常是上游的并符合 K8s 标准,这意味着我们可以在不修改应用代码的情况下,无缝地将工作负载从一个服务迁移到另一个服务。你可能仍然需要使用云服务商特定的附加组件,以便与相应的云服务进行集成。由于这一点,在本书的其余部分,我们将使用 AWS 云和 Amazon EKS。你也可以使用其他云服务商的产品进行类似的设置。
Amazon EKS 是一项区域性的 AWS 服务,消除了在 AWS 上安装、操作和维护 K8s 控制平面的需要。一个 Amazon EKS 集群提供一个单租户、高可用的 K8s 控制平面,分布在三个可用区(AZ)中,以应对可用区范围内的故障。对于数据平面,你可以从 图 3.2 中展示的选项中进行选择:
图 3.2 – 亚马逊 EKS 数据平面选项
让我们详细了解这些选项:
-
自管理节点:这是由用户手动管理的一组 Amazon EC2 (
aws.amazon.com/ec2/
) 实例。Amazon EC2 是一项托管服务,提供可扩展的计算能力,用于 AWS 云中。客户负责启动工作节点,使其能够加入集群,并管理其生命周期操作(如配置、更新和销毁)。此选项提供了对节点配置、设置和管理的精细控制,但也增加了操作开销。 -
EKS 托管节点组:这是 EKS 用户中最受欢迎的选择。它提供了自动化的 API,用于配置和管理工作节点的生命周期。每个托管节点组都作为 Amazon EC2 自动扩展组 (ASG) (
docs.aws.amazon.com/autoscaling/ec2/userguide/auto-scaling-groups.html
) 的一部分进行配置,并由 Amazon EKS 管理。ASG 会自动管理 EC2 实例的扩展,确保运行适当数量的实例来处理负载。欲了解更多信息,请访问docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html
。 -
AWS Fargate:这是一种无服务器计算引擎,用于运行容器化工作负载。使用 AWS Fargate,您无需管理运行 K8s Pods 的底层计算基础设施。AWS 负责管理工作节点的配置、补丁和扩展。它会自动配置与您的 Pod 资源要求匹配的计算容量,并通过为每个 Pod 提供专用内核来提供更高级别的安全隔离。访问
docs.aws.amazon.com/eks/latest/userguide/fargate.html
了解更多信息。 -
Karpenter 管理节点:Karpenter 是一个高性能的集群自动扩展解决方案,它会自动启动合适数量的计算资源来处理集群的工作负载。它会观察未调度 Pod 的聚合资源需求,并做出启动和终止工作节点的决策。我们将在第六章中详细探讨这个问题。访问
karpenter.sh/
了解更多信息。
关于这些数据平面选项的详细比较,您可以参考 EKS 文档:docs.aws.amazon.com/eks/latest/userguide/eks-compute.html
。
我们将使用一个 IaC 工具来自动化配置所需 AWS 基础设施的过程。Terraform(www.hashicorp.com/products/terraform
)是最流行的云无关的 IaC 工具,由 HashiCorp 公司开发(www.hashicorp.com/
)。它被用来自动配置和管理任何云或数据中心中的资源。Terraform 使用一种称为HashiCorp 配置语言(HCL)(github.com/hashicorp/hcl
)的领域特定语言来定义基础设施,并支持输入和输出变量,从而使得配置可以定制化。要了解更多关于 Terraform 的信息,请参阅 HashiCorp 网站上的 AWS 入门教程:developer.hashicorp.com/terraform/tutorials/aws-get-started
。
为了促进模块化,多个配置文件可以组织成一个 Terraform 模块,以封装一组相关的资源。Terraform 有一个庞大的社区,维护着一个开源模块的注册表,您可以在registry.terraform.io/
中找到供公众使用的模块。在本教程中,我们将使用以下社区模块来配置所需的基础设施:
-
AWS VPC Terraform模块(
registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest
)将用于创建 Amazon VPC 资源。 -
AWS EKS Terraform 模块(
registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest
)将用于创建 Amazon EKS 集群、节点组、AWS Fargate 配置文件等。 -
Amazon EKS 蓝图插件模块(
registry.terraform.io/modules/aws-ia/eks-blueprints-addons/aws/latest
)将用于在 Amazon EKS 集群上部署 K8s 插件。
让我们从设置环境开始,以便使用 Terraform 配置 EKS 集群。
重要提示
在本教程中,我们将创建一些不属于 AWS 免费套餐的 AWS 资源,因此你会产生费用。请参考 AWS 定价计算器 calculator.aws/#/
获取费用估算。
前提条件
以下是在云中设置 K8s 集群的前提条件:
-
如果你还没有 AWS 账户,可以在
aws.amazon.com/free/
创建一个免费的 AWS 账户。AWS 提供了许多服务的慷慨免费套餐。Amazon EKS 不属于免费套餐,因此本教程中配置的任何资源都会产生费用。 -
按照
docs.aws.amazon.com/streams/latest/dev/setting-up.html#setting-up-iam
中的说明创建一个具有管理员权限的 IAM 用户,并生成编程访问凭证。 -
通过访问
docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html
安装 AWS CLI,并使用管理员用户的 AWS 访问凭证进行配置。 -
通过访问
developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli
安装 Terraform CLI。 -
通过访问
docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html
安装 kubectl,这是与 K8s 集群交互的官方 CLI 工具。
配置 Amazon EKS 集群
重要提示
Terraform 提供者和模块经常更新,带来新的功能和修复。在编写时,我们使用了可用的最新兼容版本。你可以通过访问 Terraform 注册表 registry.terraform.io/
来更新到最新版本。
让我们从设置 Terraform 项目开始:
-
创建一个名为
genai-eks-demo
的新项目目录:$ mkdir -p genai-eks-demo versions.tf file that defines a list of Terraform providers and their respective versions. A versions.tf file from our GitHub repository at https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/versions.tf:
terraform {
required_version = ">= 1.11"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.96"
}
helm = {
source = "hashicorp/helm"
version = ">= 2.17"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = ">= 2.36"
}
}
locals.tf
文件用于存储本地变量,如名称、AWS 区域和 VPC 可用区(azs),用于获取给定 AWS 区域(us-west-2)中的 AWS 可用区(AZs),并按 opt-in 状态进行过滤。你可以从我们的 GitHub 仓库下载locals.tf
文件,链接地址为github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/locals.tf
:data "aws_availability_zones" "azs" { filter { name = "opt-in-status" values = ["opt-in-not-required"] } } locals { name = "eks-demo" region = "us-west-2" vpc_cidr = "10.0.0.0/16" azs = slice (data.aws_availability_zones.azs.names, 0, 3) }
-
创建一个
providers.tf
文件,用于配置 Terraform 提供程序。在这里,我们使用 EKS 集群凭证初始化 K8s 和 Helm 提供程序,以便与集群进行交互。你可以从我们的 GitHub 仓库下载providers.tf
文件,链接地址为github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/providers.tf
:provider "aws" { region = local.region } provider "kubernetes" { host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) exec { api_version = "client.authentication.k8s.io/v1beta1" command = "aws" args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] } } provider "helm" { kubernetes { host = module.eks.cluster_endpoint cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) exec { api_version = "client.authentication.k8s.io/v1beta1" command = "aws" args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] } } }
-
创建一个
vpc.tf
文件,用于定义 Amazon VPC、公共和私有子网、互联网网关、NAT 网关以及 EKS 集群所需的其他网络资源。你可以参考 AWS 文档docs.aws.amazon.com/vpc/latest/userguide/how-it-works.html
来了解更多关于这些概念的信息。你可以从我们的 GitHub 仓库下载vpc.tf
文件,链接地址为github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/vpc.tf
:module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "~> 5.21" name = local.name cidr = local.vpc_cidr azs = local.azs private_subnets = [for k, v in local.azs: cidrsubnet(local.vpc_cidr, 4, k)] public_subnets = [for k, v in local.azs: cidrsubnet(local.vpc_cidr, 8, k + 48)] enable_nat_gateway = true single_nat_gateway = true public_subnet_tags = { "kubernetes.io/role/elb" = 1 } private_subnet_tags = { "kubernetes.io/role/internal-elb" = 1 "karpenter.sh/discovery" = local.name } } output vpc_id { description = "VPC ID" value = "${module.vpc.vpc_id}" }
-
要使用 Terraform 部署 Amazon VPC 资源,首先运行
terraform init
(developer.hashicorp.com/terraform/cli/commands/init
)。此命令将初始化你的工作目录,下载所需的 Terraform 提供程序插件,并配置后端来存储 Terraform 的状态。它确保你的环境已正确设置,才能进行任何更改。接下来,运行terraform plan
(developer.hashicorp.com/terraform/cli/commands/plan
)生成执行计划,让你预览 Terraform 将采取的操作,比如哪些资源将被创建、修改或销毁,而不会实际进行更改。审查计划有助于及早发现潜在的配置错误。最后,运行terraform apply
(developer.hashicorp.com/terraform/cli/commands/apply
)将更改应用到你的基础设施上。此命令将配置定义的 VPC 资源,并在进行任何更改之前提示确认:
$ terraform init $ terraform plan $ terraform apply -auto-approve
-
完成后,你将在输出中看到 VPC ID:
Apply complete! Resources: 23 added, 0 changed, 0 destroyed. Outputs: vpc_id = "vpc-1234567890"
-
在
eks.tf
文件中,我们必须使用terraform-aws-modules/eks
(registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest
)模块来定义 EKS 集群。这个模块通过抽象化底层的 AWS API 调用,简化了 EKS 的配置。在这里,我们需要指定所需的集群版本、VPC 子网和托管节点组。我们定义了一个托管节点组,至少包含两台来自通用目的(m)
实例系列的 EC2 工作节点。如果遇到InsufficientInstanceCapacity
(docs.aws.amazon.com/AWSEC2/latest/UserGuide/troubleshooting-launch.html#troubleshooting-launch-capacity
)错误,可以随时修改instance_types
属性的值。你可以从此页面选择通用目的
实例类型:aws.amazon.com/ec2/instance-types/
。 -
在这个示例中,我们为 EKS 集群定义了一个托管节点组(
eks-mng
)。这个节点组是一组 EC2 实例,将作为集群中的工作节点。在m5.large
、m6i.large
、m6a.large
等实例类型中,EKS 将根据可用性尝试使用这些实例之一。这使得实例类型更加灵活,有助于提高弹性,并可能通过利用 Spot 实例池或区域可用性来降低成本。你可以从我们的 GitHub 仓库下载eks.tf
文件:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/eks.tf
。module "eks" { source = "terraform-aws-modules/eks/aws" version = "~> 20.36" cluster_name = local.name cluster_version = "1.32" enable_cluster_creator_admin_permissions = true cluster_endpoint_public_access = true vpc_id = module.vpc.vpc_id subnet_ids = module.vpc.private_subnets eks_managed_node_groups = { eks-mng = { instance_types = ["m5.large","m6i.large","m6a.large","m7i.large","m7a.large"] max_size = 3 desired_size = 2 } } node_security_group_tags = { "karpenter.sh/discovery" = local.name } } output "configure_kubectl" { description = "Configure kubectl" value = "aws eks --region ${local.region} update-kubeconfig --name ${module.eks.cluster_name}" }
-
应用 Terraform 代码以部署 EKS 集群和托管节点组资源。EKS 还会在集群中部署默认的网络附加组件,如
vpc-cni
、kube-proxy
和 CoreDNS。$ terraform init $ terraform plan $ terraform apply -auto-approve
部署成功后,你将看到以下输出。你可以使用它来配置
kubectl
CLI,以便它与 EKS 集群进行交互。Apply complete! Resources: 33 added, 0 changed, 0 destroyed. Outputs: configure_kubectl = "aws eks --region us-west-2 update-kubeconfig --name eks-demo" vpc_id = "vpc-1234567890"
复制并在终端中运行以下命令,将
kubectl
CLI 指向eks-demo
集群:$ aws eks --region us-west-2 update-kubeconfig --name eks-demo
-
接下来,我们将安装所需的操作附加软件,使我们的 EKS 集群准备好投入生产使用。这包括来自我们 GitHub 仓库的
addons.tf
等附加组件:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/addons.tf
。我们正在使用eks-blueprints-addons(github.com/aws-ia/terraform-aws-eks-blueprints-addons
)Terraform 模块,将这些附加组件部署到 EKS 集群中。module "eks_blueprints_addons" { source = "aws-ia/eks-blueprints-addons/aws" version = "~> 1.21" cluster_name = module.eks.cluster_name cluster_endpoint = module.eks.cluster_endpoint cluster_version = module.eks.cluster_version oidc_provider_arn = module.eks.oidc_provider_arn enable_aws_load_balancer_controller = true ... } module "karpenter" { source = "terraform-aws-modules/eks/aws//modules/karpenter" ... }
-
应用 Terraform 代码以在集群上部署附加软件:
$ terraform init $ terraform plan $ terraform apply -auto-approve
-
通过运行以下
kubectl
命令验证安装。你会注意到集群中有两个工作节点和 10 个 K8s Pod 在运行:$ kubectl get nodes -o wide $ kubectl get pods -A -o wide
-
这就完成了 EKS 集群的初始设置。该过程如图 3.3所示:
图 3.3 – 我们 EKS 集群的高级架构
我们从设置所需的工具开始,这些工具包括 Terraform、AWS CLI、kubectl 等,并创建了一个 Terraform 项目来配置各种 AWS 网络组件,如 Amazon VPC、子网和互联网网关,然后在此基础上部署了 Amazon EKS 集群,并添加了 AWS 负载均衡器和 Karpenter 插件。我们将在本书中继续构建这个设置,并创建一个生产就绪的系统,用于部署和运营生成型 AI 工作负载。
在 K8s 集群中部署我们的第一个生成型 AI 模型
在本节中,我们将在 EKS 集群上部署我们在第二章中构建的 Llama 模型,并使用最新的nginx
容器镜像,通过 AWS 的nginx-pod
暴露给终端用户,该容器运行在80
端口。你可以从我们的 GitHub 仓库下载此清单文件:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/nginx-pod.yaml
:
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
K8s 还提供了高级抽象构件,使我们可以声明性地管理我们的工作负载:
-
部署 (
kubernetes.io/docs/concepts/workloads/controllers/deployment/
) -
副本集 (
kubernetes.io/docs/concepts/workloads/controllers/replicaset/
) -
有状态副本集 (
kubernetes.io/docs/concepts/workloads/controllers/statefulset/
) -
守护进程集 (
kubernetes.io/docs/concepts/workloads/controllers/daemonset/
)
每个工作负载或工作负载的组件都作为容器在 Pod 内运行,而管理这些 Pod 通常需要大量的工作。例如,如果一个 Pod 失败,则需要创建一个新的替代 Pod,或者必须推出工作负载的新版本。这些高级构件有助于抽象这些复杂性。
部署是最常见的 K8s 集群工作负载部署方式。它自动化了滚动更新过程,确保可以在不中断的情况下发布工作负载的新版本。我们还可以应用扩展策略,轻松地根据需求增加或减少 Pod 的数量。此外,部署使用副本集(ReplicaSets)确保高可用性,自动重启失败的 Pod 并保持应用程序的期望状态,这对于生产环境的弹性至关重要。
在 K8s 中,服务(Service)是一种抽象,允许您通过网络暴露一个或多个 Pod。它定义了一组逻辑上的端点,通常由 Pod 组成,并包含一个管理其可访问性的策略。K8s 服务对象有多种类型:
-
ClusterIP (
kubernetes.io/docs/concepts/services-networking/service/#type-clusterip
) -
负载均衡器(LoadBalancer) (
kubernetes.io/docs/concepts/services-networking/service/#loadbalancer
) -
NodePort (
kubernetes.io/docs/concepts/services-networking/service/#type-nodeport
) -
ExternalName (
kubernetes.io/docs/concepts/services-networking/service/#externalname
)。
在本次操作指南中,我们将使用负载均衡器(LoadBalancer)服务类型来暴露我们的应用程序,进而在 AWS 云中配置我们的 NLB。
让我们为在第二章中创建的my-llama
容器创建一个部署规范。为此,我们需要将本地容器镜像上传到容器注册表,以便 EKS 可以下载。ecr.tf
文件包含以下 Terraform 代码,用于创建一个 Amazon ECR 仓库并启用镜像标签不可变性,以防止镜像标签被覆盖。您可以从我们的 GitHub 仓库下载ecr.tf
文件:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/ecr.tf
。
resource "aws_ecr_repository" "my-llama" {
name = "my-llama"
image_tag_mutability = "MUTABLE"
}
output "ecr_push_cmds" {
description = "Command to authenticate with ECR and push the container image."
value = <<EOT
aws ecr get-login-password --region ${local.region} | docker login --username AWS --password-stdin ${aws_ecr_repository.my-llama.repository_url}
docker tag my-llama ${aws_ecr_repository.my-llama.repository_url}
docker push ${aws_ecr_repository.my-llama.repository_url}
EOT
}
运行以下命令来创建 ECR 仓库:
$ terraform plan
$ terraform apply -auto-approve
运行terraform output
命令以列出 ECR 上传命令。复制并粘贴这些输出命令到终端中,将my-llama
容器镜像推送到 ECR 仓库:
$ terraform output -raw ecr_push_cmds
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama
docker tag my-llama 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama
docker push 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama
使用my-llama
ECR 镜像创建 K8s 部署资源:
$ kubectl create deploy my-llama --image 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama
deployment.apps/my-llama created
K8s 部署可能需要几分钟才能进入Ready
状态。您可以通过运行以下命令并查看Ready
状态来验证:
$ kubectl get deploy my-llama
NAME READY UP-TO-DATE AVAILABLE AGE
my-llama 1/1 1 1 6m
现在 my-llama
模型正在部署到 EKS 集群中,下一步是通过 AWS NLB 将其暴露到集群外部。我们将使用包含以下内容的 my-llama-svc.yaml
。这将在端口 80
上创建一个 LoadBalancer
类型的 K8s 服务,并将流量转发到 my-llama
容器的端口 5000
。在这里,我们还添加了注释,使其成为面向互联网的 NLB。
您可以从我们的 GitHub 仓库下载 my-llama-svc.yaml
文件:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch3/my-llama-svc.yaml
重要提示
我们正在为测试目的创建一个面向公众的 NLB。请随时通过更新 NLB 安全组的入站规则来限制访问您的 IP 地址。有关详细信息,请参阅 AWS NLB 文档:docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-security-groups.html
。
apiVersion: v1
kind: Service
metadata:
labels:
app: my-llama-svc
name: my-llama-svc
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
spec:
ports:
- port: 80
protocol: TCP
targetPort: 5000
type: LoadBalancer
selector:
app: my-llama
通过运行以下命令部署 K8s 服务并获取 NLB 主机名:
$ kubectl apply -f my-llama-svc.yaml
service/my-llama-svc created
$ export NLB_URL=$(kubectl get svc my-llama-svc -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')
$ echo $NLB_URL
K8s Pod 端点注册到 AWS NLB 并变为健康状态可能需要最多 5 分钟。您可以运行以下命令查看 K8s Pod 日志、事件和服务状态:
$ kubectl describe pod -l app=my-llama
$ kubectl logs -f -l app=my-llama
$ kubectl describe svc my-llama-svc
现在,我们可以通过该 NLB 端点调用 Llama 模型。运行以下 curl
命令将测试提示发送到 my-llama
服务。它将把提示转发给 Llama 模型以生成响应:
$ curl -X POST http://$NLB_URL/predict -H "Content-Type: application/json" -d '{"prompt":"Create a poem about humanity?","sys_msg":"You are a helpful, respectful, and honest assistant. Always provide safe, unbiased, and positive responses. Avoid harmful, unethical, or illegal content. If a question is unclear or incorrect, explain why. If unsure, do not provide false information."}' | jq .
在本节中,我们将 my-llama
容器映像作为 K8s 部署部署在 EKS 集群中,并通过创建 K8s LoadBalancer 服务将其暴露到互联网。最后,我们使用 curl
实用程序针对 AWS NLB 端点的 URL 对我们的 LLM 进行推理。类似地,您可以将其他公开可用模型如 Mistral (huggingface.co/mistralai
), Falcon (huggingface.co/tiiuae
), 和 DeepSeek (huggingface.co/deepseek-ai
) 容器化,并部署到您的 EKS 集群中。
摘要
在本章中,我们讨论了云计算如何从根本上改变我们访问和利用计算资源的方式,通过按需付费模型提供了可扩展性、可访问性和成本效益。主要云服务提供商提供的托管 K8s 服务简化了 K8s 集群的部署和管理,这对于运行 GenAI 模型尤其有利。我们以 Amazon EKS 为例,介绍了如何自动化设置 EKS 集群的过程。接着,我们使用 Terraform 进行集群配置,设置了一个包含所需提供程序的 Terraform 项目,并利用社区模块创建了 AWS 资源,如 VPC、EKS 集群、EKS 管理的节点组和集群附加组件。之后,我们将my-llama
模型作为 K8s 部署应用在 EKS 集群上,并通过 K8s LoadBalancer 服务将其暴露到互联网上。
在下一章中,我们将讨论如何优化通用基础模型,以便用于特定领域的用例,如聊天机器人。我们将介绍一些特定的技术,如检索增强生成(RAG)和微调方法,并深入探讨如何实现这些技术。
加入拥有 44000+订阅者的 CloudPro 时事通讯
想了解云计算、DevOps、IT 管理、网络等领域的最新动态吗?扫描二维码订阅CloudPro,我们每周发布的时事通讯,已有 44,000+名技术专业人士订阅,帮助他们保持信息领先。
第二部分:使用 K8s 将 GenAI 工作负载投入生产
本部分提供了一个全面的指南,帮助您在生产 K8s 环境中部署、扩展和优化 GenAI 应用。通过实际案例(例如电商聊天机器人),本部分涵盖了模型优化、成本和资源管理、网络与安全最佳实践以及 GPU 资源优化等关键技术,帮助实现从实验阶段到生产就绪解决方案的平稳过渡。
本部分包括以下章节:
-
第四章,针对特定领域用例优化 GenAI 模型
-
第五章,在 K8s 上使用 GenAI:聊天机器人示例
-
第六章,在 Kubernetes 上扩展 GenAI 应用
-
第七章,在 Kubernetes 中优化 GenAI 应用的成本
-
第八章,在 Kubernetes 中部署 GenAI 的网络最佳实践
-
第九章,在 Kubernetes 中部署 GenAI 的安全最佳实践
-
第十章,在 Kubernetes 中优化 GPU 资源以支持 GenAI 应用
第四章:在 K8s 上使用 GenAI:聊天机器人示例
在本章中,我们将基于第四章中讨论的示例,开始在 K8s/Amazon EKS 上部署这些示例。我们将从在 EKS 上部署 JupyterHub (jupyter.org/hub
) 开始,这可以用于模型实验。接下来,我们将在 EKS 上微调 Llama 3 模型 并进行部署。最后,我们将设置一个 RAG 驱动的聊天机器人,它将为 电商 公司使用案例提供个性化推荐。
我们将涵盖以下关键主题:
-
电商的 GenAI 使用案例
-
使用 JupyterHub 进行实验
-
在 K8s 上微调 Llama 3
-
在 K8s 上部署微调模型
-
在 K8s 上部署 RAG 应用
-
在 K8s 上部署聊天机器人
技术要求
在本章中,我们将使用以下工具,其中一些工具需要您注册帐户并创建访问令牌:
-
Hugging Face:
huggingface.co/join
-
OpenAI:
platform.openai.com/signup
-
可以通过 Hugging Face 访问的 Llama 3 模型:
huggingface.co/meta-llama/Meta-Llama-3-8B
-
一个 Amazon EKS 集群,如第三章所示
电商的 GenAI 使用案例
正如我们在第一章中讨论的那样,在探索GenAI 应用的部署选项时,考虑 关键绩效指标 (KPIs) 和 商业目标 是至关重要的。对于电商平台,可能的使用案例包括用于回答客户问题的聊天机器人、个性化推荐、产品描述的内容创作和个性化营销活动。
假设我们有一个电商公司,名为 MyRetail,我们被赋予了探索和部署 GenAI 使用案例的责任。该公司发展迅速,具有明确的目标和强大的差异化竞争力:为客户提供个性化、无缝的购物体验。为了保持竞争力,MyRetail 旨在将前沿的 AI 技术整合到客户服务中,同时专注于以下两个功能:
-
使用 RAG 系统 创建个性化产品推荐。
-
使用微调后的 GenAI 模型 提供自动化回应,解答关于公司忠诚计划的咨询。
MyRetail 的客户群体多样化,这意味着通用的产品推荐和传统的 FAQ 系统已不再足够。客户期待个性化的购物体验,而公司的 MyElite 忠诚计划 需要提供有关奖励、积分和帐户状态的实时、详细信息。
为了实现这些目标,MyRetail 决定采用开源的 K8s 编排平台,并选择在云中部署 Amazon EKS。他们计划构建一个聊天机器人应用程序,使用两个 GenAI 模型:第一个将是经过微调的 Llama 3 模型,该模型基于他们的 MyElite 忠诚计划常见问题解答来回答用户查询;第二个将是一个 RAG 应用程序,它通过提供上下文购物目录数据来补充用户查询,从而提升他们的购物体验。该解决方案的整体架构如 图 5.1 所示:
图 5.1 – 聊天机器人架构
然而,在我们将这个聊天机器人和RAG 系统部署到 EKS 上之前,让我们先为数据科学家们创建一个基于 JupyterHub 的实验环境,以便他们进行实验并优化 GenAI 模型。
使用 JupyterHub 进行实验
实验 在任何 GenAI 项目的生命周期中都扮演着至关重要的角色,因为它使工程师和研究人员能够迭代、改进和优化模型,同时提升性能。为此有多种工具可以使用;回想一下我们在 第四章 中使用了 Anaconda 和 Google Colab。这些工具主要帮助我们与 GenAI 模型进行交互式实验,进行可视化、监控,并与流行的 AI/ML 框架进行集成,结合云服务,且与他人协作。由于其灵活性和易用的 Web 界面,Jupyter Notebook (jupyter.org/
) 在数据科学家和 ML 工程师中得到了广泛应用。这从笔记本包的平均每日下载量(90 万到 100 万次下载) (pypistats.org/packages/notebook
) 中可以看出,数据来源是 Python 包索引 (pypi.org/
)。传统上,我们将这些 notebook 安装在本地机器上,但它们通常需要专用资源,如 GPU,才能进行有意义的分析。为了解决这个问题,我们将利用 K8s 集群,根据需要通过 JupyterHub 启动 Jupyter notebook。
JupyterHub 提供了一个集中式平台,用于运行 Jupyter notebook,使用户能够访问计算资源,而无需单独安装或维护。系统管理员可以有效地管理用户访问权限,并根据用户的特定需求,通过预配置的工具和设置定制环境。
让我们从学习如何在 Amazon EKS 集群上安装 JupyterHub 开始:
-
首先,部署
addons.tf
来安装 CSI 插件,并添加必要的 IAM 权限。完整的代码可以在 GitHub 上找到,地址是github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/addons.tf
:module "eks_blueprints_addons" { .... eks_addons = { aws-ebs-csi-driver = { service_account_role_arn = module.ebs_csi_driver_irsa.iam_role_arn } .... } module "ebs_csi_driver_irsa" { ... role_name_prefix = format("%s-%s", local.name, "ebs-csi-driver-") attach_ebs_csi_policy = true ... }
-
现在,我们需要为 EBS CSI 驱动程序创建一个默认的 StorageClass(
kubernetes.io/docs/concepts/storage/storage-classes/
),该类指定了回收策略、存储提供程序以及动态卷配置中使用的其他参数。在这里,我们将新型的 gp3 设置为 EBS CSI 驱动程序创建的卷的默认类型。请参考 Amazon EBS 文档docs.aws.amazon.com/ebs/latest/userguide/ebs-volume-types.html
以了解更多关于不同卷类型的信息:resource "kubernetes_annotations" "disable_gp2" { annotations = { "storageclass.kubernetes.io/is-default-class": "false" ... metadata { name = "gp2" ... resource "kubernetes_storage_class" "default_gp3" { metadata { name = "gp3" annotations = { "storageclass.kubernetes.io/is-default-class": "true" ... }
-
运行以下命令将 EBS CSI 驱动程序部署到 EKS 集群:
$ terraform init $ terraform plan $ terraform apply -auto-approve
-
你可以通过运行以下命令来验证附加组件的安装状态:
$ aws eks describe-addon --cluster-name eks-demo --addon-name aws-ebs-csi-driver ... "addonName": "aws-ebs-csi-driver", "clusterName": "eks-demo", "status": "ACTIVE", eks-data-addons (https://registry.terraform.io/modules/aws-ia/eks-data-addons/aws/latest) Terraform module on the EKS cluster. This open source module can be utilized to deploy commonly used data and AI/ML K8s add-ons. Please refer to the Terraform documentation page at https://registry.terraform.io/modules/aws-ia/eks-data-addons/aws/latest#resources to find a list of available add-ons. Make sure you download the aiml-addons.tf file from https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/aiml-addons.tf; it includes Terraform code to deploy the JupyterHub add-on on the EKS cluster. Let’s walk through what the Terraform code does:* A random 16-character string is created to secure access to JupyterHub: Important note In this setup, we are using a dummy authentication method where JupyterHub uses a static username and password. It also provides other authentication methods, as listed at [`jupyterhub.readthedocs.io/en/latest/reference/authenticators.html`](https://jupyterhub.readthedocs.io/en/latest/reference/authenticators.html). Use the method that fits your needs. ``` resource "random_password" "jupyter_pwd" { length = 16 special = true override_special = "_%@" } ``` * A new K8s namespace called `jupyterhub` is defined to deploy the JupyterHub Helm chart: ``` resource "kubernetes_namespace" "jupyterhub" { metadata { name = "jupyterhub" } } ``` * A K8s service account and an IAM role with appropriate S3 permissions to read from the S3 buckets are defined for interacting with S3 via Jupyter notebooks. We are using the *IAM roles for service accounts* ([`docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html`](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html)) feature of Amazon EKS, which provides IAM credentials to applications running in K8s Pods securely: ``` module "jupyterhub_single_user_irsa" { ... role_name = "${module.eks.cluster_name}-jupyterhub-single-user-sa" role_policy_arns = { policy = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess" } ... resource "kubernetes_service_account_v1" "jupyterhub_single_user_sa" { metadata { name = "${module.eks.cluster_name}-jupyterhub-single-user" annotations = {"eks.amazonaws.com/role-arn": module.jupyterhub_single_user_irsa.iam_role_arn} ... ``` * Now, we must deploy the JupyterHub Helm chart. We are using Helm values from a public S3 bucket available at [`kubernetes-for-genai-models.s3.amazonaws.com/chapter5/jupyterhub-values.yaml`](https://kubernetes-for-genai-models.s3.amazonaws.com/chapter5/jupyterhub-values.yaml). It contains the necessary configuration to enable dummy authentication using the password we randomly generated previously and uses a K8s service account for Jupyter notebook Pods: ``` data "http" "jupyterhub_values" { url = "https://kubernetes-for-genai-models.s3.amazonaws.com/chapter5/jupyterhub-values.yaml" } module "eks_data_addons" { source = "aws-ia/eks-data-addons/aws" ... enable_jupyterhub = true jupyterhub_helm_config = { values = [local.jupyterhub_values_rendered] ... ``` * Run the following commands to deploy JupyterHub on the EKS cluster: ``` $ terraform init $ terraform plan $ terraform apply -auto-approve ``` * Verify that JupyterHub has been installed by running the following commands: ``` $ helm list -n jupyterhub NAME NAMESPACE REVISION STATUS eks.tf 文件包含 GPU 节点(g6.2xlarge)。在这里,我们使用 EC2 Spot 实例定价以最小化 AWS 费用;有关定价详情,请参考 [`aws.amazon.com/ec2/spot/pricing/`](https://aws.amazon.com/ec2/spot/pricing/)。我们还为 K8s 节点添加了污点([`kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/`](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/)),确保只有 GPU 工作负载被调度到这些工作节点。K8s 污点是键值对,应用于节点,以防止某些 Pod 在不容忍污点的情况下调度到这些节点,从而更好地控制工作负载的位置。通过应用污点,我们可以确保非 GPU 工作负载不会调度到 GPU 节点,从而将这些节点专门保留给 GPU 优化的 Pod。你可以从 [`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/eks.tf`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/eks.tf) 下载 eks.tf 文件。 ``` module "eks" { .... eks_managed_node_groups = { eks-gpu-mng = { instance_types = ["g6.2xlarge"] capacity_type = "SPOT" taints = { gpu = { key = "nvidia.com/gpu" value = "true" effect = "NO_SCHEDULE" .... } ``` ``` * Run the following commands to add the GPU node group to the EKS cluster. Please note that this may take 5-10 minutes. You can verify the GPU node’s status by running the following `kubectl` command, which outputs the node’s name and status: ``` $ terraform init $ terraform plan $ terraform apply -auto-approve $ kubectl get nodes -l nvidia.com/gpu.present=true NAME STATUS ip-10-0-17-1.us-west-2.compute.internal Ready ``` * Now, we can connect to the JupyterHub console to create a notebook. In this setup, we’ve limited JupyterHub console access to within the cluster by exposing it as a **ClusterIP** service. Run the following commands to connect to the console locally; alternatively, you can set the service type to **LoadBalancer** to expose it via a public NLB: ``` $ kubectl port-forward svc/proxy-public 8000:80 -n jupyterhub ``` * You can launch the JupyterHub console by navigating to http://localhost:8000/ in your web browser. You’ll see a login page, similar to what’s shown in *Figure 5**.2*. Here, we’ve pre-created a user named *k8s-admin1* as part of our JupyterHub installation. Run the following command to retrieve the password of that user: ``` $ terraform output jupyter_pwd ```
图 5.2 – JupyterHub 登录页面
- 登录后,您将看到三个 notebook 选项。由于我们使用 JupyterHub 进行需要 GPU 支持的 GenAI 任务,请选择数据科学 (GPU)并点击开始,如图 5.3所示:
图 5.3 – JupyterHub 主页
-
如图 5.4所示,这将启动一个新的 notebook 实例,该实例将在 K8s Pod 中运行,并请求一个持久卷声明(PVC)(
kubernetes.io/docs/concepts/storage/persistent-volumes
),以便 EBS CSI 驱动程序创建一个 Amazon EBS 卷并将其附加到 notebook。我们在这里使用持久卷,以便在 Pod 重启和终止时保留 notebook 的状态、数据和配置。这使得 notebook 实例可以在一段时间不活动后终止,并在用户返回时重新启动,从而提高了成本效率:$ kubectl get pods -n jupyterhub -l component=singleuser-server NAME READY STATUS RESTARTS AGE jupyter-k8s-2dadmin1 1/1 Running 0 9m
图 5.4 – 我们的 Jupyter notebook
-
现在,您可以从第四章导入以下 notebook,并执行必要的命令来测试 RAG 和 微调 示例:
有了这个设置,我们已经在 EKS 集群上部署了 JupyterHub,并用它启动了一个 Jupyter notebook 来测试我们的 GenAI 实验脚本。接下来,我们将把微调和 RAG 脚本容器化,并作为 K8s Pods 在 EKS 集群上运行。
在 K8s 中微调 Llama 3
正如在第三章中讨论的那样,在 K8s 上运行微调工作负载有多个优势,包括可扩展性、高效的资源利用率、可移植性和监控。在本节中,我们将把在 Jupyter notebook 中进行的Llama 3 微调任务容器化,并将其部署到 EKS 集群上。这对于自动化端到端的 AI/ML 流水线以及模型版本管理至关重要。
微调 Llama 3 模型的步骤如下:
-
收集训练和评估数据集并将其存储在Amazon S3中。
-
创建容器镜像并将其上传到Amazon ECR。
-
在EKS 集群中部署微调任务。
数据准备
我们将利用两个数据集(训练集和评估集)来微调并验证kubernetes-for-genai-models
。Amazon S3 (aws.amazon.com/s3/
) 是一个对象存储服务,用于存储和检索任何数量的数据,适用于任何地方的访问,因此它是共享大规模数据集进行协作的理想选择:
$ aws s3 ls s3://kubernetes-for-genai-models/chapter5/
...
loyalty_qa_train.jsonl
loyalty_qa_val.jsonl
...
在本节中,我们探讨了将用于微调 Llama 3 模型的数据库,这些数据库用于我们的电子商务应用场景。我们还介绍了最佳实践,例如将这些数据库存储在 Amazon S3 等外部存储服务中,而不是将它们打包到容器镜像中。在下一节中,我们将专注于为微调任务创建容器镜像。
创建容器镜像
为了为微调任务创建容器镜像,我们需要一个Dockerfile、一个微调脚本和一个依赖项列表。我们已经创建了这些工件,并将它们上传到 GitHub: github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch5/llama-finetuning
。让我们开始构建容器,以便我们可以使用这些工件微调 Llama 3 模型:
-
创建名为
llama-finetuning
的目录:$ mkdir -p llama-finetuning datasets library:
...
train_dataset_file = os.environ.get('TRAIN_DATASET_FILE')
eval_dataset_file = os.environ.get('EVAL_DATASET_FILE')
train_dataset = load_dataset('json', data_files=train_dataset_file, split='train')
eval_dataset = load_dataset('json', data_files=eval_dataset_file, split='train')
-
训练后,我们需要保存模型权重、配置文件和分词器配置,以便它们可以用于后续创建推理容器:
trainer.save_model(f"./{fine_tuned_model_name}") tokenizer.save_pretrained(f"./{fine_tuned_model_name}")
-
将模型权重和配置文件导出到 S3 存储桶:
... def sync_folder_to_s3(local_folder, bucket_name, s3_folder): s3 = boto3.client('s3') for root, dirs, files in os.walk(local_folder): for file in files: ... try: s3.upload_file(local_path, bucket_name, s3_path) except Exception as e: print(f'Error uploading {local_path}: {e}') ... sync_folder_to_s3('./'+fine_tuned_model_name+'/', model_assets_bucket, fine_tuned_model_name)
-
创建一个 Dockerfile,我们可以用来构建微调容器镜像。它应当以nvidia/cuda (
hub.docker.com/r/nvidia/cuda
) 父镜像开始,安装所需的 Python 依赖,并包含微调脚本。完整文件可在github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/llama-finetuning/Dockerfile
获取:FROM nvidia/cuda:12.8.1-runtime-ubuntu24.04 ... RUN pip install torch transformers datasets peft accelerate bitsandbytes sentencepiece s3fs boto3 ... COPY fine_tune.py /app/fine_tune.py CMD ["python", "fine_tune.py"]
-
通过运行以下命令来创建容器镜像:
$ docker build -t my-llama-finetuned .
-
你可以使用以下 docker 命令验证容器镜像:
$ docker images REPOSITORY TAG IMAGE ID my-llama-finetuned latest 207a07f1bf00
到此为止,我们已经成功构建了容器镜像,以便微调 Llama 3 模型。接下来,我们将把它上传到 Amazon ECR 仓库,并将其部署到 EKS 集群中。
部署微调任务
为了部署微调任务,我们需要将容器镜像保存到Amazon ECR,并创建一个S3 存储桶,在其中保存模型资产,同时还需在EKS 集群中创建一个K8s 任务:
-
在
genai-eks-demo
目录中创建一个ecr.tf
文件。完整代码可以在 GitHub 上找到:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/ecr.tf
:resource "aws_ecr_repository" "my-llama-finetuned" { name = "my-llama-finetuned" ...
-
创建一个 Amazon S3 存储桶,以便存储微调后的模型资产。我们将使用 Terraform 创建一个。请从
github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/model-assets.tf
下载model-assets.tf
文件:resource "random_string" "bucket_suffix" { length = 8 ... resource "aws_s3_bucket" "my_llama_bucket" { bucket = "my-llama-bucket-${random_string.bucket_suffix.result}" ... output "my_llama_bucket" { value = "${aws_s3_bucket.my_llama_bucket.id}" ...
-
我们还需要创建一个之前下载的
eks.tf
文件:module "llama_fine_tuning_irsa" { ... role_name = "${module.eks.cluster_name}-llama-fine-tuning" role_policy_arns = { policy = "arn:aws:iam::aws:policy/AmazonS3FullAccess" } ... resource "kubernetes_service_account_v1" "llama_fine_tuning_sa" { metadata { name = "llama-fine-tuning-sa" ...
-
运行以下命令以创建ECR 仓库和S3 存储桶。S3 存储桶的名称将在输出中显示:
$ terraform init $ terraform plan terraform output command to list the ECR upload commands. Copy and paste those output commands into your terminal to push the my-llama-finetuned container image to ECR:
$ terraform output -raw my_llama_finetuned_ecr_push_cmds
aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama-finetuned
docker tag my-llama-finetuned 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama-finetuned
docker push 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama-finetuned
-
现在我们已经创建了所需的基础设施,接下来让我们将微调任务部署到EKS 集群。为此,我们需要创建一个K8s 任务清单文件,将其作为 K8s 任务运行。从 GitHub 下载清单文件:
github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/llama-finetuning/llama-finetuning-job.yaml
,并替换镜像、Hugging Face 令牌以及之前创建的包含模型资产的 S3 存储桶名称:apiVersion: batch/v1 kind: Job metadata: name: my-llama-job spec: ... containers: - name: my-llama-job-container image: <<Replace your ECR image name here>> env: - name: MODEL_ASSETS_BUCKET value: "<<Replace your S3 bucket here>>" - name: HUGGING_FACE_HUB_TOKEN value: "<<Replace your Hugging face token here>>" - name: TRAIN_DATASET_FILE value: "s3://kubernetes-for-genai-models/chapter5/loyalty_qa_train.jsonl" - name: EVAL_DATASET_FILE value: "s3://kubernetes-for-genai-models/chapter5/loyalty_qa_val.jsonl" ...
-
运行以下命令以在 EKS 集群上运行该任务:
$ kubectl apply -f llama-finetuning-job.yaml job.batch/my-llama-job is created
-
一个 K8s Pod 将被调度到 GPU 节点上并启动微调过程。你可以通过查看日志来监控其进度:
$ kubectl logs -f job/my-llama-job
-
微调完成后,任务将自动将模型资产上传到 S3 存储桶,如图 5.5所示:
图 5.5 – 一个包含微调模型资产的 Amazon S3 存储桶
在本节中,我们首先修改了我们的 Llama 3 精细调整脚本,使其可以用容器方式运行。然后,我们创建了一个包含该脚本及其依赖库的容器镜像,使用DockerHub上的nvidia/cuda
镜像作为基础。最后,我们创建了一个 Amazon ECR 存储容器镜像的存储库,并将其作为 K8s 作业部署到 EKS 集群中。在接下来的部分,我们将利用经过精细调整的模型资产创建推理容器镜像,并在 EKS 集群中部署它。
在 K8s 上部署经过精细调整的模型
在本节中,我们将使用Python FastAPI将经过精细调整的Llama 3 模型容器化,并部署为 EKS 集群中的 K8s 部署。
让我们从 S3 桶中的存储的精细调整模型资产开始创建推理容器。我们将使用 Python FastAPI (fastapi.tiangolo.com/
)将模型暴露为HTTP API。FastAPI 是一个现代高性能的 Python 构建 API 的 Web 框架:
-
创建名为
llama-finetuned-inf
的目录:$ mkdir -p llama-finetuned-inf $ cd llama-finetuned-inf
-
从 S3 桶下载模型资产到本地目录,以便我们可以将它们复制到容器镜像中:
nvidia/cuda as the parent image, installing the necessary Python dependencies, and adding the fine-tuned model from the model-assets directory. The complete file is available at https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/inference/Dockerfile:
FROM nvidia/cuda:12.8.1-runtime-ubuntu24.04
...
RUN pip install torch transformers peft accelerate bitsandbytes sentencepiece fastapi uvicorn
COPY model-assets /app/model-assets
COPY main.py /app/main.py
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
-
接下来,我们将使用
/generate
将精细调整的 Llama 3 模型包装成一个接受HTTP POST请求并在调用模型后返回响应的 API。您可以从github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/inference/main.py
下载完整的代码:... app = FastAPI() # Load tokenizer and model tokenizer = AutoTokenizer.from_pretrained('./model-assets') # Define the quantization configuration for 8-bit base_model_id = "meta-llama/Meta-Llama-3-8B" bnb_config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_use_double_quant=True, bnb_4bit_quant_type="nf4", bnb_4bit_compute_dtype=torch.bfloat16 ) base_model = AutoModelForCausalLM.from_pretrained(base_model_id, torch_dtype=torch.float16, quantization_config=bnb_config, device_map='auto') # Load the Peft Model from pre-trained assets model = PeftModel.from_pretrained(base_model, './model-assets') ... @app.route('/generate') async def generate(request: Request): ... inputs = tokenizer(prompt, return_tensors="pt").to(device) with torch.no_grad(): outputs = model.generate( **inputs, max_new_tokens=100, ... # Decode the response and return it response = tokenizer.decode(outputs[0], skip_special_tokens=True) return {"response": response}
-
通过运行以下命令创建容器镜像:
$ docker build -t my-llama-finetuned:inf .
-
我们将重用ECR 存储库用于此镜像。或者,您可以根据 Terraform 创建一个新的存储库,如第三章所述。在运行这些命令之前,请替换以下命令中的帐户号码和区域值:
$ aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama-finetuned $ docker tag my-llama-finetuned:inf 123456789012.dkr.ecr.us-west-2.amazonaws.com/my-llama-finetuned:inf finetuned-inf-deploy.yaml K8s manifest from https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/inference/finetuned-inf-deploy.yaml and replace the ECR image and Hugging Face access token:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-llama-finetuned-deployment
spec:
...
containers:
- name: llama-finetuned-container
image: <<替换您的 ECR 镜像位置>>
...
env:
- name: HUGGING_FACE_HUB_TOKEN
value: "<<替换您的 Hugging Face 令牌位置>>"
...
-
最后,通过运行以下命令部署模型:
$ kubectl apply -f finetuned-inf-deploy.yaml deployment.apps/my-llama-finetuned-deployment created service/my-llama-finetuned-svc created
-
由于需要从 ECR 下载镜像,K8s Pod 可能需要几分钟准备就绪。运行以下命令来验证其状态:
$ kubectl get all -l app.kubernetes.io/name=my-llama-finetuned NAME READY STATUS RESTARTS AGE pod/my-llama-finetuned-deployment-54c75f55fc-77tbc 1/1 Running 0 116s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/my-llama-finetuned-svc ClusterIP 172.20.86.243 <none> 80/TCP 116s ...
在本节中,我们采用了通过微调过程生成的模型资产,并使用 Python FastAPI 将其包装成 HTTP API。接着,我们创建了一个容器,将其部署到 EKS 集群,并通过 K8s ClusterIP 服务暴露出来。这演示了如何使用您的数据集自定义通用 LLM 的行为,并通过 K8s 服务它们。在下一节中,我们将探索如何在 K8s 上部署 RAG 应用程序。
在 K8s 上部署 RAG 应用程序。
如 第四章 中所述,RAG 使我们能够将外部知识源集成到 LLM 响应生成过程 中,从而生成更准确、更具上下文相关性的响应。通过在输入中提供最新且相关的信息,RAG 还减少了诸如幻觉现象的错误 (www.ibm.com/topics/ai-hallucinations
)。在本节中,我们将探索如何将 RAG 应用程序部署到 K8s 集群。以下是涉及的高层步骤:
-
设置一个向量数据库。
-
创建一个 RAG 应用程序,用于查询向量存储,并通过输入和上下文数据调用 LLM。
-
在 K8s 上部署 RAG 应用程序。
-
在向量存储中加载和索引数据。
我们将首先设置一个 向量数据库,它是一个专门用于存储和查询高维向量嵌入的数据存储。在 RAG 中,这些数据库通过允许对输入请求进行相似性搜索并检索相关信息,起着至关重要的作用。市面上有很多开源和商业向量数据库,例如 Pinecone (www.pinecone.io/
)、Qdrant (qdrant.tech/
)、Chroma (www.trychroma.com/
) 和 OpenSearch (opensearch.org/
)。由于这些产品很多都可以作为托管或 SaaS 模型使用,因此一个自然产生的问题是:我们应该什么时候选择在 K8s 中运行自托管的向量数据库?我们需要考虑的主要因素如下:
-
性能和延迟
-
数据主权和合规性
-
配置的可定制性
-
成本控制
-
如何避免供应商锁定
按照以下步骤进行:
-
对于我们的设置,我们使用 Qdrant 向量数据库来存储 MyRetail 的销售目录。让我们从我们的 GitHub 仓库下载
qdrant.tf
文件,地址是github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/qdrant.tf
,并运行terraform apply
命令。这将安装 Qdrant 向量数据库作为 K8s StatefulSet (kubernetes.io/docs/concepts/workloads/controllers/statefulset/
),并在 EKS 集群的qdrant
命名空间中使用公开的 Helm chart 进行部署。... resource "helm_release" "qdrant" { name = "qdrant" repository = "https://qdrant.github.io/qdrant-helm" chart = "qdrant" namespace = "qdrant" create_namespace = true }
-
创建一个
ecr.tf
文件,或者你可以从 GitHub 下载完整代码:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/ecr.tf
:resource "aws_ecr_repository" "rag-app" { name = "rag-app" }
-
运行以下命令部署 Qdrant
kubectl
命令。以下输出显示qdrant
向量数据库 Pod 已部署并处于Running
状态:$ terraform init $ terraform plan $ terraform apply -auto-approve $ kubectl get pods -n qdrant NAME READY STATUS RESTARTS AGE qdrant-0 1/1 Running 0 2m
-
可选地,你可以连接到 Qdrant 的 Web UI 与向量数据库进行交互。运行以下命令在本地连接到 Qdrant Web UI。请参考 Qdrant 文档:
qdrant.tech/documentation/interfaces/web-ui/
了解其各种功能:/load_data that takes an input filename and creates vector embeddings by calling the OpenAI API endpoint and stores the embeddings in the Qdrant database:
@app.post("/load_data")
async def load_data(request: LoadDataModel):
...
response = requests.get(request.url)
reader = csv.DictReader(file_content)
...
qdrant_store = QdrantVectorStore(
embedding=OpenAIEmbeddings(),
collection_name=collection_name,
client=qdrant_client
)
qdrant_store.add_documents(docs)
-
暴露一个名为
/generate
的 Python FastAPI 端点,接受用户输入的提示和可选的会话 ID。它使用OpenAIEmbeddings为输入提示生成嵌入,并在 Qdrant 向量数据库中执行相似性搜索以检索相关的上下文信息:@app.post("/generate") async def generate_answer(prompt_model: PromptModel): try: prompt = prompt_model.prompt session_id = prompt_model.session_id ... qdrant_store = QdrantVectorStore( embedding=OpenAIEmbeddings(), collection_name=collection_name, client=qdrant_client) history_aware_retriever = create_history_aware_retriever(llm, qdrant_store.as_retriever(), contextualize_q_prompt)
-
创建一个对话式 RAG 应用程序,记住用户的先前问题和回答,并应用可以纳入当前请求的逻辑。在这里,我们使用LangChain包中的
history_aware_retriever
(python.langchain.com/api_reference/langchain/chains/langchain.chains.history_aware_retriever.create_history_aware_retriever.html
)构建rag_chain
。它接受输入提示和之前的聊天记录,并调用 LLM(OpenAI 的GPT-3.5 Turbo)来获取上下文化的输入。LangChain 是一个用于构建由 LLM 驱动的应用程序的框架,旨在简化多个模型和数据管道的集成、管理和协调,用于创建聊天机器人、生成文本等任务:... rag_chain = create_retrieval_chain(history_aware_retriever, question_answer_chain) conversational_rag_chain = RunnableWithMessageHistory( rag_chain, get_session_history, input_messages_key="input", history_messages_key="chat_history", output_messages_key="answer") ...
-
调用 RAG 链,传入输入并返回响应,以及JSON 格式的会话 ID:
result = conversational_rag_chain.invoke( {"input": prompt}, config= {"configurable": {"session_id": session_id}}, )["answer"] ... return JSONResponse({"answer": result, "session_id": session_id}, status_code=200)
图 5.6 展示了完整的 RAG 应用流程。流程开始时使用 LLM 对最新的用户提示进行上下文化处理,LLM 会根据聊天记录重新组织查询。然后,检索组件会将重新措辞的查询用于从对话中收集相关上下文。最后,question_answer_chain
将检索到的上下文、聊天记录和当前的用户输入结合起来,生成最终答案:
图 5.6 – RAG 应用流程
-
创建一个名为
rag-app
的目录:$ mkdir -p rag-app $ cd rag-app
-
从
github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch5/rag-app
下载main.py
和requirements.txt
文件,这些文件包含了我们的 RAG 应用程序代码和依赖库,并将它们放入rag-app
目录中。 -
下一步是为这个 RAG 应用程序创建一个 Dockerfile。我们将从一个 Python 父镜像开始,安装必要的 FastAPI 依赖项,添加 Python 应用程序代码,并运行 uvicorn(
www.uvicorn.org/
)命令来启动 FastAPI 应用程序的 Web 服务器。完整的 Dockerfile 可在github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/rag-app/Dockerfile
查看:FROM python:slim ... RUN pip install --no-cache-dir -r requirements.txt COPY main.py /app ... CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
-
构建容器镜像并将其推送到 ECR 仓库。在运行之前,请替换 账户号 和 区域 值:
$ docker build -t rag-app . $ aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-west-2.amazonaws.com/rag-app $ docker tag rag-app 123456789012.dkr.ecr.us-west-2.amazonaws.com/rag-app $ docker push image and OPENAI_API_KEY with your own values. Follow the OpenAI documentation at https://platform.openai.com/docs/quickstart/create-and-export-an-api-key to learn how to generate an API key. The following manifest creates a K8s deployment for the RAG application with one replica and injects the OpenAI API key as an environment variable so that the application can use it to connect to the OpenAI API:
apiVersion: apps/v1
类型:部署
元数据:
name: rag-app-deployment
...
容器:
- name: rag-app-container
镜像:<<在此替换您的 ECR 镜像>>
...
环境变量:
- name: OPENAI_API_KEY
值:"<<在此替换您的 OpenAI API 密钥>>"
...
-
更新清单后,你可以运行以下命令将 RAG 应用程序部署到集群中。这将创建一个名为
rag-app-deployment
的 K8s 部署,并创建一个副本,通过 ClusterIP 服务暴露在端口80
上。你可以通过运行以下命令来验证:$ kubectl apply -f rag-deploy.yaml deployment.apps/rag-app-deployment created service/rag-app-service created $ kubectl get po,svc -l app.kubernetes.io/name=rag-app NAME READY STATUS RESTARTS AGE pod/rag-app-deployment-c4b4b49d4-wclwz 1/1 Running 0 4m26s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE qdrant-restore-job.yaml file from https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/rag-app/qdrant-restore-job.yaml and run the following command to restore the snapshot:
$ kubectl apply -f qdrant-restore-job.yaml
批处理/作业 qdrant-restore-job 已创建
在本节中,我们使用 LangChain 框架、Qdrant 向量数据库和 OpenAI GenAI 模型创建了一个对话式 RAG 应用程序。我们将应用程序容器化,将其作为 Python FastAPI 服务并部署到 EKS 集群中。此外,我们还创建了另一个示例 RAG 应用程序,使用 Amazon Bedrock(aws.amazon.com/bedrock/
)和 Anthropic Claude(aws.amazon.com/bedrock/claude/
)模型代替了 OpenAI。你可以在 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch5/bedrock-rag-app
找到它,并根据个人喜好选择一个。在接下来的章节中,我们将使用聊天机器人 UI 将所有内容整合在一起。
在 K8s 上部署聊天机器人
到目前为止,我们已经部署了一个经过微调的 Llama 3 模型,该模型已在 MyElite 忠诚度计划的常见问题解答和一个基于销售目录数据的对话式 RAG 应用程序上进行训练。现在,我们将构建一个 聊天机器人 UI 组件,该组件将向 MyRetail 客户展示这两项服务。
我们将使用Gradio (www.gradio.app/
)来构建聊天机器人 UI,Gradio 是一个开源 Python 包,用于构建机器学习模型、API 等的演示或 Web 应用程序。您可以参考www.gradio.app/guides/quickstart
上的快速入门指南,了解更多关于 Gradio 框架的信息。或者,您也可以探索使用 UI 框架,如Streamlit (streamlit.io/
)、NiceGUI (nicegui.io/
)、Dash (github.com/plotly/dash
)和Flask (flask.palletsprojects.com/en/stable/
)来构建聊天机器人界面。
为了方便您,我们已经创建了一个聊天机器人容器,并已将其公开发布在 DockerHub 上:hub.docker.com/repository/docker/k8s4genai/chatbot-ui/
。该应用程序的源代码可以在 GitHub 上找到:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch5/chatbot
,供您参考。
重要提示
我们正在创建一个面向公众的 NLB 进行测试。您可以通过更新 NLB 安全组的入站规则来限制对您 IP 地址的访问。有关更多详细信息,请参阅 AWS NLB 文档:docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-security-groups.html
。
让我们在 EKS 集群上部署聊天机器人应用程序,并将其与微调的 Llama 3 部署和 RAG 应用程序配置在一起:
-
从
github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/chatbot/chatbot-deploy.yaml
下载聊天机器人 UI K8s 部署清单,并通过运行以下命令将其部署到 EKS 集群。这将创建一个包含一个副本的聊天机器人 UI 应用程序,并通过AWS 网络 负载均衡器将其暴露到公共互联网:$ kubectl apply -f chatbot-deploy.yaml deployment.apps/chatbot-ui-deployment created service/chatbot-ui-service created
-
通过运行以下命令获取 AWS 网络负载均衡器端点:
$ export NLB_URL=$(kubectl get svc chatbot-ui-service -o jsonpath='{.status.loadBalancer.ingress[0].hostname}') $ echo $NLB_URL
-
通过在网页浏览器中启动负载均衡器 URL 打开聊天机器人 UI,如图 5.7所示。现在,您可以通过选择购物助手(RAG 应用程序)或忠诚度计划助手(微调的 Llama 3 模型)并在聊天框中输入问题来与聊天机器人互动——例如,请建议适合 60 岁男性的步行鞋, 以表格形式展示:
图 5.7 – 聊天机器人 UI
- 一旦结果显示出来,您可以继续提出后续问题——例如,您能按价格降序排列结果吗。如图 5.8所示,您将看到结果已按要求排序。由于购物助手是通过对话式 RAG 开发的,它在回答当前提示时会考虑用户的先前对话历史。在这个例子中,我们请求聊天机器人请建议适合 60 岁男性的步行鞋,以表格格式展示,然后是您能按价格降序排列结果吗。对于第二个查询,RAG 应用程序考虑了用户的先前对话,并返回了排序后的结果:
图 5.8 – 聊天机器人 UI 结果
- 您还可以通过使用选择助手下的选项,在购物助手和忠诚度计划助手之间切换,并提出与忠诚度计划相关的问题,如图 5.9所示:
图 5.9 – 选择忠诚度计划助手
在本节中,我们将一个使用 Gradio Python 包开发的聊天机器人 UI 应用程序部署到 EKS 集群,并通过 K8s LoadBalancer 服务将其暴露。该应用程序连接到我们在本章中开发的 RAG 应用程序和微调的 Llama 3 模型,从而能够回答用户关于 MyRetail 的 MyElite 忠诚度计划和购物目录的查询。
总结
在本章中,我们介绍了如何在使用 Amazon EKS 的 K8s 环境中微调和部署 GenAI 模型。我们使用一个虚构公司 MyRetail 作为示例,突出展示了在电子商务/零售业务中如何通过使用 GenAI 模型为客户创造个性化购物体验。这使我们能够为公司的忠诚度计划自动化响应,并提供产品推荐。
我们首先讨论了在整个 GenAI 项目生命周期中实验的重要性,并在 EKS 上部署了 JupyterHub。JupyterHub 提供了集中式访问计算资源,如 GPU,使其更适合大规模 AI 任务。然后,我们创建了一个 Llama 3 微调容器镜像,并将其部署到 EKS 集群中。微调任务利用来自 Amazon S3 的训练和验证数据集对 Llama 3 模型进行微调,并将模型资产导出到 S3。我们使用这些模型资产对推理容器进行了容器化,并将其作为 K8s 部署部署到 EKS 集群中。
本章还概述了如何部署一个 RAG 应用,该应用查询向量数据库(Qdrant)以获取与上下文相关的信息,然后调用 LLM 生成响应。通过引入外部数据,这样可以减少幻觉现象并提高响应的准确性。最后,我们部署了一个聊天机器人 UI,并将其与微调后的 Llama 3 模型和 RAG 应用连接,以提升 MyRetail 客户的购物体验。在下一章中,我们将探索 K8s 提供的各种自动扩展构件,并讨论如何利用它们来优化 GenAI 工作负载。
第五章:在 Kubernetes 上扩展 GenAI 应用
在本章中,我们将介绍 Kubernetes 的应用扩展策略和最佳实践。应用扩展是一个过程,K8s 可以动态扩展资源以匹配应用需求,从而确保高效且具成本效益的资源利用,并优化应用性能。Kubernetes 提供了不同的扩展机制,基于如 CPU 使用率、内存或自定义指标等度量来扩展应用。
在本章中,我们将覆盖以下主要内容:
-
扩展指标
-
水平 Pod 自动扩展器 (HPA)
-
垂直 Pod 自动扩展器 (VPA)
-
Kubernetes 事件驱动 自动扩展器 (KEDA)
-
集群 自动扩展器 (CA)
-
Karpenter
扩展指标
为了正确扩展应用,选择合适的指标至关重要,以确保高效的资源利用和无缝的终端用户体验。这些指标可以分为常规指标和自定义指标。
常规指标
这些是在 Kubernetes 中用于水平或垂直扩展的常见指标:
-
CPU 使用率:这衡量 CPU 利用率的百分比。高 CPU 利用率可能表示应用负载过重,需要更多实例(Pod)来处理需求,而持续较低的 CPU 使用率则可能表示资源的过度配置,此时 Pod 数量可以缩减。
-
内存使用率:这衡量 Pod 消耗的内存量。与 CPU 相似,高内存使用率可能表明应用正在处理大量数据,意味着需要更多资源以防止内存不足。当容器中的进程超出内存限制时,容器将被容器运行时(CRI)终止,这与 CPU 限制不同。如果设置了 CPU 限制,且容器超出该限制,容器不会被终止,而是会被限制或降低速度。通常,将内存使用率作为扩展指标并不是最佳实践,因为应用程序通常在内存管理方面表现不佳。
自定义指标
自定义指标允许 K8s 根据更多应用或特定用例的指标进行扩展,从而更精细地控制应用性能。一些自定义指标的示例如下:
-
HTTP 请求率:这衡量应用每秒接收到的 HTTP/HTTPS 请求或 API 调用的数量。我们可以使用监控工具,如 Prometheus,来追踪请求率,并在请求激增并超过某个阈值时扩展应用。
-
队列长度:这衡量了队列中未处理的任务或消息数量,如Amazon SQS或RabbitMQ。队列积压表明应用程序无法跟上负载,需要更多资源以及时处理任务。这是一个关键指标,特别是在事件驱动架构(EDA)中。K8s 的扩展机制,如 KEDA,支持基于队列指标进行扩展。
-
延迟/响应时间:这衡量了应用程序响应请求所需的时间。高延迟通常表示应用程序在当前负载下难以应对,扩展更多实例可以帮助保持较低的响应时间。
-
错误率:这衡量了请求失败的数量。错误率的增加可能表示当前资源数量不足以处理负载,导致失败;可能需要扩展资源。
-
并发/活跃会话:这衡量了与应用程序交互的活跃连接、用户或会话的数量。对于在线游戏或视频流平台等应用程序,活跃用户数量可能是应用程序负载的关键指标。
-
GPU 利用率:这衡量了应用程序消耗的 GPU 容量的百分比。在 K8s 上扩展 GenAI 应用程序时,使用 GPU 利用率作为扩展指标是有效的,因为它反映了应用程序的负载和资源的重度依赖。对于NVIDIA GPUs,可以使用DCGM-Exporter插件(
github.com/NVIDIA/dcgm-exporter
)导出指标,该插件可以通过 Helm 安装,使得观察性代理(如Prometheus)能够抓取这些指标。我们将在我们的 EKS 集群中配置此项,作为第十二章的一部分。
这些是 K8s 中常用的一些指标,用于扩展资源。有些应用程序可能需要混合使用多个指标。例如,一个 web 应用程序可能会同时使用网络 I/O 和请求速率,以确保资源利用率和性能的最优化。K8s 的自动扩展默认不提供自定义指标;需要安装自定义指标适配器,以使它们从相应的指标源中可用。该适配器充当指标系统与 K8s 之间的桥梁,通过 K8s 自定义指标 API 暴露这些指标。一些例子包括prometheus-adapter(github.com/kubernetes-sigs/prometheus-adapter
)和Datadog Cluster Agent(docs.datadoghq.com/containers/guide/cluster_agent_autoscaling_metrics
)。
在本节中,我们讨论了不同类型的扩展度量,如常规度量和自定义度量,并查看了一些示例。我们还讨论了自定义 GPU 利用率度量,并发现它是扩展 GenAI 工作负载的有效指标。接下来,让我们探索水平 Pod 自动扩展,并了解如何使用这些度量来自动扩展 K8s Pods。
水平 Pod 自动扩展器(HPA)
HPA(kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
)是 K8s 的一项功能,基于用户定义的度量(如 CPU 或内存利用率)调整部署中 Pod 的数量。HPA 的主要目标是通过动态地扩展或收缩 Pod 的数量,确保应用能够处理不同的负载。HPA 不适用于不能扩展的对象,如DaemonSets。
HPA 使用度量服务器或监控系统(如 Prometheus)收集定义度量的实时数据。HPA 有一个控制器组件,在 Kubernetes 控制平面中运行。它定期检查目标应用(如部署)的当前度量,并将其与 HPA 资源配置中指定的期望阈值进行比较。
基于这些度量,控制器调整所需的 Pod 数量。如果资源使用率(如 CPU 利用率)超过阈值,HPA 会增加 Pod 的数量,而如果使用率低于阈值,HPA 会减少 Pod 的数量,如图 6.1所示。
图 6.1 – HPA 概述
以下 YAML 文件显示了如何在 K8s 中为我们的电子商务聊天机器人 UI 部署实现 HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: chatbot-ui-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: chatbot-ui-deployment
minReplicas: 1
maxReplicas: 5
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
在这个例子中,我们将kind
设置为HorizontalPodAutoscaler
,这表示这个清单是针对 HPA 的,并将name
设置为chabot-ui-hpa
。在spec
部分,我们将此 HPA 的扩展目标设置为chatbot-ui-deployment
,这是一个部署,而apps/v1
是目标资源的 API 版本。
接下来,我们将副本的最小数量设置为1
(minReplicas: 1
),最大副本数量设置为5
(maxReplicas: 5
)。最后,我们设置 HPA 可以监控并用来做扩展决策的度量。在这个例子中,我们使用所有chatbot-ui-deployment部署 Pod 的平均 CPU 利用率作为度量。
在 target
规范中,averageUtilization: 70
将目标 CPU 利用率设置为 70
。如果平均 CPU 利用率超过 70%,HPA 将开始扩展副本数以满足此目标;然而,由于我们定义了最大副本数限制,它不会超过五个副本。当 CPU 利用率降到 70% 以下时,它将开始缩减副本数,但会确保始终有一个副本在运行(minReplicas
)。你还可以使用 K8s 强制命令来创建和管理 HPA 资源。有关命令的示例,请参考官方 K8s 文档 kubernetes.io/docs/tasks/manage-kubernetes-objects/imperative-command/
。
你可以从 GitHub 仓库下载 HPA 清单,链接地址为 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch6/chatbot-ui-hpa.yaml
,并执行以下命令在我们的 EKS 集群中应用 HPA 策略。
$ kubectl apply -f chatbot-ui-hpa.yaml
horizontalpodautoscaler.autoscaling/chatbot-ui-hpa created
为了防止频繁的扩缩,以下 HPA 行为配置可供使用:
behavior:
scaleDown:
stabilizationWindowSeconds: 180
policies:
- type: Percent
value: 50
periodSeconds: 15
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Pods
value: 2
periodSeconds: 15
selectPolicy: Max
scaleDown
部分控制 HPA 缩减或移除副本的速度。在此示例中,最大允许的缩减比例已设置为 50%,这意味着 HPA 在每次缩减事件中可以移除最多 50% 的当前副本。periodSeconds
定义了在此时间窗口内评估扩缩规则的秒数。stabilizationWindowSeconds
指定了 HPA 在检测到较低资源利用率后,缩减副本之前要等待的时间(秒)。这有助于防止由于暂时的使用下降而导致频繁且过于激进的缩减。
在此情况下,稳定窗口设置为 180 秒,这意味着 HPA 在负载下降后,会等待 3 分钟才会减少副本数量。
scaleUp
部分的行为分类控制 HPA 扩展或添加新 Pod 的速度。在此情况下,我们定义了一个固定数量的 Pod 来作为扩展策略,而不是基于百分比的方法。基于百分比的策略会根据当前副本数的百分比来增加 Pod 数量。例如,如果我们有 5 个 Pod,并且策略允许 60% 的增加,HPA 就会扩展 3 个 Pod。
在此示例中,扩展是按每次增加两个 Pod 的固定增量进行的,如果需要更多副本,HPA 每 15 秒内最多可以增加两个 Pod。我们将 stabilizationWindowSeconds
设置为 0
,这意味着扩展之前没有延迟。
在本教程中,我们基于 Pod 级别的指标创建了一个 HPA 策略,该指标汇总了 Pod 内所有容器的资源使用情况。然而,在多容器 Pod 中,这个指标可能无法准确反映单个容器的性能。为了解决这个问题,K8s 引入了容器资源指标,允许 HPA 在扩展目标资源时,跟踪跨 Pod 的特定容器资源使用情况。这种方法使您能够为最关键的容器设置扩展阈值。例如,如果您的 Pod 包含一个 Web 应用程序和一个提供日志记录的 sidecar 容器,您可以将 HPA 配置为仅根据 Web 应用程序的 CPU 利用率进行扩展,而忽略 sidecar 的资源使用。以下代码片段演示了如何使用web-app
容器的 CPU 利用率来扩展整体部署:
type: ContainerResource
containerResource:
name: cpu
container: web-app
target:
type: Utilization
averageUtilization: 60
在本节中,我们探讨了 HPA,这是一个 K8s 功能,旨在根据用户定义的指标(如 CPU、内存或 GPU 利用率)自动调整 K8s 部署、StatefulSet 或 ReplicaSet 中的 Pod 数量。其主要目标是保持足够数量的 K8s Pods,以有效处理动态工作负载。我们还介绍了如何配置 HPA 策略并自定义扩展行为。接下来,让我们深入了解 VPA。
VerticalPodAutoscaler(VPA)
VPA 可以根据实际使用情况和配置调整 Pod 的 CPU 和内存资源请求和限制。这与 HPA 不同,HPA 根据定义的指标调整 Pod 的数量。VPA 有四种操作模式:
-
关闭:在此模式下,VPA 仅提供资源建议,但不应用任何更改。
-
自动:VPA 会对资源请求进行修改并重启 Pod(如果需要的话)。
-
重建:VPA 在 Pod 创建时应用更改,并通过驱逐现有 Pod 来更新它们。
-
初始:VPA 仅在创建新 Pod 或重启现有 Pod 时应用资源建议,而不会干扰正在运行的 Pod。
VPA 通过 K8s Metrics API 收集资源使用数据,并建议适当的 CPU 和内存值,用于资源请求和限制。在自动模式下,它可以自动驱逐 Pod,从而使它们重新调度并应用更新的资源请求,如图 6.2所示。
图 6.2 – VPA 概述
与 HPA 不同,VPA 并不默认包含在 K8s 中;它是一个独立的项目,可以在 GitHub 上找到,网址为github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler
。按照github.com/kubernetes/autoscaler/blob/master/vertical-pod-autoscaler/docs/installation.md
上的说明安装 VPA 插件。
下面的 YAML 文件在我们部署在 K8s 中的电子商务聊天机器人 UI 应用程序中实现了 VPA:
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: chatbot-ui-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: chatbot-ui-deployment
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: "*"
minAllowed:
cpu: "1000m"
memory: "2Gi"
maxAllowed:
cpu: "2000m"
memory: "4Gi"
controlledValues: "RequestsAndLimits"
您可以从 GitHub 存储库下载 VPA 清单,网址为github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch6/chatbot-ui-vpa.yaml
,并执行以下命令将 VPA 策略应用于我们的 EKS 集群:
$ kubectl apply -f chatbot-ui-vpa.yaml
verticalpodautoscaler.autoscaling.k8s.io/chatbot-ui-vpa created
在此示例中,我们将kind
设置为VerticalPodAutoscaler
,将updateMode
设置为Auto
,这意味着 VPA 将自动调整资源请求和限制。containerName *
通配符表示该策略应适用于 Pod 中的所有容器。
maxAllowed
限制确保 VPA 不会将资源值设置超出指定范围。在此配置中,minAllowed
限制定义为1000m
(相当于一个 vCPU)和 2GB 内存,而最大允许的 CPU 为2000m
(或两个 vCPUs)和 4GB 内存。将controlledValues
定义为RequestsAndLimits
表示 VPA 应同时管理资源请求和限制。资源请求是 Pod 在启动时向 Kubernetes 调度程序请求的 CPU 或内存量。如果我们只希望 VPA 调整资源请求而不是限制,可以将其设置为RequestsOnly
。
结合 HPA 和 VPA
建议不要在同一集群中结合使用 HPA 和 VPA(除非将 VPA 设置为“off”),因为这可能导致潜在的冲突。例如,VPA 调整 Pod 的 CPU/内存资源请求和限制,而 HPA 根据当前利用率扩展 Pod 的数量。如果同时使用 HPA 和 VPA,VPA 的更改可能会使 HPA 感到困惑,因为单个 Pod 的资源使用频繁波动,影响 HPA 用于扩展的指标。
在本节中,我们探讨了 VPA 及其操作模式,以及一个示例清单。VPA 监视工作负载的资源使用情况,并自动调整资源请求以优化 K8s 集群中的资源分配。我们还讨论了同时在自动操作模式下结合使用 HPA 和 VPA 可能引发的潜在冲突。
KEDA
随着云采用的普及,微服务和 EDA 采用趋势日益增长。在这种实施中,不同的构建模块被划分为基于微服务的实现,这些实现是自包含的,仅通过 API 调用彼此通信。这种实现允许更容易进行更新,并具备添加新功能的灵活性。
KEDA (keda.sh/
) 是一个开源项目,将事件驱动的自动缩放引入 Kubernetes。它通过允许应用程序根据外部事件源(如消息队列深度、事件流大小或任何自定义指标)进行缩放,扩展了 Kubernetes 内置的 HPA 功能。
KEDA 的ScaledObject是一个自定义资源,定义了如何根据事件驱动或外部指标自动扩展目标(例如部署)。当我们在 KEDA 中定义一个 ScaledObject 时,它会在后台自动创建一个 HPA 资源。然后,HPA 资源根据 KEDA 提供的指标扩展部署。ScaledObject 定义了扩展逻辑(例如,使用哪个外部指标、扩展阈值以及最小/最大副本数),而 KEDA 则根据此配置管理 HPA。
KEDA 特别适用于事件驱动架构,在这种架构中,工作负载可能是偶发的,只有在发生事件时才需要扩展,例如新用户登录或新物品加入购物车。
KEDA 支持各种扩展器,这些扩展器与外部服务集成,如 Amazon SQS、Apache Kafka 和 Prometheus。这些扩展器监控指标或事件的变化,例如队列中的消息数量或 HTTP 请求的速率。
注意
请参考 KEDA 文档:keda.sh/docs/latest/scalers/
,以获取可用扩展器的列表。
KEDA 的一个独特特点是,当没有事件需要处理时,它可以缩放到零,这在无服务器和事件驱动的应用程序中非常有用。
为了说明这种行为,让我们看一个示例ScaledObject配置,该配置使 KEDA 能够根据 Amazon SQS 队列中的消息数量扩展部署。
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: amazonsqs-scaler
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: demo-deployment
minReplicaCount: 0
maxReplicaCount: 10
pollingInterval: 15
cooldownPeriod: 180
triggers:
- type: aws-sqs-queue
metadata:
queueURL: "http://<aws-sqs-url"
awsRegion: "us-east-1"
queueLength: "10"
authenticationRef:
name: keda-service-account
我们定义了一个用于基于 Amazon SQS 进行扩展的 ScaledObject,amazonsqs-scaler
。
将minReplicaCount
设置为0
定义了部署的最小副本数。在此示例中,当 Amazon SQS 队列中没有消息时,KEDA 可以将部署缩减为零。这有助于在没有工作负载处理时节省资源。将maxReplicaCount
设置为10
指定了 KEDA 可以将部署扩展到的最大副本数。这确保了即使队列大小增加,部署也不会扩展超过 10 个 Pod。
将pollingInterval
设置为15
使得 KEDA 每 15 秒检查一次队列长度。在这种情况下,KEDA 将每 15 秒查询一次 Amazon SQS API,以检查队列的大小。将cooldownPeriod
设置为 180 秒表示,在扩展后,KEDA 会等待 3 分钟再缩放部署,即使工作负载减少。这可以防止在暂时流量激增后快速缩放,并实现更稳定的扩展。
我们使用aws-sqs-queue
作为扩展器类型,它与 Amazon SQS 服务兼容。queueURL
定义了 Amazon SQS 服务的管理端点,KEDA 可以在此查询队列深度。queueLength
设置为10
定义了扩展的阈值。当队列中的消息数量超过 10 时,KEDA 将触发扩展。最后,keda-service-account
指的是 KEDA 将用于与 Amazon SQS 服务进行身份验证的服务账户。
以下是一个示例 ScaledObject,它会自动扩展 K8s 中的 GenAI 模型部署。它基于两个触发器进行扩展:第一个触发器是基于传入请求的平均数量,第二个触发器是基于推理 Pods 的 GPU 利用率。这两个指标来自本地 Prometheus 设置,Prometheus 从 NVIDIA DCGM 导出程序和应用程序指标端点收集这些指标:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: my-llama-deployment-scaler
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-llama-deployment
...
triggers:
- type: prometheus
metadata:
serverAddress: http://prometheus-server.default.svc:9090
metricName: http_requests_total
threshold: "10"
query: sum(rate(http_requests_total[1m]))
- type: prometheus
metadata:
serverAddress: http://prometheus-server.default.svc:9090
metricName: DCGM_FI_DEV_GPU_UTIL
threshold: "70"
query: avg(DCGM_FI_DEV_GPU_UTIL{pod=~"my-llama-.*"}[30s])
在这一部分,我们探讨了 KEDA 项目,它为 K8s 带来了基于事件的自动扩展。KEDA 通过使应用能够基于来自多个来源的自定义指标进行扩展,扩展了内置的 HPA,包括基于事件的指标,如消息队列深度。通过将 KEDA 集成到 K8s 中,我们可以根据外部事件动态扩展 K8s 工作负载,使资源分配更加响应和高效。
集群自动扩展器 (CA)
Kubernetes CA 是一个根据集群中运行的工作负载需求调整 Kubernetes 集群中节点数量的工具。它通过增加或删除节点来扩大或缩小集群,以满足待调度或低利用率 Pods 的资源需求。
当 HPA 检测到资源使用超过配置的阈值时,它会增加 Pod 副本的数量。然而,如果集群中现有的节点没有足够的容量来调度新的 Pods,这些 Pods 将保持未调度状态。
这就是 CA 发挥作用的地方。CA 检测到存在无法调度的 Pods,并配置更多的节点来容纳这些新创建的、未调度的 Pods。一旦节点准备好,待处理的 Pods 会被调度并开始在新节点上运行。
CA 支持 扩展,即当 Pods 因资源不足无法调度时;也支持 缩减,即当节点未被充分利用时,CA 可以移除它们以优化资源利用率并降低成本。
CA 持续监控 Kubernetes 集群,并与云提供商的 API 交互,依据当前的资源使用情况添加或删除节点。它主要关注因资源不足而无法调度的待处理 Pods 和具有剩余容量的低利用率节点。
CA 确定一个节点组,例如 AWS 自动扩展组,该组能够提供具有必要资源的额外节点。一旦新节点可用,待调度的 Pods 会被安排在该节点上运行:
-
缩减:当节点未被充分利用,即它们运行时资源使用率低或没有显著的工作负载时,CA 会检查是否可以将这些未充分利用节点上的 Pods 安全地重新调度到集群中的其他节点上。
-
扩展:当应用程序遇到高资源需求,而现有节点无法容纳新的或待处理的工作负载时,CA 会触发扩展。它会配置额外的节点以确保工作负载调度。一旦新节点加入集群,待调度的 Pods 会被调度到这些节点上,以保持所需的性能。
虽然 CA 一直是扩展 Kubernetes 集群的传统选择,但它存在一些局限性,例如依赖于预定义的节点组和基于轮询的扩展决策机制。这就是Karpenter可以提供帮助的地方。Karpenter 旨在解决传统自动扩展方法的低效问题,下一节将详细介绍其功能。
Karpenter
Karpenter 是由 AWS 开发的开源、灵活且高性能的 Kubernetes 集群自动扩展器。它首次亮相于 2021 年的 AWS re:Invent (aws.amazon.com/blogs/aws/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/
) 大会,主要目的是改善和简化 K8s 的自动扩展体验。
与原生 Kubernetes CA 主要侧重于响应待处理 Pods 来扩展节点不同,Karpenter 会根据工作负载的具体需求动态地提供合适的计算资源。Karpenter 在以下几个方面优化了效率和性能:
-
更快速和更高效的扩展:Karpenter 可以直接与 Kubernetes API 服务器通信,以了解待处理 Pod 的需求,并能够更快地启动新节点,从而减少调度延迟。Karpenter 通过分析待处理 Pod 的具体需求,几乎实时地做出扩展决策。这意味着节点是根据即时需求进行配置的,从而减少了延迟并提高了对工作负载变化的响应能力。
-
更好的节点利用率:与 CA 通常从预配置的实例组或节点池中配置节点不同,Karpenter 可以动态选择最佳的实例类型。它可以选择与待处理 Pods 的资源需求匹配的实例大小和类型,从而减少浪费的容量并优化资源分配。
-
整合能力:Karpenter 会持续监控集群,并在可能的情况下通过重新打包工作负载到更少的节点上来整合它们,从而终止那些使用不足的节点。这种整合有助于通过更有效地利用现有的节点资源来降低成本,而 CA 通常根据预设的阈值进行缩减节点,而不积极整合工作负载。
-
支持多种实例类型:Karpenter 可以从多种实例类型中选择,包括不同的代际和大小。它根据当前的可用性和定价进行选择,确保 Pods 被调度到最具成本效益且资源合适的节点上。
-
漂移:Karpenter 会自动检测已漂移的节点,并以滚动方式替换它们。此功能可用于执行修补程序升级或 Karpenter 管理节点的 K8s 版本升级。
Karpenter 会在集群中查找被 kube-scheduler 标记为不可调度的待处理 Pods,并汇总这些 Pods 的资源和调度需求,从而做出决策启动新的工作节点。它执行箱形打包以确保正确的节点大小和数量被配置。它还会主动寻找机会,通过终止空闲、使用不足或可以被更便宜节点替换的工作节点来减少集群的整体成本,如图 6.3所示。
图 6.3 – Karpenter 概述
Karpenter 提供了两个自定义资源,NodePools (karpenter.sh/v1.4/concepts/nodepools/
) 和 NodeClasses (karpenter.sh/v1.4/concepts/nodeclasses/
),用于配置。NodePools 定义了 Karpenter 创建的节点的配置约束集,例如实例类型、可用区、CPU 架构、容量类型、污点和标签。NodeClasses 配置了云提供商特定的设置,例如 VPC 子网、IAM 角色、AMI ID 和 EC2 安全组。可以创建多个 NodePools,以满足不同的工作负载需求,例如用于生成 AI 训练和推理应用程序的 GPU 专用 NodePool,以及用于容纳 Web 服务器和微服务的通用 NodePools。始终确保 NodePools 具有互不重叠的配置;如果匹配多个 NodePools,Karpenter 会随机选择一个来启动工作节点。
Karpenter 可以作为 Helm 图表安装到 EKS 集群中,具体安装步骤请参考 karpenter.sh/docs/getting-started/getting-started-with-karpenter/
中的入门指南。在我们的设置中,我们使用 Terraform Helm 提供程序在第三章中作为 EKS 集群设置的一部分安装了 Karpenter。
您可以使用以下命令验证安装情况,该命令将显示部署的状态、版本和其他详细信息:
$ helm list -n kube-system
NAME NAMESPACE STATUS
karpenter kube-system deployed
现在,让我们利用 Karpenter 的功能为我们的 GenAI 工作负载自动启动 GPU 实例。在 第五章 中,我们创建了一个专用的 EKS 管理节点组,使用 G6 EC2 实例来部署 Llama 3 微调任务和推理应用程序。这种方法的一个缺点是,无论 GenAI 应用程序是否运行,GPU 工作节点始终附加在集群上,这对最昂贵的资源来说是一种低效的利用方式。现在,让我们删除该节点组,并配置 Karpenter 来管理 GPU 实例的配置。
在 eks.tf
文件中注释以下代码,并运行 Terraform 命令来删除 eks-gpu-mng
节点组。完整文件可见于 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch6/eks.tf
:
module "eks" {
source = "terraform-aws-modules/eks/aws"
...
eks_managed_node_groups = {
...
# eks-gpu-mng = {
# instance_types = ["g6.2xlarge"]
# ami_type = "AL2_x86_64_GPU"
# max_size = 2
# desired_size = 1
# capacity_type = "SPOT"
# disk_size = 100
# labels = {
# "hub.jupyter.org/node-purpose" = "user"
# }
# taints = {
# gpu = {
# key = "nvidia.com/gpu"
# value = "true"
# effect = "NO_SCHEDULE"
# }
# }
# }
...
$ terraform plan
$ terraform apply -auto-approve
你可以通过检查 EKS 控制台中的计算标签页,或者使用以下命令来确认节点组已被删除:
$ kubectl get nodes -l nvidia.com/gpu.present
现在我们已经删除了集群中的 GPU 工作节点,接下来让我们通过创建 NodePool 和 EC2NodeClass 资源来配置 Karpenter,以启动 GPU 实例。创建一个名为 eks-gpu-np
的 GPU NodePool,并设置以下要求,选择 G6 实例系列,来自按需或竞价容量。完整文件可见于 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch6/eks-gpu-np.yaml
:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: eks-gpu-np
spec:
...
nodeClassRef:
group: karpenter.k8s.aws/v1
kind: EC2NodeClass
name: default
requirements:
- key: "karpenter.k8s.aws/instance-generation"
operator: In
values: ["g6"]
- key: "karpenter.sh/capacity-type"
operator: In
values: ["spot", "on-demand"]
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
...
$ kubectl apply -f eks-gpu-np.yaml
nodepool.karpenter.sh/eks-gpu-np created
接下来,创建 default-gpu
EC2NodeClass 来配置 AMI、IAM 角色、VPC 子网、EC2 安全组等。完整文件可见于 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch6/eks-gpu-nc.yaml
:
apiVersion: karpenter.k8s.aws/v1
kind: EC2NodeClass
metadata:
name: default-gpu
spec:
amiFamily: AL2023
...
role: "eks-demo"
...
$ kubectl apply -f eks-gpu-nc.yaml
ec2nodeclass.karpenter.k8s.aws/default-gpu created
现在我们已经配置了 Karpenter,接下来让我们重新运行微调任务,看看计算自动扩展的实时效果。执行以下命令来启动微调任务。你可以下载 K8s 清单文件,文件位于 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch5/llama-finetuning/llama-finetuning-job.yaml
。在运行命令之前,请替换容器镜像、Hugging Face 令牌和模型资源 S3 桶名称值:
$ kubectl delete job my-llama-job
job.batch "my-llama-job" deleted
$ kubectl apply -f llama-finetuning-job.yaml
job.batch/my-llama-job is created
鉴于集群中没有 GPU 工作节点,kube-scheduler 会将微调 Pod 标记为不可调度。我们可以通过使用以下命令来验证这一点。第一个命令会输出待处理状态,第二个命令显示该状态的原因。
$ kubectl get pods -l app=my-llama-job
NAME READY STATUS RESTARTS AGE
my-llama-job-plgb5 0/1 Pending 0 22s
$ kubectl describe pod -l app=my-llama-job | grep Scheduling
Warning FailedScheduling 101s default-scheduler 0/2 nodes are available: 2 Insufficient nvidia.com/gpu. preemption: 0/2 nodes are available: 2 No preemption victims found for incoming pod
Karpenter 正在积极寻找待处理的无法调度的 Pods,并启动 G6 系列的 EC2 实例来响应我们的微调任务。让我们通过查看 Karpenter 日志来验证这一点:
$ kubectl logs --selector app.kubernetes.io/instance=karpenter -n kube-system
{"level":"INFO","time":"2025-01-31T14:34:03.899Z","logger":"controller","message":"found provisionable pod(s)","commit":"b897114","controller":"provisioner","namespace":"","name":"","reconcileID":"bbdfdd41-e86f-4cfe-8fb5-e161f3ce4a72","Pods":"default/my-llama-job-plgb5","duration":"33.662142ms"}
...
{"level":"INFO","time":"2025-01-31T14:36:01.277Z","logger":"controller","message":"initialized nodeclaim","commit":"b897114","controller":"nodeclaim.lifecycle","controllerGroup":"karpenter.sh","controllerKind":"NodeClaim","NodeClaim":{"name":"eks-gpu-np-gkx7t"},"namespace":"","name":"eks-gpu-np-gkx7t","reconcileID":"f2b28556-4808-4577-a559-f946a451b46c","provider-id":"aws:///us-west-2c/i-02e475780d3aed0a1","Node":{"name":"ip-10-0-32-176.us-west-2.compute.internal"},"allocatable":{"cpu":"15890m","ephemeral-storage":"95551679124","hugepages-1Gi":"0","hugepages-2Mi":"0","memory":"60398040Ki","nvidia.com/gpu":"1","pods":"234"}}
一旦节点启动并加入集群,微调任务将会在该节点上调度。你可以通过使用以下命令来验证:
$ kubectl get pods -l app=my-llama-job
NAME READY STATUS RESTARTS AGE
my-llama-job-plgb5 1/1 Running 0 9m20s
$ kubectl get nodes -l nvidia.com/gpu.present
NAME STATUS ROLES
ip-10-0-32-176.us-west-2.compute.internal Ready <none>
任务完成几分钟后,Karpenter 会自动检测到空闲节点,应用一个污点以防止新的工作负载被调度,逐出现有的 Pods,然后终止该节点。你可以通过查看 Karpenter 日志来验证这一点:
$ kubectl logs --selector app.kubernetes.io/instance=karpenter -n kube-system
"level":"INFO","time":"2025-01-31T14:50:18.104Z","logger":"controller","message":"tainted node","commit":"b897114","controller":"node.termination","controllerGroup":"","controllerKind":"Node","Node":{"name":"ip-10-0-32-176.us-west-2.compute.internal"},"namespace":"","name":"ip-10-0-32-176.us-west-2.compute.internal","reconcileID":"874424bd-d2f7-45ab-a399-41e5314fb3d3","taint.Key":"karpenter.sh/disrupted","taint.Value":"","taint.Effect":"NoSchedule"}
{"level":"INFO","time":"2025-01-31T14:54:03.281Z","logger":"controller","message":"deleted node","commit":"b897114","controller":"node.termination","controllerGroup":"","controllerKind":"Node","Node":{"name":"ip-10-0-32-176.us-west-2.compute.internal"},"namespace":"","name":"ip-10-0-32-176.us-west-2.compute.internal","reconcileID":"eb99f292-2ea2-4aba-ac88-528746cc7e89"}
在本节中,我们探讨了使用 Karpenter 的优势,并通过 Terraform 和 Helm 在 EKS 集群中安装了它。然后,我们配置了 Karpenter 启动 G6 实例,以支持我们的 Llama 3 微调任务和推理工作负载。Karpenter 在微调任务期间启动了一台 G6 实例,并在任务完成后自动终止该实例。
总结
本章讨论了 K8s 应用程序的扩缩容策略和最佳实践,旨在确保资源的高效利用和最佳性能。本章涵盖了扩缩容的关键主题,包括指标、HPA、KEDA、VPA、CA 和 Karpenter。
在 Kubernetes 中进行扩缩容涉及选择合适的扩缩容指标。常规指标帮助确定是否需要添加或删除 Pod,而自定义指标则用于在扩缩容决策中实现更精细的控制。
HPA 可以根据 CPU 或内存使用率等指标自动调整部署中 Pod 的数量,而 VPA 可以调整单个 Pod 的资源请求和限制。VPA 确保资源的最佳分配,但如果同时使用 HPA 和 VPA,可能会产生冲突。
KEDA 将事件驱动的自动扩缩容引入 K8s,支持基于外部事件(如消息队列深度)进行扩缩容。它创建了一个 HPA 资源,用于在事件触发(如 API 调用激增)时管理扩缩容。KEDA 可以将应用程序扩缩容到零,因此非常适合无服务器和事件驱动的使用场景。CA 可以根据 Pod 的需求调整集群中节点的数量。CA 与云提供商的 API 紧密合作,动态管理节点。
最后,我们介绍了 Karpenter,作为 CA 的替代方案。Karpenter 可以动态提供适当大小的计算资源,以处理待处理的 Pod。它通过选择合适的实例类型并终止未充分利用的节点来优化性能和成本效率,以降低成本。为了演示其功能,我们重新运行了 Llama 3 微调任务,在此过程中,Karpenter 根据资源需求启动了一台带 GPU 能力的节点,并在任务完成后自动终止该节点。在下一章中,我们将讨论优化在 K8s 上运行 GenAI 应用程序的整体成本的不同策略。
第六章:在 Kubernetes 上优化 GenAI 应用程序的成本
本章将涵盖在云端部署 GenAI 应用程序的关键成本组成部分,包括计算、存储和网络成本。接着,我们将讨论优化这些成本的选项,例如通过调整资源规模来防止过度配置,思考如何进行高效存储管理,以及网络最佳实践。本章还将介绍监控和优化工具,如 Kubecost,以识别资源使用模式和节省成本的机会。
本章将涵盖以下主要主题:
-
理解关键成本组成部分
-
成本优化技术
理解关键成本组成部分
在云端部署应用程序时,主要的成本组成部分通常包括计算、存储和网络成本:
-
计算成本:计算可能是 GenAI 应用程序的一个重要成本因素,因为其资源密集型的特性。计算成本基于实例大小,包括 CPU、GPU 和内存大小。在 AWS 上,这些计算实例按秒计费,最小为 60 秒。所以,在第一个分钟后,这些费用将按秒增量计费。欲了解更深层次的内容,请参考 AWS 定价文档:
aws.amazon.com/ec2/pricing/
。 -
存储成本:GenAI 模型通常需要大量的数据用于训练和推理,因此存储是另一个关键的成本组成部分。主要的存储成本包括对象存储和块存储。对象存储,如 Amazon S3,通常用于存储数据集,例如图像、文本或视频文件。对象存储的费用通常基于存储的数据量(GB/月)和任何相关的取回费用。块存储,如Amazon Elastic Block Storage(EBS),提供可以附加到 Amazon EC2 实例的块级存储卷。EBS 通常用于 GenAI 应用程序中需要一致、低延迟访问的工作负载,如模型检查点、日志和其他中间文件。块存储的费用基于存储量和类型,例如固态硬盘(SSDs)与硬盘驱动器(HDDs)的区别。
-
网络成本:在云部署中,网络成本可能会增加,特别是对于涉及跨区域或可用区的大规模数据传输的 GenAI 应用程序。网络成本组成部分包括流入/流出费用、跨区域传输费用、NAT 网关费用以及内容分发网络(CDN)费用。
-
外流成本包括从云中传输出的数据的费用。如果需要将大型 AI 模型提供给最终用户,或者在区域之间或与本地环境之间移动数据,这些费用可能会累积。云中入站数据的 入流成本 通常较低或免费。如果一个应用程序涉及多个云区域、可用区或不同云提供商之间的通信,例如在混合云或多云设置中,区域间或外部的 数据传输成本 也可能非常高。
-
NAT 网关费用包括固定的每小时费用以及基于数据传输量的处理费用。数据传输成本根据数据流向的不同而有所不同。来自互联网的入站数据传输通常是免费的;然而,从云中到互联网的外部数据传输可能会根据数据量(GB)收取费用。
-
我们已经介绍了运行 GenAI 应用程序中涉及的各种成本组件。现在,让我们探索如何在 Kubernetes(K8s)集群中获得对基础设施成本的细粒度可视化。K8s 和云环境中有多个可用的成本分配工具。一些示例包括 OpenCost (www.opencost.io/
)、Kubecost (www.kubecost.com/
)、Spot.io (spot.io/
)、Cast.ai (cast.ai/
)、PerfectScale (www.perfectscale.io/
)、IBM Cloudability (www.apptio.com/products/cloudability/
)、Harness (www.harness.io/
)、云服务提供商特定的解决方案等。在本章中,我们将探讨 Kubecost 用于 K8s 集群成本分析。
Kubecost
Kubecost 是一款专为 K8s 环境设计的成本监控与优化工具。它通过提供有关在 K8s 集群中运行工作负载的各类成本组件的详细洞察,帮助跟踪、分配和优化成本。
Kubecost 可以通过提供以下功能帮助可视化 K8s 部署的成本组件:
-
按命名空间、Pod 和服务的成本细分:Kubecost 允许我们查看分配给不同 K8s 对象的详细成本,如 命名空间,以跟踪不同团队或应用程序的成本。它还可以突出显示在单个 Pod 层级、服务层级或部署层级的成本归属。Kubecost 可以汇总特定服务或部署的成本,清晰展示我们在微服务或特定部署上花费了多少。这种细粒度的细分对于多租户 K8s 集群尤为重要,因为多个团队或服务可能共享同一个集群。
-
计算成本:Kubecost 可以跟踪 EC2 实例成本,帮助您判断是否使用了最具成本效益的节点类型。Kubecost 还可以突出显示任何节点或计算资源是否未被充分利用。
-
存储成本:Kubecost 可以跟踪附加到 K8s 工作负载的存储卷成本。Kubecost 可以区分不同类型的 EBS 卷,如 gp2、gp3 和 io1,并分析它们的成本,帮助您根据性能需求和成本优化。它还可以检测未使用或低效利用的存储卷。
-
网络成本:Kubecost 可以跟踪与网络相关的成本,如不同节点、区域之间,甚至 AWS 服务之间的数据传输费用。这有助于优化网络配置,减少不必要的跨区域或跨 AZ 数据传输。
-
流量进出:可以跟踪和可视化进出您的 K8s 集群的流量成本,尤其在为外部应用程序或用户提供服务时,这一点尤为相关。
-
负载均衡器:Kubecost 可以识别并分解与 K8s 服务或入口资源相关的负载均衡器(如 AWS NLB 或 ALB)成本。
-
与 AWS 成本和使用报告 (CUR) 的集成:Kubecost 可以与 AWS CUR (
docs.aws.amazon.com/cur/latest/userguide/what-is-cur.html
) 集成,提供全面的成本视图,包括您的 EKS 工作负载所依赖的非 K8s AWS 服务,如 Amazon S3 或 RDS。如果您有多个 EKS 集群,Kubecost 还可以汇总所有集群的成本,并提供全球层面的洞察,或深入特定集群进行更详细的成本分析。
Kubecost 支持 按标签分配成本,这对于多租户环境尤其有用,在这种环境中,需要将成本归属到不同的团队、项目或环境(例如开发、QA、预发布或生产)。Kubecost 提供历史成本追踪,并允许您可视化成本趋势。
Kubecost 不仅可以帮助您可视化成本,还能提供可操作的优化建议,例如通过分析资源使用情况(CPU、内存、存储)并建议调整工作负载大小,从而避免资源的过度配置或低效利用,推荐合适的资源规模。Kubecost 还可以建议您在哪些地方切换到 EC2 Spot 实例,以节省计算成本,这对于非关键或可中断的工作负载尤其重要。
设置 Kubecost
Kubecost 可以作为 Helm chart 安装在我们的 EKS 集群中。有关不同安装选项,请参阅 Kubecost 文档中的 Amazon EKS 集成部分:www.ibm.com/docs/en/kubecost/self-hosted/2.x?topic=installations-amazon-eks-integration
。
在我们的设置中,我们将使用 Terraform Helm 提供程序安装 Kubecost。从github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch7/addons.tf
下载addons.tf
文件到 Terraform 项目文件夹,并运行以下命令:
$ terraform init
$ terraform plan
$ terraform apply -auto-approve
你可以使用以下命令验证安装情况,该命令显示部署的状态、版本及其他详细信息:
$ helm list -n kubecost
NAME NAMESPACE STATUS CHART
kubecost kubecost deployed cost-analyzer-2.7.2
要访问 Kubecost UI 控制台,请运行以下命令以启用端口转发:
$ kubectl port-forward -n kubecost deployment/kubecost-cost-analyzer 9090:9090
现在,你可以通过在浏览器中访问 http://localhost:9090 来访问 Kubecost UI。在 Kubecost UI 控制台中,展开左侧面板中的Monitor部分。你将看到各种仪表盘,如分配、资产、云费用、网络、集群、外部费用等,这些都提供了 K8s 工作负载的成本可视化、节省建议和治理工具。例如,选择分配以导航到分配仪表盘,如图 7**.1所示,它允许你查看所有原生 K8s 构造(如命名空间、服务、部署和 K8s 标签)上的分配开支。
图 7.1 – Kubecost 分配仪表盘
要查看部署在第五章中的 GenAI 应用程序的费用,请将Aggregate By
查询从命名空间更改为部署,并应用默认命名空间筛选器。你将看到经过微调的 Llama 3 部署、RAG API 和聊天机器人 UI 应用程序的费用,如图 7**.2所示。
重要说明
Kubecost 仅监控自安装在集群上的时间开始的费用,因此 Llama 3 微调作业的费用不可用。你可以重新运行该作业以查看相关费用。
图 7.2 – GenAI 应用程序的成本分析
请参考Kubecost 文档以了解有关这些仪表盘的更多信息。
在本节中,我们探讨了运行 GenAI 应用程序所涉及的各种成本组件,如计算、存储、网络等。我们还了解了多种工具,以深入了解 K8s 工作负载的成本,部署了 Kubecost 在我们的 EKS 集群上,并使用分配仪表盘按命名空间和部署汇总费用。
在下一节中,我们将深入探讨各种成本优化技术。
成本优化技术
为了有效降低在 K8s 上运行 GenAI 工作负载的成本,重要的是优化每个关键成本组件:计算、存储和网络。在这一部分,我们将讨论每个组件的各种策略,以降低成本同时保持最佳性能。
计算最佳实践
计算通常是 GenAI 成本中最重要的部分,因为这些应用程序通常需要访问像 GPU 这样昂贵且稀缺的专业硬件。让我们看看各种技术,如何高效利用计算资源并降低成本。
资源调整
资源调整是优化 GenAI 工作负载成本效率的最重要步骤。这涉及通过分析应用程序的运行情况并根据 K8s 工作负载的实际利用情况配置适当的资源请求(CPU、内存、GPU),从而理解应用程序的特性。
在 K8s 中调整资源大小可以最大限度地减少浪费并提高效率。例如,资源配置不足可能导致性能下降和用户体验差,而资源配置过多则会导致不必要的云开支。通过准确设置资源请求和限制,用户可以在性能和成本之间找到平衡。选择合适的实例大小可以提高集群密度,使更多工作负载能够在更少的节点上运行,从而进一步优化成本。
在 K8s 社区中,有许多工具可以帮助我们估算资源请求和限制。一些值得注意的工具有 Goldilocks (goldilocks.docs.fairwinds.com/
)、StormForge (stormforge.io/optimize-live/
)、KRR (github.com/robusta-dev/krr
)、Kubecost 等等。我们将在这里深入探讨 Goldilocks 的一些细节。
Goldilocks 是一款旨在帮助 K8s 用户优化资源请求和限制的工具,它提高了 K8s 集群的效率和成本效益。Goldilocks 使用垂直 Pod 自动扩展器(VPA)在 推荐器 模式下,建议 K8s Pods 的最佳资源请求和限制。VPA 会监控运行中 Pods 的实际 CPU 和内存使用情况,并持续收集来自 K8s 度量的使用数据。Goldilocks 获取 VPA 的历史 CPU 和内存利用数据,并根据应用程序的实际需求提供推荐的资源请求和限制。这些建议有助于确保你不会过度配置或配置不足资源。Goldilocks 提供了一个仪表盘或 CLI 工具,用于可视化其结果。它展示了当前的资源请求/限制以及基于观察到的使用情况推荐的值。
请参阅 Goldilocks 安装指南goldilocks.docs.fairwinds.com/installation/
以获取有关在 EKS 集群中设置 Goldilocks 的详细说明。安装完成后,你可以通过使用goldilocks.fairwinds.com/enabled=true
标记目标命名空间来启用监控。例如,你可以执行以下命令在默认命名空间上启用监控:
$ kubectl label namespace default goldilocks.fairwinds.com/enabled=true
namespace/default labeled
一旦目标命名空间被标记,你可以在 Goldilocks UI 控制面板中查看推荐项,如图 7.3所示,该面板显示了我们 Chatbot UI 应用程序的资源使用推荐。
图 7.3 – Goldilocks 控制面板
Kubecost 还提供了 K8s 工作负载的右-sizing 建议。在 Kubecost UI 控制台的左侧菜单中选择Savings查看节省成本的建议。节省见解提供了各种建议,如右-sizing 集群节点、容器、修复废弃工作负载等,以降低 K8s 和云成本,如图 7.4所示。
图 7.4 – Kubecost 节省见解
在www.ibm.com/docs/en/kubecost/self-hosted/2.x?topic=ui-savings
的 Kubecost 文档中了解更多这些见解。
计算容量选项
在云上部署工作负载时,你有多种选择来管理计算容量。这些选项在成本、性能和可用性方面有所不同。以下是适用于 Amazon EKS 的不同容量类型的细分,包括预留实例(RIs)、Spot 实例以及 x86 与 ARM 架构的选择:
-
EC2 按需实例 (
aws.amazon.com/ec2/pricing/on-demand/
) 是在 EKS 上部署时的默认容量类型。它们提供灵活的计算选项,无需长期承诺,你按使用的实例按分钟或秒计费。按需实例是最贵的,但提供了最高的灵活性和可用性。这种灵活性在 GenAI 工作负载的开发和实验阶段特别有用,因为这些工作负载的模式和资源需求可能有显著差异。 -
EC2 预留实例 (
aws.amazon.com/ec2/pricing/reserved-instances/
) 提供了与按需定价相比显著的折扣(最高可达 72%),以换取一到三年的承诺。预留实例非常适合可预测的工作负载,在这些工作负载中,你期望随着时间的推移保持一致的使用量。预留实例有两种不同的类型:-
标准 RIs:这些提供最大的折扣,但需要为特定的实例类型做出更长时间的承诺。通常,承诺期越长,折扣越大。
-
可转换 RIs:这些提供了在承诺期内更改实例系列、操作系统或租用方式的灵活性。与标准 RIs 相比,这种灵活性提供的折扣略小。
-
-
EC2 Spot 实例 (
aws.amazon.com/ec2/spot/
) 允许你以显著更低的成本(最多可节省 90%)使用闲置的 EC2 容量。然而,当 AWS 需要恢复容量时,Spot 实例可能会被中断,因此它们适用于容错工作负载。在 AWS 回收你的 Spot 实例之前,你会收到两分钟的通知。设计你的工作负载时,必须考虑如何优雅地处理中断,使用如检查点、分布式作业管理或按需实例备份等技术。Spot 实例可用于批处理、无状态 Web 服务器、CI/CD 管道或任何能够容忍偶尔中断或延迟的工作负载。像 Ray、Kubeflow 和 Horovod (github.com/horovod/horovod
) 这样的工具可以配置为利用 Spot 实例运行 GenAI 工作负载的分布式训练/微调,提供如 检查点、中断处理 等功能。当与像 Karpenter 这样的计算自动扩展解决方案结合使用时,这些工具可以在没有 Spot 容量时自动回退到按需容量,确保既具成本效益又可靠。 -
Savings Plans (
docs.aws.amazon.com/savingsplans/latest/userguide/what-is-savings-plans.html
) 通过提供实例类型和大小的灵活性,为 RIs 提供了一种替代方案,同时承诺在一年或三年的时间里维持一定的使用量,从而获得显著的折扣。你可以将 Savings Plan 的折扣应用于不同的 EC2 实例系列、AWS 区域,甚至计算服务,如 EC2、AWS Fargate 和 AWS Lambda。 -
AWS Graviton instances (基于 ARM 架构) (
aws.amazon.com/ec2/graviton/
) AWS 提供了两种主要的处理器架构用于 EC2 实例:x86 架构(通常是 Intel 或 AMD 处理器)和基于 ARM 架构(AWS Graviton 处理器)。在这两者之间的选择可能影响性能和成本。基于 Graviton 的实例可以为各种应用提供高达 40%的性价比优势。请参阅 AWS 文档,了解更多有关各种 Graviton 实例类型及其用途的信息,网址是aws.amazon.com/ec2/instance-explorer/
。这些实例为 GenAI 工作负载的 CPU 密集部分提供了一种经济高效的计算选择,例如数据准备、轻量级模型推断(当不需要 GPU 加速时)、聊天机器人 UI 和其他基于微服务的工作负载。 -
AWS Fargate (
aws.amazon.com/fargate/
) 是一种面向容器的无服务器计算引擎,可与 Amazon ECS 和 Amazon EKS 配合使用。它允许在不管理底层 EC2 基础设施的情况下运行 K8s Pods。这提供了一种无服务器体验,您只需支付 Pods 使用的计算资源费用。使用 Fargate 容量类型,无需管理 EC2 实例进行操作系统更新、打补丁或节点扩展。与 Graviton 一样,我们可以利用 AWS Fargate 进行数据准备、聊天机器人界面和其他基于微服务的工作负载。 -
EC2 Capacity Blocks for ML (
aws.amazon.com/ec2/capacityblocks/
) 为将来启动日期提供预留的加速计算能力,用于在 AWS 上运行 AI/ML 工作负载。EC2 Capacity Blocks 支持 EC2 P5e (aws.amazon.com/ec2/instance-types/p5/
)、P5 (aws.amazon.com/ec2/instance-types/p5/
)、P4d (aws.amazon.com/ec2/instance-types/p4/
),以及由 NVIDIA GPU 提供动力支持的其他 EC2 实例,Trn1 和 Trn2 实例由 AWS Trainium 处理器提供支持。这些有助于确保模型实验、调度大规模培训和微调作业的保证容量。容量块位于Amazon EC2 UltraClusters (aws.amazon.com/ec2/ultraclusters/
) 中,专为高性能 ML 工作负载设计,并为分布式训练提供低延迟、高吞吐量的网络连接。
本节我们探讨了优化运行 GenAI 工作负载在 K8s 集群中所涉及的计算成本的最佳实践。计算资源,包括 CPU、GPU 和自定义加速器节点,通常是运营费用的最大来源,尤其是对于 GenAI 模型。我们讨论了如合理配置资源、使用 Kubecost 和 Goldilocks 等工具来获得资源配置建议、使用不同的容量类型(如 Spot 实例、RIs 和容量块),以及使用不同工作负载的 Savings Plans 等技术。通过遵循这些实践,您可以在保持 GenAI 工作负载性能和可扩展性的同时,最大限度地减少计算成本。
网络最佳实践
为了在 Amazon EKS 中实现高可用性,建议将工作负载分布在多个可用区(AZ)之间。这样的架构提高了系统的可靠性,尤其是在某个 AZ 出现故障或基础设施故障时。然而,K8s Pod、节点和 AZ 之间的数据传输和延迟可能会迅速增加,尤其是在处理资源密集型工作负载(如模型训练和数据准备任务)时。为了控制因 AZ 或区域之间通信产生的数据传输成本,必须进行有效的网络管理。接下来,我们将探讨一些技术,以在保持性能的同时最小化网络成本。
Pod 到 Pod 的通信
跨 AZ 的 Pod 间流量可能会产生显著的成本。通过将通信限制在同一 AZ 内来减少跨区流量,有助于降低这些费用。拓扑感知路由 (kubernetes.io/docs/concepts/services-networking/topology-aware-routing/
) 确保服务之间的流量被路由到同一 AZ 内最近的 Pod。K8s 使用 EndpointSlices (kubernetes.io/docs/concepts/services-networking/endpoint-slices/
) 和区特定的提示,确保 kube-proxy 根据源区来指引流量,从而最小化跨 AZ 的流量。然而,为了使其在 K8s 集群中有效运作,需要考虑许多因素。请参考 AWS 博客 aws.amazon.com/blogs/containers/exploring-the-effect-of-topology-aware-hints-on-network-traffic-in-amazon-elastic-kubernetes-service/
以深入了解。
也可以使用 topology.kubernetes.io/zone
限制工作负载到特定的 AZ,如下所示:
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: single-az-example
spec:
...
requirements:
- key: "topology.kubernetes.io/zone"
operator: In
values: ["us-west-2a"]
...
负载均衡器配置
AWS 负载均衡器控制器(kubernetes-sigs.github.io/aws-load-balancer-controller/latest/
)管理 ELB 资源,包括应用程序负载均衡器和网络负载均衡器。在IP 模式(docs.aws.amazon.com/elasticloadbalancing/latest/application/target-group-register-targets.html#register-ip-addresses
)下,K8s Pod 被直接注册为 ELB 目标,流量直接路由到目标 Pod。这减少了额外的网络跳数,降低了网络延迟,并消除了跨可用区(AZ)之间的数据传输成本。在实例模式(docs.aws.amazon.com/elasticloadbalancing/latest/application/target-group-register-targets.html#register-instances
)下,流量首先路由到 EC2 工作节点,然后转发到适当的 K8s Pod。这一额外的路由步骤可能导致额外的网络跳数,并可能产生跨 AZ 的数据传输成本,尤其是当 K8s Pod 位于不同的 AZ 时。请参阅 AWS 博客 aws.amazon.com/blogs/networking-and-content-delivery/exploring-data-transfer-costs-for-classic-and-application-load-balancers/
以了解使用应用负载均衡器时的数据传输成本。
数据传输和 VPC 连接
为了降低服务之间的数据传输成本,VPC 端点(docs.aws.amazon.com/whitepapers/latest/aws-privatelink/what-are-vpc-endpoints.html
)允许直接访问 AWS 服务,无需通过公共互联网路由。这消除了部署互联网网关(docs.aws.amazon.com/vpc/latest/userguide/VPC_Internet_Gateway.html
)和网络地址转换(NAT)网关(docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html
)与 AWS 服务通信的需求。对于跨不同 VPC 部署的工作负载,建议使用VPC 对等连接(docs.aws.amazon.com/vpc/latest/userguide/vpc-peering.html
)或AWS 传输网关(docs.aws.amazon.com/vpc/latest/userguide/extend-tgw.html
)来实现低成本的 VPC 间通信。
优化从 Amazon ECR 拉取镜像
这一策略有助于降低网络成本并提高 K8s Pod 启动速度。来自 Amazon ECR 的区域内镜像拉取是免费的,但你将被收取 NAT 网关的数据处理费用。因此,你可以使用 Amazon ECR 和 Amazon S3 的 VPC 端点来私密访问 ECR 镜像。对于大型 GenAI 工作负载,将容器镜像预先缓存到自定义 AMI 中,可以进一步优化在工作节点/Pod 启动期间的镜像拉取时间。这种方法能够在扩展事件中减少数据传输,并加速实例就绪,特别适用于动态自动扩展的环境。欲了解更多详细信息,请参考 AWS 博客 aws.amazon.com/blogs/containers/start-pods-faster-by-prefetching-images/
。
在本节中,我们探讨了优化 K8s 集群中 GenAI 工作负载网络成本的最佳实践。有效的网络策略有助于减少与数据传输、NAT 网关和负载均衡相关的高额成本。使用拓扑感知路由和 ALB 中的 IP 目标等技术可以帮助减少延迟和数据传输成本。我们还讨论了优化来自 ECR 的镜像拉取的好处,以加速启动时间并降低网络成本。
在接下来的部分中,我们将探讨 K8s 集群中与存储相关的最佳实践。
存储最佳实践
在 K8s 环境中有多种存储选项可供选择,选择合适的存储方式对于优化应用程序的性能和成本至关重要。根据工作负载的不同,用户可以选择临时存储或持久存储来满足应用程序的需求。
临时存储
临时卷是临时存储卷,它们的生命周期与 Pod 相同,Pod 终止后会消失,因此适合用作临时存储或缓存。这些卷通常由主机系统的根磁盘或内存支持,意味着一旦 Pod 终止,它们就不会持久化。为了提高成本效益,正确配置临时存储至关重要,以避免过度配置,并且在可能的情况下,可以利用节点本地存储来处理临时任务,从而减少对外部存储系统的依赖,降低成本。
对象存储
虽然临时卷和 EBS 卷为许多工作负载提供了低延迟、高性能的存储,但像 Amazon S3 这样的对象存储解决方案提供了一种灵活、可扩展且具有成本效益的替代方案,用于存储大型数据集、模型产物和日志。Amazon S3 CSI 驱动程序的挂载点 (github.com/awslabs/mountpoint-s3-csi-driver
) 使你能够将 S3 存储桶作为文件系统挂载到 K8s Pods 中,允许应用程序使用熟悉的文件系统语义与对象存储进行交互。与传统的 S3 访问方法相比,它提供了显著的性能提升,特别适合数据密集型工作负载和 AI/ML 训练。
Mountpoint for S3 CSI 驱动程序支持 Amazon S3 Standard 和 S3 Express One Zone 存储类。S3 Express One Zone 是一个高性能存储类,专为单一可用区(AZ)部署设计。它提供一致的单数毫秒级数据访问,非常适合频繁访问的数据和对延迟敏感的应用程序。通过将存储和计算资源共置于同一 AZ 内,您可以优化性能,并可能降低网络成本。
除了性能考量,优化对象存储成本在处理大规模训练数据集时至关重要。最佳实践包括根据访问模式选择适当的 S3 存储类。例如,对于频繁访问的数据,使用 S3 Standard 或 S3 Intelligent-Tiering,并将不常访问的数据转移到 S3 Infrequent Access 或归档存储类(如 Glacier)。实施生命周期策略来自动执行数据的过渡和过期,从而降低不必要的存储成本。有关各种 S3 存储类的详细信息,请参阅 AWS 文档:aws.amazon.com/s3/storage-classes/
。
使用 Mountpoint for S3 CSI 驱动程序时,您可以通过创建 PersistentVolume 将现有的 S3 存储桶附加到 K8s Pods,如下所示:
apiVersion: v1
kind: PersistentVolume
metadata:
name: s3-demo-pv
spec:
capacity:
storage: 1200Gi # Ignored, required
accessModes:
- ReadWriteMany
mountOptions:
- allow-delete
- region us-west-2
csi:
driver: s3.csi.aws.com
volumeHandle: s3-csi-driver-volume
volumeAttributes:
bucketName: <Your-S3-Bucket-Name>
通过利用 Mountpoint-S3 并遵循成本效益存储的最佳实践,您可以将 Amazon S3 对象存储无缝集成到您的 K8s 工作负载中。这种方法使得您的 GenAI 工作负载能够访问大规模、成本效益高的存储,并具有熟悉的文件系统语义,同时优化性能和成本。
EBS 卷
Amazon EBS 卷提供块级持久存储。EBS 卷通过 Amazon EBS CSI 驱动程序进行管理,支持通过 K8s 动态供应。容器存储接口 (CSI) 是一个标准化的 K8s API,确保 K8s 和外部存储系统之间的互操作性。K8s 应用程序通过创建 Persistent Volume Claims (PVCs) 来请求存储,指定大小和访问模式。EBS CSI 驱动程序根据关联的 StorageClass 配置 EBS 卷。例如,以下 YAML 代码将创建一个 gp3 存储类:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: gp3-sc
provisioner: ebs.csi.aws.com
parameters:
type: gp3
encrypted: "true"
fsType: ext4
allowVolumeExpansion: true
reclaimPolicy: Delete
StorageClass 的回收策略可以是 delete 或 retain。delete 策略确保当关联的 Pod 被删除时,PersistentVolume 会被自动删除。retain 策略即使 PVC 被删除,PV 也会被保留。该卷保持完整,包含所有数据,但会变为未绑定状态,可供手动干预。
以下 YAML 文件正在创建一个 PVC,它链接到先前的存储类,并为 10 GB 的存储分配空间:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: demo-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: gp3-sc
为了进行成本优化,确保根据应用的需求分配正确数量的存储是一个好主意。过度配置未充分利用的大容量存储会导致不必要的费用。同样,未使用的PVs和EBS 快照会随着时间的推移积累,因此应持续监控存储成本,或使用删除作为回收策略。你可以使用 Kubecost 查看未分配的卷、孤立资源、持久卷调整建议等内容。打开 Kubecost UI 控制台,在左侧菜单中选择 Savings -> Insights,进入Savings页面,在此页面可以查看成本节省建议,如图 7.5和图 7.6所示。
图 7.5 – Kubecost UI 控制台中未分配卷的概览
这些洞察帮助你通过突出显示已分配但未被积极使用的持久卷,识别节省成本的机会。通过分析这些模式,Kubecost 使你能够采取有根据的措施,如回收未使用的资源或调整现有卷的大小,更好地适应工作负载需求,如图 7.6 所示。
图 7.6 – Kubecost UI 控制台中持久卷调整建议的概览
为了优化成本,推荐使用 gp3 卷,因为与 gp2 相比,gp3 卷提供最高 20%的成本节省,并允许独立扩展 IOPS 和吞吐量,而无需增加卷的大小。对于高性能需求,io2 Block Express卷支持高达 256,000 IOPS,但它们更昂贵,并且需要特定的 EC2 实例类型。
对于访问数据频率较低的工作负载,如日志和备份,可以使用Cold HDD (sc1)或Throughput Optimized HDD (st1)。这些选项比 SSD 支持的卷便宜。有关详细定价,请参考 Amazon EBS 定价页面:aws.amazon.com/ebs/pricing/
。
在优化 K8s 应用的存储成本时,还需要考虑以下几个因素:
-
优化容器镜像存储:容器镜像可能会消耗大量存储,特别是在使用大型多层镜像的 Amazon EKS 集群中。优化容器镜像对于减少存储成本和启动时拉取镜像的时间至关重要。为此,最好尽可能使用较小、轻量的父镜像。此外,通过使用多阶段构建(
docs.docker.com/build/building/multi-stage/
),只有必要的组件被包含在最终的容器镜像中,从而进一步减少镜像大小。这些优化不仅节省了存储空间,还加快了部署时间,并降低了存储和拉取容器镜像的成本。 -
使用数据保留策略:实施数据保留策略,自动删除随着时间积累的旧的、不必要的数据集,如日志、指标和备份等。像Elasticsearch和AWS CloudWatch Logs这样的工具提供控制选项,可以设置适当的保留策略,以删除旧日志并降低存储成本。对于备份,同样应设置适当的保留策略,确保仅保留必要的备份,同时删除旧的、冗余的备份。
在本节中,我们讨论了各种可用的存储选项,以及如何使用 CSI 驱动程序在 K8s 集群中动态地配置存储卷。我们还探讨了减少容器镜像大小的重要性,这不仅可以降低存储成本,还能提高 K8s Pod 的启动时间。
摘要
在本章中,我们探讨了通过专注于计算、存储和网络三大关键组件,优化在云中部署 GenAI 应用程序成本的最佳实践。我们还介绍了像 Kubecost 和 Goldilocks 这样的工具,用于监控资源使用情况,确保资源的高效分配。
对于计算成本,选择合适的实例类型至关重要。监控资源使用情况,以确保工作负载运行在优化大小的实例上也非常重要。对于存储,选择合适的存储类型是优化用于模型训练和推理的大型数据集存储成本的关键。
Kubecost 是一个有效的工具,用于监控和优化 K8s 集群的成本。它提供按命名空间、Pod 和服务的详细成本分解,帮助将费用归因于各个团队或应用程序。Kubecost 还可以识别使用率低的节点,推荐更具成本效益的实例类型,并检测存储和网络效率问题,如不必要的跨可用区数据传输。Goldilocks 利用 VPA 分析历史的 CPU 和内存使用情况,提供资源请求和限制的适当大小建议。
在网络方面,我们讨论了确保 Pod 通信在同一可用区(AZ)内对齐的重要性,以减少跨 AZ 流量成本。使用拓扑感知路由可以确保流量在同一 AZ 内路由,从而减少跨 AZ 的传输费用。
本章还强调了持续监控、资源的合理配置,以及在计算、存储和网络之间做出战略性权衡的重要性,以有效优化成本。
在下一章中,我们将深入探讨并介绍在 K8s 中部署 GenAI 应用的网络最佳实践。
加入 CloudPro 新闻通讯,拥有超过 44,000 名订阅者
想了解云计算、DevOps、IT 管理、网络等领域的最新动态吗?扫描二维码订阅CloudPro,我们的每周通讯,面向 44,000 多位希望保持信息领先的技术专业人士。
第七章:在 K8s 上部署 GenAI 的网络最佳实践
在本章中,我们将探讨在Kubernetes(K8s)上部署 GenAI 应用时云网络的最佳实践。有效的网络连接对于确保 Pod 之间的无缝通信、优化性能和增强安全性至关重要。本章将首先介绍 K8s 网络的基础知识,例如容器网络接口(CNI)(kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/
)以设置 Pod 网络和网络策略,从而在 K8s 集群内实施安全和访问控制,此外,我们还将深入探讨使用优化的云网络接口,如弹性网络适配器(EFA)(aws.amazon.com/hpc/efa/
),以提升网络性能。通过为 Pod 和服务之间的通信定义细粒度的规则,组织可以减少潜在的安全威胁,从而保护其知识产权和 GenAI 模型。
在本章中,我们将讨论以下主要主题:
-
理解 Kubernetes 网络模型
-
使用服务网格进行高级流量管理
-
使用 Kubernetes 网络策略保护 GenAI 工作负载
-
优化 GenAI 的网络性能
理解 Kubernetes 网络模型
K8s 网络已从Docker 的网络模型(docs.docker.com/engine/network/
)演变而来,以更好地解决在分布式环境中管理大型容器集群的复杂性。Docker 最初的网络使用的是单主机、基于桥接的网络模型,其中同一主机上的容器可以通过本地桥接网络进行通信。然而,不同主机上的容器需要额外的配置,显式地创建容器之间的链接,或将容器端口映射到主机端口,以使其他主机上的容器能够访问。K8s 通过确保无缝的跨主机 Pod 通信、自动服务发现和负载均衡简化了这一网络模型。
K8s 的网络模型(kubernetes.io/docs/concepts/services-networking/#the-kubernetes-network-model
)具有以下关键原则:
-
K8s 集群中的每个 Pod 都有其独特的 IP 地址,Pod 内的所有容器共享一个私有网络命名空间。位于同一 Pod 内的容器可以通过 localhost 相互通信。
-
Pods 可以直接跨集群相互通信,无需代理或地址转换,例如网络地址转换(NAT)。
-
Service API 提供了一个 IP 地址或主机名来表示服务,即使构成这些服务的 Pods 发生变化。K8s 管理 EndpointSlice (
kubernetes.io/docs/concepts/services-networking/endpoint-slices/
) 对象来跟踪这些 Pods。 -
NetworkPolicy (
kubernetes.io/docs/concepts/services-networking/network-policies/
) 是一个内建的 K8s API,允许控制 Pod 与外部源之间的流量。
我们将在本章后面详细介绍 Service API 和网络策略。K8s 网络模型的一些关键组件包括 Kubelet (kubernetes.io/docs/reference/command-line-tools-reference/kubelet/
)、容器运行时接口 (CRI) (kubernetes.io/docs/concepts/architecture/cri/
) 和 容器网络接口 (CNI) (kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/network-plugins/
),它们负责集群中容器的生命周期管理和网络连接。
-
Kubelet:Kubelet 是在 K8s 集群中每个工作节点上运行的一个代理,它确保 K8s API 描述的容器在节点上正确运行。Kubelet 与 CRI 交互,根据 Pod 规格中设置的配置启动、停止和监控容器。
-
CRI:CRI 是一个 API,允许 Kubelet 通过抽象底层容器运行时(如 Containerd 或 CRI-O),以标准化的方式与不同的容器运行时进行通信。
-
CNI:CNI 是一个开源的 API 规范,设计时考虑了简洁性和模块化。它允许 K8s 通过使用任何兼容 CNI 的插件,以统一的即插即用方式处理容器网络。
当一个 Pod 被调度到工作节点时,Kubelet 会指示 CRI 为该 Pod 创建容器。一旦容器准备好,Kubelet 调用 CNI 插件来设置 Pod 网络——附加网络接口、分配 IP 地址、配置路由并确保网络策略得到执行。这使得 Pod 之间以及与外部网络的通信无缝进行,遵循 K8s 网络模型。
K8s 有多个可用的 CNI 插件,每个插件都有独特的功能和优势。一些流行的 CNI 插件包括Calico(www.tigera.io/project-calico/
)、Cilium(github.com/cilium/cilium
)、Weave Net(github.com/weaveworks/weave
)、Antrea(antrea.io/
)和Amazon VPC CNI(github.com/aws/amazon-vpc-cni-k8s
)。有关详细列表,请参阅 K8s 文档:kubernetes.io/docs/concepts/cluster-administration/addons/#networking-and-network-policy
。
其他重要的 K8s 网络组件包括IP 地址管理(IPAM),CNI 插件使用它为 K8s 集群中的 Pod 分配和管理 IP 地址,以及IPTables(man7.org/linux/man-pages/man8/iptables.8.html
),它负责数据包过滤,并是 Linux 内核的一部分。在 K8s 中,像 kube-proxy 和某些 CNI 网络插件这样的组件使用IPTables来管理网络规则并在工作节点内部进行流量引导。这些 IPTables 规则使 Pod 能够在集群内相互通信、管理外部流量,并根据网络插件的不同帮助实现网络策略。
图 8.1 显示了 kubelet、CNI、IPAM 和 IPTables 如何在 K8s 工作节点内协同工作,以设置和管理 Pod 的网络:
-
在第 1 步中,Kubelet 与 CNI 插件进行通信,请求创建和配置 Pod 的网络。
-
在第 2 步中,CNI 插件创建一个网络命名空间,并调用 IPAM 模块为 Pod 保留一个 IP 地址。
-
在第 3 步中,CNI 插件配置 IPTables 以管理 Pod 的网络流量规则。此步骤确保 Pod 能够根据集群的网络策略与其他 Pod 和外部网络进行通信。
-
在最后一步,分配的 IP 地址(例如,10.0.0.12)被分配给 Pod。这使得 Pod 可以通过分配的 IP 地址在集群内进行访问。
图 8.1 – 工作节点中的 IP 分配流程
选择 GenAI 应用程序的 CNI 网络模式
在选择 CNI 插件时,了解覆盖网络和本地网络之间的区别是非常重要的。
使用覆盖网络的 CNI 插件在现有网络上创建了一个额外的抽象层,通过隧道封装流量,以隔离 Pod 网络并简化节点之间的路由。虽然这提供了灵活性和网络分段,但通常会因为封装开销而导致更高的延迟和较低的吞吐量。
原生网络插件,如 Amazon VPC CNI 和 Cilium,直接与底层基础设施的路由进行集成,允许 Pod 使用真实的网络接口和 IP 地址进行通信,而无需封装。这种集成可以带来更高的吞吐量和更低的延迟,使得原生网络成为需要高性能的应用的最佳选择。对于 GenAI 工作负载,快速的数据传输和最小的网络延迟对于有效的模型训练和推理至关重要,因此通常建议使用原生网络 CNI 插件。
K8s 中的服务实现
服务是 K8s 资源,提供网络端点和负载均衡功能,能够跨一组 Pod 实现负载均衡,简化将应用暴露给其他服务或外部客户端的过程。以下是 K8s 服务实现的主要特点:
-
K8s 服务为一组 Pod 提供一致的 IP 地址和 DNS 名称,因此即使 Pod 被创建或销毁,服务性能也不会受到影响。
-
服务会根据 Pod 的标签自动分配传入流量到可用的 Pod,从而实现负载均衡并确保可用性。
-
K8s 支持以下四种不同类型的服务:
-
aws-load-balancer-controller
(kubernetes-sigs.github.io/aws-load-balancer-controller/latest/
) 插件,用于通过 AWS 网络负载均衡器(NLB)在 第三章中公开我们的 GenAI 模型。 -
ExternalName 将服务映射到外部 DNS 名称,允许 K8s 服务连接到集群外部的外部服务。
-
GenAI 应用通常需要可扩展、低延迟和高效的网络,以有效地为客户提供服务。K8s 的 LoadBalancer 服务可以用于将模型暴露到集群外部。它通过底层云提供商(如 AWS、Azure 或 GCP)配置外部负载均衡器。这种设置可以实现将传入流量无缝分配到多个 Pod,确保高可用性和可扩展性。另一方面,ClusterIP 服务可以在仅限集群内的其他 K8s Pod 访问 GenAI 模型时使用。它为每个服务分配一个友好的服务查找名称和 IP 地址,从而促进 Pod 之间的可靠服务发现和通信,而无需将它们暴露给外部。
默认情况下,使用 K8s 的 aws-load-balancer-controller
时,会创建一个 NodePort 服务,将来自 AWS NLB 的流量转发到 K8s 工作节点。从那里,kube-proxy 将流量路由到各个 Pod,引入了额外的网络跳跃,这可能会增加延迟和吞吐量。为了减少这种开销,可以配置 LoadBalancer 服务,将流量直接路由到 K8s Pod,通过将 Pod 注册为 NLB 目标。
这可以通过向 K8s 服务添加 service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
注解来实现,如下所示:
apiVersion: v1
kind: Service
metadata:
name: my-llama-svc
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
spec:
type: LoadBalancer
...
服务健康检查
确保最佳性能和可靠性的另一个重要考虑因素是配置 healthcheck 设置在 LoadBalancer 服务类型上。GenAI 模型通常资源密集型,并且根据输入复杂性和服务器负载可能具有不同的启动和响应时间。如果没有适当的健康检查,NLB 可能会将目标标记为不健康,这将导致替换 K8s Pods、性能下降或可能的停机。
LoadBalancer 健康检查确保流量仅路由到能够处理请求的健康 Pod。例如,在一个 GenAI 模型通过 K8s 类型为 LoadBalancer 的服务提供推理服务的场景中,健康检查可以监控每个后端 Pod 上的 /healthz
端点。如果模型已完全加载、所需的资源(例如 GPU 内存)可用且推理服务正常运行,该端点可能会返回 HTTP 200 OK 状态。如果由于资源耗尽或进程崩溃等问题导致某个 Pod 未通过健康检查,LoadBalancer 会自动将其从可用端点池中排除,将流量引导到健康的 Pod 上。这种机制可以防止请求失败,确保终端用户的无缝体验,同时保持 GenAI 工作负载的整体稳定性和可扩展性。
请参阅 aws-load-balancer-controller
文档,网址为 kubernetes-sigs.github.io/aws-load-balancer-controller/latest/guide/service/annotations/#health-check
,了解各种健康检查设置。
虽然 K8s LoadBalancer 服务提供了一种简单直接的方式来将 K8s 工作负载暴露到外部,但在更复杂的路由场景中,它们可能会受到限制。Ingress 和 Gateway API 提供了更大的灵活性和流量控制,支持路径或基于主机的路由、TLS 终止等功能。在下一节中,我们将介绍 Ingress 和 Gateway API,它们负责处理 K8s 工作负载的输入流量。
Ingress 控制器
在 K8s 中,Ingress API(kubernetes.io/docs/concepts/services-networking/ingress/
)用于通过 HTTP/S 协议将应用程序暴露到集群外部。它作为一个路由层,依据 HTTP URL 路径、主机名、头信息等,将传入请求定向到 K8s 应用程序。Ingress 控制器负责配置所需的基础设施资源,如应用负载均衡器(在 AWS 中),配置路由规则,并终止 SSL 连接,以满足 Ingress 资源的需求。一些流行的Ingress 控制器(kubernetes.io/docs/concepts/services-networking/ingress-controllers/
)包括ingress-nginx、aws-lb-controller、HAProxy Ingress和Istio Ingress。有关详细的控制器列表,请参阅 K8s 文档:kubernetes.io/docs/concepts/services-networking/ingress-controllers/#additional-controllers
。在 K8s 集群中部署 GenAI 工作负载时,您应该考虑使用 Ingress 来将多个应用程序/模型暴露在一个入口点(域名)下,如图 8.2所示,这通常需要集中式流量路由。此外,Ingress 支持高级路由功能,如基于路径和基于主机的路由,使您能够根据 URL 和 HTTP 头将特定请求定向到不同的模型版本或应用程序,这对于A/B 测试(aws.amazon.com/developer/application-security-performance/articles/a-b-testing/
)或金丝雀发布非常有用,后者是将新的应用程序变更逐步推出给少数用户,允许团队在全面部署之前监控性能并解决问题。除了路由外,Ingress 控制器还与监控工具如Prometheus集成,以提供详细的指标,如延迟、请求率和错误率。
图 8.2 – Ingress 概述
网关 API(gateway-api.sigs.k8s.io/
)旨在解决 Ingress 的局限性,提供一种更灵活、可扩展和标准化的方式来管理 K8s 集群中的流量,特别是对于复杂的网络需求。它是 K8s 的官方项目,专注于 L4 和 L7 路由,代表了 Ingress、负载均衡和服务网格 API 的下一代。像 Ingress 一样,您需要安装网关控制器来提供必要的基础设施资源,并支持高级路由功能。此 API 有许多实现,完整的实现列表请参考 K8s 文档:gateway-api.sigs.k8s.io/implementations/
。
在本节中,我们学习了 K8s 网络的基础概念、核心原则,以及它与其他编排工具的不同之处。我们讨论了 CNI 插件在配置 Pod 网络中的作用,IPAM 用于管理 IP 地址分配,以及选择 CNI 网络模式时需要考虑的因素。我们还探讨了各种 K8s 服务、Ingress 和网关 API,以便将 GenAI 模型暴露到集群外部。在下一节中,我们将讨论高级应用程序网络构造,如服务网格。
高级流量管理与服务网格
随着 GenAI 应用程序复杂度的增长,涉及的服务数量也在增加,例如模型推理、数据摄取、数据处理、微调和训练,这也增加了管理服务间通信的复杂性。通常包括流量管理(负载均衡、重试、速率限制)、安全性(身份验证、授权、加密)和可观察性(日志、度量、跟踪)。
服务网格是一个基础设施层,它使微服务之间的通信变得可靠、安全和可观察。它抽象了服务间通信的复杂性,包括流量管理、负载均衡、安全性和可观察性,无需修改应用程序代码。
服务网格通常通过部署边车代理,如Envoy(www.envoyproxy.io/
),与应用程序一起使用,拦截所有进出应用程序的网络流量。
服务网格有两个关键组件:
-
控制平面,负责管理边车代理,分发配置和策略到各个边车代理,并收集遥测数据以便集中控制和监控。服务网格中可以实现不同类型的策略,如下所示:
-
流量管理策略,定义了流量应如何路由、断路规则、负载均衡和重试选项
-
安全策略,可以建立加密(如 mTLS)、身份验证(例如 JWT 令牌)和授权(RBAC)等规则
-
弹性和容错策略,可以定义重试机制、超时和故障切换选项。
-
-
数据平面,它结合了边车代理,负责服务之间的实际网络流量。每个边车可以独立应用策略,确保每个服务实例遵守在控制平面设置的网络规则和策略。边车容器是与主应用容器一起在同一 Pod 内运行的辅助容器。它们通过提供额外的功能,如日志记录、监控、安全性或数据同步,来补充主容器的功能,无需更改应用程序代码。
一些在 K8s 中流行的服务网格实现包括Istio、Linkerd和Ambient Mesh。传统的网格如 Istio 和 Linkerd 依赖于边车,而 Istio 最近推出的 Ambient Mesh 则提供了一种无边车的架构,可以利用 eBPF 进行高效的流量管理和安全性。图 8.3展示了服务网格实现如何在网格内外路由流量、拦截请求,并使用边车代理实现端到端的 TLS 加密或 mTLS。
图 8.3 – 服务网格实现
该图展示了一个服务网格架构,流量通过入口网关进入,经过边车代理在 Pods 之间流动,然后通过出口网关离开。服务网格控制平面集中配置和管理边车代理,以执行流量策略和安全性。这个设置使得集群内微服务的通信更加高效和安全。参考aws.amazon.com/blogs/opensource/getting-started-with-istio-on-amazon-eks/
中的在 Amazon EKS 上使用 Istio 入门文章,按步骤说明如何在 Amazon EKS 上部署 Istio 服务网格。
本节我们探讨了服务网格的高级流量管理特性,如负载均衡、安全性、可观察性以及高层次的架构概览。在下一节中,我们将深入探讨如何使用 K8s 原生网络策略保护 GenAI 工作负载。
使用 Kubernetes 网络策略保护 GenAI 工作负载
网络策略是 K8s 的原生功能,用于控制集群内 Pods 之间的流入(入站)和流出(出站)流量。它们通过 K8s 的NetworkPolicy API实现,允许管理员定义规则来决定哪些 Pods 或 IP 地址可以相互通信。
与提供高级流量管理和安全功能的服务网格不同,网络策略侧重于流量隔离和网络分段,目的是出于安全考虑,例如命名空间隔离。
默认情况下,K8s 集群中的所有 Pod 都可以互相通信;然而,网络策略可以限制这一点,并允许对网络流量进行细粒度控制。这在多租户集群中尤其有用,其中不同的团队或应用需要出于安全或合规性的原因进行隔离。网络策略的一些关键特性如下:
-
入口和出口规则:入口规则定义了哪些源可以与特定 Pod 或一组 Pod 进行通信。类似地,出口规则定义了 Pod 或一组 Pod 可以连接到哪些目的地。
-
app: backend
可以配置为仅允许来自具有标签app: frontend
的 Pod 的入口流量。 -
默认拒绝模型:默认情况下,如果没有应用网络策略,则允许所有流量。然而,一旦网络策略应用于 Pod,只有符合指定策略规则的流量才被允许。
这是一个网络策略示例,它仅允许来自同一命名空间中具有标签app: frontend
的其他 Pod 的流量进入带有标签app: backend
的 Pod。所有其他入口流量默认被拒绝:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-example
spec:
podSelector:
matchLabels:
app: backend
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
接下来,让我们探讨如何实现 K8s 网络策略,以确保电子商务聊天机器人应用不同组件之间的流量安全。
在聊天机器人应用中实现网络策略
在第五章中,我们在 EKS 集群中部署了一个包含四个组件(chatbot-ui、向量数据库、RAG 应用和微调的 Llama3 模型)的聊天机器人应用,如图 8.4所示。默认情况下,所有组件都可以在任何端口/协议上相互通信,这不是一个安全的最佳实践。我们的目标是实现网络分段,以便只有受信任的组件才能在批准的端口和协议上相互通信。
图 8.4 – 电子商务聊天机器人应用架构
在此设置中,聊天机器人 UI 应用通过 HTTP 协议在端口 80 上与 RAG 应用和微调的 Llama 3 模型进行通信。因此,我们在 RAG 和 Llama 3 应用上创建一个入口网络策略,仅允许来自聊天机器人 UI 应用的 HTTP/80 入口流量,以确保网络流量的安全。我们可以使用分别应用于 K8s 部署的标签来创建网络策略。
以下网络策略选择了带有app.kubernetes.io/name: rag-app
标签的 RAG 应用 Pod,并应用入口规则,仅允许来自由app.kubernetes.io/name=chatbot-ui
标签标识的 Pod 的 HTTP/80 流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: rag-app-ingress-policy
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: rag-app
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app.kubernetes.io/name: chatbot-ui
ports:
- protocol: TCP
port: 80
我们还可以为微调的 Llama 3 应用创建另一个网络策略,仅允许来自聊天机器人 UI 应用的 HTTP/80 流量,通过使用app.kubernetes.io/name: my-llama-finetuned
、app.kubernetes.io/name: chatbot-ui
标签。
类似地,RAG 应用程序通过 HTTP 在端口 6333 上与向量数据库通信。为了限制入站流量,我们可以创建一个网络策略,适用于标记为 app.kubernetes.io/name: qdrant
的 Pod,仅允许来自标记为 app.kubernetes.io/name: rag-app
的 Pod 的 HTTP/6333 流量。
您可以在 GitHub 代码库中找到所有网络策略,网址为 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch8
。您可以使用 kubectl apply
命令在 EKS 集群中下载并应用这些策略。在本操作指南中,我们重点配置入站流量的 Ingress 规则。为了进一步加强安全性,您可以扩展这些策略以包括对出站流量的 egress 规则。然而,如果您的应用程序在 K8s 服务上执行 DNS 查询,请务必允许 DNS 流量进入 kube-system
命名空间。
默认情况下,上游的 K8s 网络策略仅支持有限的规则来定义流量流向(主要包括 IP 地址、端口、协议、podSelector 和 namespaceSelector),不支持基于域的规则或集群或全局策略。当使用外部 API(如 OpenAI 或 Claude)时,这可能不足够支持 GenAI 应用程序。为了弥补这些差距,Cilium (docs.cilium.io/en/stable/security/
)、Calico (docs.tigera.io/calico/latest/network-policy/
) 等提供了高级功能,如基于 DNS 的策略,允许指定动态策略强制执行的完全限定域名,以及全局(集群范围)策略,确保所有命名空间间的统一安全姿态。这些特性简化了策略管理,增强了集群范围的数据治理,并保持了一致的流量控制。
总结一下,K8s 网络策略有助于定义 OSI 模型的网络和传输层上的流量规则和分段,但在应用层流量管理和可观察性方面缺乏高级功能。在接下来的部分中,我们将比较网络策略与服务网格技术,突出这些解决方案在关键特性上的不同。
服务网格与 K8s 网络策略
虽然服务网格和 K8s 网络策略都有助于保护和管理 K8s 网络,但它们的用途不同,通常互补:
特性 | 服务网格 | K8s 网络策略 |
---|---|---|
主要焦点 | 可观察性、流量管理(如负载均衡)、重试和安全性(支持 mTLS) | 使用命名空间进行安全性和流量隔离 |
流量路由 | 高级路由和负载均衡 | 基本 |
OSI 层 | 主要在第 7 层(应用层) | 主要在第 3 和第 4 层(网络和传输层) |
双向 TLS | 支持 | 不支持 |
复杂性 | 需要 sidecar 代理 | 更简单,原生支持 Kubernetes |
在许多生产环境中,通常会同时部署服务网格和 K8s 网络策略,以增强安全性和流量管理。例如,您可以使用 K8s 网络策略来强制执行基于命名空间的访问限制,然后使用服务网格处理路由、重试、负载均衡和 mTLS。
优化 GenAI 的网络性能
本节将介绍在 K8s 上部署 GenAI 工作负载时的一些重要网络优化,包括 Kube-Proxy、IP 地址耗尽问题,以及一些高级网络功能,如单根输入/输出虚拟化(SR-IOV)和扩展贝尔克利数据包过滤器(eBPF)。
Kube-Proxy – IPTables 与 IPVS
Kube-Proxy (kubernetes.io/docs/reference/command-line-tools-reference/kube-proxy/
) 是 K8s 的核心组件,负责管理集群内的网络。它通过在每个节点上设置网络规则和配置,确保服务和 Pod 之间的无缝通信。Kube-Proxy 维护网络规则,将流量导向为每个服务提供服务的后端 Pods,使得集群内部流量能够到达正确的目的地。默认情况下,Kube-Proxy 使用 IPTables 模式,它通过基于 IP 规则高效地拦截并重定向网络请求,适用于中小型集群。
然而,对于大规模的 K8s 集群,特别是那些运行数据密集型工作负载(如 GenAI)的集群,IPTables 模式可能成为性能瓶颈。随着集群规模的扩大,服务和端点达到数百或数千个时,维护和更新这些 IPTables 规则可能导致延迟增加和网络性能下降,进而影响 AI 模型的整体效率。
为了解决这些限制,K8s 提供了在 IP 虚拟服务器(IPVS)模式下运行 Kube-Proxy 的选项。IPVS 通过利用 Linux 内核的 IPVS 模块提供了先进的负载均衡功能,它比 IPTables 更具可扩展性和高效性,能够处理高流量。IPVS 维护一个内存中的哈希表用于服务到 Pod 的路由,使得数据包处理速度更快且 CPU 开销最小。
IPVS 模式提供了诸多优势,如更精细的负载均衡算法(例如轮询、最少连接数、源地址哈希)、更好的动态和大规模服务环境处理能力,以及减少数据包转发的延迟。
可以通过更新 kube-proxy-config
ConfigMap
在 kube-system
命名空间中启用 IPVS:
apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"
...
要在 Amazon EKS 集群设置中启用 IPVS,请参阅 EKS 文档:docs.aws.amazon.com/eks/latest/best-practices/ipvs.html
,查看逐步操作说明。
尽管像 IPVS 这样的高级网络配置有助于提高 K8s 中流量管理和可扩展性,但大规模集群的另一个关键挑战在于有效管理 IP 地址分配,我们将在下一节中讨论这一问题。
IP 地址耗尽问题和自定义网络
当使用像 Amazon VPC CNI 这样的 CNI 插件时,在原生网络模式下,每个 K8s Pod 直接从 VPC CIDR 块中获取 IP 地址。这种方法使每个 Pod 在 VPC 内都能完全寻址,可以使用 VPC 流日志 和其他监控工具查看 Pod 的 IP 地址。然而,在运行密集型工作负载如 GenAI 的大规模集群中,这种模式可能导致 IP 地址耗尽,因为每个 Pod 都会从有限的 VPC CIDR 池中消耗一个 IP 地址。
为了缓解这一问题,一个有效的解决方案是使用 IPv6 地址。IPv6 提供了比 IPv4 更大的地址空间,减少了 IP 耗尽的风险,并允许集群在不担心地址用尽的情况下扩展。但并不是所有组织都准备好采用 IPV6。
解决 IP 耗尽的另一种方法是通过 VPC CNI 自定义网络。这涉及通过将额外的、不可路由的次级 CIDR 块与 VPC 关联,并从这些 CIDR 创建新的子网来增强 VPC 设计。这些子网专门用于 Pod IP 分配,而主要的可路由 CIDR 用于节点 IP 和其他资源。然后,我们配置 VPC CNI 从这些不可路由的子网中分配 Pod IP,从而释放主要的 CIDR 用于其他网络需求,并减少 IP 耗尽的风险。以下链接解释了如何在 Amazon EKS 中实现自定义网络:docs.aws.amazon.com/eks/latest/userguide/cni-custom-network-tutorial.html
。
为了解决 K8s 中的网络挑战,包括 IP 地址耗尽和性能优化,值得探索一些高级网络解决方案,如 VPC CNI 自定义网络或 IPV6。新兴技术如 eBPF 和 SR-IOV 提供了创新的方法来提高网络效率和可扩展性,我们将在下一节讨论这些技术。
eBPF 和 SR-IOV
扩展伯克利数据包过滤器(eBPF)(ebpf.io/what-is-ebpf/
) 允许在不修改内核代码的情况下进行 Linux 内核的高级可编程性。它可以用来在内核级别直接创建强大、轻量级的网络、安全和可观察性解决方案。例如,利用 eBPF 的 Cilium CNI 插件提供了细粒度的网络安全、负载均衡和可观察性功能。对于 GenAI 工作负载,其中性能和数据传输速度至关重要,eBPF 的最小开销和内核级处理使其成为低延迟和高吞吐量数据传输的理想选择。
单根输入/输出虚拟化 (SR-IOV)使单个物理网络接口卡 (NIC)能够划分为多个虚拟功能,为虚拟机或容器提供直接的硬件访问。每个虚拟功能充当独立的接口,提供高吞吐量和低延迟的网络吞吐量,尤其适用于涉及大量数据传输的 GenAI 工作负载。SR-IOV 通过将数据包处理卸载到 NIC,从而减少了 CPU 开销,确保计算密集型任务的资源利用率更高。它还提供专用的网络路径,确保网络隔离和可预测的性能,这对于一致的 AI 模型推理至关重要。该技术通过优化资源分配提升了可扩展性,并支持具有隔离的高性能网络的多租户集群。
eBPF 和 SR-IOV 等技术优化了 K8s 集群中的网络性能和资源效率,支持高速且可靠的数据处理。这些进展的补充是 CoreDNS 自动扩展,它确保无缝的服务发现和高效的 DNS 解析,这对于服务间通信至关重要。
CoreDNS
CoreDNS (github.com/coredns/coredns
)是 K8s 中的一个关键组件,提供 DNS 服务,促进 K8s 集群内部的服务发现和网络通信。它充当集群的 DNS 服务器,使 Pods 能够使用简单的服务名称找到并相互通信。为了实现最佳的性能和管理,建议在 EKS 中使用CoreDNS 托管插件 (docs.aws.amazon.com/eks/latest/userguide/coredns-add-on-create.html
)。监控 CoreDNS 对于维持高效的网络性能也至关重要,因为 DNS 解析问题可能会导致服务中断或延迟,特别是在需要大量服务间通信的工作负载中,如 GenAI。
为了满足大规模 K8s 集群日益增长的需求,建议实现 CoreDNS 自动扩展,以使 CoreDNS Pods 的数量与集群的大小成比例。你可以利用cluster-proportional-autoscaler
(github.com/kubernetes-sigs/cluster-proportional-autoscaler
)插件,它监控集群中可调度节点和 CPU 核心的数量,并根据需要调整核心资源(如 CoreDNS)的副本数。在 EKS 中使用 CoreDNS 托管插件时,该功能是原生提供的,可以通过docs.aws.amazon.com/eks/latest/userguide/coredns-autoscaling.html
中描述的插件配置启用。
另一个用于提高 K8s 集群中 DNS 性能的工具是 cluster.local
后缀),本地缓存代理会将查询转发给 CoreDNS 服务。
CoreDNS 是 K8s 中的一个重要组件,通过充当集群的 DNS 服务器来实现内部服务发现和网络连接。为了优化大规模集群的性能,可以使用 CoreDNS 自动扩展与 cluster-proportional-autoscaler
,并利用 NodeLocal DNSCache 来减少延迟。在接下来的章节中,我们将介绍一些其他优化网络延迟和吞吐量的选项。
网络延迟和吞吐量增强
GenAI 工作负载,例如大规模机器学习模型的训练和推理,需要在多个计算节点之间进行大量通信。在分布式训练场景中,模型参数需要在节点之间进行同步,获取高带宽和低延迟对提高性能、降低训练/推理成本至关重要。
在本节中,我们将讨论两种云特定的技术来实现这一目标。
Amazon EC2 放置组
Amazon EC2 放置组 (docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html
) 提供了一种组织节点的方式,旨在实现特定的网络或弹性目标。在 AWS 中,支持以下三种放置组:
-
Cluster,将实例放置在同一个可用区内,提供低延迟网络性能,适用于高性能计算应用。
-
Partition,将实例分布在不同的逻辑分区中,使每个分区中的实例组与其他分区中的实例不共享底层硬件。这种策略通常用于大规模分布式和复制工作负载,例如 Hadoop、Cassandra 和 Kafka。
-
Spread,将小组实例分布在不同的底层硬件上,以最小化相关故障的风险,并提高弹性。
集群放置组将实例物理上靠近地放置在单一数据中心或可用区内,提供节点间低延迟和高吞吐量的网络连接。它可能会提升分布式 GenAI 应用程序的性能。
以下命令在 AWS 中创建一个名为 custom-placement-group
的放置组,并设置集群或邻近放置目标:
aws ec2 create-placement-group --group-name custom-placement-group --strategy cluster
现在,可以通过创建一个自动扩展组,并使用以下启动模板,在此放置组中启动 EKS 工作节点:
{
"LaunchTemplateData": {
"Placement": {
"GroupName": "custom-placement-group"
},
# Other Launch Details, such as instance types, key pair
}
}
简而言之,EC2 放置组提供了组织 EC2 实例的策略,以优化网络连接和吞吐量。在接下来的章节中,我们将介绍 Elastic Fabric Adapter (EFA),这是一种专用网络接口,提供超低延迟和高吞吐量的节点间通信。
EFA
EFA (aws.amazon.com/hpc/efa/
) 是 AWS 设计的一种网络接口,旨在为节点间通信提供超低延迟和高吞吐量的网络接口。这对于 GenAI 工作负载至关重要,因为它确保了节点之间的数据传输快速且高效。EFA 支持 远程直接内存访问 (RDMA),可以减少节点间数据传输的开销,提供低延迟、高吞吐量的传输路径。通过 RDMA,数据可以在两个计算节点的内存之间直接传输,而无需操作系统或 CPU 的参与。EFA 还支持 NVIDIA 集体通信库 (NCCL) (developer.nvidia.com/nccl
),为 AI 和 ML 应用提供高性能、可扩展的分布式训练,通过加速多个节点间 GPU 之间的通信。这一集成减少了延迟并提高了 GPU 之间的带宽,使得需要同步集体操作(如数据并行和模型并行)的 GenAI 应用能够更快地进行模型训练。
要在 EKS 集群中将 EFA 集成到 K8s Pods 中,可以创建支持 EFA 的实例类型的工作节点,并部署 aws-efa-k8s-device-plugin
(github.com/aws/eks-charts/tree/master/stable/aws-efa-k8s-device-plugin
),该插件可以检测并将 EFA 接口作为可分配资源向集群广播:
module "eks" {
source = "terraform-aws-modules/eks/aws"
cluster_name = local.name
...
# Allow EFA traffic
enable_efa_support = true
...
eks_managed_node_groups = {
nvidia-efa = {
# Expose all available EFA interfaces on the launch template
enable_efa_support = true
labels = {
"vpc.amazonaws.com/efa.present" = "true"
"nvidia.com/gpu.present" = "true"
}
...
请参考 EKS 文档 docs.aws.amazon.com/eks/latest/userguide/node-efa.html
,获取逐步操作指南和示例演练。
在本节中,我们讨论了各种网络优化技术,如选择 Kube-proxy 选项、扩展 CoreDNS Pods、解决 IP 耗尽问题的策略、K8s 网络领域的新兴趋势以及其他云提供商特定的优化措施,包括 EFA 和 EC2 部署组,用于在 K8s 集群中管理大规模 GenAI 工作负载。
总结
在本章中,我们重点讨论了优化云网络以便在 K8s 上部署 GenAI 应用,强调了高效、安全和高性能网络的最佳实践。我们从 K8s 网络基础知识开始,介绍了 容器网络接口 (CNI)、kubelet 和 容器运行时接口 (CRI) 等关键组件,这些组件负责管理 Pod 网络并确保集群间的连接性。
K8s 网络模型由 CNI 插件支持,例如 Calico、Cilium 和 Amazon VPC CNI,每种插件都有其特定的优势。CNI 插件有两种工作模式:覆盖网络和本地网络。覆盖网络,如 Flannel,增加了网络抽象的灵活性,但可能会增加延迟。另一方面,本地网络(例如 Amazon VPC CNI)与底层云基础设施集成,提供更低的延迟,推荐用于 GenAI 工作负载。
K8s 中的服务管理提供稳定的 IP 和 DNS 名称,确保即使 Pods 被添加或删除,服务也能保持可靠性。服务网格工具,如 Istio 或 Linkerd,通过拦截所有流量并通过边车代理实施负载均衡、重试机制和 TLS 加密策略,在提升流量管理、安全性和可观察性方面可能非常有效。
NetworkPolicy 是一个本地功能,通过控制 K8s 集群内的流入和流出流量,进一步加强安全性,允许在多租户环境中实现团队或应用之间的隔离。为了满足 GenAI 的特定需求,K8s 支持高级网络选项,如带有 IPVS 模式的 Kube-Proxy,提供可扩展的负载均衡,适用于高需求集群。此外,随着 K8s 集群的扩展,当使用 CNI 的本地网络模式时,IP 地址耗尽可能成为一个挑战;可以采用 IPv6 地址分配和自定义网络配置等解决方案,以缓解大规模部署中的 IP 限制问题。
其他现代技术支持高性能网络,例如扩展伯克利数据包过滤器(eBPF)和单根输入/输出虚拟化(SR-IOV),它们提供最小开销的内核级网络,非常适合低延迟、高吞吐量的数据处理。最后,K8s 网络受益于云环境中特定的增强功能,如 AWS 的放置组和弹性网络适配器(EFA)。在下一章中,我们将基于这些概念,讨论如何保护在 K8s 中运行的 GenAI 应用。
第八章:在 Kubernetes 上部署 GenAI 的安全最佳实践
在本章中,我们将探讨在Kubernetes(K8s)上部署 GenAI 应用的安全最佳实践。我们将从介绍“深度防御”概念开始,然后涵盖保护 GenAI 工作负载的关键支柱——供应链、安全主机、安全网络和运行时安全。此外,我们还将介绍管理机密信息的最佳实践以及最小权限原则,以防止未经授权访问关键数据和应用凭证。
在本章中,我们将覆盖以下主要内容:
-
深度防御
-
K8s 安全考虑事项
-
GenAI 应用的额外考虑事项
-
在聊天机器人应用中实施安全最佳实践
技术要求
在本章中,我们将使用以下内容,首先需要你设置一个账户并创建访问令牌:
-
Hugging Face:
huggingface.co/join
-
如第三章所示,亚马逊 EKS 集群的设置
深度防御
保护云中应用的最佳方式是使用深度防御概念(csrc.nist.gov/glossary/term/defense_in_depth
)。这是一种实施多层安全的策略,旨在保护免受不同攻击向量的威胁。图 9.1展示了深度防御的概念图,每个同心圆表示我们希望保护的某一层或攻击向量。
让我们先回顾一下安全最佳实践的概念,然后再针对容器和 K8s 的每一层深入探讨。
图 9.1 – 分层安全模型
以下是前述图中所示各层的详细信息:
-
用户数据:这是系统中最内层的核心,包含敏感的用户数据,如用户密码、个人身份信息(PII)等。为了保护这一层,应该使用数据静态和传输中的加密。
-
配置:应用的敏感配置数据包括环境变量、应用设置、机密信息以及应用运行所需的 API 密钥。配置设置上的疏忽可能导致数据泄漏、权限提升或应用行为被破坏。
-
应用代码:应用代码中的漏洞可能导致攻击,如 SQL 注入或远程代码执行。为防止此类问题,用户应定期进行静态和动态代码分析,及时修复漏洞,并更新依赖项到最新的安全版本。
-
依赖项:代码依赖项包括应用程序依赖的库、框架和外部包。脆弱或过时的依赖项是攻击者最常见的入口点之一。为了防止这种情况,用户应定期扫描依赖项以发现漏洞。
-
容器:容器运行时和镜像也可能创建另一个威胁向量,因为配置错误或不受信任的容器可能会提升权限、攻击主机或暴露敏感信息。为了防止这种情况,用户可以使用镜像签名来验证镜像的真实性,并使用如Falco之类的工具实施运行时安全,监控恶意行为。
-
主机:这是部署容器的底层服务器或节点。主机内核在所有容器之间共享,这使得它成为一个关键的安全层。如果主机被攻破,所有容器都可能受到影响。为了防止这种情况,建议通过禁用未使用的服务、应用补丁以及使用优化过的容器操作系统(如Bottlerocket)来加固主机操作系统。
在本节中,我们了解了深度防御的概念,这是一种通过实施多层安全策略来防范不同攻击向量威胁的策略。我们还从概念上看了各种攻击向量和每一层的最佳实践。在下一节中,我们将讨论在 K8s 中部署工作负载时的关键安全考虑事项。
K8s 安全考虑
现在我们已经从高层次了解了深度防御的概念,让我们讨论一下容器和 K8s 的安全最佳实践。在云环境中思考容器安全至关重要,因为容器在一个动态和共享的环境中运行。
容器将软件和依赖项封装在一起,使其高度便携;然而,这种便携性也带来了一些风险。全面的安全策略应当解决容器生命周期中的漏洞——从构建流水线到运行时环境。
关注的关键领域包括保护供应链、保护主机系统和监控运行时活动。每一层安全都增加了对潜在威胁的韧性,通过遵循最佳实践,可以确保容器的安全部署和运行。
以下是我们将深入探讨的容器安全的一些关键领域:
-
供应链安全
-
主机安全
-
运行时安全
-
网络安全
-
秘密管理
供应链安全
容器供应链包括从构建镜像到在生产环境中部署和监控镜像的所有阶段,如图 9.2所示。
图 9.2 – 容器供应链
现在,让我们讨论前五个阶段及其相应的安全最佳实践。
构建阶段
在构建阶段,恶意/易受攻击的代码可能通过未经验证的依赖项或不安全的配置进入容器镜像。为了保障构建阶段的安全,应该只使用来自验证源的可信基础镜像。为了最小化攻击面并提高安全性,考虑使用轻量级的父镜像。Alpine Linux (hub.docker.com/_/alpine
)、distroless (www.docker.com/blog/is-your-container-image-really-distroless/
) 或 scratch (hub.docker.com/_/scratch
) 镜像内置的包较少,限制了潜在的漏洞,因此通常被推荐使用。
DockerSlim (github.com/slimtoolkit/slim
) 是一个开源工具,帮助通过减少容器镜像的大小来优化镜像,从而通过识别运行时所需的部分来提高安全性。这显著减少了攻击面,并在不改变容器化应用功能的情况下提升了性能。
还建议通过使用声明式的 Dockerfile 和可重复构建来强制执行容器镜像的不可变性。latest
标签是可变的,这意味着随着新镜像的推送,它可能会指向不同的镜像,而像 v1.0
这样的标签是不可变的,因为它确保始终引用相同的镜像,无论未来如何推送。不可变性对于 GenAI 应用特别有利,可以确保训练/微调任务和推理镜像是在一致的环境下构建的,维护模型完整性,并简化故障排除工作。
多阶段构建 (docs.docker.com/build/building/multi-stage/
) 是一种在构建阶段使用的技术,它允许将构建过程分解为多个独立的阶段。每个阶段可以使用适当的父镜像专注于特定任务,比如编译代码或安装依赖项,然后选择性地将仅需要的工件复制到最终的最小镜像中。
对于 GenAI 应用,多阶段构建有助于确保大型训练框架、数据预处理脚本或模型优化步骤可以有选择地从最终的生产镜像中排除。这将生成更小、更安全且高效的镜像,能够快速部署进行推理。
测试阶段
镜像中的未检测到的漏洞和配置错误可能导致下游问题。为减轻这种风险,建议将自动化安全测试集成到 CI/CD 管道中,使用像Snyk(snyk.io/
)这样的工具。你应对代码进行静态分析,并对构建后的镜像进行动态测试,以发现漏洞。确保你的测试过程还涵盖与内部安全政策和相关行业标准的合规性。结合测试用例评估资源使用情况,验证最小权限原则的遵守情况,并监控运行时行为。通过这样做,你可以帮助在整个软件生命周期中保持安全、合规和可预测的操作。
存储阶段
在容器注册表的镜像存储阶段,镜像可能会被篡改或包含过时的依赖项。为了安全地保护镜像,建议使用像Amazon ECR(aws.amazon.com/ecr/
)这样的安全容器注册表。
你还应该使用像 Docker Content Trust 或 Cosign 这样的工具启用镜像签名和验证。AWS Signer(docs.aws.amazon.com/signer/latest/developerguide/Welcome.html
)是一个完全托管的服务,允许你对代码、应用程序和容器进行数字签名,以确保软件的完整性和真实性。
为确保在生产环境中仅部署签名的容器镜像,建议使用政策即代码解决方案,例如Open Policy Agent(OPA)或其 K8s 原生实现OPA Gatekeeper(github.com/open-policy-agent/gatekeeper
)。OPA Gatekeeper 是一个开源项目,它利用 OPA 在 K8s 集群中执行策略。它使管理员能够通过根据预定义约束验证和修改 K8s 资源来实施精细的治理。Gatekeeper 使用自定义资源定义(CRDs)来定义这些策略和约束,从而使用户能够根据特定需求定制治理。它作为一个准入控制器,确保在创建或更新时,任何不符合定义规则的资源都将被拒绝。此外,它支持审计功能,使用户能够识别和修复现有资源中的政策违规行为。还建议使用基于角色的访问控制(RBAC)来限制对镜像的访问,用户应定期清理未使用或弃用的镜像,以减少潜在的攻击面。
加密阶段
镜像中的敏感数据应始终被拦截。应该避免将秘密(如 API 密钥或密码)直接嵌入镜像中,以防止在容器镜像可能被攻破时泄露。
你可以使用像 HashiCorp Vault (www.hashicorp.com/products/vault
)、Kubernetes Secrets (kubernetes.io/docs/concepts/configuration/secret/
) 或 AWS Secrets Manager (docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html
) 这样的机密管理工具来存储机密。例如,AWS Secrets Manager 是一个安全的机密管理服务,用于管理数据库凭证、API 密钥和其他敏感配置数据。通过 AWS Key Management Service (AWS KMS) 加密机密,并通过 AWS Identity and Access Management (AWS IAM) 启用细粒度的访问控制,Secrets Manager 确保敏感数据受到保护,仅授权用户和应用程序可以访问。它还支持版本控制和机密的自动轮换,减少了凭证泄露的风险,并最小化了手动操作的开销。
要将外部机密管理解决方案与 K8s 集成,可以使用像 secrets-store-csi-driver (github.com/kubernetes-sigs/secrets-store-csi-driver
) 这样的项目,该项目允许 K8s 将存储在外部机密存储中的多个机密、密钥和证书挂载到 K8s Pods 中作为卷。一旦卷被挂载,数据就会被载入容器的文件系统。请参阅 AWS 文档 docs.aws.amazon.com/secretsmanager/latest/userguide/integrating_csi_driver.html
,了解如何在 Amazon EKS 集群中使用 AWS Secrets Manager 的示例教程。在 GenAI 应用程序的情况下,Hugging Face 和 OpenAI 的外部 API 密钥可以安全地存储在这些解决方案中,应用程序可以在运行时安全地访问它们。
扫描阶段
镜像或依赖项中的漏洞可能会被恶意行为者利用。为了防止这种情况,应在构建和存储阶段持续扫描镜像。大多数容器注册表解决方案提供开箱即用的此功能,或者可以使用像 Trivy (github.com/aquasecurity/trivy
) 这样的开源工具来扫描容器镜像中的漏洞。这些工具通过分析镜像层与已知漏洞数据库(例如 CVE 数据库)进行对比,从而识别潜在的风险,提供早期问题检测。
在云环境中,还提供了更先进的镜像扫描选项。例如,Amazon ECR 支持通过Amazon Inspector(aws.amazon.com/inspector/
),这是一项旨在自动化安全评估的托管服务,进行高级漏洞扫描。此集成提供了增强的、持续的容器镜像扫描,确保它们在整个生命周期中保持安全。Amazon Inspector 可以自动扫描 ECR 中的容器镜像,无需手动触发,每当新镜像被推送或更新时,都会进行扫描。有关这些选项以及如何在容器库中启用它们的详细信息,请参考 Amazon ECR 文档:docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning.html
。
主机安全
为容器的主机环境提供安全保障至关重要,因为多个容器可能共享主机操作系统和内核。以下是确保容器主机安全的一些最佳实践和策略:
-
部署在私有子网中:将主机/工作节点部署在私有子网中,可以通过限制来自互联网的直接访问,最小化暴露于外部威胁的风险。为了将应用程序暴露给公共互联网,可以使用 K8s 负载均衡器或 Ingress 资源类型,将负载均衡器资源部署在公共子网中。
-
禁用 SSH 访问:SSH 访问应限制到最小化攻击面。相反,应该使用会话管理工具,例如AWS Systems Manager(SSM)(
aws.amazon.com/systems-manager/
),以便在不暴露 SSH 的情况下实现对主机的安全、可审计访问。如果必须使用 SSH,强制实施基于密钥的身份验证,并使用安全组限制访问。 -
1
并强制使用 IMDSv2,如以下 Terraform 代码所示:resource "aws_launch_template" "example" { name = "example" ... metadata_options { http_endpoint = "enabled" http_tokens = "required" http_put_response_hop_limit = 1 instance_metadata_tags = "enabled" } ...
-
加密所有存储:加密存储可以保护主机上的敏感数据,以防止物理或逻辑攻击。使用 AWS KMS 加密附加到主机的Amazon Elastic Block Store(Amazon EBS)卷。如果使用带有NVMe实例存储的 EC2 实例,则所有数据都将使用 XTS-AES-256 块加密算法进行加密。
-
加固节点 Amazon 机器映像 (AMI):加固的 AMI 通过仅包含必要的软件、配置和安全设置来减少漏洞。使用容器优化的操作系统,如 Bottlerocket (
aws.amazon.com/bottlerocket/
),它是从零开始构建,专为容器设计。它们通常包含只读根文件系统、原子更新和最小化的工具集,这些工具集旨在高效且安全地运行容器。你应该禁用并卸载未使用的软件,以减少攻击面,并根据 CIS 基准配置操作系统,适用于 Docker、Kubernetes 和 Linux。使用 AppArmor (apparmor.net/
)、SELinux (www.redhat.com/en/topics/linux/what-is-selinux
) 或 seccomp (kubernetes.io/docs/tutorials/security/seccomp/
) 来限制容器权限。
容器运行时安全
在运行时保护容器对防止权限升级和未经授权的访问至关重要。这包括限制资源、限制权限、执行安全标准以及集成监控和策略工具。以下是核心的运行时安全实践:
-
仅提供正常运行所需的资源:无限制的资源分配可能导致 拒绝服务(DoS)攻击和集群不稳定,某些容器可能会消耗整个节点的资源。为了防止这种情况,你应该在 K8s 清单中定义 CPU 和内存限制,反映容器可能需要的最佳资源。
-
securityContext
,如下所示:securityContext: runAsUser: 1000 runAsGroup: 1000 securityContext, as shown here:
securityContext:
capabilities:
drop:
- ALL
...
-
遵循 Pod 安全标准:遵守 Pod 安全标准确保容器符合最佳的安全配置实践。使用 K8s 内置的 Pod 安全准入来强制执行如限制性配置文件等标准。同时,使用不同安全级别(例如,限制性或基准)的命名空间隔离工作负载。Pod 安全标准定义了三种不同的策略级别(特权、基准和限制),广泛覆盖安全范围。你可以在命名空间上应用标签和注释,以指定所需的安全配置级别,如下所示:
$ kubectl label namespace test-ns \ /etc/passwd or /etc/shadow, and network connections to untrusted IP addresses. It can also detect when a Pod tries to access a K8s Service account token improperly, which may indicate a compromise or misconfiguration.When such behavior violates its defined security rules, Falco triggers alerts instantly. For example, alerts may look like this: **Terminal shell detected in nginx container** or **Suspicious file access by unknown process**. To make these alerts actionable, teams should implement clear response strategies, such as isolating the Pod, initiating incident response procedures, or alerting security teams. Combining these technical capabilities with practical workflows allows teams to confidently enforce runtime security and respond to threats in production-grade K8s environments.Some other third-party tools also offer advanced runtime protection, integration, and reporting capabilities. Use agents provided by solutions such as **Prisma Cloud’s Container Security** ([`www.paloaltonetworks.com/prisma/cloud/container-security`](https://www.paloaltonetworks.com/prisma/cloud/container-security)), **Aqua Security** ([`www.aquasec.com/products/kubernetes-security/`](https://www.aquasec.com/products/kubernetes-security/)), or **Wiz container security** ([`www.wiz.io/solutions/container-and-kubernetes-security`](https://www.wiz.io/solutions/container-and-kubernetes-security)) to monitor and protect containers.
网络安全
K8s 中的网络安全对于保护容器化环境中的数据和应用程序至关重要。K8s 的分布式特性为安全网络流量带来了独特的挑战。以下是一些确保 K8s 网络安全的关键考虑因素和最佳实践:
-
网络分段与隔离:K8s 命名空间 提供了一种在集群内隔离资源和工作负载的方法,充当独立的安全边界。可以使用 NetworkPolicy 资源来强制执行网络分段,如在 第八章 中讨论的那样,它定义了控制 Pod 层级的入口(传入)和出口(传出)流量的规则。
-
保护 Pod 到 Pod 的通信:默认情况下,K8s 允许 Pods 之间的通信,这可能会使 K8s 集群暴露于安全风险之下。实施零信任原则确保 Pods 仅与明确授权的对等体进行通信。像 Istio 或 Linkerd 这样的服务网格技术提供 mTLS 进行加密和身份验证,确保 Pod 到 Pod 之间的通信安全且已验证。
-
入口和出口安全:入口安全侧重于控制和保护进入集群的流量,通常使用带有 TLS 证书的 HTTPS 进行加密。额外的保护措施,如 Web 应用防火墙(WAFs),有助于检测和阻止恶意流量。出口安全涉及限制 Pods 的外向流量,以防止未经授权访问外部资源,可以通过出口策略来强制执行。
-
API 服务器保护:K8s API 服务器是一个关键的管理点,需要采取强有力的安全措施。RBAC 根据角色限制用户权限,而像 OpenID Connect(OIDC)或 AWS IAM 等身份验证机制确保只有授权用户才能访问该服务器。网络策略或防火墙应进一步限制只有来自受信源的访问才能进入 API 服务器。
-
DNS 安全:CoreDNS 是 K8s 中的默认 DNS 服务器,应该防范欺骗和相关攻击。虽然启用加密 DNS 协议如 DNS-over-TLS 或 DNS-over-HTTPS (
www.cloudflare.com/learning/dns/dns-over-tls/
) 在技术上是可行的,但 K8s 默认并不常实现这一功能。相反,使用网络策略来限制哪些 Pods 可以查询 CoreDNS 服务器,从而减少未经授权请求的风险。此外,确保 CoreDNS 指向可信的上游解析器,并采用 DNSSEC (www.internetsociety.org/deploy360/dnssec/basics/
) 来确保数据完整性。 -
网络监控:监控网络流量对于检测和缓解威胁至关重要。像 Cilium、Calico、Sysdig、服务网格或 Datadog 这样的工具可以提供集群流量的可见性,使集群管理员能够识别可疑行为。
-
保护外部连接:与外部服务(如云服务提供商服务或数据库)的连接应当加密并进行身份验证。K8s Secrets 提供了一种安全的方式来管理外部连接的凭据,确保敏感信息(如 API 密钥和密码)不被未经授权的访问。
Secrets 管理
K8s 中的 Secrets 管理是保护和管理敏感信息(如 API 密钥、密码、证书和令牌)的关键组成部分。K8s 提供了内置机制来管理 Secrets,但要确保其安全,需要仔细配置并遵循以下最佳实践,以降低未经授权访问或意外泄露的风险:
-
K8s Secrets 资源:K8s 提供了一种名为 Secrets 的原生资源,用于以 Base64 编码格式存储敏感数据。Secrets 会作为环境变量或文件挂载到 Pods 中,使得应用程序能够访问它们,而无需直接在容器镜像或配置文件中嵌入敏感数据。尽管方便,但 Base64 编码并非加密,因此需要采取额外的安全措施。
-
加密静态 Secrets:默认情况下,K8s 将 Secrets 存储在 etcd 中,这是集群的键值存储。为了保护这些敏感信息,必须启用 etcd 的静态加密。当使用如 Amazon EKS 这样的托管 K8s 服务时,etcd 卷默认会使用 AWS 托管的加密密钥进行加密。作为额外的安全措施,您还可以使用如封装加密的技术,在写入 etcd 数据库之前加密 Secret 值。在 Amazon EKS 中,您可以利用 AWS KMS 启用封装加密;请参考 AWS 文档
docs.aws.amazon.com/eks/latest/userguide/envelope-encryption.html
了解更多信息。 -
RBAC 访问控制:RBAC 是保护 K8s Secrets 的基础部分。RBAC 策略应当执行最小权限原则,仅授予用户、Pods 和 Services 访问其所需的 Secrets。正确配置的角色和角色绑定有助于防止未经授权的用户或应用程序访问敏感信息。
-
secrets-store-csi-driver
插件或其他操作符,支持无缝访问外部管理的机密数据。 -
自动化 Secret 轮换:定期轮换 Secrets 可降低长期泄露的风险。K8s 的 Secrets 本身不原生支持自动轮换,但外部的秘密管理工具通常包含此功能。将这些工具集成到 K8s 工作流中,可以在不干扰应用程序的情况下定期更新 secrets,确保遵循安全最佳实践。
-
保护传输中的机密:敏感数据,包括机密信息,在客户端、节点和 etcd 存储之间传输时必须得到保护。在与内部和外部系统交换敏感凭据时,请启用 TLS 加密。
-
审计和监控机密访问:审计日志对于监控机密访问和检测潜在的滥用行为至关重要。K8s 可以生成 API 操作的审计日志,包括涉及机密的操作。将这些日志与集中监控工具或安全信息和事件管理(SIEM)解决方案集成,帮助管理员检测可疑活动并及时响应。机密管理工具也提供审计日志,这些日志可以集成到 SIEM 解决方案中。
在本节中,我们探讨了在 K8s 中运行容器化工作负载时的各种安全考虑因素,包括供应链安全、主机安全、网络安全,以及使用 K8s 机密保护敏感信息的重要性。在下一节中,让我们探讨一些针对 GenAI 应用程序的额外安全考虑因素。
GenAI 应用程序的额外考虑事项
在 K8s 上部署 GenAI 应用程序通常涉及专有模型工件、大量敏感的训练数据,以及可能来自不受信任源的复杂推理请求。除了 K8s 安全的标准做法外,还必须考虑与模型完整性、数据隐私、软件供应链漏洞以及高价值硬件资源(如 GPU)隔离相关的新攻击向量。让我们现在来探索一些这些考虑因素:
数据隐私和合规性
GenAI 模型通常依赖存储在数据湖、数据中心和数据仓库中的专有和敏感数据集进行训练和微调。在我们的演示中(第五章),我们使用Amazon S3存储桶存储了微调过程中使用的数据集。根据数据分类和法规要求,必须实施适当的安全控制措施,例如静态数据加密、严格的访问控制政策和审计。当从在 K8s 上运行的 GenAI 应用程序访问这些数据存储库时,确保所有通信都通过 TLS 加密,并通过利用 K8s 和相关机制遵循最小权限原则。在 Amazon EKS 上运行 GenAI 应用程序时,您可以利用服务账户的 IAM 角色(IRSA)(docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
) 或EKS Pod 身份(docs.aws.amazon.com/eks/latest/userguide/pod-identities.html
)特性来获取临时的 IAM 凭证并访问来自其他服务的数据,如 Amazon S3。
服务账户的 IAM 角色
IRSA 于 2019 年推出,用于将细粒度的 IAM 角色与 K8s 服务账户关联。运行在 K8s Pod 中的应用程序可以使用它们的服务账户身份假设该角色,并根据角色权限访问其他 AWS 服务。
以下是该过程中的高层次步骤;有关详细指导,请参考 Amazon EKS 文档:docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
:
-
在 AWS IAM 中为 Amazon EKS 集群设置 OIDC 提供程序。
-
创建细粒度的 IAM 策略,为 AWS 服务授予必要的权限。有关创建 IAM 策略的指导,请参考
docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html#grant-least-privilege
中的安全最佳实践文档。 -
创建一个带有步骤 2权限策略的 IAM 角色,并为 K8s 服务账户创建信任策略以假设角色:
... "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::<account_id>:oidc-provider/<oidc_provider>" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "<oidc_provider>:aud": "sts.amazonaws.com", "<oidc_provider>:sub": "system:serviceaccount:<namespace>:<service_account>" ...
-
创建一个 K8s 服务账户,并使用步骤 3中的 IAM 角色进行注释:
apiVersion: v1 kind: ServiceAccount metadata: name: example-sa namespace: example-ns annotations: eks.amazonaws.com/role-arn: arn:aws:iam::<account_id>:role/<iam_role_name>
-
使用注释的服务账户部署 K8s 应用程序:
apiVersion: apps/v1 kind: Deployment metadata: name: example-app ... spec: serviceAccount: example-sa containers: - name: example-container ...
-
当应用程序使用 AWS SDK 访问其他 AWS 资源时,SDK 将调用
AssumeRoleWithWebIdentity
,这是一个 AWS 安全令牌服务(STS)API 操作,允许应用程序使用受信的 Web 身份令牌,而不是长期访问密钥或 IAM 用户,来获取临时的 IAM 凭证。图 9.3总结了这些步骤:
图 9.3 – IRSA 概览
这展示了如何通过 IRSA 和AssumeRoleWithWebIdentity
(docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html
)机制,安全地由 Amazon EKS 集群中的 Pod 假设 IAM 角色。当与注释服务账户关联的 Pod 被创建时,运行在 EKS 控制平面中的 Pod 身份 webhook 会修改 Pod 规格,以挂载投影的服务账户令牌。然后,运行在 Pod 中的 AWS SDK 使用该令牌调用 AWS STS,使用AssumeRoleWithWebIdentity
API。STS 通过 OIDC 提供程序验证令牌,检查 IAM 角色的信任策略,并返回临时 AWS 凭证,Pod 使用这些凭证安全地访问其他 AWS 资源。
接下来,我们将介绍最近推出的EKS Pod 身份功能,它进一步简化了 IAM 权限过程。
Amazon EKS Pod 身份
在 2023 年,AWS 引入了 EKS Pod Identity 功能,作为 IRSA 的演变,旨在简化为 K8s 应用设置 IAM 权限的体验。它去除了许多 IRSA 的底层复杂性,如在 IAM 中设置 OIDC 提供者、复杂的信任策略、标注服务账户等。EKS Pod Identity 引入了新的 API 来创建 K8s 服务账户与 IAM 角色之间的 Pod Identity 关联,从而无需 OIDC 提供者、服务账户注释等。涉及的高层步骤如下:
-
在 EKS 集群上安装
eks-pod-identity-agent deamonset
。 -
创建细粒度的 IAM 策略,为 AWS 服务授予必要的权限。
-
创建一个 IAM 角色,授予来自步骤 2的权限策略,并为 EKS Pod Identity 主体创建一个信任策略,以便它可以假设该角色:
... "Effect": "Allow", "Principal": { "Service": "pods.eks.amazonaws.com" }, "Action": [ "sts:AssumeRole", "sts:TagSession" ...
-
在 IAM 角色和 K8s 服务账户之间创建 Pod Identity 关联:
aws eks create-pod-identity-association --cluster-name my-cluster --role-arn arn:aws:iam::<account_id>:role/my-role --namespace <namespace> --service-account <service_account>
-
使用服务账户部署 K8s 应用:
apiVersion: apps/v1 kind: Deployment metadata: name: example-app ... spec: serviceAccount: example-sa containers: - name: example-container ...
EKS Pod Identity 还支持为分配给每个 Pod 的临时凭证引入会话标签,包括集群名称、命名空间、Pod UID、服务账户名称等属性。这些标签使得管理员能够配置一个可跨多个服务账户使用的 IAM 角色,根据匹配的标签授予或限制对 AWS 资源的访问。通过引入角色会话标签,组织可以在集群和工作负载之间创建更精细的安全边界,同时继续使用相同的 IAM 角色和策略。
保护模型端点
在我们讨论如何保护 GenAI 模型端点之前,我们应该先保护好微调后的模型工件、模型权重等。微调过程完成后,您可以通过适当的访问控制将这些模型工件安全地存储在对象存储服务(如 Amazon S3)中,或者将它们与容器镜像一起打包并推送到容器注册表。您可以使用之前讨论的方式(IRSA 或 EKS Pod Identity)安全地获取凭证,以访问相应的 AWS 服务。
构建好模型后,443
并在 K8s 负载均衡服务上部署 ACM 的 TLS 证书:
apiVersion: v1
kind: Service
metadata:
name: example-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
# Reference the ACM certificate ARN for TLS termination at the load balancer
service.beta.kubernetes.io/aws-load-balancer-ssl-cert: "arn:aws:acm:us-east-1:<account_id>:certificate/<certificate_id>"
# The port(s) that should use SSL/TLS
service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "443"
spec:
type: LoadBalancer
...
同样,您可以将注释应用于由 AWS ALB 支持的 K8s Ingress 资源,创建一个受 ACM TLS 证书保护的 HTTPS 监听器,如下所示的代码片段所示:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
# Define which ports the ALB should listen on; here we set HTTPS on port 443
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
# The ARN of the ACM certificate for TLS termination
alb.ingress.kubernetes.io/certificate-arn: "arn:aws:acm:us-east-1:<account_id>:certificate/<certificate_id>"
...
此外,您可以将 AWS WAF(aws.amazon.com/waf/
)附加到 AWS ALB(K8s Ingress 资源)上,以保护模型端点免受常见攻击向量的侵害,包括注入攻击、恶意内容输入等。您可以使用以下注释将 AWS WAF 与 AWS ALB 关联。
alb.ingress.kubernetes.io/wafv2-acl-arn: arn:aws:wafv2:us-west-2:xxxxx:regional/webacl/xxxxxxx/yyyyyyyyy
在本节中,我们探讨了在 K8s 中部署 GenAI 工作负载的最佳实践。我们研究了 IRSA 和 EKS Pod 身份功能,以安全地为 K8s 应用程序提供临时 IAM 凭证。对于 GenAI 应用程序来说,这一点尤为重要,因为它确保了对存储在 Amazon S3 中的敏感训练数据或模型工件的安全、最小权限访问。我们还探讨了保护模型端点的技术,包括强制 TLS 加密通信并集成 WAF 以防范常见攻击向量。在下一节中,我们将在我们的聊天机器人应用程序中实现这些安全最佳实践。
在聊天机器人应用中实施安全最佳实践
在本节中,我们将会在我们的电子商务聊天机器人应用程序中实现前面讨论的安全最佳实践。我们已经在前几章实现了其中一些,例如 第五章 中的 IRSA 和 第八章 中的 K8s 网络策略。让我们开始吧:
-
容器镜像加密:Amazon ECR 将容器镜像存储在 Amazon S3 桶中,由 ECR 管理,默认情况下,使用 S3 的服务器端加密功能对数据进行静态加密。它使用 高级加密标准(AES)加密算法,使用由 S3 服务管理的加密密钥。
我们可以使用以下 AWS 命令在我们创建的 ECR 仓库中验证此操作:
$ aws ecr describe-repositories --repository-names my-llama-finetuned --query 'repositories[0].encryptionConfiguration.encryptionType' --output text ecr-kms-key and configures the sample-app-repoECR repository to use it for encryption:
resource "aws_kms_key" "ecr_kms_key" {
deletion_window_in_days = 7
enable_key_rotation = true
}
resource "aws_kms_alias" "ecr_kms_alias" {
name = "alias/ecr-kms-key"
target_key_id = aws_kms_key.ecr_kms_key.key_id
}
resource "aws_ecr_repository" "sample-app-repo" {
name = "sample-app-repo"
encryption_configuration {
encryption_type = "KMS"
kms_key = aws_kms_key.ecr_kms_key.arn
}
在 ecr.tf 文件中将 image_tag_mutability 属性设置为 IMMUTABLE,如下所示代码片段:
resource "aws_ecr_repository" "my-llama-finetuned" { name = "my-llama-finetuned" image_tag_mutability = "IMMUTABLE" }
-
ecr.tf
文件,它在我们 AWS 账户中的my-llama-finetuned
ECR 仓库上启用了增强扫描功能。或者,您也可以从 GitHub 仓库下载源代码:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch9/ecr.tf
此配置确保镜像在推送时以及随后持续扫描,以检测任何新漏洞:
resource "aws_ecr_registry_scanning_configuration" "ecr_scanning_configuration" { scan_type = "ENHANCED" rule { scan_frequency = "CONTINUOUS_SCAN" repository_filter { filter = "my-llama-finetuned" filter_type = "WILDCARD" } } rule { scan_frequency = "SCAN_ON_PUSH" repository_filter { filter = "my-llama-finetuned" filter_type = "WILDCARD" } } }
运行以下 Terraform 命令以在您的 AWS 账户中应用此配置:
$ terraform plan my-llama-finetuned repository and display the vulnerabilities in the AWS console.
-
主机安全:通过使用专门设计的操作系统(如 Bottlerocket)来增强工作节点的安全性。这只包括运行容器所需的必要软件,并确保底层软件始终保持安全。根据客户案例研究,它还将节点启动时间从约 1.5 分钟缩短到 20 秒(链接:
aws.amazon.com/bottlerocket/
)。此外,配置工作节点以使用最新版本的 IMDSv2(链接:docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
),限制对敏感元数据的访问。以下代码片段展示了如何使用 Bottlerocket EKS AMI 和 IMDSv2;完整的源代码可以在 GitHub 仓库中找到,链接:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch9/eks.tf
:module "eks" { source = "terraform-aws-modules/eks/aws" ... eks_managed_node_groups = { eks-mng = { ami_type = "BOTTLEROCKET_x86_64" metadata_options { http_endpoint = "enabled" http_tokens = "required" http_put_response_hop_limit = 1 } ... eks-gpu-mng = { ami_type = "BOTTLEROCKET_x86_64_NVIDIA" ...
-
K8s Pod 安全标准:实施 K8s Pod 安全标准对确保你的工作负载以最小权限运行至关重要,从而降低特权提升和容器逃逸的风险。由于我们的聊天机器人应用部署在默认命名空间中,我们可以使用以下命令来强制执行基础安全策略,同时针对更严格的设置接收警告:
$ kubectl label namespace default pod-security.kubernetes.io/enforce=baseline pod-security.kubernetes.io/warn=restricted pod-security.kubernetes.io/audit=restricted
使用以下命令通过尝试运行具有特权设置的 pod 来测试强制执行的策略:
$ kubectl run pss-demo --image=nginx --privileged my-llama-finetuned inference endpoint. In this walkthrough, we’ll enhance security by storing the Hugging Face token in AWS Secrets Manager. We’ll then use secrets-store-csi-driver to dynamically retrieve and inject the secret into the K8s Pod during creation. Let’s get started:1. Store the Hugging Face access token in AWS Secrets Manager and run the following command to create a secret in your AWS account. Replace the value with your access token created in the Hugging Face portal: ``` $ export HUGGING_FACE_TOKEN=<<你的令牌放这里>> 在 EKS 集群中使用 secrets-store-csi-driver 和 secrets-store-csi-provider-aws 插件,通过 secrets-store-csi-provider-aws 集成 AWS Secrets Manager 和 AWS Systems Manager Parameter Store 与 Kubernetes 集成,从而将外部秘密存储与 K8s 集成。要在 EKS 集群中设置这些插件,请从 GitHub 仓库下载 addons.tf 文件,链接:[`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch9/addons.tf`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch9/addons.tf),然后运行以下命令: ``` $ terraform init $ terraform plan kube-system namespace using the following command; you will notice secrets-store-csi-driver and secrets-store-csi-driver-provider-aws in the output: ``` $ kubectl get ds -n kube-system NAME secrets-store-csi-driver my-llama-finetuned K8s Pod 可以使用 EKS Pod 身份特性接收临时 IAM 凭证。要设置这些资源,请从 GitHub 仓库下载 iam.tf 文件,链接:[`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch9/iam.tf`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch9/iam.tf),然后运行以下命令: ``` $ terraform init $ terraform plan secrets-store-csi-driver, we need to configure SecretProviderClass, a custom Kubernetes resource that defines how secrets-store-csi-driver should connect to and retrieve secrets from external providers such as AWS Secrets Manager. Let’s create one for our setup to retrieve hugging-face-secret from AWS Secrets Manager. To create this, download the secret-provider-class.yaml file from the GitHub repository at https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch9/inference/secret-provider-class.yaml and run the following command: ``` my-llama-finetuned 应用程序使用 my-llama-sa 服务账户。这种方法可以安全地将 AWS Secrets Manager 的秘密注入到应用程序容器中,而不会在 Kubernetes 清单中暴露。要重新部署该应用程序,请从 GitHub 仓库下载 finetuned-inf-deploy.yaml 文件,链接为 https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch9/inference/finetuned-inf-deploy.yaml。然后,使用您自己的 ECR 镜像名称更新该文件,并执行以下命令: ``` env command inside the Pod using the following commands: ``` ``` $ kubectl get pods -l app.kubernetes.io/name=my-llama-finetuned $ kubectl exec -it $(kubectl get pods -l app.kubernetes.io/name=my-llama-finetuned -o jsonpath="{.items[0].metadata.name}") -- env | grep HUGGING_FACE ``` ``` ``` ``` ``` ```
在本节中,我们在运行于 EKS 的聊天机器人应用程序中实施了多项安全最佳实践。我们确保了 ECR 镜像不可变性,以防止镜像标签被覆盖,使用 Bottlerocket AMI 强化主机安全,启用了 IMDSv2,并应用了 Pod 安全标准来强制执行最小权限执行。此外,我们还实施了静态数据加密,将漏洞扫描集成到 Amazon ECR 中的容器镜像,并通过加密的秘密和 AWS Secrets Manager 等外部解决方案增强了 Kubernetes 的秘密管理,后者通过 secrets-store-csi-driver
实现。
总结
本章中,我们讨论了在 K8s 上部署应用程序的安全最佳实践,重点介绍了深度防御和容器生态系统的关键安全方面,包括供应链、安全主机、网络、运行时和密钥管理。
深度防御是一种安全理念,涉及多层次的安全措施,以防范不同的攻击向量。
容器由于其可移植性,带来了独特的风险,需要在每个生命周期阶段采取主动措施,从构建到运行时。供应链安全强调在构建、测试和存储阶段保护容器镜像。主机安全专注于保护托管容器的底层服务器。运行时安全通过限制权限、强制执行资源限制,并使用如 Falco 这样的工具进行异常检测,确保容器的安全操作。网络安全利用命名空间、网络策略以及像 Istio 这样的服务网格进行隔离和加密。密钥管理对于安全处理敏感信息至关重要。IRSA 和 EKS Pod 身份功能可以安全地为 K8s 应用程序提供临时 IAM 凭证。GenAI 工作负载可以利用这些功能安全地访问敏感的训练数据或将模型工件导出到 Amazon S3。GenAI 模型端点可以通过 WAF 解决方案和强制执行 TLS 来防止常见攻击向量,并确保加密通信。通过集成这些最佳实践,K8s 部署可以在应对不断发展的威胁时实现强大的安全性。
本章集中讨论了 K8s 安全最佳实践;在下一章中,我们将开始深入探讨 K8s 的 GPU 资源优化,这是 GenAI 应用程序中最昂贵的资源之一。
第九章:在 Kubernetes 中优化 GPU 资源以适应 GenAI 应用
本章将讨论在 K8s 中部署 GenAI 应用时最大化图形处理单元(GPU)(aws.amazon.com/what-is/gpu/
)效率的策略,因为 GPU 实例非常昂贵,且经常被低效使用。我们还将讨论 GPU 资源管理、调度最佳实践,以及分区选项,如多实例 GPU(MIG)(www.nvidia.com/en-us/technologies/multi-instance-gpu/
)、多进程服务(MPS)(docs.nvidia.com/deploy/mps/index.html
)和GPU 时间切片(docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html
)。最后,我们将讨论如何监控 GPU 性能、跨节点平衡工作负载以及自动扩展 GPU 资源以有效应对动态 GenAI 工作负载。
本章将覆盖以下主要内容:
-
GPU 和自定义加速器
-
在 K8s 中分配 GPU 资源
-
理解 GPU 利用率
-
分割和共享 GPU 的技术
-
扩展性和优化考虑
技术要求
在本章中,我们将使用以下工具,其中一些工具需要你设置账户并创建访问令牌:
-
Hugging Face:
huggingface.co/join
。 -
Llama-3.2-1B模型,可以通过 Hugging Face 访问:
huggingface.co/meta-llama/Llama-3.2-1B
。 -
如 第三章 所示的Amazon EKS 集群。
-
AWS 服务配额用于运行家庭 EC2 实例。你可以在AWS 控制台中申请配额增加(
docs.aws.amazon.com/servicequotas/latest/userguide/request-quota-increase.html
)。
GPU 和自定义加速器
在 K8s 中部署 GenAI 工作负载需要根据工作负载的计算需求(如训练、推理或微服务实现)选择合适的硬件。计算选项包括 CPU、GPU,或者自定义加速器,如Inferentia(aws.amazon.com/ai/machine-learning/inferentia/
)和Trainium(aws.amazon.com/ai/machine-learning/trainium/
),以及来自 AWS 的加速器或张量处理单元(TPU)(cloud.google.com/tpu
)来自 Google Cloud。
CPU 通常是 K8s 中的默认计算资源,适合轻量级的 GenAI 任务,包括小规模推理、数据预处理、暴露 API 和实现经典的机器学习算法,如XGBoost(xgboost.readthedocs.io/en/stable/
)决策树。然而,它们在需要高并行度和大量矩阵乘法的任务(例如训练基础模型)上效率较低。K8s 允许您定义 CPU 请求和限制,确保所有工作负载之间的资源分配公平且高效。
GPU 擅长于大规模并行处理(MPP),因为它们拥有成千上万的核心和非常高的内存带宽,这使得它们比 CPU 更有效地处理深度学习中核心的矩阵乘法和线性代数计算。然而,GPU 并未在 K8s 中被原生识别。得益于其可扩展的架构,设备厂商可以开发设备插件(kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/
),将 GPU 资源暴露给 K8s 控制平面。这些插件通常作为守护进程集(DaemonSets)(kubernetes.io/docs/concepts/workloads/controllers/daemonset/
)在集群中部署,使得 K8s 调度器能够正确地识别、分配和管理容器化工作负载的 GPU。最受欢迎的设备插件之一是NVIDIA Kubernetes 设备插件(github.com/NVIDIA/k8s-device-plugin
),它将每个 K8s 工作节点上附加的 NVIDIA GPU 暴露出来,并持续监控其健康状态。
作为 GPU 的替代方案,许多公司正在投资创建专用加速器,专为 AI/ML 工作负载量身定制。例子包括AWS Inferentia 和 Trainium,Google TPUs,现场可编程门阵列(FPGAs)(www.arm.com/glossary/fpga
),以及各种特定应用集成电路(ASICs)(www.arm.com/glossary/asic
),它们都设计用于擅长核心操作,如矩阵乘法、利用基于 Transformer 的模型,提供更高的性能,并确保更低的能耗。与 GPU 类似,这些加速器通过硬件厂商提供的自定义设备插件与 K8s 集成。这些插件可以发现、分配并监控附加到 K8s 工作节点上的专用硬件资源,从而与其他计算资源一起实现无缝调度和管理。
自定义加速器特别适用于大规模训练或低延迟推理。例如,AWS Trainium(aws.amazon.com/ai/machine-learning/trainium/
)是一系列由 AWS 开发的 AI 芯片,通过提供高性能并降低成本来增强 GenAI 训练。第一代 Trainium 芯片驱动了Amazon EC2 Trn1 实例(aws.amazon.com/ec2/instance-types/trn1/
),与类似的 EC2 实例相比,提供了最多 50% 的训练成本节省。Trainium2 芯片,出现在Amazon EC2 Trn2 实例和Trn2 UltraServers(aws.amazon.com/ec2/instance-types/trn2/
)中,是训练和推理 GenAI 模型(拥有数百亿到万亿参数)时最强大的 EC2 实例。它们提供了比前代产品高达四倍的性能,并且比 EC2 P5e 和 P5en 系列实例提供了 30% 至 40% 更好的价格性能比。
自定义加速器通常依赖于与通用 CPU 或 GPU 不同的专用硬件架构和指令集。因此,像TensorFlow和PyTorch这样的 AI/ML 框架无法将高级操作直接转换为加速器可以理解的低级指令。Neuron SDK是 AWS 设计的软件开发工具包,用于在 AWS 的定制 AI 加速器上高效运行和优化 AI/ML 工作负载,如 AWS Trainium 和 Inferentia。它包括编译器、运行时、训练和推理库以及性能分析工具。Neuron 在整个 ML 开发生命周期中为客户提供支持,包括构建和部署深度学习和 AI 模型。Neuron SDK 与流行的 ML 框架无缝集成,如PyTorch(pytorch.org/
)、TensorFlow(www.tensorflow.org/
)和JAX(jax.readthedocs.io/
),同时支持超过 100,000 个模型,包括来自 Hugging Face 的模型。客户,如Databricks,报告称使用 Trainium 驱动的实例时,性能显著提升,成本节省高达 30%(aws.amazon.com/ai/machine-learning/trainium/customers/
)。
在选择 CPU、GPU 和加速器时,需要平衡性能需求、工作负载强度和预算限制,以优化 GenAI 工作负载的资源利用率。通过对自定义加速器的概述,让我们更深入地探讨如何在 K8s 中为 GenAI 应用分配 GPU 资源。
在 K8s 中分配 GPU 资源
要在 K8s 中使用 GPU 和自定义加速器资源,必须使用相应的设备插件。例如,NVIDIA Kubernetes 设备插件使得 K8s 集群能够识别和调度 NVIDIA GPU,而 Neuron 设备插件则使 AWS Trainium 和 Inferentia 加速器也能被 K8s 集群识别和调度。此机制确保任何自定义加速器都能在 K8s 集群中正确地被发现、分配和管理。
除了安装设备插件,还应确保底层操作系统上已安装相应的 GPU/加速器驱动程序。AWS 提供了用于 NVIDIA 以及 Trainium 和 Inferentia 加速器的加速 AMI,您可以使用这些 AMI 启动 K8s 工作节点。这些 AMI 包含 NVIDIA 和 Neuron 驱动程序,nvidia-container-toolkit (github.com/NVIDIA/nvidia-container-toolkit
) 等,都是基于标准的 EKS 优化 AMI。有关更多信息,请参考 AWS 文档 docs.aws.amazon.com/eks/latest/userguide/eks-optimized-ami.html#gpu-ami
。
K8s 设备插件框架的高级架构如图 10.1所示。让我们看看这个过程涉及的步骤:
-
K8s 设备插件作为 DaemonSets 部署,确保所有(或部分)节点都运行插件 Pod 的副本。通常,这些插件会被调度到具有特定标签(GPU、自定义加速器)的工作节点上,以优化资源利用。
-
在初始化时,设备插件会执行各种特定厂商的初始化任务,并确保设备处于就绪状态。
-
插件将自己注册到 kubelet,并声明它管理的自定义资源——例如,
nvidia.com/gpu
用于 NVIDIA GPU,aws.amazon.com/neuroncore
用于 AWS Trainium/Inferentia 设备。 -
注册成功后,插件会向 kubelet 提供托管设备的列表。然后,kubelet 会执行节点状态更新,将这些资源通告给 K8s API 服务器。可以通过运行以下命令来验证:
$ kubectl get node <replace-node-name> -o jsonpath='{.status.allocatable}' | jq . { "nvidia.com/gpu": "1", ...
-
当 Pod 被调度到工作节点时,kubelet 会通知设备插件容器的需求(GPU 数量),以便它执行必要的准备任务来分配请求的资源。
-
设备插件会持续监控它所管理设备的健康状态,并相应地更新 kubelet,以确保资源的最佳可用性。
图 10.1 – K8s 设备插件架构
-
在我们在第五章的演示中,我们使用
eks-data-addons
Terraform 模块安装了 NVIDIA 设备插件,如下所示:module "eks_data_addons" { source = "aws-ia/eks-data-addons/aws" ... enable_nvidia_device_plugin = true
-
它使用 Terraform Helm 提供程序在 EKS 集群中部署了NVIDIA 设备插件 Helm 图表(
github.com/NVIDIA/k8s-device-plugin?tab=readme-ov-file#deployment-via-helm
)。您可以通过运行以下命令验证正在使用的 Helm 版本和 DaemonSet:$ helm list -n nvidia-device-plugin NAME NAMESPACE CHART nvidia-device-plugin nvidia-device-plugin nvidia-device-plugin $ kubectl get ds -n nvidia-device-plugin --no-headers nvidia-device-plugin nvidia-device-plugin-gpu-feature-discovery nvidia-device-plugin-node-feature-discovery-worker
-
下一步是启动 GPU 工作节点并将其注册到集群中。这在第五章的操作指南中有介绍,我们创建了一个名为
eks-gpu-mng
的新 EKS 管理节点组,使用的是 G6 系列的 EC2 实例。此外,还对这些节点应用了 K8s 污点,以确保只有需要 GPU 资源的 Pods 会被调度到这些节点上:module "eks" { source = "terraform-aws-modules/eks/aws" ... eks_managed_node_groups = { eks-gpu-mng = { instance_types = ["g6.2xlarge"] taints = { gpu = { key = "nvidia.com/gpu" value = "true" effect = "NO_SCHEDULE" ...
-
GPU 工作节点还可以进行标记,从而实现高级调度。例如,我们可以使用
hardware-type=gpu
来标识启用了 GPU 的节点。这使得在调度需要 GPU 的工作负载时可以针对特定节点。我们可以通过使用kubectl
命令或运行必要的 Terraform 代码来实现:$ kubectl label node <node-name> hardware-type=gpu
我们还可以使用 Terraform 对 K8s 工作节点进行标记,如以下代码片段所示:
... eks-gpu-mng = { labels = { "hardware-type" = "gpu" } ...
-
下一步是为容器化工作负载分配 GPU 资源,例如 GenAI 训练和推理工作负载。要请求 GPU 资源,需要在 K8s 部署和 Pod 规格中指定资源请求和限制。以下代码片段演示了如何执行以下操作:
-
为 K8s Pod 分配与节点污点匹配的容忍度
-
将 K8s Pods 专门调度到标有
hardware-type: gpu
的节点上 -
使用
nvidia.com/gpu
属性请求一个 GPU 资源:apiVersion: v1 kind: Pod metadata: name: gpu-demo-pod spec: tolerations: - key: "nvidia.com/gpu" operator: "Exists" effect: "NoSchedule" nodeSelector: hardware-type: gpu containers: - name: gpu-container image: nvidia/cuda resources: limits: nvidia.com/gpu: 1
-
在 K8s 中请求 GPU 资源时,必须始终在规格的limits部分定义它们,可以单独定义,也可以与匹配的请求值一起定义;仅指定requests而不指定limits是不允许的。同样,在使用 AWS Trainium/Inferentia 加速器时,可以使用 aws.amazon.com/neuroncore
属性来请求资源。
本节开始时,我们研究了 K8s 设备插件架构以及在 EKS 集群中创建工作节点的步骤。我们还了解了 K8s 调度技术,如污点、容忍度和节点选择器,这些都可以用于将 GPU Pods 调度到各自的节点上。
理解 GPU 利用率
GPU 构成了运行 GenAI 工作负载的主要成本,因此有效利用它们对于实现最佳性能和成本效益至关重要。如果没有适当的监控,GPU 未充分利用可能导致计算效率低下和运营成本增加,而 GPU 过度利用则可能导致请求限制和潜在的应用程序故障。本节将探讨监控 GPU 利用率的解决方案,重点介绍如何导出指标并利用它们实现高效的自动扩展策略。
NVIDIA 数据中心 GPU 管理器(DCGM)
DCGM (developer.nvidia.com/dcgm
) 是一个轻量级的库和代理,旨在简化 NVIDIA GPU 的管理,使用户、开发人员和系统管理员能够监控和管理跨集群或数据中心的 GPU。
DCGM 提供了 GPU 健康诊断、行为监控、配置管理、遥测收集和策略自动化等功能。它既可以作为通过 NVIDIA 主机引擎的独立服务运行,也可以作为嵌入式组件在第三方管理工具中运行。其主要特性包括 GPU 健康诊断、作业级遥测、多 GPU 或主机的组中心资源管理,以及自动化管理策略,以增强可靠性并简化管理任务。DCGM 可以与 K8s 工具如 NVIDIA GPU Operator 无缝集成,实现容器化环境中的遥测收集和健康检查。通过支持将度量数据导出到诸如 Prometheus 等系统,DCGM 还能够促进 GPU 数据在 Grafana 等工具中的实时可视化和分析。
DCGM 可以通过以下方式在 K8s 集群中实施:
-
gpu-operator
(github.com/NVIDIA/gpu-operator
) 利用 K8s 中的operator 模式 (kubernetes.io/docs/concepts/extend-kubernetes/operator/
) 自动化管理为 GPU 提供所需的所有 NVIDIA 软件组件。这些组件包括 NVIDIA 驱动程序(以启用 CUDA)、K8s 设备插件(用于 GPU)、NVIDIA 容器运行时、自动节点标记、基于 DCGM 的监控等。此方法非常适合那些希望为 NVIDIA GPU 资源提供全面自动化解决方案的环境,包括安装、更新和配置。 -
DCGM-Exporter (
github.com/NVIDIA/dcgm-exporter
):建立在 NVIDIA DCGM 框架之上,DCGM-Exporter 收集并以 Prometheus 兼容格式暴露广泛的 GPU 性能和健康度量数据,如利用率、内存使用、温度和功耗等。这有助于与 Prometheus 和 Grafana 等流行的监控和可视化工具无缝集成。DCGM-Exporter 非常适用于那些 GPU 组件已安装,且您需要启用 GPU 监控而不增加额外负担的场景。
请参考 NVIDIA 文档中的docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/amazon-eks.html
以获取关于根据您的操作需求选择正确方法的详细指导。
到目前为止,在我们的操作过程中,我们已经安装了必要的组件,如 NVIDIA 设备插件和相关驱动程序。因此,我们将使用第二种方法(DCGM-Exporter)来监控 GPU 的健康状况和使用情况指标。对于第一种方法,您可以参考 NVIDIA 文档,网址为docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/getting-started.html#operator-install-guide
,以获取详细的设置说明。接下来,我们将使用 Terraform 中的 Helm 提供者来安装 DCGM-Exporter。首先,从github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch10/aiml-addons.tf
下载aiml-addons.tf
文件。
dcgm-exporter
Helm 图表将从nvidia.github.io/dcgm-exporter/
Helm 仓库中部署到dcgm-exporter
命名空间,如下所示的代码片段:
resource "helm_release" "dcgm_exporter" {
name = "dcgm-exporter"
repository = "https://nvidia.github.io/dcgm-exporter/"
chart = "dcgm-exporter"
namespace = "dcgm-exporter"
...
执行以下命令将dcgm-exporter
Helm 图表部署到 EKS 集群,并使用以下kubectl
命令验证其安装。输出应确认dcgm-exporter
已作为DaemonSet部署,并且其 Pod 正在运行:
$ terraform init
$ terraform plan
$ terraform apply -auto-approve
$ kubectl get ds,pods -n dcgm-exporter
NAME DESIRED CURRENT READY
daemonset.apps/dcgm-exporter 2 2 2
NAME READY STATUS RESTARTS AGE
pod/dcgm-exporter-729qb 1/1 Running 0 84s
pod/dcgm-exporter-rtmtw 1/1 Running 0 84s
现在,DCGM-Exporter 已经正常运行,您可以通过连接到 DCGM-Exporter 服务来访问 GPU 的健康状况和利用率指标。执行以下命令以本地连接到该服务,并使用curl
命令查询/metrics
端点以查看 GPU 指标:
$ kubectl port-forward svc/dcgm-exporter -n dcgm-exporter 9400:9400
Forwarding from 127.0.0.1:9400 -> 9400
Forwarding from [::1]:9400 -> 9400
$ curl http://localhost:9400/metrics
# HELP DCGM_FI_DEV_GPU_UTIL GPU utilization (in %).
# TYPE DCGM_FI_DEV_GPU_UTIL gauge
DCGM_FI_DEV_GPU_UTIL{gpu="0",UUID="GPU-173ced1d-4c1d-072d-6819-86522b018187",pci_bus_id="00000000:31:00.0",device="nvidia0",modelName="NVIDIA L4",Hostname="ip-10-0-34-196.us-west-2.compute.internal"} 0
# HELP DCGM_FI_DEV_MEM_COPY_UTIL Memory utilization (in %).
# TYPE DCGM_FI_DEV_MEM_COPY_UTIL gauge
DCGM_FI_DEV_MEM_COPY_UTIL{gpu="0",UUID="GPU-173ced1d-4c1d-072d-6819-86522b018187",pci_bus_id="00000000:31:00.0",device="nvidia0",modelName="NVIDIA L4",Hostname="ip-10-0-34-196.us-west-2.compute.internal"} 0
...
在第十二章中,我们将部署并配置一个 Prometheus 代理,来抓取这些 GPU 指标并通过 Grafana 仪表板进行可视化。一旦指标在 Prometheus 中可用,我们可以安装Prometheus 适配器(github.com/kubernetes-sigs/prometheus-adapter
),以创建自动扩展策略,使 GPU 工作负载能够动态扩展,从而实现最佳资源利用。
GPU 利用率挑战
在 K8s 中,将 GPU 分配给 Pod 会将该 GPU 独占分配给该 Pod 的整个生命周期,即使该 Pod 没有在积极使用它。默认情况下,K8s 不支持在多个 Pod 之间共享 GPU 或为每个 Pod 分配部分 GPU(< 1)。这种设计确保了隔离,避免了冲突,因为大多数 GPU 本身并未设计为处理没有专用软件的并发工作负载。
然而,独占 GPU 分配可能会导致资源利用不足,如果 Pod 没有充分利用其分配的 GPU。这个挑战在生成 AI 场景中尤为突出,因为以下因素常常发挥作用:
-
不同模型大小(大模型与小 LLM):尽管最先进的(SOTA)LLM 可能需要整个 GPU 甚至多个 GPU,但对这些模型的更小或蒸馏版本的需求也在增加。根据它们的大小,这些较小的 LLM 可能只需要 GPU 的一小部分内存和计算能力。在 K8s 的默认“整块 GPU”分配模式下,即使是只需要部分 GPU 的小模型,也会被分配整个设备,导致资源过度配置。
-
动态工作负载模式:GenAI 工作负载,如微调或训练 LLM,或运行复杂的基于扩散的模型推理,可能会产生突发性的 GPU 使用模式。在训练/微调/推理过程中,GPU 使用率可能会飙升至 100%,用于计算密集型操作(矩阵乘法、反向传播等),但在周期或数据加载/处理步骤之间会显著下降。由于 K8s 默认不支持按比例分配 GPU 资源,这些波动峰谷会导致 GPU 利用率低效。
-
调度复杂性和碎片化:K8s 的默认调度器缺乏先进 GPU 共享策略的智能。即使有节点亲和性、污点和容忍度等技术,也没有现成的方法可以动态地将未充分利用的 GPU 重新分配给另一个 Pod。因此,较小的 LLM 可能会独占整个 GPU,即使它们只需要其中一小部分的计算能力。随着更多 Pod 的部署,多个 GPU 会变成部分利用,但被完全预留,从而导致集群中的 GPU 资源碎片化。
我们可以采取一些方法来应对这些利用率挑战,比如 NVIDIA 的MIG、MPS和GPU 时间切片选项。在一项使用 GPU 共享技术提供视频内容的案例研究中,价格性能改善最高可达 95%。有关此方面的更详细基准数据,请参考以下 AWS 博客:aws.amazon.com/blogs/containers/delivering-video-content-with-fractional-gpus-in-containers-on-amazon-eks/
。
在这一节中,我们探讨了为什么监控 GPU 健康状况和指标至关重要,并在我们的 K8s 集群中部署了 NVIDIA DCGM-Exporter 插件,以实时跟踪 GPU 性能和健康指标。我们还探讨了 GPU 利用率的挑战,以及在 GenAI 领域中造成低效的因素。接下来,我们将深入研究可用于解决这些问题的 GPU 划分技术。
GPU 划分和共享技术
GPU 划分和共享技术通常是厂商特定的,这意味着它们可能并不适用于所有加速器。在这一节中,我们将探讨 NVIDIA 为其 GPU 提供的一些常见方法,如 MIG、MPS 和时间切片,并讨论它们如何帮助提高 GenAI 工作负载的 GPU 利用率。
NVIDIA MIG
MIG(docs.nvidia.com/datacenter/tesla/mig-user-guide/index.html
)是 NVIDIA 在 Ampere 及后续架构(A100、H100 等)中引入的一项功能,允许将单个物理 GPU 划分为多个独立的 GPU 实例。每个 MIG 实例拥有自己的专用内存、计算核心和其他 GPU 资源,从而在工作负载之间提供严格的隔离。MIG 最小化了实例之间的干扰,并确保可预测的性能,最终实现了更高效、灵活的 GPU 容量利用。
实现 MIG 的过程包括定义 GPU 实例,这些实例捆绑了 GPU 的一部分内存和计算能力。例如,在具有 40 GB 内存的 NVIDIA A100 GPU 上,您可以配置最多七个实例,每个实例拥有 5 GB 的专用内存和相应的计算资源份额;这可以在图 10.2中看到。在这个示例中,我们可以在不同的 GPU 分区上运行多个工作负载,例如 Jupyter 笔记本、机器学习任务等,从而实现 GPU 实例的高效利用:
图 10.2 – A100 NVIDIA GPU 中的多实例 GPU
这些实例由MIG 配置文件指定(docs.nvidia.com/datacenter/tesla/mig-user-guide/index.html#supported-mig-profiles
),该配置文件概述了每个实例的大小和形状——例如,1g.5gb 配置文件分配 5 GB 内存和相应份额的 GPU 核心。这些实例作为独立的 GPU 实例运行,每个实例具有保证的性能并且相互之间的干扰最小。下表显示了 NVIDIA MIG 用户指南中最新 NVIDIA H200 GPU 的 MIG 配置文件(docs.nvidia.com/datacenter/tesla/pdf/NVIDIA_MIG_User_Guide.pdf
):
MIG 配置文件 | GPU 切片 | GPU 内存(GB) | 实例数量 |
---|---|---|---|
1g.18gb | 1 | 18 | 7 |
1g.18gb+me | 1 | 18 | 1 |
1g.35gb | 1 | 35 | 4 |
2g.35gb | 2 | 35 | 3 |
3g.70gb | 3 | 70 | 2 |
4g.70gb | 4 | 70 | 1 |
7g.141gb | 7 | 141 | 1 |
表 10.1 – NVIDIA H200 GPU 的 GPU 实例配置文件
在此表中,GPU 切片表示分配给 MIG 配置文件的 GPU 部分,GPU 内存(GB)表示分配给该 MIG 实例的内存量(以 GB 为单位),实例数量表示可以为给定配置文件创建的实例数。
在 K8s 中,你可以使用NVIDIA GPU Operator来简化 MIG 设置,该设置会部署 MIG Manager 以管理 GPU 节点上的 MIG 配置及其他关键配置。有关如何在 K8s 集群中设置 MIG 的详细步骤,请参考 NVIDIA 文档:docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html
。
K8s 使用独特的GPU 实例 ID和计算实例 ID来识别各个 MIG 实例。这些实例作为扩展资源(例如,nvidia.com/mig-1g.18gb)暴露给 K8s。设备插件会为节点标注可用的 MIG 配置及其数量,从而使 K8s 调度器能够将 Pod 资源请求匹配到相应的 MIG 实例。例如,下面的代码片段显示了一个 K8s Pod 请求将 nvidia.com/mig-1g.18gb 调度到具有该特定配置可用实例的节点:
resources:
limits:
nvidia.com/mig-1g.18gb: 1
现在我们已经了解了在 K8s 中如何识别和分配各个 MIG 实例,接下来让我们探索这些分区可以如何配置。主要有两种策略:单一和混合。
使用单一 MIG 策略(docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html#example-single-mig-strategy
)时,节点上的所有 GPU 切片大小相同。例如,在一个配有八个 80 GB 内存的 H100 GPU 的 p5.48xlarge EC2 实例上,你可以创建 56 个 1g.10gb 切片,24 个 2g.20gb 切片,16 个 3g.40gb 切片,甚至是 8 个 4g.40gb 或 7g.80gb 切片。这种统一的策略特别适用于多个团队有相似 GPU 需求的情况。在这种情况下,你可以为每个团队分配相同大小的切片,确保公平访问,并最大化 p5.48xlarge 实例的利用率,适用于诸如微调和推理等任务,这些任务对 GPU 容量的需求在各个任务中是一致的。
相比之下,混合 MIG 策略(docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-operator-mig.html#example-mixed-mig-strategy
)通过允许在节点上的每个 GPU 中创建不同大小的 GPU 切片,提供了更大的灵活性。例如,在一个配备有八个 NVIDIA H100 GPU 的 p5.24xlarge EC2 实例上,你可以结合不同的 MIG 切片配置,例如 16 个 1g.10gb 切片 + 8 个 2g.20gb 切片 + 8 个 3g.40gb 切片。这种异构分配对于处理具有不同 GPU 需求的工作负载的集群尤为有益。
假设有一个 AI 初创公司,拥有三种专门化工作负载:一个图像识别应用、一个自然语言处理应用和一个视频分析应用。通过混合策略,该初创公司可以根据每个应用的需求定制 GPU 分配。例如,图像识别应用可能会分配两个 1g.10gb 的切片,自然语言处理应用可能会使用一个 2g.20gb 的切片,视频分析应用可能会从一个 3g.40gb 的切片中受益,所有应用都在同一 H100 GPU 上并行运行。这种方法确保每个应用获得适当的 GPU 资源,而不会过度配置,从而最大化 GPU 资源的整体利用率和效率。
NVIDIA MPS
MPS (docs.nvidia.com/deploy/mps/index.html
) 是一种软件功能,旨在优化多个进程间的 GPU 资源共享。它使多个进程能够并发提交任务,从而减少 GPU 空闲时间并提高资源利用率。如 图 10.3 所示,每个进程保留自己的独立 GPU 内存空间,同时计算资源动态共享,这使得低延迟调度成为可能,并且对于那些可能无法单独完全利用 GPU 资源的工作负载来说,能够提供更好的性能:
图 10.3 – NVIDIA MPS
在 MPS 中,不同的并发运行的进程共享 GPU 全局内存,这带来了需要仔细考虑的挑战。当进程或线程必须小心管理,以避免在同时访问共享内存时发生竞争条件或不一致的状态时,同步开销可能会出现。此外,当多个进程或线程争夺同一内存空间时,可能会发生资源争用,进而导致性能瓶颈。共享全局内存与私有分配的平衡增加了编程的复杂性,需要仔细规划并深入理解 CUDA (developer.nvidia.com/about-cuda
) 内存管理,以确保效率和正确性。
使用 MPS 的内存隔离问题已部分解决,最初由Volta 架构(docs.nvidia.com/deploy/mps/index.html#volta-mps
)解决。Volta GPU 为每个客户端引入了独立的 GPU 地址空间,确保一个进程的内存分配不会被其他进程直接访问,这是内存隔离的一大进步。此外,客户端可以直接向 GPU 提交工作,而无需使用共享上下文,减少了竞争并实现了更精细的资源分配。执行资源的配置也确保了对 GPU 计算资源的更好控制,防止单个客户端垄断资源。然而,仍然存在一些限制,所有进程共享全局内存带宽,如果某个进程过度使用,可能导致性能下降。此外,MPS 缺乏故障隔离,意味着一个进程中的关键错误仍然可能干扰其他进程。尽管这些改进使得 Volta 及更新版 GPU 中的 MPS 更适合处理多进程工作负载,并且在隔离要求不那么严格时表现更佳,但当需要完全的故障隔离时,NVIDIA 的 MIG 仍然是推荐的解决方案。
目前,NVIDIA 的 K8s 设备插件不支持 MPS 分区,并且这一问题正在 GitHub 上的问题追踪中进行跟踪,编号为#443(github.com/NVIDIA/k8s-device-plugin/issues/443
)。不过,存在一个分支版本的插件,地址为github.com/nebuly-ai/k8s-device-plugin
,它支持在 K8s 集群中启用 MPS。请参考Medium上的以下文章,获取逐步操作指南:medium.com/data-science/how-to-increase-gpu-utilization-in-kubernetes-with-nvidia-mps-e680d20c3181
。一旦通过此分支的 NVIDIA 设备插件启用 MPS,多个 K8s Pod 可以共享单个 GPU。MPS 动态管理计算资源的分配,同时保持 Pod 之间的内存隔离。此功能使 Pod 资源定义中支持按比例请求 GPU,从而使 K8s 能够高效地将多个 Pod 调度到同一 GPU 上。
GPU 时间切片
对于 NVIDIA GPU,时间切片(docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html
)是另一种技术,通过将执行时间分割为多个时间片,允许多个进程或应用动态共享 GPU 资源,从而实现对 GPU 的顺序访问。图 10.4展示了 GPU 如何随着时间的推移在不同进程之间交替,使它们能够共享资源,同时每个进程通常保持各自的内存分配:
图 10.4 – GPU 时间切片
时间分片支持那些通常需要独占 GPU 访问的工作负载,提供共享访问,但没有 NVIDIA MIG 特性的内存或故障隔离能力。时间分片功能特别适用于不支持 MIG 特性的老旧 GPU。它还可以补充 MIG,使多个进程共享单一 MIG 分区的资源。然而,时间分片可能会引入延迟,因为进程需要等待轮到它们执行。这会带来上下文切换开销,涉及在切换到下一个进程之前保存和恢复每个进程的状态。
时间分片非常适合通用、多进程 GPU 使用,并且在虚拟化、多租户系统和混合工作负载中表现有效。然而,对于需要严格资源隔离或高响应性的场景,MIG 可能是更合适的解决方案。
下面是前面提到的 GPU 共享技术(即 MIG、MPS 和时间分片)的高级对比:
特性 | MIG | MPS | 时间分片 |
---|---|---|---|
资源 隔离 | 强(硬件级别) | 对于 Volta+ GPU 没有或有限 | 无 |
性能 | 可预测 | 提高了 GPU 利用率,但增加了延迟 | 依赖于工作负载特性 |
可扩展性 | 受支持分区数限制 | 高 | 高 |
开销 | 最小 | 低 | 随着上下文切换增加开销 |
使用场景 | 多租户,推理 | 高性能计算,多个进程工作负载 | 非关键任务 |
兼容性 | Ampere+ GPUs | 所有支持 CUDA 的 GPU | 所有支持 CUDA 的 GPU |
表格 10.2 – NVIDIA GPU 共享技术对比
K8s 中的 NVIDIA 时间分片特性支持 GPU 超额分配,允许多个工作负载通过交错执行动态共享单个 GPU。这是通过 NVIDIA GPU Operator 和在 K8s 中增强的 NVIDIA 设备插件配置选项实现的。为了在 EKS 集群设置中启用时间分片特性,我们需要创建一个time-slicing-config
。在以下示例中,我们创建了 10 个副本(虚拟的“时间分片”GPU),这样每个请求一个nvidia.com/gpu
资源的 K8s Pod 将被分配到这些虚拟 GPU 中的一个,并在底层物理 GPU 上进行时间分片:
apiVersion: v1
kind: ConfigMap
metadata:
name: time-slicing-config
namespace: nvidia-device-plugin
data:
any: |-
version: v1
flags:
migStrategy: none
sharing:
timeSlicing:
resources:
- name: nvidia.com/gpu
time-slicing-config ConfigMap:
$ kubectl apply -f nvidia-ts.yaml
configmap/time-slicing-config 创建完成
Now, update the NVIDIA K8s device plugin configuration so that it can use this ConfigMap in the Terraform code. Download the `aiml-addons.tf` file from [`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch10/aiml-addons.tf`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch10/aiml-addons.tf). This leverages the `time-slicing-config` ConfigMap to initialize the NVIDIA device plugin, as shown in the following code snippet:
module "eks_data_addons" {
source = "aws-ia/eks-data-addons/aws"
...
enable_nvidia_device_plugin = true
nvidia_device_plugin_helm_config = {
name = "nvidia-device-plugin"
values = [
...
config:
name: time-slicing-config
...
Execute the following command to apply the Terraform configuration:
$ terraform apply -auto-approve
The NVIDIA device plugin automatically reconciles the time-slicing configuration and updates the K8s node details accordingly. We can verify this by running the following command, which displays the GPU count as 10 on a g6.2xlarge EC2 instance. This indicates that one physical NVIDIA L4 GPU on the node has been virtualized into 10 replicas, making them available for the scheduler to allocate:
$ kubectl get nodes -o custom-columns=NAME:.metadata.name,INSTANCE:.metadata.labels."node.kubernetes.io/instance-type",GPUs:.status.allocatable."nvidia.com/gpu"
NAME INSTANCE GPUs
ip-10-0-40-57.us-west-2.compute.internal g6.2xlarge 10
To demonstrate the use of NVIDIA time-slicing, let’s say we are deploying a small LLM such as **Meta’s Llama-3.2-1B** ([`huggingface.co/meta-llama/Llama-3.2-1B`](https://huggingface.co/meta-llama/Llama-3.2-1B)) parameter model in our application. This model requires 1.8 GB of GPU memory to load and perform inference. In a traditional setup, we would assign a single L4 GPU with 24 GB of memory to one K8s Pod, resulting in 92% of the GPU memory being underutilized. However, by creating 10 time-sliced replicas, we can deploy multiple instances of the Llama-3.2-1B parameter model on a single GPU.
We’ve already containerized the Llama-3.2-1B model into a Python FastAPI application and published it on Docker Hub. Using this image, we can deploy multiple copies of the model’s endpoint on the K8s node. Download the K8s deployment manifest from [`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch10/llama32-inf/llama32-deploy.yaml`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch10/llama32-inf/llama32-deploy.yaml). Then, update the manifest with your Hugging Face token and execute the following commands to create a K8s deployment with five replicas of this model. You can verify that all replicas will run on the same node while sharing one physical GPU efficiently:
$ kubectl apply -f llama32-deploy.yaml
deployment/my-llama32-deployment 创建完成
$ kubectl get pods -o wide
<显示在同一节点上运行的 5 个 Pod>
In this walkthrough, we applied the same time-slicing configuration across all nodes in the K8s cluster, irrespective of the GPU type. If you are using multiple GPU types (e.g., A100, L4, L40S, H100, etc.) in the same cluster, you can consider using a **multiple node-specific configuration** setup. In this approach, you define different time-slicing configurations for each GPU type in the ConfigMap and use node labels to specify which configuration should be applied to each GPU node. Please refer to the NVIDIA documentation at [`docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html#applying-multiple-node-specific-configurations`](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/gpu-sharing.html#applying-multiple-node-specific-configurations) for a detailed walkthrough.
In this section, we learned about various NVIDIA GPU partitioning techniques (MIG, MPS, and time-slicing) and the differences among them. MIG provides strong isolation with minimal overhead and predictable performance, making it ideal for multi-tenant environments and inference workloads. On the other hand, MPS optimizes resource utilization by enabling multiple processes to share a single GPU concurrently. Finally, time-slicing allows multiple workloads to interleave on a single GPU by allocating compute resources in a round-robin fashion. It is compatible with all CUDA-capable GPUs, including older architectures, making it a versatile option. However, time-slicing introduces higher context-switching overhead and lacks both isolation and the efficiency improvements of MPS. In the next section, we will explore additional scaling and optimization considerations when using GPUs in K8s clusters.
Scaling and optimization considerations
When scaling K8s using GPU metrics, you can integrate NVIDIA DCGM to dynamically adjust workloads based on GPU health, utilization, and performance metrics. DCGM collects telemetry data, such as GPU utilization, memory usage, power consumption, and error rates, that is exposed through DCGM-Exporter for integration with Prometheus. Prometheus metrics are then consumed by autoscaling components such as **Horizontal Pod Autoscaler** (**HPA**) or **Vertical Pod Autoscaler** (**VPA**) to scale the K8s workloads.
For instance, in an ML training job that’s running on multiple nodes, DCGM-Exporter can track metrics such as `nvidia_gpu_utilization`, feeding them to Prometheus for analysis. In this instance, we can create an HPA policy that tracks GPU utilization and automatically increases the number of replicas (up to 10) when utilization exceeds 80%, thereby distributing processing across more GPUs:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: genai-training-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: genai-training
minReplicas: 1
maxReplicas: 10
metrics:
- type: Object
object:
metricName: DCGM_FI_DEV_GPU_UTIL
targetAverageValue: 80
Similarly, in an image recognition service, if the GPU utilization stays below 20% for 10 minutes, the autoscaler can reduce the number of Pods to save resources. DCGM also supports defining policies for *error handling* and *health monitoring*, ensuring K8s can reschedule Pods or drain nodes with unhealthy GPUs. For example, if DCGM detects memory errors or overheating, K8s can remove that node and migrate workloads to healthier nodes. These use cases demonstrate how GPU metrics enable fine-grained control over scaling to optimize resource usage.
However, GPU-based scaling also presents challenges. Metrics such as utilization or memory usage may not directly correlate with application performance, thereby requiring careful threshold tuning. Additionally, in environments using GPU time-slicing or MIG, workloads share resources, and DCGM metrics must account for such configurations to avoid over-scaling.
Continuously monitoring GPU metrics in large clusters can also add overhead, necessitating efficient integration with Prometheus and optimized alert rules. Scaling must also incorporate GPU health data, such as error rates or temperature, to avoid scheduling workloads on faulty hardware.
Best practices for GPU scaling include leveraging DCGM’s built-in policies for error detection and recovery, using custom Prometheus metrics that combine GPU and application-specific data, integrating the NVIDIA GPU Operator for seamless management, and regularly testing and fine-tuning scaling thresholds. By combining NVIDIA DCGM with K8s’s autoscaling capabilities, you can optimize GPU utilization, reduce costs, and enhance the performance of GPU-accelerated applications, ensuring scalability and reliability in resource-intensive environments.Bottom of Form
NVIDIA NIM
**NVIDIA Inference Microservices** (**NIM**) ([`developer.nvidia.com/nim`](https://developer.nvidia.com/nim)) is a component of NVIDIA AI Enterprise that provides developers with GPU-accelerated inference microservices for deploying pretrained and customized AI models either on the cloud or in on-premises environments. These microservices are built on optimized inference engines from NVIDIA and the community, providing latency and throughput optimization for the specific combination of the foundation model and GPU system that’s detected at runtime. Additionally, NIM containers offer standard observability data feeds and built-in support for autoscaling on K8s with GPUs. Refer to the following AWS blog for a detailed walkthrough of deploying GenAI applications with NVIDIA NIM on Amazon EKS: [`aws.amazon.com/blogs/hpc/deploying-generative-ai-applications-with-nvidia-nims-on-amazon-eks/`](https://aws.amazon.com/blogs/hpc/deploying-generative-ai-applications-with-nvidia-nims-on-amazon-eks/).
Each NIM container encapsulates a model, its runtime dependencies, and the inference engine. These containers are pre-configured for easy deployment and include APIs for interaction with applications. NIM microservices are deployed in a K8s cluster, enabling orchestration, scaling, and fault-tolerant management. Developers can use NIM to create custom AI workflows tailored to specific applications, such as RAG for chat-based question-answering or simulation pipelines for scientific research.
NIM’s architecture is designed to streamline the deployment of AI applications by offering prebuilt microservices that can be customized and scaled according to specific use cases. For instance, developers can deploy **RAG pipelines** for chat-based question-answering using NIM-hosted models available in the NVIDIA API catalog. These microservices are regularly updated so that they incorporate the latest advancements in AI models across various domains, including speech AI, data retrieval, digital biology, digital humans, simulation, and LLMs.
Developers interested in utilizing NIM can access it through the NVIDIA developer program, which offers free access for research, development, and testing purposes on up to 16 GPUs across any infrastructure – be it the cloud, a data center, or a personal workstation. For production deployments, NVIDIA AI Enterprise provides a comprehensive suite of tools, including NIM, that come with enterprise-grade security, support, and API stability ([`www.nvidia.com/en-us/data-center/products/ai-enterprise/`](https://www.nvidia.com/en-us/data-center/products/ai-enterprise/)).
GPU availability in the cloud
Like CPU instances, GPU instances can be requested in the cloud as on-demand instances, reserved instances, or spot instances. However, the growing demand for GPUs has led to challenges in availability. In response, cloud providers are introducing innovative strategies to improve GPU accessibility and meet the needs of users more effectively.
For example, AWS has introduced **Amazon EC2 Capacity Blocks for ML** ([`aws.amazon.com/ec2/capacityblocks/`](https://aws.amazon.com/ec2/capacityblocks/)), which allows you to reserve GPU instances in **Amazon EC2 UltraClusters** ([`aws.amazon.com/ec2/ultraclusters/`](https://aws.amazon.com/ec2/ultraclusters/)) for future dates. At the time of writing, you can reserve capacity for durations ranging from 1 to 14 days, with the option to extend up to 6 months, and schedule start times up to 8 weeks in advance using **Capacity Blocks**. These blocks can be used for a wide range of ML workloads, from small-scale experiments to large-scale distributed training sessions, and are available for various instance types, including P5, P5e, P5en, P4d, Trn1, and Trn2, all powered by NVIDIA GPUs and AWS Trainium chips.
Summary
In this chapter, we covered options for optimizing GPU resources in K8s for GenAI applications. First, we described custom AI/ML accelerators, such as AWS Inferentia, Trainium, and Google TPUs. These specialized devices offer high performance and cost-efficiency for GenAI workloads, such as training LLMs or low-latency inference use cases. K8s supports these accelerators through device plugins, allowing them to be integrated into existing clusters seamlessly.
We also covered options to optimize GPU utilization in K8s. This is a critical step due to the high costs of GPU instances and their underutilization. This chapter highlighted various techniques you can implement to address the inefficient use of resources, such as MIG, MPS, and time-slicing.
MIG allows a single GPU to be partitioned into multiple isolated instances, providing a more granular and efficient allocation of resources. MPS, on the other hand, allows multiple processes to share GPU compute resources concurrently. Time-slicing further enables sequential access to the GPU by dividing execution time across different processes, a technique that’s beneficial for older GPUs that lack MIG support.
Finally, we covered GPU scaling practices within K8s and emphasized the role of NVIDIA DCGM, which can collect GPU telemetry data such as GPU utilization, memory usage, and power consumption. By integrating DCGM with Prometheus and K8s auto-scalers, you can dynamically scale workloads based on real-time GPU performance metrics.
NIM simplifies the deployment of AI models by providing pre-configured inference microservices optimized for specific GPU architectures. Cloud GPU availability remains a challenge, with demand often exceeding supply. In the next chapter, we will dive deeper into observability best practices for K8s.
第三部分:在 K8s 上运行 GenAI 工作负载
本节讨论了 GenAI 应用在生产 K8s 环境中的日常操作,涵盖了从自动化流水线到弹性策略的关键方面。本节探讨了 GenAIOps 实践、使用行业标准工具的全面可观察性实现,以及高可用性和灾难恢复策略。还强调了 GenAI 编程助手在自动化和管理 K8s 集群方面的变革性影响,并以进一步阅读建议作为结尾。
本部分包括以下章节:
-
第十一章,GenAIOps:数据管理与 GenAI 自动化流水线
-
第十二章,可观察性 – 获取 GenAI 在 K8s 上的可见性
-
第十三章,GenAI 应用的高可用性和灾难恢复
-
第十四章,总结:GenAI 编程助手与进一步阅读
第十章:GenAIOps:数据管理与 GenAI 自动化流水线
生成性 AI 操作 (GenAIOps) 指的是一套旨在通过生命周期部署、监控和优化生成性 AI 模型的工具、实践和工作流。与传统的 机器学习 (ML) 模型的 MLOps 类似,GenAIOps 专注于生成性 AI 系统所面临的独特挑战,如基础模型(FMs)、大型语言模型(LLMs)和扩散模型。在本章中,我们将涵盖 GenAIOps 的关键概念,例如创建自动化数据流水线、数据收集、清理、模型训练、验证和部署策略,以及持续监控和维护。我们还将讨论数据隐私和模型偏差等主题,并提供最佳实践。
在本章中,我们将涵盖以下主要主题:
-
GenAI 流水线概述
-
K8s 上的 GenAIOps
-
数据隐私、模型偏差和漂移监控
技术要求
在本章中,我们将使用以下工具,其中一些工具需要您设置帐户并创建访问令牌:
-
Hugging Face:
huggingface.co/join
-
Llama-3-8B-Instruct 模型可以通过 Hugging Face 访问,链接地址:
huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct
-
如 第三章 所示,设置 Amazon EKS 集群
GenAI 流水线概述
在本节中,我们将探讨构建、部署和维护 GenAI 应用程序的端到端过程,如 图 11.1 所示。从数据管理开始,组织收集、清理和组织数据集,为高质量的实验奠定基础。然后,实验阶段允许选择适合给定业务用例的 FM/LLM,并做出架构决策,决定如何调整模型。一旦确定了模型,模型适应(包括微调、蒸馏或提示工程)有助于将模型输出与实际应用案例对齐。最后的关键步骤涉及模型服务,启用高效且可靠的推理以及模型监控,通过识别性能回退、数据漂移以及持续改进的机会,闭环反馈。
图 11.1 – GenAI 流水线概述
流水线包括以下阶段:
-
数据管理:在这个阶段,原始数据通过多种来源进行获取,例如内部数据库、第三方 API、流平台、数据湖和公共数据集。原始数据会被转换,以提取有意义的特征用于模型训练和推理。这个过程通常被称为特征工程,涉及清洗、规范化和结构化数据,以生成高质量的机器学习特征,这些特征可以存储在离线特征存储库中以供后续使用。K8s 可以通过使用Apache Spark(
spark.apache.org/
)、Ray(ray.io/
)和Flink(flink.apache.org/
)等工具,部署容器化的工作负载,从而协调数据准备工作流。例如,Spark on K8s 可以通过启动工作节点 Pods,平行处理数据集的各个部分,从而显著加速预处理任务。Amazon EKS 上的数据(DoEKS)(awslabs.github.io/data-on-eks/docs/introduction/intro
)项目提供了在 EKS 上运行数据分析/Spark 工作负载的最佳实践和蓝图。 -
实验:这是原型设计和假设测试的关键阶段。在这个阶段,数据科学家可以尝试不同的模型集合,并决定哪种模型能为特定的业务目标提供最优的结果。Jupyter Notebook提供了一个协作环境,支持交互式数据分析、可视化和模型开发,并且可以部署在 K8s 中。数据科学家可以进行探索性数据分析、特征工程和基准模型的创建。
在这个阶段,存储实验数据和笔记本,并对其进行版本控制,以便后续更容易重现实验结果是至关重要的。这确保了不同的迭代、配置和结果可以随时间进行回溯或对比。通过对笔记本和数据进行版本控制,团队可以跟踪模型的演变,并在必要时恢复到先前的状态。实验数据和笔记本通常存储在可扩展和便捷的存储解决方案中,如Amazon S3。Amazon S3 原生支持桶的版本控制,允许您维护对象的多个版本。S3 对象标签提供了另一种跟踪不同训练数据集的方式。S3 对象标签是键值对,您可以将其分配给 Amazon S3 中的对象,以便管理和组织它们。每个标签由一个键值对组成,例如{“Key”: “Project_Name”, “Value”: “P1”}或{“Key”: “Version”, “Value”: “v1”}。这些标签作为对象元数据存储,并有助于组织训练数据集。
-
模型适配:在这一阶段,预训练模型演变为与您的使用案例独特需求精确对接的解决方案。此阶段通常涉及对基础模型的特定层或参数进行微调,以捕捉领域特定的细微差别,同时不放弃模型对语言或图像的更广泛理解。在某些情况下,适配可能使用迁移学习技术,其中您冻结预训练模型的大部分内容,以保留通用模式,同时仅更新某些层以专注于特定任务。这种定制的强度可以从对大规模数据集的端到端训练到对计算资源有限的场景进行轻量级提示工程或低秩适配(LoRA)。所有这些技术都需要大量的计算资源和各种任务的精确协调。K8s 及Kubeflow、Ray、Argo Workflows等工具可以通过提供一致的容器化环境,支持分布式训练、自动超参数调优和可扩展的微调工作流,极大地简化适配阶段。
-
模型服务:这是 GenAI 管道的最后阶段,经过训练的模型工件被部署以提供实时推理或批处理推理。在实时场景中,通常采用基于微服务的架构,通过 REST 或 gRPC 端点暴露模型。这种设置支持负载均衡、自动扩展,并与持续部署策略(如金丝雀发布和 A/B 测试)集成。为了高效地处理大量推理请求,像KServe、Ray Serve和Seldon Core等工具可以帮助管理 K8s 上的模型部署。对于批处理推理,可以协调工作流定期加载数据集,进行大规模推理,并将结果写入像 Amazon S3 这样的对象存储服务。在这两种方法中,启用监控和日志记录对于跟踪延迟、吞吐量和潜在错误至关重要。通过结合这些实践,我们可以确保 GenAI 模型在生产环境中保持高效、稳定,并能够处理动态的生产工作负载。
-
模型监控:在生产环境中持续监控模型的性能对于确保其满足不断变化的业务和技术需求至关重要。应实时跟踪关键绩效指标(KPIs),并配合警报或仪表盘进行更快速的问题识别。当检测到模型性能下降或分布变化(例如数据漂移或概念漂移)时,反馈回路将启动,触发重新训练或微调。这种迭代方法使模型能够适应新的模式,保持相关性和可靠性。
除了原始指标,模型监控还包括偏差检测和遵守安全边界,确保输出结果保持公平、合规,并符合领域特定的约束。将模型监控与更广泛的 MLOps 基础设施集成,可以在新模型版本表现不佳时实现自动回滚或金丝雀部署。通过定期的真实数据审查和不断更新数据集,我们可以在模型生命周期中持续提高模型的准确性和可靠性。
现在我们已经介绍了 GenAIOps 流水线的关键步骤,让我们更深入地探讨一些在 K8s 环境中常用的工具和工作流引擎。
GenAIOps 在 K8s 上的应用
K8s 提供了执行复杂任务所需的可扩展性和灵活性,如工作流编排、模型训练和实验跟踪,使组织能够更快速地部署和迭代。在 K8s 生态系统中,Kubeflow、MLflow、JupyterHub、Argo Workflows和Ray等工具提供了独特的能力,支持从实验和自动化管道执行到分布式计算的方方面面。在本节中,我们将深入探讨这些平台如何与 K8s 集成,重点介绍它们的关键特性,并比较它们应对 GenAIOps 多样化需求的方法。我们已经在第五章中详细讨论了 JupyterHub,因此这里将介绍其余的工具。
KubeFlow
Kubeflow (www.kubeflow.org/
) 是一个重要的工具,用于在 K8s 环境中管理和执行 GenAI 模型。GenAI 应用需要大量的计算资源和分布式工作流,Kubeflow 在这些领域提供了巨大的价值。
Kubeflow 通过与 TensorFlow 和 PyTorch 等框架的集成,为大型模型提供分布式训练,支持在多个 GPU 或定制加速器上进行并行处理。这减少了对海量数据集的训练时间,并实现了高效的资源利用。通过利用 K8s 的编排能力,Kubeflow 可以根据工作负载需求动态地扩大或缩小资源,确保高效的 GPU 利用率,减少空闲资源。对于在训练和推理过程中计算需求波动的 GenAI 工作负载,这种弹性至关重要。图 11.2展示了 Kubeflow 生态系统的概览,以及它如何与更广泛的 K8s 和 AI/ML 领域相关联。请参考 Kubeflow 的入门指南,网址为www.kubeflow.org/docs/started/installing-kubeflow/
,以获取各种部署选项和逐步的操作说明。
图 11.2 – Kubeflow 生态系统(来源:https://www.kubeflow.org/docs/started/architecture/)
以下是 Kubeflow 的关键组件:
-
Kubeflow Notebooks (
www.kubeflow.org/docs/components/notebooks/overview/
): 该组件提供了一个强大、可扩展的基于 Web 的开发环境,特别适用于 GenAI 项目的实验阶段。数据科学家和机器学习工程师可以利用 Kubeflow Notebooks 在 K8s 管理的基础设施中启动 Jupyter notebooks,简化资源配置,特别是针对 GenAI 常见的 GPU 密集型工作负载。平台管理员可以通过预安装必要的包并使用 Kubeflow 的 基于角色的访问控制 (RBAC) 管理访问权限,从而为组织标准化笔记本镜像。这种方法简化了协作,确保跨组织共享笔记本既安全又高效。 -
Katib (
www.kubeflow.org/docs/components/katib/overview/
): 超参数调优是 GenAI 模型开发中的重要环节,Kubeflow 提供了 Katib,一个自动化调优工具,用于优化模型配置和架构。Katib 可以同时运行多个调优任务,加速寻找表现最佳模型的过程。 -
Kubeflow Pipelines (
www.kubeflow.org/docs/components/pipelines/overview/
): 该组件通过编排数据预处理、模型训练、微调和部署,自动化复杂的工作流,简化了整个机器学习生命周期。Pipeline 被结构化为 有向无环图 (DAGs) (www.kubeflow.org/docs/components/pipelines/concepts/graph/
),确保可复现性,并减少训练过程中的人工干预。 -
KServe (
www.kubeflow.org/docs/external-add-ons/kserve/introduction/
): 一旦模型训练完成,Kubeflow 的 KServe 组件提供了可扩展的高效模型部署,支持 K8s 集群中的批处理和实时推理。KServe 提供动态扩展、A/B 测试和金丝雀发布,确保 GenAI 模型能够无缝过渡到生产环境。
Kubeflow 还通过将数据增强和特征提取等预处理步骤直接集成到其流水线中,解决了 GenAI 的 数据密集型 特性。这减少了错误,并确保每次运行都遵循一致的数据准备过程。所有工件,包括数据集、模型和评估指标,都可以存储在 Kubeflow 的工件库中,从而确保可复现性。元数据追踪确保所有流水线运行、工件和实验都是可追溯的,从而简化了调试和必要时重新训练模型的过程。
Kubeflow 提供了用于编排大语言模型(LLM)工作流的模板,使得在 K8s 环境中能够高效地部署和微调。通过支持多租户环境和命名空间隔离,Kubeflow 确保了跨组织的安全合规工作流,防止了团队之间的资源冲突。Kubeflow 对于需要大量实验、模型再训练和部署流水线的 GenAI 项目尤其有价值。它能够自动化整个机器学习生命周期,从数据摄取和分布式训练,到超参数调优、部署和监控,减少了数据科学家和 DevOps 团队的负担。
MLflow
MLflow (mlflow.org/
) 是一个开源平台,旨在简化 AI/ML 生命周期,提供实验、模型版本控制和可重现性等工具。MLflow 与 K8s 搭配使用,提供了可扩展性和编排能力,用于管理分布式环境中的复杂工作流。
以下是 MLflow 的一些核心组件:
-
Mlflow 跟踪 (
mlflow.org/docs/latest/tracking.html
) 提供了一个 API 和用户界面,用于记录整个机器学习过程中参数、代码版本、指标和工件。集中记录诸如参数、指标、工件、数据和环境配置等细节,使团队能够深入了解其模型随时间的演变。当在 K8s 上部署时,它通常以 Pod 的形式运行,并配有持久存储(例如 Amazon S3)来安全存储工件和元数据。 -
MLflow 模型注册中心 (
mlflow.org/docs/latest/model-registry.html
) 提供了一种系统化的模型管理方法,帮助处理属于机器学习生命周期不同阶段(如预发布、生产和归档)的不同版本的模型及其追踪。它还提供了一个集中存储、API 和用户界面,用于协作管理模型的血统、版本、别名、标签和注释。当与跟踪服务器一起部署在 K8s 上时,它能够享受高可用性和水平 Pod 自动扩缩的好处,支持大规模操作。 -
MLflow 项目 (
mlflow.org/docs/latest/projects.html
) 提供了一种标准化的格式,用于打包机器学习代码和容器化机器学习实验,使其能够在不同环境间移植。当在 K8s 上部署时,这些项目可以使用 Argo Workflows 或 Kubeflow Pipelines 等工具作为分布式任务进行编排,实现超参数调优和模型优化等任务的并行执行。 -
MLflow 模型 (
mlflow.org/docs/latest/models.html
) 提供了一种标准格式,用于打包机器学习模型,这些模型可以在各种下游工具中使用,例如通过 REST API 进行实时服务或在 Apache Spark 上进行批量推断。在 K8s 环境中,这些模型可以通过 KServe、Seldon Core 或 Ray Serve 等框架提供服务,利用 K8s 的特性实现无缝扩展、负载均衡以及与其他 K8s 服务的集成。
例如,在一个真实的使用案例中,MLflow 可以用于跟踪实验,数据科学家在优化超参数时,确保记录每次运行的指标、参数和工件,以便重现和分析。然后,表现最好的模型可以注册到 MLflow 模型注册表中,便于将其部署到 KServe Pods 进行实时服务。通过 K8s 的自动扩展,已部署的模型可以根据需求动态扩展,处理高峰期间的用户流量,确保强大和高效的性能。
Argo Workflows
Argo Workflows (argo-workflows.readthedocs.io/en/latest/
) 是一个开源的、原生 K8s 工作流引擎,旨在协调 K8s 环境中的复杂管道。它允许用户将工作流定义为 DAGs(有向无环图)(argo-workflows.readthedocs.io/en/latest/walk-through/dag/
) 或逐步指令。DAG 的每个步骤作为 K8s 集群中的单独 Pod 运行。这种架构利用了 K8s 的可扩展性和容错能力,使其成为机器学习管道的理想解决方案。
Argo Workflows 是通过 K8s 的自定义资源定义(CRD)规范实现的。每个工作流可以在步骤之间动态传递数据,进行并行任务执行,并有条件地执行分支,使其具有很高的适应性。
Argo Workflows 的一个主要优势是其能够水平扩展,并且能够同时协调成千上万的工作流而不会产生显著的开销。诸如自动重试、错误处理、工件管理和资源监控等功能简化了 Argo Workflow 的执行并提高了其弹性。许多 K8s 生态系统工具使用 Argo Workflows 作为底层工作流引擎。一些例子包括 Kubeflow Pipelines、Seldon、Katib 等。有关详细的逐步安装说明,请参阅Argo Workflows 入门指南。
Argo Workflows 是一个通用的工作流引擎,可以应用于许多使用场景,包括机器学习管道、数据和批处理、基础设施自动化、持续集成/持续交付(CI/CD)等。有关这些使用场景的详细介绍,请参考 Argo Workflows 文档:argo-workflows.readthedocs.io/en/latest/#use-cases
。
Ray
Ray(www.ray.io/
)是一个开源框架,旨在支持可扩展和分布式计算,使得基于 Python 的应用程序可以跨多个节点执行。Ray 提供了一个统一的接口用于构建分布式应用程序,并且提供了丰富的库生态系统,包括 Ray Serve(docs.ray.io/en/latest/serve/index.html
)用于可扩展的模型服务、Ray Tune(docs.ray.io/en/latest/tune/index.html
)用于超参数调优、Ray Train(docs.ray.io/en/latest/train/train.html
)用于分布式训练、Ray RLlib(docs.ray.io/en/latest/rllib/index.html
)用于可扩展的强化学习,以及 Ray Data(docs.ray.io/en/latest/data/data.html
)用于分布式数据预处理和加载。当部署在 K8s 上时,Ray 利用 K8s 的编排能力来高效地管理和扩展分布式工作负载。
Ray 可以通过 KubeRay 操作符(github.com/ray-project/kuberay
)部署在 K8s 上,如 图 11.3 所示,KubeRay 提供了一种 K8s 原生的方法来管理 Ray 集群。一个典型的 Ray 集群由一个头节点 Pod 和多个工作节点 Pod 组成。KubeRay 操作符简化了这些集群的创建、扩展和管理,确保与 K8s 环境的无缝集成。

图 11.3 – KubeRay 架构(来源:docs.ray.io/en/latest/cluster/kubernetes/index.html
)
KubeRay 提供了多个 CRD 来简化 Ray 集群管理:
-
RayCluster(
docs.ray.io/en/latest/cluster/kubernetes/getting-started/raycluster-quick-start.html
):定义了 Ray 集群的期望状态,包括头节点和工作节点的规格。此 CRD 允许用户自定义资源分配、环境变量以及与 Ray 集群相关的其他配置。 -
RayJob(
docs.ray.io/en/latest/cluster/kubernetes/getting-started/rayjob-quick-start.html
):使得可以将 Ray 作业提交到 Ray 集群。通过指定作业的入口点和运行时环境,用户可以在不进行手动干预的情况下执行分布式应用程序。 -
RayService(
docs.ray.io/en/latest/cluster/kubernetes/getting-started/rayservice-quick-start.html
):促进 Ray Serve 应用程序的部署,用于可扩展的模型服务。这个 CRD 管理 Ray Serve 部署的生命周期,确保高可用性和无缝更新。
KubeRay 提供了自动扩展能力,允许 Ray 集群根据工作负载需求调整大小。该功能通过根据需要增加或删除 Ray Pods 来确保高效的资源利用,适应不同的计算需求。KubeRay 支持异构计算环境,包括配备 GPU 的节点。这种灵活性使得能够执行各种工作负载,从通用计算到需要硬件加速的专门任务。
在 K8s 集群上部署 KubeRay
在本节中,我们将在 EKS 集群环境中部署 KubeRay 操作员。KubeRay 操作员可以作为 Helm chart 部署,Helm chart 可在 kuberay-helm
(github.com/ray-project/kuberay-helm
)仓库中找到。接下来,我们将更新 Terraform 代码,使用 Terraform Helm Provider 安装 KubeRay 操作员。将以下代码添加到 aiml-addons.tf
中(或者,你可以从 GitHub 仓库下载完整文件:github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch11/aiml-addons.tf
):
resource "helm_release" "kuberay-operator" {
name = "kuberay-operator"
repository = "https://ray-project.github.io/kuberay-helm/"
chart = "kuberay-operator"
namespace = "kuberay-operator"
create_namespace = true
depends_on = [
module.eks
]
}
执行以下命令,在 EKS 集群中部署 kuberay-operator
Helm chart,并使用 kubectl
命令验证安装。输出结果应该确认 kuberay-operator
已以 Running
状态部署:
$ terraform init
$ terraform plan
$ terraform apply -auto-approve
$ kubectl get deploy,pods -n kuberay-operator
NAME DESIRED CURRENT READY
deployment.apps/kuberay-operator 1 1 1
NAM READY STATUS RESTARTS AGE
pod/kuberay-operator-5dd6779f94-4tzsr 1/1 Running 0 84s
现在我们已经成功在 EKS 集群中安装了kuberay-operator
,接下来我们可以使用 Ray 的一些功能,比如 Ray Serve,来服务 GenAI 模型。如前所述,Ray Serve 提供了一种可扩展的方式,通过 Ray 框架来服务 AI/ML 模型。将 Ray Serve 与 vLLM(github.com/vllm-project/vllm
)后端结合使用,进行 LLM 推理,带来了多个显著的优势,特别是在可扩展性、效率和部署简易性方面。
vLLM 是一个开源库,旨在通过更高效的内存管理和并行化策略优化 LLM 推理。它采用了一种新颖的PagedAttention(huggingface.co/docs/text-generation-inference/en/conceptual/paged_attention
)机制,这是一种受操作系统虚拟内存分页启发的创新注意力算法。它显著减少了 GPU 内存碎片,使多个推理请求能够并行运行,并且开销更小。此外,vLLM 通过连续批处理传入请求,将它们组合在一起,以优化计算资源并提高推理速度。另一个主要优势是 vLLM 在并行采样期间高效的内存共享,生成来自单个提示的多个输出序列,从而将内存使用减少最多 55%,并提高吞吐量最多 2.2 倍(blog.vllm.ai/2023/06/20/vllm.html
)。综合来看,这些功能使得在大规模部署 LLM 时,能够实现更高的吞吐量、更低的延迟以及更低的硬件成本。此外,vLLM 与流行库如Hugging Face Transformers无缝集成,使其易于采用,而无需进行大量代码修改。
在本节中,我们将使用 Ray Serve 和 vLLM 后端在 Amazon EKS 集群上部署 Llama-3-8B 模型。首先,我们需要创建一个包含 Hugging Face API 密钥的 K8s Secret 资源,Ray Serve 部署将使用该密钥下载并托管 Llama 模型。执行以下命令创建名为hf-secret
的 K8s Secret:
$ export HF_TOKEN=<Your Hugging Face access token>
$ kubectl create secret generic hf-secret --from-literal=hf_api_token=${HF_TOKEN}
secret/hf-secret created
从 GitHub 仓库下载ray-service-vllm.yaml
文件(github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch11/ray-service-vllm.yaml
),并执行以下命令以创建 Ray 服务:
$ kubectl apply -f ray-service-vllm.yaml
rayservice.ray.io/llama-31-8b created
这个 Ray 服务示例执行以下操作:
-
创建一个具有指定容器镜像和资源的 Ray 集群,包括头节点和工作节点
-
下载并安装 vLLM 推理所需的代码/依赖项
-
使用 YAML 中定义的serveConfigSpecs启动 Ray Serve
-
根据并发性和资源使用情况自动扩展 Ray 集群和 Ray Serve 副本
KubeRay 将作为 K8s Pod 启动头节点和工作节点。我们可以使用kubectl
来验证这一点:
$ kubectl get pods -l app.kubernetes.io/name=kuberay
NAME READY STATUS RESTARTS AGE
llama-3-8b-raycluster-vw67l-gpu-group-worker-r2n96 0/1 Pending 0 49s
llama-3-8b-raycluster-vw67l-head-94452 0/1 Pending 0 49s
如果 EKS 集群缺少足够的计算或 GPU 资源,这些 K8s Pod 可能会首先进入Pending
状态。集群中的 Karpenter 将自动检测到这一点,并根据资源请求启动 Amazon EC2 实例。因此,Pod 进入Running
状态可能需要 10 到 15 分钟:
$ kubectl wait --for=condition=Ready pods -l app.kubernetes.io/name=kuberay --timeout=900s
pod/llama-3-8b-raycluster-vw67l-gpu-group-worker-r2n96 condition met
pod/llama-3-8b-raycluster-vw67l-head-94452 condition met
最后,让我们通过端口转发到 8000
端口上的 Ray 服务,验证 Llama 3 模型的推理。使用以下命令设置 Ray Serve 应用程序的端口转发,然后向推理端点发送测试提示:
$ kubectl port-forward svc/$(kubectl get svc -l app.kubernetes.io/name=kuberay,ray.io/node-type=head -o jsonpath='{.items[0].metadata.name}') 8000:8000
Forwarding from 127.0.0.1:8000 -> 8000
Forwarding from [::1]:8000 -> 8000
$ curl http://localhost:8000/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "meta-llama/Meta-Llama-3-8B-Instruct",
"messages": [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Provide a brief sentence describing the Ray open-source project."}
],
"temperature": 0.7
}'
此外,我们可以连接到8265
以查看度量、日志和整体集群状态。Ray 仪表板提供有关资源利用率、活动演员和集群中运行任务的实时度量。我们还可以使用它检查日志、监控自动扩展事件并管理 Ray Serve 部署,从而使调试和优化应用程序变得更容易。
$ kubectl port-forward svc/$(kubectl get svc -l app.kubernetes.io/name=kuberay,ray.io/node-type=head -o jsonpath='{.items[0].metadata.name}') 8265:8265
Forwarding from 127.0.0.1:8265 -> 8265
Forwarding from [::1]:8265 -> 8265
在浏览器中访问 localhost:8265
以进入 Ray 仪表板。
图 11.4 – Ray 仪表板
在本节中,我们介绍了如何在 K8s 环境中部署 KubeRay。在接下来的章节中,我们将比较 Kubeflow、MLFlow 和 Ray,这三种框架通常用于 MLOps 部署。
比较 KubeFlow、MLFlow 和 Ray
Kubeflow、MLflow 和 Ray 是开源框架,旨在构建 AI/ML 流水线并促进 MLOps。以下是一个比较表,突出了它们的独特功能,帮助您选择适合您具体用例的框架:
功能 | Kubeflow | MLflow | Ray |
---|---|---|---|
关键应用 | 编排和管理端到端 ML 工作流 | 实验跟踪、模型版本管理和生命周期管理 | 分布式计算、可扩展训练和 ML 应用程序服务解决方案 |
核心优势 | 工作流编排和多用户环境 | 实验跟踪和模型注册 | 分布式执行、超参数调优和服务 |
与 K8s 的集成 | K8s 原生,支持无缝资源扩展 | 可以在 K8s 中运行以支持可扩展性 | 与 K8s 的分布式工作负载良好集成 |
模型注册 | 通过元数据和输出进行基本跟踪 | 集中式模型注册和生命周期管理 | 无原生模型注册;与外部工具如 MLflow 集成进行生命周期管理 |
部署 | 支持通过 KServe 或自定义工作流进行模型部署 | 支持部署到云端、边缘和本地环境 | 通过 Ray Serve 进行分布式模型服务 |
超参数 调优 | 通过 Katib 集成进行 AutoML | 有限;需要外部库支持 | 通过 Ray Tune 提供原生支持 |
框架 兼容性 | 支持 TensorFlow、PyTorch、XGBoost 等 | 框架无关 | 支持 TensorFlow、PyTorch、XGBoost 和自定义 Python |
监控 | 通过 K8s 工具(例如 Prometheus)进行监控 | 部署需要自定义监控 | 通过 Ray 仪表板提供原生可观察性,并与第三方工具进行自定义集成 |
适合 | 需要 K8s 原生 MLOps 解决方案的团队 | 专注于追踪、管理和部署模型的团队 | 构建可扩展、分布式 AI/ML 应用的团队 |
表格 11.1 – 框架比较:Kubeflow vs MLflow vs Ray 用于 MLOps
在本节中,我们探讨了 K8s 生态系统中各种帮助实现 GenAI 自动化管道的工具。像 Kubeflow 简化了 ML 管道,笔记本便于实验,MLflow 提供了强大的实验追踪和模型管理,Argo Workflows 使得自动化管道执行更加高效,Ray 则提供了强大的分布式计算能力。每个平台都能与 K8s 生态系统无缝集成,带来独特的功能,以满足 GenAIOps 不断发展的需求。我们还在 EKS 集群中部署了 KubeRay 操作符,并使用 Ray Serve 框架托管了 Llama 3 模型。
在下一节中,让我们探讨数据隐私和模型监控,以确保我们的 GenAI 工作负载不仅高效,而且安全和可靠。
数据隐私、模型偏见和漂移监控
在快速发展的 GenAI 领域,确保数据隐私、解决模型偏见和监控模型漂移是构建值得信赖和可靠的 AI 系统的关键。本节将探讨在 K8s 生态系统中可用的策略和工具,以保护敏感数据,检测和缓解 AI 模型中的偏见,并持续监控模型表现以识别漂移迹象。通过应对这些挑战,我们可以保持合规性,增强透明度,并确保 GenAI 解决方案在生产环境中提供一致和公平的结果。
测试偏见和方差的方法
在 K8s 环境中测试模型偏见和方差可以通过利用 ML 管道、专用监控工具和可扩展的分布式框架实现自动化和简化。像 Kubeflow、MLflow 和 Argo Workflows 这样的工具可以与偏见检测库和统计分析框架集成,以自动化这一过程。
公平性和可解释性库
像IBM AI Fairness 360(AIF360)、Fairlearn和SHapley Additive exPlanations(SHAP)这样的库可以直接集成到 K8s 中的 AI/ML 管道中。如果这些工具已容器化,它们可以与模型部署一起扩展。这些库通过比较在种族和性别等受保护属性上的表现差异来评估偏见。
现在,让我们探讨如何将 AIF360 集成到基于 K8s 的机器学习流水线中,以评估并减少偏差。假设有一家金融机构正在开发一个机器学习模型,用于预测基于信用评分、收入和年龄等特征的贷款批准情况。为了确保该模型不会对某些人口群体(例如种族或性别)产生偏见,AIF360 可以集成到流水线中以评估公平性。AIF360 可以容器化并作为 K8s Pod 部署。该 Pod 从持久化存储中检索存储的预测结果,该存储在模型和公平性检查 Pod 之间共享,并包含任何必要的测试数据。使用这些输入,AIF360 计算公平性指标,如不公平影响和机会平等差异,以评估敏感属性的偏见。如果检测到偏见,流水线可以触发重新训练任务,采用 AIF360 提供的缓解技术,如重新加权、优化预处理、对抗性去偏等。此外,AIF360 还可以在数据预处理阶段用于检测和解决训练数据集中的偏见,避免模型开发前出现问题。有关交互式演示,请参考 AIF360 文档:github.com/Trusted-AI/AIF360
。
模型漂移监控与反馈循环。
在机器学习项目生命周期中,数据漂移可能以各种形式出现,每种形式对模型的影响不同,可能会降低模型的效果。以下是一些示例:
-
协变量漂移(Covariate drift)发生在输入特征的分布发生变化时,而特征与目标变量之间的关系保持不变。请参见以下示例:
-
在电子商务中,季节性变化可能导致节假日期间对衣物和礼品的搜索量激增,从而改变输入数据的分布。
-
在医疗保健领域,人口老龄化可能导致用于预测疾病风险的数据集中平均年龄的提高。
-
-
标签漂移(Label drift)发生在目标变量的分布发生变化时,即使输入特征的分布保持不变。以下是一个示例:
- 在零售行业,经济繁荣可能导致高端商品的购买率增加,从而改变目标变量的分布。
-
概念漂移(Concept drift)发生在输入特征与目标变量之间的关系发生变化时。以下是一个示例:
- 在广告投放平台中,当新竞争者进入市场时,用户的偏好可能发生变化,导致用于预测广告点击的模型效果降低。
-
时间漂移(Temporal drift)反映了数据分布随时间逐渐变化的情况。以下是一个示例:
- 在社交媒体分析中,语言使用趋势或话题标签可能会变化,影响情感分析模型的表现。
-
采样漂移(Sampling drift)发生在数据收集过程发生变化时,导致样本分布发生偏移。以下是一个示例:
- 在客户调查中,调查方法的改变可能会开始针对不同的群体,从而改变数据集的组成。
-
特征交互漂移涉及特征之间交互方式的变化,即使各个特征的分布保持稳定。以下是一个示例:
- 在零售行业,一种产品的促销可能会以意想不到的方式影响互补产品的销售。
理解这些不同类型的漂移——协变量漂移、标签漂移、概念漂移、时间漂移、采样漂移和特征交互漂移——对于确保模型在时间推移中保持可靠和有效至关重要。
以下是一些常用的统计方法,用于衡量不同类型的漂移:
-
目标漂移检测(TDD)有助于识别目标变量分布的变化。例如,在欺诈检测系统中,TDD 可以检测到欺诈交易与非欺诈交易的比例变化。它使用统计方法,如 KL 散度、卡方检验等,来比较当前的目标分布与历史分布,从而警告用户可能会影响模型性能的漂移。
-
Kolmogorov-Smirnov 检验(KS 检验)是一种统计方法,用于比较两个分布并确定它们是否存在显著差异。它对于检测协变量漂移非常有用,协变量漂移发生在输入特征分布发生变化时。KS 检验衡量两个数据集的累积分布函数(CDFs)之间的最大差异,提供一个检验统计量和 p 值,以量化漂移的程度和显著性。例如,KS 检验可以揭示电子商务平台上用户行为的变化,其中特征分布如购买频率和产品偏好可能会随着时间推移发生变化。
-
概念漂移检测(CDD)关注输入特征与目标变量之间关系的变化。它识别那些相同输入导致不同结果的情况,表明模型关于数据的假设不再有效。概念漂移在推荐系统等应用中尤为重要,因为用户偏好随时间演变;在信用评分系统中,监管变化也可能改变什么构成一个值得信贷的个体。
漂移检测与修复
当模型监控组件检测到数据漂移并超过设定的阈值时,可以通过事件驱动的工作流(使用如 Argo Workflows 或 Kubeflow 等工具)启动新的重训练任务。该重训练任务可以拉取存储在数据湖(如 Amazon S3)中的最新生产数据,并使用预定义的容器镜像或自定义训练任务 CRD 启动模型训练或微调任务。使用如 AIF360、SHAP 和 Fairlearn 等工具的偏差和可解释性检查可以嵌入流水线的中间步骤,以确保更新后的模型不仅满足性能要求,还符合公平性政策。
在重训练后,模型会与已建立的基准进行验证,并将准确率、F1 分数等指标与先前版本进行比较。如果新模型符合验收标准,它将被打包为容器并推送到容器注册表。然后,通过蓝绿部署或金丝雀发布策略进行部署。
所有事件和模型工件都被记录并存储在版本化的存储桶或数据库中,以便进行根本原因分析和调试,如 图 11.5 所示。
图 11.5 – GenAI 流水线中的自动化数据漂移响应流程
这种实现方式促进了 GenAI 模型部署的稳健性、公平性和弹性,而无需持续的人工监督。
总结
本章探讨了 GenAIOps 的基础概念,重点介绍了部署、监控和优化 GenAI 模型所需的工具和工作流。它解决了 GenAI 工作负载的独特挑战,如自动化数据流水线、确保数据隐私、管理模型偏差和维护生命周期优化。
该过程从数据准备开始。模型实验涉及原型设计和测试不同的模型,以确定特定业务目标的最佳方法。Jupyter Notebook 和 Kubeflow Notebooks 等协作工具促进了探索性分析。在模型优化过程中,可以使用 Katib 和 Ray Tune 等工具进行超参数调优和神经网络架构搜索。模型训练和微调在分布式系统中使用 TensorFlow 或 PyTorch 等框架执行。训练完成后,模型可以部署到实时或批量推理环境中。持续监控确保模型性能随着数据模式的变化而保持稳定。
原生 K8s 工具,如 Argo Workflows、Kubeflow 和 MLflow,可以简化流水线编排,实现分布式训练、超参数调优和模型服务。这些工具无缝集成公平性和可解释性库,用于评估和缓解模型偏差,并启用强大的工作流来检测和解决数据漂移,确保模型随时间保持可靠性。
这种全面的 GenAIOps 方法在性能优化与伦理考量之间取得平衡,为 GenAIOps 创建了一个可扩展、可重复且值得信赖的框架。在下一章中,我们将基于这些概念,深入研究 K8s 的可观察性栈,以增强监控和故障排除能力。
加入拥有 44,000+订阅者的 CloudPro 通讯
想了解云计算、DevOps、IT 管理、网络等领域的最新动态吗?扫描二维码订阅CloudPro,这是每周发送给 44,000+技术专业人士的通讯,帮助他们保持信息领先,走在行业前沿。
第十一章:可观察性 – 获取 GenAI 在 K8s 上的可见性
本章将涵盖监控 GenAI 应用程序在 Kubernetes(K8s)中的关键可观察性概念。我们将深入探讨为什么监控对优化 GenAI 工作负载至关重要,分析系统级指标和特定应用信号。通过集成 Prometheus 进行指标收集,使用 Grafana 进行可视化,并利用 LangChain 的调试功能,你将学习如何构建一个全面的监控框架,提供实时和可操作的洞察。
在本章中,我们将涵盖以下主要内容:
-
可观察性关键概念
-
K8s 中的监控工具
-
可视化和调试
可观察性关键概念
可观察性 是用于识别、调查和修复系统问题的基础框架,如 图 12.1 所示。它提供了系统行为和性能的整体视图。可观察性建立在三个核心支柱上:日志、指标 和 追踪。日志捕捉详细的事件信息,指标量化系统性能,追踪提供请求流程的端到端视图。这些组件共同作用,实现了对复杂分布式系统的高效监控和故障排除。这一整合确保了对维护系统可靠性和性能的可操作性洞察。
图 12.1 – 可观察性框架
日志
系统日志 包括诸如事务、系统错误和用户操作等事件。K8s 在不同层级生成日志,如容器日志、节点日志和集群级别日志。你可以使用以下命令查看 Pod 的日志:
$ kubectl logs <pod-name>
如果 Pod 中有多个容器,可以使用以下命令观察特定容器的日志:
$ kubectl logs <pod-name> -c <container-name>
默认情况下,K8s 中的日志是临时的,Pod 重启时会丢失。为了持久化日志,可以使用 sidecar 容器或将日志转发到集中存储后端(如 Amazon CloudWatch Logs、Elasticsearch 或 Loki)的日志代理。
K8s 默认不提供集群级别的日志记录。为了实现集群级日志记录,可以使用 Loki 等解决方案,或者使用 Datadog、Splunk、Amazon CloudWatch 和 New Relic 等托管服务。
标准日志能提供关于 Pod 级事件和错误的宝贵信息;然而,它们无法涵盖 API 响应的完整跟踪。这时,K8s 审计日志就派上用场了,它提供了更详细的 API 交互视图,主要用于安全性和合规性目的。我们将在下一节中讨论 K8s 审计日志。
K8s 审计日志
K8s 提供 审计日志功能,跟踪所有对 K8s API 服务器的 API 请求。这提供了一个详细的记录,记录了用户、服务账户和控制器执行的操作,帮助进行安全性、合规性和故障排除。
要启用审计日志,请通过添加以下内容修改 K8s API 服务器配置:
--audit-log-path=/var/log/kubernetes/kube-apiserver-audit.log
--audit-policy-file=/etc/kubernetes/audit-policy.yaml
以下 YAML 示例展示了一个审计策略,它将在不记录请求正文的情况下记录请求元数据:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
在托管的 K8s 服务中,如 Amazon EKS,控制平面审计日志可以配置为流式传输到 Amazon CloudWatch 日志。有关如何设置审计日志的说明,请参考EKS 文档。
虽然日志提供了有关离散事件(如错误、系统活动和用户操作)的详细视图,但指标通过捕捉随时间变化的持续性能数据,提供了互补的视角。在下一节中,我们将焦点从基于事件的洞察转向使用指标进行性能监控。
指标
指标涉及时间序列数据,用于跟踪系统性能指标,如内存使用情况和延迟。在 K8s 中,指标提供有关集群、节点、Pod 和容器的实时性能数据。这些指标有助于监控、扩展和故障排除在 K8s 集群中运行的工作负载。
K8s 中的指标在不同层级进行收集:
-
节点指标,例如节点级别的 CPU、内存、磁盘和网络使用情况
-
Pod 和容器指标,例如每个 Pod 和容器的资源消耗
-
集群级指标,例如集群的整体健康状况和性能
-
应用层指标,例如请求延迟和错误率等自定义应用程序指标
在第六章中,我们讨论了对于应用程序扩展和最终用户体验至关重要的关键指标。我们涵盖了传统指标,如 CPU 和内存使用情况,也涉及了自定义指标,如队列长度和 HTTP 请求率。Prometheus 是 K8s 环境中常用的收集指标的方式,我们将在后面的部分讨论这一点。
跟踪
跟踪提供了一个视觉化表示,展示了单个请求如何在分布式系统中流动,跟踪其与各种服务、API、数据库和组件的交互。在 K8s 中,跟踪有助于跟踪请求如何在不同的服务、容器和节点之间传播。跟踪提供了对请求生命周期的端到端可视化,使开发人员和运维人员能够理解微服务架构中的延迟问题、故障和依赖关系。
在 K8s 中,跟踪至关重要,因为应用程序通常由多个微服务组成,这些微服务通过网络进行通信。与日志和指标不同,日志和指标提供的是系统行为的快照,而跟踪则提供了关于请求流的上下文洞察。
OpenTelemetry(OTel)(opentelemetry.io/
)是一个常用的框架,用于收集追踪。一旦收集,OTel 可以将追踪数据导出到Zipkin(zipkin.io/
)、Jaeger(www.jaegertracing.io/
)和AWS X-Ray(aws.amazon.com/xray/
)。我们将在后续章节中详细介绍 OTel。
在本节中,我们探讨了 K8s 中可观测性的基础知识以及三个核心支柱——日志、指标和追踪。在下一节中,我们将探讨 K8s 环境中用于监控 GenAI 工作负载的各种工具。
K8s 中的监控工具
在 K8s 中实现真正的可观测性需要一种整体的方法,集成日志、指标和追踪。每个支柱都有其独特的优势,适用于特定的使用场景。通过采用最佳实践来结合日志、指标和追踪,可以优化监控策略,提高系统的可靠性和弹性,最终增强用户体验。
以下是可观测性的示例堆栈,如图 12.2所示:
-
Fluentd 和 Fluent Bit收集来自 Kubernetes 节点、Pods 和应用程序的日志,并将其转发到 Amazon CloudWatch/Loki/OpenSearch 进行存储。
-
OTel收集追踪和特定应用程序的指标,并将其导出到 Prometheus(用于指标)和 Jaeger/AWS X-Ray/Zipkin(用于追踪)。
-
Grafana提供了一个单一的界面,用于可视化日志(来自 Loki)、指标(来自 Prometheus)和追踪(来自 AWS X-Ray)。开发人员和运维人员使用 Grafana 仪表板来分析性能、调试问题,并根据日志、指标和追踪设置告警。
图 12.2 – K8s 中的可观测性堆栈
我们将在接下来的章节中讨论这些工具。
Fluentd 和 Fluent Bit
/var/log/containers
、K8s API 服务器事件和系统日志。然后,它会解析、过滤并将这些日志路由到 Elasticsearch、Loki、Splunk、Amazon S3 和云端日志解决方案(如 Amazon CloudWatch)等目的地。
Fluentd 通过基于插件的架构高度可配置。提供 100 多个插件,支持不同的日志格式和后端(www.fluentd.org/plugins
)。它采用结构化日志方式,使日志以 JSON 格式处理,从而提高可搜索性和索引效率。Fluentd 支持通过附加 K8s 元数据(如命名空间或 Pod/容器名称)来丰富日志,然后再将日志发送到存储系统。它还支持日志过滤、缓冲和压缩,有助于在大规模 K8s 环境中优化资源使用。在 K8s 日志管道中,Fluentd 通常与 Loki(用于高效的日志存储)或 Elasticsearch(用于全文日志搜索)一起使用。
Fluent Bit (fluentbit.io/
) 是 Fluentd 的轻量级版本,针对低资源环境和边缘计算进行了优化,具有更小的内存占用要求。以下表格提供了 Fluentd 和 Fluent Bit 的高层次对比,突出它们的关键差异和典型使用场景:
功能 | Fluentd | Fluent Bit |
---|---|---|
资源使用 | 显著的 CPU 和内存使用 | 轻量级;最小的 CPU 和内存使用 |
架构 | 使用 Ruby 和 C 编写 | 使用 C 编写 |
插件生态系统 | 拥有超过 1,000 个外部插件的大型插件库 | 超过 100 个内置插件 |
部署模型 | 作为 K8s DaemonSet 或 Sidecar 部署,支持多个插件 | 作为 K8s DaemonSet 或 Sidecar 部署,支持多个插件 |
可扩展性 | 在大规模下需要更多资源 | 在资源受限的环境中非常可扩展 |
使用案例 | 容器/服务器 | 嵌入式 Linux/容器/服务器 |
Loki
Loki (github.com/grafana/loki
) 是一个轻量级、可扩展的日志聚合系统,专为 K8s 环境设计。与传统的日志管理系统(如 Elasticsearch)不同,Loki 只索引元数据(标签),而不是完整的日志内容,使其在大规模部署中更高效、成本更低。Loki 与 Prometheus 和 Grafana 集成,允许用户将日志与指标关联,从而更好地进行故障排查和可观测性分析。
Loki 使用 Fluentd 或 Fluent Bit 等代理从 K8s Pods 收集日志,这些代理作为 DaemonSets 运行,将日志转发到 Loki 进行存储和查询,如 图 12.3 所示。这使得开发人员和运维人员能够高效地使用 日志查询语言(LogQL)(grafana.com/docs/loki/latest/query/
) 搜索日志,按命名空间、Pod 或容器过滤日志,并在 Grafana 中可视化日志数据。
图 12.3 – Fluent Bit/Loki 在 K8s 中的部署
OpenTelemetry
OTel 是一组 API、SDK 和工具,您可以使用它们来检测、生成、收集和导出遥测数据(指标、日志和追踪)。它通常用于收集并导出遥测数据到各种后端服务,如 Prometheus、Jaeger、Datadog 和 AWS X-Ray。
在 K8s 中,OTel 通过收集指标、追踪和日志,实现统一的可观测性,这些数据可以从 Pods、容器或节点中收集。它支持多种后端进行数据存储,并与 Go、Java、Python 和 Node.js 等编程语言的自动化仪表化兼容。有关自动化仪表化的详细步骤,请参考 OTel 文档:opentelemetry.io/docs/platforms/kubernetes/operator/automatic/
。
OTel 收集器 (opentelemetry.io/docs/collector/
) 充当中央代理,接收、处理并导出遥测数据。这些收集器支持三种遥测信号(指标、追踪和日志),使其成为一个强大的统一可观测性解决方案。OTel 导出器 (opentelemetry.io/docs/languages/go/exporters/
) 用于将收集的数据发送到后端系统,如 Prometheus、Jaeger 和 Datadog。
要在 K8s 中部署 OTel,可以通过使用 Helm 图表安装 OTel 收集器,并配置收集器以定义遥测数据的接收器、处理器和导出器。请访问 opentelemetry.io/docs/demo/kubernetes-deployment/
了解 K8s 中的 OTel 部署文档。或者,您也可以使用 AWS 发行版的 OTel 项目——AWS Distro for OpenTelemetry(ADOT)(aws-otel.github.io/
)——以获得一个安全、生产就绪的开源发行版,具备可预测的性能。要在 Amazon EKS 上部署 ADOT,请安装 ADOT 管理的附加组件,并配置收集器将可观测性数据转发到您的首选目标。请参考 aws-otel.github.io/docs/getting-started/adot-eks-add-on
获取详细的安装说明。
Prometheus
Prometheus (prometheus.io/
) 是一个开源监控和报警工具,旨在收集和查询时间序列指标。它最初由SoundCloud (soundcloud.com/
) 于 2012 年开发,解决动态和分布式系统的挑战,并于 2016 年作为 K8s 之后的第二个托管项目加入了Cloud Native Computing Foundation(CNCF)。Prometheus 收集并存储指标作为时间序列数据,其中每个数据点都记录了时间戳和被称为标签的键值对。它在 K8s 环境中被广泛使用,为系统性能和资源利用提供实时洞察。在第十章中,我们简要讨论了 Prometheus 代理和适配器;现在我们来深入了解一下。
下图展示了 Prometheus 的高层架构及其生态系统中的一些关键组件:
图 12.4 – Prometheus 架构
以下是 Prometheus 架构的关键组件:
-
Prometheus 服务器 (
github.com/prometheus/prometheus
): Prometheus 服务器是一个核心组件,它从配置的端点(如节点、Pod 或服务)抓取指标,并将这些指标存储在时间序列数据库中,可以通过 Prometheus 查询语言(PromQL)进行查询。Prometheus 在 K8s 中执行自动目标发现,以简化在动态的容器化环境中的监控。Prometheus 不需要手动指定监控的端点,而是通过 K8s API 动态发现目标,如 Pod、端点和服务。这确保了当工作负载在集群内扩展或迁移时,Prometheus 能自动调整并继续监控,而无需更改配置。 -
Prometheus 导出器 (
prometheus.io/docs/instrumenting/exporters/
): Prometheus 导出器是以 Prometheus 格式暴露指标的库。导出器对于将 Prometheus 与本身不原生暴露指标的系统或应用程序集成至关重要。它们充当中介,收集目标系统的数据并通过 HTTP 端点将其暴露出来供 Prometheus 抓取。在 K8s 中,导出器被广泛用于监控集群的各个组件,包括节点、应用程序和外部系统。对于 GenAI 工作负载,导出器尤为关键,用于监控 GPU、推理延迟和资源利用率。NVIDIA DCGM 导出器暴露了 GPU 的指标,如利用率、内存使用、温度和功耗,这对监控 GPU 工作负载至关重要。我们在 第十章 中的 EKS 集群设置中部署了此附加组件。 -
Prometheus 警报管理器 (
prometheus.io/docs/alerting/latest/overview/
): Prometheus 警报管理器处理由 Prometheus 生成的警报,将通知发送到不同的渠道,如 Slack (slack.com/
)、电子邮件或 PagerDuty (www.pagerduty.com/
)。它可以配置用于 GenAI 用例,如资源饱和警报或更高的推理延迟。Prometheus 警报管理器支持去重功能,能够整合由多个 Prometheus 实例生成的重复警报,防止通知泛滥,还支持警报归组,将相似的警报归为一个通知,以提高可读性。 -
Prometheus Pushgateway (
prometheus.io/docs/instrumenting/pushing/
):这是 Prometheus 生态系统的一个组件,允许短暂的作业将它们的指标推送到 Prometheus。这对于短暂的工作负载(如无法直接被 Prometheus 抓取的短 GenAI 任务)特别有用。在 K8s 环境中,Pushgateway 充当中间件,允许批处理作业、cron 作业和其他短暂工作负载将指标发布到持久端点。然后,Prometheus 定期从这个端点抓取数据。 -
PromQL (
prometheus.io/docs/prometheus/latest/querying/basics/
):PromQL 是一种强大而灵活的查询语言,用于提取和分析存储在 Prometheus 生态系统中的时间序列数据,例如 Prometheus 服务器本身或依赖 Prometheus 作为后端的工具(如 Grafana)。它允许用户对指标执行计算、基于标签过滤和聚合,并通过查询推断出洞察。
现在我们已经介绍了 Prometheus stack 的关键组件,让我们讨论如何在 K8s 环境中部署它。
部署 Prometheus stack
在 K8s 中,推荐将 Prometheus 服务器部署为 StatefulSet。StatefulSet 是一个 K8s 控制器,用于管理需要稳定标识和持久存储的有状态应用程序。与 Deployments 不同,StatefulSets 为每个 Pod 分配一个唯一的、稳定的主机名,并确保存储卷在 Pod 重新启动时保持持久。这确保了依赖于在重新启动或重新调度时保持状态的工作负载的一致性和可靠性。将 Prometheus 服务器部署为 StatefulSet 可以确保其具有用于指标的持久存储访问,使用 PersistentVolumeClaim (PVC)。
为了简化 K8s 设置,Prometheus 社区开发了 Helm charts 来部署所有必要的组件,可以在 github.com/prometheus-community/helm-charts
找到。在我们的设置中,我们将使用 Terraform Helm provider 部署 kube-prometheus-stack
。这个 Helm chart 在我们的 EKS 集群中部署了 Prometheus 和 Grafana 实例,如 Figure 12**.5 所示。部署完成后,我们将配置 Prometheus 从集群内各个组件(如 NVIDIA DCGM exporter、Qdrant 向量数据库、Ray Serve 部署等)中抓取指标。Prometheus 会自动发现相关的目标端点,并定期收集指标。通过 Grafana web 控制台,我们可以可视化和查询这些指标,构建和导入交互式仪表板,并定义警报规则。
Figure 12.5 – 在 EKS 集群中设置 Prometheus 和 Grafana
要开始使用,下载 GitHub 仓库中的addons.tf
和kube-prometheus.yaml
文件,地址为github.com/PacktPublishing/Kubernetes-for-Gen-AI-Models/tree/main/ch12/
。kube-prometheus-stack
Helm 图表以及prometheus-adapter
将被部署到monitoring命名空间中,如下代码片段所示:
module "eks_blueprints_addons" {
source = "aws-ia/eks-blueprints-addons/aws"
...
enable_kube_prometheus_stack = true
kube_prometheus_stack = {
namespace = "monitoring"
values = [
templatefile("${path.module}/kube-prometheus.yaml", {
storage_class_type = kubernetes_storage_class.default_gp3.id
})]
...
helm_releases = {
"prometheus-adapter" = {
repository = "https://prometheus-community.github.io/helm-charts"
chart = "prometheus-adapter"
namespace = module.eks_blueprints_addons.kube_prometheus_stack.namespace
...
Prometheus 设置通过kube-prometheus.yaml
Helm 值文件进行定制。它执行以下操作:
-
gp3
存储类 -
将
alertmanager.enabled
值设置为true
* Grafana 配置:- 部署 Grafana 并启用默认监控仪表盘
执行以下terraform
命令,在 EKS 集群中部署kube-prometheus-stack
和prometheus-adapter
Helm 图表:
$ terraform init
$ terraform plan
$ terraform apply -auto-approve
使用以下kubectl
命令验证安装情况。输出应确认 Prometheus StatefulSet、节点导出 DaemonSet、Grafana、kube-state-metrics
、prometheus-adapter
和prometheus-operator
部署处于READY状态:
$ kubectl get ds -n monitoring
NAME READY
kube-prometheus-stack-prometheus-node-exporter 6
$ kubectl get deploy -n monitoring
NAME READY
kube-prometheus-stack-grafana 1/1
kube-prometheus-stack-kube-state-metrics 1/1
kube-prometheus-stack-operator 1/1
prometheus-adapter 1/1
$ kubectl get sts -n monitoring
NAME READY
prometheus-kube-prometheus-stack-prometheus 1/1
在更大的环境中,建议为不同的指标分配足够的 CPU 和内存资源,并使用适当的抓取间隔来优化资源使用。例如,对于关键指标如 CPU 或内存利用率,建议使用 10 到 15 秒的间隔,而对于不太关键的指标,间隔可以设置为分钟级别。
在本节中,我们介绍了如何在 EKS 集群中部署 Prometheus 堆栈。然而,要深入了解 GPU 性能(对于 AI/ML 工作负载至关重要),我们需要启用 GPU 的指标抓取。在下一节中,我们将介绍如何通过配置服务监控器将 NVIDIA 的 DCGM 导出器与 Prometheus 集成。
启用 GPU 监控
在第十章中,我们部署了NVIDIA DCGM Exporter 插件,以便获取 GPU 利用率指标。在设置过程中,我们禁用了服务监控器,这些监控器使 Prometheus 能够定期抓取指标。Prometheus 服务监控器(prometheus-operator.dev/docs/developer/getting-started/#using-servicemonitors
)和Pod 监控器(prometheus-operator.dev/docs/developer/getting-started/#using-podmonitors
)是自定义资源定义(CRDs),允许 Prometheus Operator 在 K8s 集群中自动发现并配置监控目标。通过在 K8s 服务和 Pod 上使用标签选择器,这些监控器简化了收集指标的过程。
让我们更新 Terraform 代码,以通过服务监视器启用 dcgm-exporter
指标的抓取。首先从 GitHub 仓库下载 aiml-addons.tf
文件,链接为 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch12/aiml-addons.tf
:
resource "helm_release" "dcgm_exporter" {
name = "dcgm-exporter"
...
values = [
<<-EOT
serviceMonitor:
terraform commands to update the NVIDIA DCGM exporter Helm chart in the EKS cluster:
$ terraform init
$ terraform plan
$ terraform apply -auto-approve
We can verify the scraping status by launching the Prometheus dashboard and checking the scraping target’s health. By default, the Prometheus service is internal and not exposed outside of the cluster, so let’s use the `kubectl port-forward` mechanism to connect to it from the local machine. Run the following command to initiate a port-forward connection from local port `9090` to Prometheus service port `9090` in the `monitoring` namespace:
$ kubectl port-forward svc/kube-prometheus-stack-prometheus -n monitoring 9090:9090
正在从 127.0.0.1:9090 转发到 9090
正在从 [::1]:9090 转发到 9090
Now, launch the [`localhost:9090/targets?search=dcgm-exporter`](http://localhost:9090/targets?search=dcgm-exporter) URL in your browser to check the health of the DCGM exporter targets. *Figure 12**.6* shows that all three `dcgm-exporter` endpoints are in the **UP** state, with each corresponding to a GPU worker node in the cluster:

Figure 12.6 – Prometheus target health status
Next, we can query the `dcgm-exporter` metrics using PromQL. For example, *Figure 12**.7* shows the average GPU utilization across multiple GPUs over the past minute grouped by the K8s Pod, where `DCGM_FI_DEV_GPU_UTIL` is the metric exposed by `dcgm-exporter`.

Figure 12.7 – Querying GPU metrics using PromQL
Similarly, we will create additional Service Monitors to collect metrics from both the Ray Serve cluster deployed in *Chapter 11* and various e-commerce chatbot application components. You can download the corresponding K8s manifest files from the GitHub repository at [`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch12/monitoring`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch12/monitoring) and apply each file using the `kubectl apply -f` command to deploy them to the EKS cluster:
$ kubectl apply -f <替换为服务监视器文件名>
After applying the files, navigate to the Prometheus dashboard and verify the status of the newly discovered scraping targets. You will notice both `ray-workers` and `ray-head` Pod targets in the dashboard, as shown in *Figure 12**.8*.

Figure 12.8 – Prometheus target health status
In this section, we explored various tools to monitor GenAI applications in K8s, such as Fluentd, Fluent Bit, Loki, OTel, and Prometheus. We also deployed `kube-prometheus-stack` in the EKS cluster and set up the Service/Pod monitors to collect metrics from `dcgm-exporter` and other application components in our EKS cluster. In the next section, we will dive into visualization tools such as Grafana to view these metrics.
Visualization and debugging
In this section, we will explore key practices for enhancing the observability of GenAI applications deployed on K8s. We will begin by demonstrating how Grafana can be used to visualize essential metrics such as GPU utilization, system performance, and the status of GenAI applications, to provide real-time operational insights. Additionally, we will dive into debugging strategies for Gen AI workloads by examining tools such as **Langfuse** ([`github.com/langfuse/langfuse`](https://github.com/langfuse/langfuse)), an open source LLM engineering platform designed to aid in debugging and the analysis of LLM applications.
Grafana
**Grafana** ([`grafana.com/`](https://grafana.com/)) is an open source visualization and analytics platform widely used for monitoring K8s environments. It provides a centralized interface for querying, visualizing, and alerting on metrics collected from various data sources, such as Prometheus, Amazon CloudWatch, and Azure Monitor.
Grafana provides prebuilt and customizable dashboards to monitor K8s components such as API servers, etcd databases, nodes, Pods, and namespaces. These dashboards help visualize metrics such as CPU and memory utilization, network activity, and application-specific metrics. Grafana allows users to configure alerts with notifications sent to channels such as Slack, email, PagerDuty, or **Microsoft Teams**. Alerts are typically based on threshold conditions, though you can also implement anomaly detection through custom queries or external integrations.
Grafana provides the ability to define roles and permissions using **role-based access control** (**RBAC**), allowing fine-grained control over who can view or edit dashboards and alerts. Grafana supports a wide range of community-contributed plugins ([`grafana.com/grafana/plugins/`](https://grafana.com/grafana/plugins/)), custom visualization panels ([`grafana.com/grafana/plugins/panel-plugins/`](https://grafana.com/grafana/plugins/panel-plugins/)), and dashboards ([`grafana.com/grafana/dashboards/`](https://grafana.com/grafana/dashboards/)), enabling users to extend its functionality and adapt it to specific use cases.
Grafana best practices
The following lists some Grafana best practices:
* Ensure Grafana dashboards and settings persist across Pod restarts by using a **Persistent Volume** (**PV**) and a PVC. This helps maintain the state even when Grafana Pods are rescheduled.
* Automate dashboard provisioning using ConfigMaps or **infrastructure as code** (**IaC**) tools, such as Terraform, to maintain consistent observability setups across environments.
* Enable robust authentication mechanisms ([`grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/`](https://grafana.com/docs/grafana/latest/setup-grafana/configure-security/configure-authentication/)) such as OAuth, SAML, and LDAP, to control user access. Use RBAC to manage user permissions effectively.
* When exposing the Grafana dashboard outside the cluster, use an Ingress controller with TLS termination to secure network communications.
* Leverage Grafana’s rich plugin ecosystem to integrate with external data sources and specialized visualizations.
* Enhance observability by combining metrics, logs, and traces. Integrate Grafana with Loki for centralized K8s logging alongside metrics visualization.
* Monitor Grafana’s resource usage and performance within the K8s cluster. This includes setting up alerts for abnormal behavior, which helps maintain optimal performance and availability.
* Use managed Grafana offerings such as **Amazon Managed Grafana** ([`aws.amazon.com/grafana/`](https://aws.amazon.com/grafana/)), **Grafana Cloud** ([`grafana.com/products/cloud/`](https://grafana.com/products/cloud/)), and **Azure Managed Grafana** ([`azure.microsoft.com/en-us/products/managed-grafana`](https://azure.microsoft.com/en-us/products/managed-grafana)) to offload operational tasks such as scaling, patching, and security management. This enables us to focus on creating dashboards and analyzing data. These services also provide seamless cloud-native integrations, auto-scaling capabilities, and cost efficiencies.
Setting up Grafana dashboards
In our setup, we deployed Grafana as part of the `kube-prometheus-stack` installation earlier in this chapter. Alternatively, Grafana Helm charts can be leveraged to deploy as a standalone option in K8s; refer to [`grafana.com/docs/grafana/latest/setup-grafana/installation/helm/`](https://grafana.com/docs/grafana/latest/setup-grafana/installation/helm/) for step-by-step instructions. By default, the Grafana service is accessible within the cluster unless it is exposed outside via the K8s `LoadBalancer` service type or an Ingress resource. So, let’s use the `kubectl port-forward` mechanism to connect to the Grafana console:
$ kubectl port-forward svc/kube-prometheus-stack-grafana -n monitoring 8080:80
正在从 127.0.0.1:8080 转发到 80
正在从 [::1]:8080 转发到 80
Now, open [`localhost:8080/`](http://localhost:8080/) in your browser to access the Grafana console. When prompted for credentials, note that a default admin user is automatically created during the Helm chart installation. You can retrieve the credentials from the K8s secret named `kube-prometheus-stack-grafana` in the `monitoring` namespace by running the following command:
$ kubectl get secret kube-prometheus-stack-grafana -n monitoring -o go-template='{{printf "用户名: %s\n 密码: %s\n" (index .data "admin-user" | base64decode) (index .data "admin-password" | base64decode)}}'
Once logged in to the Grafana console, navigate to **Connections** | **Data sources** in the left side menu bar to view and manage the connected data sources. You will notice the local Prometheus server is already added to the data sources, as shown in *Figure 12**.9*.

Figure 12.9 – Grafana connected data sources
As Grafana has access to Prometheus metrics, let’s start exploring the Grafana dashboards. Navigate to `kube-prometheus-stack` installation, as shown in *Figure 12**.10*.

Figure 12.10 – Grafana dashboards list
These Grafana dashboards provide preconfigured monitoring views tailored for various K8s components, such as **CoreDNS**, **API server**, **Namespace**, **Pod**, **Workload**, **Node**, **Scheduler**, **Controller Manager**, and **kubelet**. You can select and explore these dashboards to gain insights into the performance, resource usage, and health of the K8s environments. For example, select the **Kubernetes** | **Compute Resources** | **Cluster** dashboard to view an overview of resource utilization, including CPU, memory, and storage metrics across the entire K8s cluster, helping you monitor and optimize cluster performance, as shown in *Figure 12**.11*.

Figure 12.11 – Kubernetes Compute Resources dashboard
In this dashboard, we can see the CPU, memory quota, and usage metrics aggregated by each K8s namespace at the cluster level.
While the default dashboards offer comprehensive insights into core K8s components, you may also need visibility into specialized resources, depending on your workload. Now, let’s take a look at how to visualize GPU metrics using the NVIDIA DCGM exporter dashboard.
NVIDIA DCGM dashboard
Earlier in this chapter, we enabled metrics collection from the NVIDIA DCGM exporter add-on using Prometheus Service Monitor resources. Now, we will visualize these metrics using Grafana dashboards. NVIDIA published a Grafana dashboard at [`grafana.com/grafana/dashboards/12239-nvidia-dcgm-exporter-dashboard/`](https://grafana.com/grafana/dashboards/12239-nvidia-dcgm-exporter-dashboard/) to monitor GPU utilization metrics.
You can import this dashboard to your Grafana instance using the instructions at [`grafana.com/docs/grafana/latest/dashboards/build-dashboards/import-dashboards/`](https://grafana.com/docs/grafana/latest/dashboards/build-dashboards/import-dashboards/). Once the import is successful, you will be able to visualize the GPU metrics, as shown in *Figure 12**.12*.

Figure 12.12 – DCGM exporter Grafana dashboard
This dashboard provides real-time visibility into key GPU performance metrics such as temperature, power usage, clock speeds, and utilization. With this information, you can quickly identify performance bottlenecks, detect potential issues, and optimize resource usage.
In addition to dashboards, we can also define Prometheus alerting rules to monitor GPU health and performance. For example, we can create a `gpu-rules.yaml` from our GitHub repository at [`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch12/monitoring/gpu-rules.yaml`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch12/monitoring/gpu-rules.yaml) and run the following command to configure them in our setup:
$ kubectl apply -f gpu-rules.yaml
Once deployed, we can visualize the rules in the Prometheus or Grafana console. In the Grafana console, navigate to **Alerting** | **Alert rules** in the left side menu bar to view the status of the alert rules, as shown in *Figure 12**.13*.

Figure 12.13 – NVIDIA GPU alert rules in the Grafana console
As shown in *Figure 12**.13*, one of the GPU alerting rules is in a **Firing** state due to low GPU utilization on one of our worker nodes. To investigate further, we can expand the alert rule to view detailed information such as the worker node, GPU identifier, and the associated K8s Pod, as illustrated in *Figure 12**.14*.

Figure 12.14 – GPU alert rule details in the Grafana console
While the DCGM dashboard provides deep visibility into GPU performance, it’s also important to monitor the higher-level services that rely on these resources, especially in GenAI workloads. One such example is Ray Serve, which plays a key role in serving models such as Llama 3 in our deployment. Let’s now set up a dedicated Grafana dashboard to monitor the performance and resource usage of Ray Serve components.
Ray Serve dashboard
In *Chapter 11*, we deployed a Ray cluster in our EKS cluster and used the Ray Serve framework to expose the Llama 3 model. Earlier in this chapter, we also created Prometheus Service and Pod Monitor resources to gather metrics from both the Ray cluster and Ray Serve deployments. Now, we will create a `ray-serve-dashboard.json`) from our GitHub repository at [`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch12/dashboards`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch12/dashboards).
Open the Grafana console, navigate to the `ray-serve-dashboard.json` file from your local filesystem and click the **Import** button. Once imported, select **Ray Serve Dashboard** from the dashboards list to view real-time information about your Ray Serve deployments, helping you identify bottlenecks and optimize performance, as shown in *Figure 12**.15*.

Figure 12.15 – Ray Serve Grafana dashboard
Just like GPU alerting rules in the previous section, we can also define Prometheus alerting rules to monitor the health and performance of Ray Serve deployments. For example, we can configure a Prometheus rule to trigger alerts based on conditions such as high error rates, increased latency, Ray worker node failures, response latency spikes, or low throughput. To create these rules, download the `ray-serve-rules.yaml` file from our GitHub repository at [`github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch12/monitoring/ray-serve-rules.yaml`](https://github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/blob/main/ch12/monitoring/ray-serve-rules.yaml) and run the following command to configure them in our setup:
$ kubectl apply -f ray-serve-rules.yaml
Once deployed, we can visualize the rules in the Prometheus or Grafana console. In the Grafana console, navigate to **Alerting** | **Alert rules** in the left side menu bar to view the status of the alert rules, as shown in *Figure 12**.16*.

Figure 12.16 – Ray Serve alert rules in Grafana
As we have covered the different observability tools for monitoring GenAI workloads in K8s, let’s now explore how to extend these concepts for GenAI frameworks, such as LangChain.
LangChain observability
**LangChain** ([`github.com/langchain-ai/langchain`](https://github.com/langchain-ai/langchain)) is a framework for building applications with LLMs, which we covered in *Chapter 4*. It integrates with various tools to enable observability and debugging.
LangChain provides built-in capabilities to log and trace the execution of chains, agents, and tools. These features allow developers and operators to understand how prompts, responses, and workflows behave during execution. The `verbose=True` in chains, agents, or tools.
LangChain can integrate with **LangChainTracer** to collect execution data, including steps, timing information, errors, and retries. The tracer can be used via **LangSmith** ([`www.langchain.com/langsmith`](https://www.langchain.com/langsmith)) or deployed as a self-hosted server in K8s.
The following code snippet defines a custom debugging and observability callback handler for LangChain, which helps track the execution flow of a chain. It logs when a chain starts, when it completes, and the time taken for execution, and handles errors gracefully:
import time
from langchain.callbacks.base import BaseCallbackHandler
from langchain.chains import LLMChain
from langchain_openai import OpenAI
from langchain.prompts import PromptTemplate
class DebugCallbackHandler(BaseCallbackHandler):
def init(self):
self.start_time = None
def on_chain_start(self, inputs, **kwargs):
"""在链条开始执行时触发。"""
self.start_time = time.time()
print("\n[调试] 链接开始...")
print(f" 输入: {inputs}")
def on_chain_end(self, outputs, **kwargs):
"""在链条成功完成执行时触发。"""
elapsed_time = time.time() - self.start_time
print(f"\n[调试] 链接在 {elapsed_time:.2f} 秒内完成")
print(f"输出: {outputs}")
def on_chain_error(self, error, **kwargs):
print("\n [错误] 链接遇到错误!")
print(f" 错误信息: {error}")
debug_handler = DebugCallbackHandler()
The `DebugCallbackHandler` class extends `BaseCallbackHandler`, making it compatible with LangChain’s callback system. The constructor initializes a `self.start_time` variable, which is used to track the execution duration.
The `on_chain_error` method is called when an error occurs during chain execution. It prints an error message along with details about the encountered exception.
This callback handler is useful for debugging and performance monitoring in LangChain applications by providing real-time logs for execution tracking, timing analysis, and error handling.
While LangChain’s built-in logging and tracing capabilities provide a solid foundation for understanding the internal workings of LLM chains and agents, there are scenarios where more advanced observability tools are needed, especially for production-grade applications. This is where platforms such as LangFuse come into play, offering richer insights, distributed tracing, and powerful dashboards tailored for LLM workflows. Let’s take a closer look at how LangFuse enhances observability for GenAI applications in the next section.
LangFuse
**LangFuse** ([`langfuse.com/`](https://langfuse.com/)) is an open source observability and monitoring platform tailored for LLM applications. It provides deep insights into the execution of AI workflows by tracking user interactions, prompts, responses, and application performance. LangFuse supports key observability features such as logging, tracing, metrics collection, and visualization, making it invaluable for debugging and optimizing LLM-based applications.
LangFuse benefits from K8s-native capabilities such as scalability, auto-healing, and seamless integration with managed services. LangFuse collects and visualizes critical metrics and traces related to prompts, model latency, response accuracy, and system health. It supports distributed tracing, allowing developers to trace user interactions across multiple components, such as API gateways, vector databases, and LLM endpoints, to diagnose performance bottlenecks or errors.
Key features of LangFuse include the following:
* It logs detailed information about requests, including the input prompt, LLM-generated responses, and associated metadata such as token usage and model-specific parameters
* It captures the end-to-end lifecycle of interactions, enabling you to monitor every step in workflows, from user input to database queries and LLM outputs
* It provides interactive dashboards to visualize system performance, latency trends, error rates, and other **key performance** **indicators** (**KPIs**)
* It links errors or delays in AI pipelines to specific users, prompts, or workflows for faster debugging and resolution
* It easily integrates with Prometheus, Grafana, OTel, and other K8s monitoring tools to enhance existing observability stacks
For K8s deployments, LangFuse offers flexibility in deployment configurations, enabling you to run the observability stack alongside your AI workloads. It is compatible with Helm charts, ensuring smooth deployment and configuration in cloud-native environments. Detailed deployment instructions and configurations for K8s and EKS are available in the LangFuse documentation at [`langfuse.com/self-hosting/kubernetes-helm`](https://langfuse.com/self-hosting/kubernetes-helm).
In this section, we explored various visualization and debugging tools for monitoring GenAI applications in K8s, including Grafana, LangChain, and LangFuse. We deployed Grafana in our EKS cluster and imported prebuilt dashboards to view the key performance metrics of various components, such as the API server, Ray Serve deployments, and so on. Additionally, LangChain and LangFuse provide advanced debugging and observability features for GenAI workloads, enabling you to trace LLM calls, monitor model outputs, and optimize prompt configurations.
Summary
In this chapter, we covered key observability concepts for monitoring GenAI applications in K8s. We understood why monitoring is critical for optimizing GenAI workloads, examining both system-level metrics and application-specific signals. We explored a comprehensive monitoring framework using tools such as Prometheus for metrics collection, Grafana for visualization, and LangFuse and LangChain for debugging.
In K8s, various tools cater to different facets of the observability framework. Prometheus excels at collecting and querying time-series metrics, offering built-in alerting capabilities and seamless integration with K8s. Fluentd and Fluent Bit serve as a unified logging layer, collecting data from diverse sources and routing it to multiple destinations. OpenTelemetry provides a vendor-neutral set of APIs and libraries for generating and processing telemetry data, spanning metrics, logs, and traces.
Grafana provides an intuitive interface to view and analyze metrics, logs, and traces, making it easy to detect anomalies and investigate performance bottlenecks. LangFuse specializes in detailed logging and observability of LLM-based requests, capturing prompts, responses, and metadata to facilitate faster debugging. LangChain offers a framework for orchestrating and experimenting with LLM workflows, helping us better understand and refine prompt engineering, chaining logic, and model responses.
In the next chapter, we will explore how to set up high availability and disaster recovery for GenAI applications on K8s.
第十二章:GenAI 应用程序的高可用性和灾难恢复
本章将探讨针对部署在Kubernetes(K8s)集群上的 GenAI 应用程序量身定制的高可用性(HA)和灾难恢复(DR)的概念。考虑到 GenAI 应用程序的动态性和资源密集型特性,确保无缝的可扩展性和强大的弹性对高质量的生产部署至关重要。我们将讨论各种架构模式和配置,使 GenAI 工作负载能够根据使用需求自动扩展,同时确保在发生区域性故障等灾难时持续提供服务。
本章将涵盖以下主要内容:
-
HA 和 DR 的设计
-
K8s 中的弹性
-
K8s 中的 DR 策略
HA 和 DR 的设计
HA (docs.aws.amazon.com/whitepapers/latest/disaster-recovery-workloads-on-aws/high-availability-is-not-disaster-recovery.html
) 确保系统在最小的停机时间内保持运行,通过消除单点故障来实现。它依赖于节点、区域或集群之间的冗余,并旨在保持持续的服务。HA 通过正常运行时间百分比、故障切换时间和系统冗余来衡量。例如,一个正常运行时间为 99.99%的系统每年只允许约 53 分钟的停机时间。在 GenAI 的背景下,基础模型通常推动着如客户支持、实时文本和图像分析等关键业务操作,停机可能非常昂贵。HA 确保以下几点:
-
推理端点保持一致的响应性,满足业务的可用性需求
-
训练作业可以在节点或服务故障时继续运行,而不会中途崩溃
DR (docs.aws.amazon.com/whitepapers/latest/disaster-recovery-workloads-on-aws/disaster-recovery-options-in-the-cloud.html
) 旨在恢复因硬件故障、网络攻击或自然灾害等灾难性故障导致的服务。它确保数据得到备份,并能够快速恢复,以恢复运营。DR 策略涉及定期的数据备份、冗余以及自动化恢复工作流。与 HA 不同,HA 旨在防止停机,而 DR 则接受一定程度的停机和数据丢失,但确保系统可以高效恢复。
定义 HA 和 DR 的三个关键指标是恢复点目标(RPO)、恢复时间目标(RTO)和最大容忍停机时间(MTD):
-
RPO(恢复点目标)表示在恢复之前可允许的数据丢失最大量。RPO 为 0 的系统需要实时数据复制以确保没有数据丢失,而 RPO 为数小时的系统可能使用定期备份。RPO 越低,备份机制需要越先进。
-
RTO(恢复时间目标)确定了服务恢复之前可接受的停机时间。低 RTO(以秒或分钟为单位)要求有活动-活动的故障转移,并且冗余系统始终处于待命状态,而较高的 RTO 则允许手动干预并从备份中恢复。
-
MTD(最大容忍停机时间)是指服务不可用的最长时间,超过这个时间会对组织造成不可接受的后果。它定义了停机的阈值,超过该阈值服务可能面临运营或财务挑战。MTD 是业务连续性计划(BCP)和灾难恢复(DR)策略的关键组成部分。
图 13.1 展示了这些关键指标——RTO、RPO 和 MTD 在数据丢失和系统故障后的停机时间背景下的含义。
图 13.1 – 不同的恢复目标
高可用性应用程序应该能够承受故障,并在部分网络中断或硬件故障的情况下保持持续运行。这要求应用程序没有单点故障,并且工作负载分布在多个独立的故障域中,例如节点、可用区(AZs)和集群。
各级冗余有助于处理潜在的故障。实现高可用性的 K8s 的关键原则包括:
-
冗余:避免应用组件和基础设施中的单点故障。使用 K8s 的 Deployment 或 ReplicaSet 对象部署多个应用副本,可以确保在故障情况下的冗余。
-
自动扩缩容:K8s 的水平 Pod 自动扩缩容(HPA)可以根据需求调整 Pod 副本的数量,确保应用程序能够高效地处理不同的负载。此外,集群自动扩展器(Cluster Autoscaler)和 Karpenter 可以根据 Pod 的调度需求帮助管理工作节点的扩展。
-
自愈:使用 K8s Deployment 部署应用程序时,K8s 会自动替换故障的 Pod,保持应用程序的期望状态。
-
更安全的升级和回滚:通过采用蓝绿部署和金丝雀部署等应用程序部署策略,您可以确保新版本的应用程序安全地引入。这些策略使得在全面推出之前,可以先在部分用户中测试新版本,从而减少广泛出现问题的风险。
-
混沌工程:定期模拟应用程序中的故障,以验证高可用性(HA)设置。根据模拟事件审查并改进运行手册和操作指南。
-
可观察性:收集日志、指标和追踪信息,以实时查看基础设施和应用程序的健康状况及性能。配置警报以检测故障的早期迹象,如延迟、错误率等。
在本节中,我们讨论了高可用性(HA)和灾难恢复(DR)对于生成型人工智能(GenAI)应用的重要性,这些应用对停机和性能下降具有独特的敏感性。我们还重点介绍了定义 HA 和 DR 的关键指标,如 RTO、RPO 和 MTD,以及有助于实现 HA 的 K8s 关键原则。
在下一节中,我们将深入探讨这些概念,重点关注 K8s 中的弹性。
K8s 中的弹性
GenAI 应用是资源密集型的,要求具备容错性和可扩展性,以处理模型训练、大规模推理和实时 AI 工作负载。GenAI 模型通常需要 GPU 来加速推理和训练,因此 GPU 的依赖性和可用性成为部署的关键因素。这些工作负载常常会出现不可预测的资源波动,导致可扩展性挑战,这就需要动态资源配置。此外,数据的可用性和一致性也至关重要,因为大型 AI 模型依赖分布式存储和缓存来确保在多个节点之间保持性能。长时间运行的过程进一步加大了弹性的难度,因为模型训练可能需要几个小时甚至几天。
K8s 为管理 GenAI 工作负载提供了坚实的基础,但要确保弹性,需要在 K8s 的每一层进行专门的配置和最佳实践,如图 13.2所示。
图 13.2 – 不同层次的 K8s 弹性
这些层次有助于确保应用保持高可用性,并能从故障中恢复。让我们从 Pod 级别的最内层开始,逐层介绍每个层次:
-
每 10 秒在端口
80
上检查/healthz
端点;同样,准备性探针被配置为检查/readyz
端点:apiVersion: apps/v1 kind: Deployment metadata: name: my-llama32-deployment ... terminationGracePeriodSeconds: 60 containers: - name: my-llama32-container ... resources: requests: nvidia.com/gpu: 1 limits: nvidia.com/gpu: 1 livenessProbe: httpGet: path: /healthz port: 80 initialDelaySeconds: 60 periodSeconds: 10 readinessProbe: httpGet: path: /readyz port: 80 initialDelaySeconds: 60 periodSeconds: 10
-
副本级别:副本是由 K8s 控制器(如ReplicaSet或Deployment)管理的 Pod 的完全副本,前面章节已经介绍过。拥有多个副本可以确保即使一个 Pod 失败,其他实例仍然可用来处理请求。对于 AI 模型服务器(如TensorFlow Serving或Triton Inference Server)来说,确保推理请求在需求增加时仍能满足服务水平协议(SLA)尤为重要。部署应根据工作负载需求和流量需求定义合适数量的副本。HPA(水平自动扩展)可以根据 CPU、内存和 GPU 使用情况动态调整副本数量,在高负载场景下提供灵活性。
对于推理工作负载,建议在更新或中断期间,使用PodDisruptionBudget(
kubernetes.io/docs/tasks/run-application/configure-pdb/
)保持最小数量的 GenAI 推理/模型服务 Pod 可用,具体操作可以参考以下 K8s 清单:apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: my-llama32-pdb spec: minAvailable: 2 selector: matchLabels: app.kubernetes.io/name: my-llama32
-
topologySpreadConstraints
用于将 Pod 副本分布到多个节点上:... topologySpreadConstraints: - labelSelector: matchLabels: app.kubernetes.io/name: my-llama32 maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway
在节点级别,K8s 通过健康检查和驱逐策略确保基本的弹性。然而,对于生产级别的 GenAI 工作负载,通常需要额外的保障措施和自动恢复。你可以利用 K8s 的
node-problem-detector
(github.com/kubernetes/node-problem-detector
)附加组件,它使集群管理堆栈中的上游层能够看到各种节点问题。它作为DaemonSet Pod 在每个工作节点上运行,扫描故障并将其报告给apiserver。亚马逊 EKS 引入了节点监控代理(
docs.aws.amazon.com/eks/latest/userguide/node-health.html
)附加组件,自动读取节点日志以检测某些健康问题,并相应地添加NodeCondition。这可以与节点自动修复(docs.aws.amazon.com/eks/latest/userguide/node-health.html#node-auto-repair
)结合使用,该功能监控节点健康,自动响应检测到的问题,并在可能的情况下替换节点。例如,当 GPU 节点上检测到Xid 错误(docs.nvidia.com/deploy/xid-errors/index.html#topic_5_1
)时,它会在 10 分钟后自动替换节点并驱逐 Pods,将其重新调度到健康的节点上。Xid 错误是由 NVIDIA GPU 驱动程序生成的错误代码,表示 GPU 遇到了问题,如死机、重置或内存故障。 -
可用区级别(AZ level):可用区(AZ)是云服务商区域内的隔离数据中心。跨多个 AZ 运行工作负载提供了更高的容错性,能够防止数据中心级别的故障。以多 AZ 配置部署的 K8s 集群确保即使整个 AZ 出现故障,应用程序仍然可以在另一个 AZ 中继续运行。你可以利用 K8s 的topologySpreadConstraints调度约束,将由 ReplicaSet 或 StatefulSet 管理的 Pod 分布到不同的故障域,如 AZ,以确保对 AZ 问题的防护。结合节点使用,可以提供额外的弹性保护层:
... topologySpreadConstraints: - labelSelector: matchLabels: app.kubernetes.io/name: my-llama32 maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: ScheduleAnyway - labelSelector: matchLabels: app.kubernetes.io/name: my-llama32 maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway
此外,Amazon EKS 支持 Amazon 应用程序恢复控制器 (ARC) 区域迁移和区域自动迁移 (
aws.amazon.com/application-recovery-controller/
)。ARC 帮助您管理和协调跨可用区(AZ)和 AWS 区域的应用恢复。通过区域迁移,您可以通过触发迁移并将集群内的网络流量重定向到健康的 AZ,暂时缓解问题和事故。为了实现完全自动化体验,您可以授权 AWS 代为管理此迁移,使用区域自动迁移。通过区域自动迁移,您可以配置演练,测试您的集群环境在少一个 AZ 的情况下是否正常运行。请参考 AWS 文档docs.aws.amazon.com/eks/latest/userguide/zone-shift.html
,了解更多有关此功能的信息,并查看启用该功能的步骤。 -
多集群部署:多集群架构涉及在多个独立的 K8s 集群中运行工作负载。这种方法对于缓解集群级别的故障非常有用,确保如果一个集群由于控制平面问题或网络中断而失败,另一个集群可以接管工作负载。多集群部署通常用于主动-主动、灾难恢复(DR)和地理分布式应用。您可以利用像Amazon Route 53 (
aws.amazon.com/route53/
) 和 AWS Global Accelerator (aws.amazon.com/global-accelerator/
) 这样的服务来执行健康检查并在多集群环境中路由流量。 -
全球部署:在最高层级,通过跨多个地理区域部署工作负载,可以确保即使整个 AWS 区域发生故障,应用程序仍然可用。这种方法不仅增强了灾难恢复能力,还为不同位置的用户提供了低延迟的访问。然而,多区域架构需要仔细管理数据一致性、复制和故障转移流程,以确保在发生区域故障时能够实现无缝恢复。由于 Amazon EKS 是区域性服务,您必须在每个 AWS 区域中配置一个单独的 EKS 集群,以实现真正的全球部署。
这些层次的每一层都为 K8s 中的系统弹性做出了贡献。通过在不同层级实现冗余,组织可以构建高可用、容错的应用程序,这些应用程序能够承受各种类型的故障,从单个 Pod 崩溃到整个区域的停机。
K8s 的其他高可用性和弹性选项包括负载均衡和服务发现:
-
负载均衡:K8s 服务提供内置的负载均衡功能,将网络流量分发到多个 Pod 实例。通过定义服务,您可以将运行在一组 Pods 上的应用程序暴露为网络服务,并由 K8s 处理流量分配,确保没有单个 Pod 成为瓶颈。
-
服务发现:K8s 提供了服务发现机制,允许应用程序和服务高效地定位并相互通信,即使实例正在创建或终止。通过环境变量或 DNS 实现这一动态发现,使得集群内的服务可以无缝互动。
在本节中,我们讨论了如何在 K8s 环境中的各个层次实现弹性,从单个 Pods 到多可用区(AZ)、多集群和多区域架构。在下一节中,我们将探讨各种灾难恢复策略,以及它们如何应用于 K8s 工作负载。
K8s 中的灾难恢复策略
灾难恢复(DR)关注的是在自然灾害、安全漏洞和重大系统故障等灾难事件后恢复服务和数据。对于 K8s 来说,一个有效的 DR 计划应当旨在最小化数据丢失(RPO)并减少停机时间(RTO)。
图 13.3 突出了 AWS 白皮书中提到的四种不同的云灾难恢复策略:docs.aws.amazon.com/whitepapers/latest/disaster-recovery-workloads-on-aws/disaster-recovery-options-in-the-cloud.html
。
随着我们从备份和恢复转向多站点的主动/主动模式,RPO(恢复点目标)和 RTO(恢复时间目标)从小时缩短到分钟。然而,复杂性、编排和云支出却增加了。
根据业务应用的正常运行时间需求和用例选择灾难恢复策略。
图 13.3 – 灾难恢复策略
让我们从一个高层次的角度来探索在 K8s 环境中架构这些灾难恢复(DR)策略:
-
备份和恢复(RPO/RTO 时间为小时):在 K8s 中,备份和恢复策略对于一些可以接受停机的低优先级工作负载至关重要。这种方法涉及定期备份存储在 PersistentVolumes(PVs)中的数据,以及其他集群资源,如 ConfigMaps、Secrets 和 基于角色的访问控制(RBAC)策略。在灾难发生时,所有 K8s 资源必须重新配置,并且备份的数据会被恢复。该方法具有成本效益,但恢复时间较长,因为恢复备份和重新配置集群可能需要几个小时。尽管这种方法对于非关键应用程序是可行的,但它无法满足生产工作负载对高可用性的需求。
开源工具如Velero (
velero.io/
),以及商业解决方案如Trilio for Kubernetes (trilio.io/products/kubernetes-backup-and-recovery/
) 和Portworx Backup (portworx.com/kubernetes-backup/
),提供自动化的备份和恢复功能。Velero 是一个开源的备份和恢复解决方案,专为 K8s 工作负载设计。它支持云原生环境,包括 AWS、Azure 和 Google Cloud。Velero 允许按需和定期备份 K8s 集群,涵盖 Pods、部署和持久化存储卷。它支持命名空间级别和全集群备份,提供对数据保护的精细控制。Velero 的一大优势是其灾难恢复(DR)和集群迁移能力。其调度功能允许用户通过基于 cron 的调度定义定期备份,确保遵守恢复和数据保留策略。该工具旨在支持多云环境,简化了混合云策略的实施。此外,Velero 支持加密以确保备份存储的安全,并通过 RBAC 强制执行安全最佳实践。
除了数据外,恢复集群配置、Secrets 和 RBAC 策略同样至关重要。这些配置可以通过相同的工具进行备份,或者通过基础设施即代码(IaC)或GitOps (
about.gitlab.com/topics/gitops/
) 工具进行部署。这使得在发生故障时可以快速恢复 K8s 环境。 -
Pilot light(RPO/RTO:几分钟):Pilot light 策略通过保持关键数据和最小的 K8s 基础设施持续运行,而将大部分服务保持空闲,直到发生灾难。这与备份和恢复相比,可以更快地恢复,因为一些资源已经在运行,无需从头开始部署。持久化存储保持活动状态,确保有状态应用程序保留其关键数据。然而,其他工作负载,如应用服务和网络配置,仅在检测到故障时才会变得活跃。这种方法通过只需持续提供一小部分资源,平衡了成本和恢复速度。像 Velero 这样的工具,支持命名空间级别和集群范围的备份,通过确保关键的 K8s 对象和数据随时可用,从而支持快速扩展需求。
-
冷备份(RPO/RTO:几分钟):冷备份配置确保生产环境的较小规模版本始终运行,将恢复时间缩短到几分钟。这种方法最适合业务关键的应用程序,其中必须将停机时间保持最小化,但维护全面复制环境将成本过高。冷备份集群持续运行,使用工作负载的缩减副本,允许在发生灾难时进行即时故障转移和快速的水平扩展。此外,用于 Kubernetes 的实时数据复制解决方案(如 Portworx 和 Trilio)保持集群之间的持久存储同步,确保数据一致性。与完全主动环境相比,这种方法显著降低了停机时间,同时保持成本效益。
-
多地点主/活动(RPO/RTO:几乎实时):多地点主/活动策略通过在不同区域或云提供商中实时运行多个 K8s 集群,提供了最高级别的弹性。这种设置确保零停机和几乎零数据丢失,非常适合需要连续可用性的关键服务。与其他方法不同,该策略需要完全的冗余,意味着所有工作负载和数据都同时在多个集群中复制和运行。跨区域集群部署和云负载均衡器动态分配流量,即使一个集群发生故障,也能确保无缝运行。诸如Istio之类的服务网格解决方案促进集群之间的安全通信,而数据库复制策略保持持久数据同步。尽管这种策略会产生显著的基础设施成本,但对于那些不能承受任何服务中断的组织来说,它提供了最可靠的灾难恢复解决方案。
假设你在 AWS US-EAST-1 地区运行一个 GenAI 应用程序,这是你的主要区域。为了确保高可用性,你在 US-WEST-2 地区维护了一个冷备份集群,具有最小的计算占用。
在 US-EAST-1 发生区域性故障时,以下步骤详细描述了故障转移过程的发生方式:
-
云监控,例如 Amazon Route 53 健康检查和 CloudWatch 警报,检测到 US-EAST-1 地区的服务不可用。应用程序级别的就绪性和存活性探测开始失败,表明服务出现了降级。
-
DNS 故障转移机制,例如Amazon Route 53 故障转移路由(
docs.aws.amazon.com/Route53/latest/DeveloperGuide/routing-policy-failover.html
)策略,自动将流量重定向到 US-WEST-2 备用集群。 -
在备用集群中,HPA/集群自动缩放器触发了扩展事件。GenAI 应用程序的端点和底层工作节点扩展以处理生产负载。
-
备用集群从被动模式切换到活动模式,提供生产流量服务。
-
一旦 US-EAST-1 区域恢复,评估数据完整性并同步任何错过的事务或日志。重新同步后,将 US-WEST-2 区域恢复为待命模式,并在主区域恢复正常操作。
额外的 K8s 灾难恢复(DR)考虑事项
在本节中,我们探讨了利用混沌工程自动化灾难恢复、验证系统韧性的重要性,以及实施主动监控以早期检测故障的必要性:
-
灾难恢复自动化与测试:自动化灾难恢复流程显著减少了人为错误并加速了恢复时间。使用基础设施即代码(IaC)工具,如 Terraform,确保在发生故障时 K8s 集群能够快速且一致地重新部署。自动故障转移解决方案,如 Amazon Route 53 健康检查,可以检测故障并自动将流量重定向到健康实例。为了验证灾难恢复的准备情况,组织应定期进行灾难恢复测试和演练。
混沌工程工具包括 Chaos Mesh(
chaos-mesh.org/
),一个云原生的开源 K8s 混沌工程平台,允许用户在 K8s 集群内模拟各种故障场景。它支持在多个层级(如 Pod、网络和存储层)进行精细化的混沌实验。它可以注入 Pod 故障、网络中断和节点崩溃到 K8s 部署中。它还支持 CustomResourceDefinition (CRD) 来声明式地定义混沌实验。 -
监控与可观测性:主动的监控和可观测性有助于在问题升级为重大故障之前及时发现。K8s 提供了通过活性和就绪探针进行的内置健康检查,能够重新启动不健康的 Pod,防止故障影响整个系统。日志和度量收集工具,如 Prometheus、Grafana、Fluentd 和 Elasticsearch,使集群性能和系统健康状况的实时可见性成为可能。实现与 PagerDuty 或 Slack 集成的告警系统确保事故触发即时通知,帮助响应团队迅速采取行动,减轻潜在的中断。配置良好的可观测性堆栈对于诊断问题和优化灾难恢复策略至关重要。
总结
在本章中,我们介绍了部署在 K8s 上的 GenAI 应用程序的高可用性(HA)和灾难恢复(DR)关键概念。鉴于 GenAI 工作负载对资源的高度依赖,确保在硬件故障和区域性停机事件中具有可扩展性和韧性至关重要。
高可用性(HA)通过在节点、集群和区域之间的冗余消除单点故障,从而最大限度地减少停机时间。K8s 中的主要 HA 策略包括自动扩展、自愈、多集群部署和负载均衡。
DR 专注于在硬件故障、网络攻击和自然灾害等故障发生后恢复服务。关键的 DR 指标包括 RPO、RTO 和 MTD。各种 DR 策略包括备份与恢复(恢复速度慢但具有成本效益)、飞行器灯(保持最小基础设施活跃以便更快恢复)、温备(缩小规模的实时环境,能够快速扩展)和多站点活动/活动部署(完全冗余集群,确保接近零的停机时间)。
此外,混沌工程、自动化、监控和可观察性对于增强 HA 和 DR 至关重要。
在下一章中,我们将介绍与 K8s 相关的其他一些高级 GenAI 主题。
第十三章:总结:GenAI 编码助手与进一步阅读
在过去的几年中,GenAI 已经取得了显著发展,现在有许多基于 GenAI 的编码助手可以帮助创建、启动和监控 K8s 集群。由于这一领域的快速发展,本章将介绍一些编码助手、发展趋势以及一些有价值的参考资料,供进一步阅读。
在本章中,我们将覆盖以下主要话题:
-
GenAI 驱动的编码助手
-
GenAI 驱动的可观察性和优化
-
亚马逊 Q 开发者通过 Amazon EKS 的操作演示
-
进一步阅读的参考资料
技术要求
在本章中,我们将使用以下服务,其中一些需要您设置帐户:
-
Docker Desktop (
www.docker.com/products/docker-desktop/
) 或Finch
(runfinch.com/
)
GenAI 驱动的编码助手
GenAI 驱动的助手正在通过自动化配置文件创建、工作负载扩展和监控来改变 K8s 集群的创建、部署和管理。一些能够帮助管理 K8s 集群的 GenAI 编码助手包括:
-
Amazon Q Developer
(aws.amazon.com/q/developer/
): 这是亚马逊的 GenAI 助手,旨在帮助云端开发,包括在Amazon Elastic Kubernetes Service (EKS) 上的 K8s 管理。它简化了部署清单的编写,自动化了基础设施即代码(IaC)以及诊断 K8s 部署中的问题。它还可以通过提供资源分配、自动扩展设置和网络配置的建议来帮助优化集群配置。在本章中,我们将提供一个如何使用 AWS Q Developer 进行 EKS 集群部署的操作演示。我们选择了 Amazon Q Developer 进行演示,因为我们是在 AWS 环境中操作。 -
GitHub Copilot
(github.com/features/copilot
): 这是一个由 AI 驱动的编码助手,可以集成到 IDE 中,如 Visual Studio Code 和 JetBrains。它可以创建 K8s 部署代码,例如部署清单、Helm charts 和 CI/CD 流水线配置。通过提供内联建议和自动补全,Copilot 可以加速 K8s 自动化并确保遵循最佳实践。 -
Google Gemini Code Assist
(codeassist.google/products/business
):这个编码助手可以跨越软件开发生命周期工作。它可以帮助优化 K8s 工作负载和基础设施管理,并提供有关扩展策略、集群健康状况和性能调优的见解。通过利用 AI,Gemini 建议优化成本效率和最小化停机时间的方式,特别是对于 GKE 集群。 -
Microsoft Copilot in Azure
(azure.microsoft.com/en-us/products/copilot
):这个工具可以帮助在Azure Kubernetes Service(AKS)中工作的开发人员。它提供集群扩展、节点池配置和安全策略的建议。Azure AI Copilot 还与 Terraform 和 Bicep 集成,使 DevOps 团队能够高效地自动化 K8s 基础设施的配置。 -
IBM watsonx Code Assistant
(www.ibm.com/products/watsonx-code-assistant
):这个工具对于在混合云环境中使用 OpenShift 和 K8s 的团队特别有用。它自动化应用程序容器化,建议对容器镜像、网络策略和安全加固的优化。它还与 Red Hat OpenShift GitOps 集成,允许通过 AI 驱动的自动化来优化 CI/CD 管道和应用程序部署策略。 -
K8sGPT (
k8sgpt.ai/
):这是一个开源工具,通过利用 GenAI 识别、分析和解释问题,将 AI 驱动的诊断引入 K8s 集群。它可以通过 CLI 在本地运行,或者部署在 K8s 集群中进行持续分析。它支持多种 AI 后端,包括 OpenAI,甚至通过 Ollama 或 LangChain 等工具支持本地 LLM,给团队根据数据隐私需求提供灵活性。它扫描各种 K8s 资源,如 Pods、Services、Deployments 和 Nodes,并检测诸如崩溃循环、配置错误或健康检查失败等问题。与 K8s 的错误信息不同,K8sGPT 可以提供清晰、易懂的解释以及建议的修复步骤,特别有助于开发人员和 SRE 排除复杂环境中的故障。例如,K8sGPT 可能会解释,Pod 崩溃是因为缺少所需的密钥或环境变量配置错误,而不是模糊的
CrashLoopBackOff
错误。
在本节中,我们探索了几种由 GenAI 驱动的编码助手,它们帮助软件部署、生成 IaC(基础设施即代码)并调试 K8s 集群。然而,在使用 AI 生成的配置时,例如 Amazon Q Developer 或任何其他 GenAI 助手,手动验证输出并在部署前在预演环境中进行测试是非常必要的。像 terraform plan
、docker scan
和 kubeval
这样的工具可以帮助验证语法正确性并突出配置问题。考虑使用政策即代码框架,如 开放政策代理 (OPA),来自动执行安全标准。
在下一节中,我们将讨论 GenAI 如何改变 K8s 可观测性和优化领域。
GenAI 驱动的可观测性和优化
除了生成 K8s 清单文件,GenAI 还通过自动化安全、监控和优化来改变 K8s 操作。这些 AI 驱动的解决方案正在使 K8s 环境变得更加高效、具有成本效益且具有自愈能力。以下是一些很好的例子:
-
AI 驱动的 K8s 自动伸缩:传统的 K8s 自动伸缩器依赖于 CPU 和内存阈值,但 AI 驱动的自动伸缩器预测工作负载需求并动态调整资源,以优化性能和成本。像 StormForge (
stormforge.io/
) 和 PredictKube (dysnix.com/predictkube
) 等工具利用 机器学习 (ML) 来增强自动伸缩策略,防止过度配置,同时确保在流量高峰时保持可用性。 -
AI 辅助的 K8s 治理和策略执行:AI 驱动的治理工具在 K8s 集群中执行合规性和安全政策。通过集成基于历史数据和策略违规情况训练的机器学习模型,可以超越静态规则定义。例如,可以分析历史上的策略违规情况并检测出模式,如哪些资源最常受影响以及在什么条件下发生。基于学习到的模式,AI 可以建议新规则,进一步加强访问控制,超越静态规则以执行政策的工具,如 OPA。
-
GenAI 在 K8s 工作流和可观测性中的应用:AI 驱动的可观测性工具分析日志、指标和追踪,以便在应用程序受到影响之前识别异常。像 New Relic AI (
newrelic.com/platform/new-relic-ai
) 和 Dynatrace Davis AI (www.dynatrace.com/platform/artificial-intelligence/
) 等解决方案可以自动化根本原因分析和警报优先级排序,帮助 DevOps 降低停机时间并提高故障排除效率。带有 AI 模型的 Prometheus 通过筛选掉非关键事件并专注于可操作的洞察来增强智能警报功能。 -
Envoy AI 网关 (
aigateway.envoyproxy.io/docs/
):该项目旨在简化将现代应用连接到 GenAI 服务的日益复杂的任务。基于 Envoy Proxy 构建,该项目提供了一个统一的层来管理大规模的 LLM/AI 流量。该项目的主要目标包括为 GenAI 工作负载提供无缝路由和策略控制,支持自动故障转移以确保服务的可用性,通过上游授权确保 AI 流量的安全,并通过灵活的策略框架启用使用限制。Envoy AI 网关的核心目标是使 GenAI 基础设施更易于集成并更安全地运营。要使用 Envoy AI 网关的可观察性功能,用户可以配置 Prometheus 来抓取网关公开的指标,其中包括 AI 特定的见解,如令牌使用情况、首次令牌的时间和每个令牌的延迟。这些指标遵循 OpenTelemetry 的 GenAI 语义约定,旨在提供关于 GenAI 模型在生产环境中表现的可见性。 -
用于 K8s IaC 的生成性 AI:AI 驱动的助手通过自动生成 YAML、Terraform 和 Helm 配置,加速了 K8s 基础设施的部署。像 Amazon Q Developer 这样的工具使团队能够用简单的语言描述他们希望的基础设施,并获得优化的配置。
-
AI 驱动的 K8s 成本优化(FinOps):在 K8s 环境中优化云成本具有挑战性,因为工作负载具有动态特性。AI 驱动的 FinOps 解决方案,如 Harness (
www.harness.io/solutions/finops-excellence
) 和 Cast AI (cast.ai/
),可以分析集群利用率,提出节省成本的措施,并调整资源分配以减少浪费。这些工具帮助组织在保持应用性能的同时优化 K8s 开支。 -
GenAI 驱动的 K8s ChatOps:AI 驱动的 ChatOps 工具通过在 Slack 和 Microsoft Teams 等平台中实现对话交互,增强了 K8s 管理。Botkube AI 助手 (
botkube.io/
) 允许用户通过聊天查询 K8s 集群并执行命令,而 K8sGPT 则充当 AI 驱动的 K8s SRE,自动诊断和解决集群问题。AI 驱动的自愈机制可以检测故障,重新启动 Pods,并主动修复问题,无需人工干预。
在这一部分,我们探讨了多种 AI 驱动的工具,这些工具可以帮助转型 K8s 环境。这些工具可以用于自动化 K8s 的关键操作,如自动扩缩容、安全性、成本优化和可观察性。在下一部分,我们将演示如何使用 GenAI 助手简化 K8s 应用的开发。
Amazon Q 开发者与 EKS 的演示
在本节中,我们将探讨如何通过 GenAI 驱动的助手,例如 Amazon Q Developer,简化 GenAI 应用程序的开发,简化 K8s 集群的创建和管理,并简化部署过程。Amazon Q Developer 在 命令行界面 (CLI) 中引入了全新的 代理体验,提供动态互动的编码体验。代理体验指的是那些通过理解上下文、提供建议并帮助指导任务完成的系统,积极协助用户。Amazon Q Developer 会根据你的反馈不断优化变更,并利用来自你的 CLI 环境的信息来协助本地文件操作、查询 AWS 资源、编写代码并自动调试问题。
重要提示
在使用 GenAI 驱动的助手进行编码和执行本地任务(如运行命令)时,请始终仔细检查生成的代码和命令。确保它们是安全的,适合你的环境,并且不会无意中影响关键资源或数据。
让我们开始吧:
按照 docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line-installing.html
上的说明安装 Amazon Q Developer for command line。
打开终端或命令行应用程序,使用以下命令与 Amazon Q Developer 开始对话:
$ q chat
你将被引导到 AWS Builder ID 登录页面 (docs.aws.amazon.com/signin/latest/userguide/sign-in-aws_builder_id.html
),以便授予 Amazon Q Developer 命令行的权限。
在 CLI 中输入以下查询。Amazon Q Developer 将处理你的输入,考虑提供的上下文,调用 Amazon Bedrock API,并以 图 14.1 所示的输出响应:
图 14.1 – 使用 Amazon Q Developer 生成 Kubectl 命令
现在,让我们请求 GenAI 助手为创建一个新的 Amazon EKS 集群以及 VPC、私有和公共子网等创建 IaC 模板。在 CLI 中使用以下提示:
$ Generate Terraform code for an Amazon EKS cluster with:
- Cluster name "eks-genai-demo" in us-west-2 region using EKS v1.32
- Dedicated VPC (CIDR 10.0.0.0/16) with public/private subnets across 3 AZs
- 1 NAT gateway for internet access from private subnets
- Standard EKS Managed add-ons (Amazon VPC CNI, CoreDNS, kube-proxy)
- Output cluster endpoint and access information
Provide modular, well-commented code with appropriate provider configurations and use open-source terraform modules where possible.
几秒钟内,Amazon Q Developer 将生成 Terraform 代码,以创建 Amazon VPC、EKS 集群等,并包含 Terraform 提供程序配置、输入变量和输出,如 图 14.2 所示:
图 14.2 – 使用 Amazon Q Developer 生成 Terraform 代码
我们运行了这个提示并将生成的文件上传到 GitHub 仓库,地址为 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch14/amazon-q-demo
。
基于 GenAI 的编码助手可能使用较旧版本的 Terraform 提供程序或模块,具体取决于其训练数据。在生成的代码中,使用了 Terraform version >= 1.0.0
、AWS provider >= 5.0.0
等等。我们还可以给出后续提示,以使用这些提供程序的特定版本,如下所示:
$ Update the previous EKS cluster Terraform code to use the following provider versions:
- Terraform version >= 1.9
- AWS provider >= 5.63
- Helm provider >= 2.15
- Kubernetes provider >= 2.32
Ensure all provider configurations are explicitly defined with these version constraints in the required_providers block, and the code remains compatible with these newer versions.
Amazon Q Developer 将解析提示,开始从本地文件系统读取 Terraform 文件,并建议修改,如图 14.3 所示:
图 14.3 – Amazon Q Developer 的输出
你可以查看生成的 Terraform 代码,并通过自然语言提示要求代理进行规划并将其部署到 AWS 账户中。
同样,你可以使用 Amazon Q Developer 自动生成完整的项目代码,从零开始创建,例如一个提供基本功能以管理待办任务的待办事项应用。在空目录中运行以下提示以生成一个待办事项应用:
$ Please create a simple TODO application with the following specifications:
Functionality
- Create new tasks with title and description
- Mark tasks as complete/incomplete
- Delete tasks
- View all tasks
Technical Requirements
- Create a single application
- Use in-memory storage for tasks (no need for a database)
- Follow good coding practices with appropriate comments
Docker Requirements
- Create a Dockerfile to containerize the application
- The Dockerfile should follow best practices
- Make it simple to build and run.
不久后,Amazon Q Developer 将开始创建分层项目结构、源代码文件、用于容器化的 Dockerfile、文档等。我们执行了这个提示,并在 GitHub 仓库中提供了生成的文件,地址是 github.com/PacktPublishing/Kubernetes-for-Generative-AI-Solutions/tree/main/ch14/todo-app
。它创建了如图 14.4 所示的项目结构和文件:
图 14.4 – Amazon Q Developer 的输出
审查生成的文件。为了在本地测试应用,我们可以提示 Amazon Q Developer 构建容器镜像并使用 Docker 或 Finch 运行它。使用以下提示构建并运行容器镜像:
$ Build and run the container image locally using Docker.
在另一个终端中,你可以使用以下命令验证容器镜像和运行中的容器:
$ docker images
$ docker ps
最后,我们可以更进一步,要求 AI 助手创建必要的 K8s 清单文件并将应用部署到 K8s 集群中。使用以下提示来创建 K8s 部署和服务资源:
$ Create necessary manifest files to deploy this application to a kubernetes cluster. Run two replicas of this app and expose it via ClusterIP service.
几秒钟内,Amazon Q Developer 将生成 K8s 部署、服务和命名空间清单文件等,用于将待办事项应用部署到 K8s 集群中,如图 14.5 所示:
图 14.5 – Amazon Q Developer 创建的 K8s 清单文件
在本节中,我们探讨了像 Amazon Q Developer 这样的 AI 编码助手如何简化软件开发任务,包括生成 K8s 配置文件和创建 AWS 及 EKS 资源的 IaC 模板。我们使用自然语言提示与这些助手进行交互,指导它们执行各种任务,如构建容器镜像、在本地运行应用程序,并修改生成的代码以满足特定要求。在下一节中,我们将提供关于这个主题进一步阅读的参考资料。
进一步阅读的参考资料
-
kubernetes.io/docs/
: 官方 Kubernetes 文档,始终更新且内容全面 -
github.com/kubernetes/kubernetes
: 核心 Kubernetes GitHub 仓库,包含源代码 -
training.linuxfoundation.org/certification/
: Linux 基金会与Certified Kubernetes Administrator
(CKA)、Certified Kubernetes Application Developer
(CKAD)和Certified Kubernetes Security Specialist
(CKS)合作的认证项目 -
kubernetes.io/blog/
: 官方博客,更新内容、最佳实践和使用案例 -
kubernetes.io/community/
: 社区中心,用于贡献、SIGs、事件和参与 -
www.cncf.io/projects/kubernetes/
: CNCF 上关于 K8s 的页面 - 项目状态、治理和整体情况 -
github.com/aws-ia/terraform-aws-eks-blueprints
: 使用 Terraform 的官方 EKS 蓝图 - 模块化且适用于生产环境 -
awslabs.github.io/data-on-eks/
: Data on EKS 是一个在 EKS 上构建、部署和扩展数据与 ML 平台的工具 -
aws.github.io/aws-eks-best-practices/
: 官方 Amazon EKS 最佳实践指南,涵盖安全、网络、扩展性、GitOps 等内容 -
wellarchitectedlabs.com/architecture-guides/containers/eks-best-practices/
: AWS Well-Architected Labs 为 EKS 提供的实验室指南和架构审查
总结
在本章中,我们讨论了基于 GenAI 的编码助手如何改变构建、部署和监控 K8s 集群的方式。这些工具自动化 IaC,优化工作负载,并增强可观察性。关键的助手包括 Amazon Q Developer、GitHub Copilot、Google Gemini Code Assist、Microsoft Azure Copilot、IBM watsonx Code Assistant 和 K8sGPT。它们支持从编写部署清单和 Terraform 配置到实时诊断和优化集群性能的所有内容。
GenAI 工具还通过 AI 驱动的自动扩缩、异常检测和策略执行来提升可观察性、安全性和成本效益。像 StormForge、PredictKube 和 Dynatrace Davis AI 这样的工具能够自动化根本原因分析和资源扩展,而其他工具,如 Harness 和 Cast AI,则帮助进行 K8s 财务运维(FinOps)。
Amazon Q Developer 提供基于 CLI 的支持,能够通过简单的自然语言提示生成和优化 Terraform 模板、构建 Docker 容器,并部署完整的应用程序。它支持为 EKS 集群创建模块化的基础设施即代码(IaC),并通过智能建议加速迭代过程。
最后,我们提供了一份精选的参考资料列表,供深度学习使用。包括官方的 K8s 文档、GitHub 仓库和提供最佳实践、模式和社区资源的博客。还重点介绍了 Linux 基金会和 CNCF 的认证课程。这些资源为掌握 K8s 和在生产环境中有效使用 GenAI 驱动的工具提供了宝贵的指导。我们希望你喜欢阅读这本书,并期待收到你关于我们如何在未来版本中改进的反馈。
在云计算和 DevOps 中保持敏锐——加入超过 44,000 名 CloudPro 订阅者
CloudPro 是一份面向云计算专业人员的每周通讯,旨在帮助你紧跟快速发展的云计算、DevOps 和基础设施工程领域的最新动态。
每期内容都聚焦于以下主题的高信号内容:
-
AWS、GCP 和多云架构
-
容器、Kubernetes 和编排
-
使用 Terraform、Pulumi 等进行基础设施即代码(IaC)
-
平台工程与自动化工作流
-
可观察性、性能调优和可靠性最佳实践
无论你是云工程师、SRE、DevOps 从业者,还是平台负责人,CloudPro 都能帮助你专注于重要的事务,避免噪音干扰。
扫描二维码免费加入,获得每周洞察直达你的邮箱: