Kubernetes-上的机器学习-全-
Kubernetes 上的机器学习(全)
原文:
annas-archive.org/md5/677f27c30764b3701bc2b6cf6de3a30e译者:飞龙
第一章:第二章:理解 MLOps
大多数软件工程背景的人都知道开发运维(DevOps)这个术语。对我们来说,DevOps 是关于在软件开发生命周期(SDLC)中跨不同团队之间进行协作和共享责任。这些团队不仅限于少数信息技术(IT)团队,而是涉及到项目中的所有利益相关者。不再区分软件构建(开发者的责任)和在生产环境中运行它(运维的责任)。相反,团队共同拥有产品。DevOps 之所以流行,是因为它帮助团队提高了正在开发的软件的速度和可靠性。
在本章中,我们将涵盖以下主题:
-
将机器学习(ML)与传统编程进行比较
-
探讨 DevOps 的好处
-
理解ML 运维(MLOps)
-
开源软件(OSS)在 ML 项目中的作用
-
在 Kubernetes 上运行 ML 项目
在我们可以将 DevOps 应用于 ML 项目之前,我们必须首先了解传统软件开发和 ML 开发过程之间的区别。
将 ML 与传统编程进行比较
与传统应用程序开发类似,ML 项目也是一个软件项目,但它们在交付方式上有根本性的区别。让我们了解一下 ML 项目与传统软件应用程序的不同之处。
在传统软件应用程序中,软件开发人员编写一个程序,其中包含一组明确定义的手工制作的规则。在运行时或预测时,构建的软件将这些明确定义的规则应用于给定的数据,程序的输出是基于编码规则计算得出的结果。
下图显示了传统软件应用程序的输入和输出(I/Os):

图 2.1 – 传统软件开发
在 ML 项目中,规则或模式并非完全已知,因此我们无法像在传统编程中那样在代码中明确描述规则。在 ML 中,有一个过程可以根据给定的数据样本对和其关联的预期结果提取规则。这个过程称为模型训练。在模型训练过程中,选择的 ML 算法根据给定的数据和验证的答案计算规则。这个过程的输出是ML 模型。生成的模型随后可用于在预测时推断答案。与传统软件开发不同的是,我们不使用明确编写的规则,而是使用生成的 ML 模型来获得结果。
下图显示了在训练时生成的 ML 模型,然后在预测时用于产生答案或结果:

图 2.2 – ML 开发
尽管传统软件开发和机器学习在本质上是不同的,但这两种方法的工程过程之间存在一些协同效应。考虑到传统软件开发在当今时代非常成熟,我们可以将其中的经验教训应用到我们的机器学习项目中。当然,最主要的是,传统编程和机器学习都属于软件开发。我们在传统领域中应用于构建软件的所有流程——例如版本控制、将软件打包成容器、自动化部署等——也可以应用到机器学习项目中。然而,我们也必须考虑到机器学习中额外的过程,如模型训练。
那么,为什么我们在机器学习项目中真的需要 DevOps 呢?它能带来什么好处?我们将在下一节中探讨这个问题。
探索 DevOps 的好处
DevOps 不仅仅是关于工具集。假设你有一个可以为你运行单元测试的工具。然而,如果团队没有编写测试用例的文化,那么这个工具就不会有任何用处。DevOps 关注的是我们如何在跨团队的任务上协作。所以,DevOps 需要专注的三个主要领域是:
-
人员:来自多个学科的团队,旨在实现共同目标
-
过程:团队合作的方式
-
技术:促进不同团队间协作的工具
DevOps 基于敏捷开发实践,旨在简化软件开发过程。DevOps 团队是跨职能的,并且拥有通过持续集成/持续交付(CI/CD)来构建软件的自主权。DevOps 鼓励团队通过快速反馈回路进行协作,以提高开发软件的效率和质量。
下图展示了传统软件开发项目中完整的 DevOps 生命周期:

图 2.3 – 展示 DevOps 过程的莫比乌斯环
通过 DevOps,团队可以拥有明确且简化的开发实践,用于构建、测试、部署和监控生产中的软件。所有这些都使得能够快速且可靠地将软件发布到生产环境中。以下是 DevOps 实践带来的一些好处:
-
CI/CD:CI 是一个阶段,软件在开发者将其推送到代码库时即进行合并和验证。CD 是一系列阶段,通过这些阶段,软件被构建、测试,并打包成准备部署的形式。持续部署(也称为 CD)是一个阶段,在这个阶段,准备部署的代码被选中并部署,以供最终用户使用。在 DevOps 中,所有这些流程都是自动化的。
-
基础设施即代码(IaC):IaC 是一种自动化 IT 基础设施配置和供应的方法。这一方面使得团队可以根据需求随时请求并配置基础设施。想象一下,团队中的数据科学家需要一个 图形处理单元(GPU)来进行模型训练。如果我们遵循 IaC 的配置和供应实践,那么系统可以自动完成对 GPU 的请求。接下来的章节中,你将看到这一能力的实际应用。
-
可观察性:可观察性与我们如何理解正在运行系统的状态有关。DevOps 通过联邦日志记录来自不同组件的日志、监控系统(例如 中央处理单元(CPU)、内存、响应时间等),并通过调用追踪提供一种将系统的各个部分关联起来的方式,从而使系统可观察。所有这些能力共同为理解系统状态提供基础,帮助调试任何问题,而不需要修改代码。
-
团队协作:DevOps 不仅仅是关于技术。事实上,团队的关键关注点是协作。协作是指来自不同团队的多个个体共同朝着一个共同目标努力。业务、开发和运维团队的合作是 DevOps 的核心。对于基于 ML 的项目,团队中除了上述角色外,还会有数据科学家和数据工程师。由于团队成员的多样性,沟通对于构建集体理解和对既定结果的共同责任感至关重要。
那么,我们如何将 DevOps 方法的优势带入到 ML 项目中呢?答案就是 MLOps。
理解 MLOps
MLOps 是一个新兴领域,它利用了现有软件开发流程的成熟性——换句话说,就是将 DevOps 与数据工程和 ML 学科结合起来。MLOps 可以简化为将 DevOps 应用于 ML 项目的工程实践。让我们更详细地了解这些学科如何构成 MLOps 的基础。
ML
ML 项目涉及一些传统编程中没有的活动。你在 图 2.3 中学习到,ML 项目中大部分的工作并不是模型开发,而是更多的数据收集与处理、数据分析、特征工程(FE)、过程管理、数据分析、模型服务等。实际上,根据 D. Sculley 等人的论文 Hidden Technical Debt in Machine Learning Systems,只有 5% 的工作是 ML 模型开发。因此,MLOps 不仅关注 ML 模型的开发任务,更多的是聚焦于大局——整个 ML 项目生命周期。
就像 DevOps 一样,MLOps 也关注人、流程和技术。但 MLOps 有一些复杂性是 DevOps 不需要处理的。我们在这里更详细地了解这些复杂性:
-
首先,与传统编程不同,你的输入不仅是代码,还包括数据。在 ML 中,模型开发阶段生成的 ML 模型高度依赖于数据。这意味着即使你没有改变代码,如果使用不同的数据集训练 ML 算法,生成的 ML 模型将不同,并且性能也会有所不同。在版本控制方面,这意味着不仅需要版本化促进模型训练的代码,还需要版本化数据。由于数据量巨大,数据版本控制相对较难。解决这个问题的一种方法是使用 Git 跟踪数据集版本,使用数据哈希存储实际数据在远程存储(如简单存储服务(S3)桶)中。一个名为数据版本控制(DVC)的开源工具可以做到这一点。
-
其次,在 ML 项目中涉及更多人物角色,需要更多的协作。你有数据科学家、ML 工程师和数据工程师与软件工程师、业务分析师和运营团队合作。有时,这些角色非常多样化。一个数据科学家可能完全不理解什么是真正的生产部署。另一方面,运营人员(有时甚至是软件工程师)不了解什么是 ML 模型。这使得在 ML 项目中进行协作比传统软件项目更加复杂。
-
第三,增加模型开发阶段为生命周期添加了更多的转折点。这使得整个过程变得更加复杂。与传统软件开发不同,你只需开发一组可工作的代码。在 ML 中,数据科学家或 ML 工程师可能会使用多个 ML 算法并生成多个 ML 模型,因为只有一个模型将被选中部署到生产环境中,这些模型将根据性能与其他模型属性进行比较。MLOps 适应了这种复杂的测试、比较和选择模型的工作流程。
传统编码生成可执行二进制文件通常需要几秒到几分钟。然而,训练 ML 算法生成 ML 模型可能需要几个小时或几天,有时甚至使用某些深度学习(DL)算法时可能需要几周。这使得建立敏捷的迭代时间限制节奏稍显复杂。MLOps 团队需要处理工作流中的这种延迟,一种方法是在等待其他模型完全训练完成时开始构建另一个模型。如果数据科学家或 ML 工程师在他们自己的笔记本电脑上训练他们的 ML 算法,这是非常难以实现的。这就是可扩展基础设施的用武之地。
- 最后,由于 ML 模型的性能依赖于训练过程中使用的数据,如果这些数据不再代表现实世界的情况,模型的准确性将会下降,从而导致预测性能变差。这被称为模型漂移,并且需要尽早检测。通常,这会作为 ML 项目生命周期监控过程的一部分进行。除了在生产中收集的传统指标外,使用 ML 模型时,你还需要监控模型漂移和异常值。然而,异常值检测的实现要困难得多,有时需要你训练并构建另一个 ML 模型。异常值检测是关于检测生产中进入的数据,这些数据与模型训练时使用的数据不相符:你不希望你的模型给出与这些无关问题的无关答案。另一个原因是,这可能是一次攻击或滥用系统的尝试。一旦你检测到模型漂移或异常值,你打算如何处理这些信息?这可能仅仅是触发警报,或者可能会触发其他自动化流程。
由于与传统编程相比,ML 增加了复杂性,因此必须解决这些复杂性,促使了 MLOps 的出现。
DevOps
在部署方面,想一想你在 ML 项目中编写的所有代码:执行数据处理的代码、促进模型训练和前端工程的代码、运行模型推理的代码以及执行模型漂移和异常值检测的代码。这些代码集需要构建、打包并部署以便大规模使用。一旦这些代码在生产环境中运行,它们还需要被监控和维护。这就是 DevOps 的 CI/CD 实践发挥作用的地方。自动化软件打包、测试、安全、部署和监控的实践源自 DevOps。
数据工程
每个 ML 项目都涉及到数据工程,而且 ML 项目处理的数据远比代码要多。因此,确保你的基础设施包括数据处理能力,并且能够与组织中现有的数据工程管道集成,是强制性的。
数据工程是一个庞大的主题—可以写一本书来讲述它。但我们在这里想强调的是,MLOps 与数据工程实践交叉,特别是在数据摄取、数据清洗、数据转换和大数据测试方面。事实上,你的 ML 项目可能只是一个小型的 ML 分类模型,它是一个更大数据工程或数据分析项目的子部分。MLOps 采纳了数据工程和分析中的最佳实践。
以下图表展示了 MLOps 的表示:

图 2.4 – MLOps 作为 ML、数据工程和 DevOps 的交集
用另一种方式来说,MLOps,如图 2.4所示,是ML、DevOps和数据工程学科的融合,专注于在生产环境中运行 ML。它还涉及将 ML 项目封装在一个高度可扩展、可靠且可观测的基础设施中。最后,它还涉及为团队建立可重复的流程,以执行成功交付 ML 项目所需的任务,如图 2.4所示,同时支持团队之间的协作。
有了对 MLOps 的基本理解后,让我们更深入地探讨 ML 项目生命周期。我们将从定义 ML 项目的一般阶段开始。
ML 项目生命周期
与 DevOps 类似,DevOps 提供了一系列活动,可以在 DevOps 周期中执行,你可以在图 2.5中看到一系列步骤,这些步骤可用于将你的 ML 项目从开始到结束。这些步骤或阶段将成为 ML 项目生命周期的一部分,并提供一种一致的方式将 ML 项目投入生产。你在本书中构建的 ML 平台是允许你实现这个流程的生态系统。在本书的后续章节中,你将使用这个流程作为平台的基础。ML 项目的各个阶段总结如下:

图 2.5 - 一个 ML 项目生命周期
以下是前面图表中呈现的每个阶段的定义:
-
编码问题并定义成功指标:在这个阶段,团队评估给定的业务问题是否可以通过 ML 来解决。请注意这里的“团队”一词,团队至少包括数据科学家和业务领域专家(SME)。团队将定义一个成功标准,以评估模型的预测结果。
-
获取、清洗和标注数据:在这个阶段,团队评估是否有可用的数据来训练模型。团队将扮演额外的角色——数据工程师,帮助推动项目在这一阶段及其后的进展。团队将构建组件,从各种来源获取数据,清洗获取的数据,可能会标注数据并存储它。这些数据将构成 ML 活动的基础。
-
FE:FE 是指将原始数据转化为与给定问题更相关的特征。假设你正在构建一个模型,用来预测任何给定的泰坦尼克号乘客是否会生还。假设你得到的数据集包含了乘客的票号。你认为票号与乘客的生还有关系吗?一位业务领域专家(SME)可能会提到,票号可能能提供乘客所在舱位的信息,而头等舱的乘客可能更容易获得船上的救生艇。
-
模型构建和调优:在这个阶段,团队开始尝试不同的模型和不同的超参数。团队会将模型与给定的数据集进行测试,并比较每次迭代的结果。然后,团队会根据给定的成功指标确定最佳模型,并将该模型存储在模型注册表中。
-
模型验证:在这个阶段,团队使用一个在训练时无法获取的新数据集来验证模型。这个阶段至关重要,因为它决定了模型是否足够泛化以应对未见过的数据,或者模型只在训练数据上表现良好,而无法处理未见过的数据——换句话说,就是避免过拟合。模型验证还包括识别模型偏差。
-
模型部署:在这个阶段,团队从模型注册表中选取模型,将其打包并部署以供使用。传统的 DevOps 流程可以在这里使用,将模型作为服务提供。在本书中,我们将专注于模型即服务(MaaS),即将模型作为表现性状态转移(REST)服务提供。然而,在某些情况下,模型可以作为库打包,供其他应用程序使用。
-
监控和验证:在这个阶段,模型将持续监控响应时间、预测的准确性,以及输入数据是否与模型训练时的数据相似。我们简要介绍了异常值检测。在实践中,它的运作方式是这样的:假设你已经为公共交通系统的高峰时段空车率训练了一个模型,且模型训练时的数据是市民使用公共交通系统超过一年的数据。数据会存在周末、公共假期和其他事件的变化。现在,假设由于 COVID-19 封锁,所有人都不允许使用公共交通系统。现实世界与我们模型训练时的数据不同。自然地,我们的模型在这种变化的世界中并不是特别有用。我们需要检测这种异常并生成警报,以便在可能的情况下,我们能够使用新的数据集重新训练模型。
你刚刚了解了机器学习项目生命周期的各个阶段。尽管这些阶段看起来很简单,但在现实世界中,确实有一些很好的理由在某些情况下需要回到之前的阶段。
快速反馈循环
一个敏锐的观察者可能已经注意到,在第一章中我们展示的敏捷和跨职能团队的一个关键特征,在本章目前展示的阶段中并没有出现。现代 DevOps 的核心就是快速反馈循环,以便在项目生命周期的早期进行调整。这个概念将为机器学习项目带来更多的价值,因为机器学习项目比传统的软件应用程序更为复杂。
让我们看看在哪些阶段可以评估和衡量团队的进展。评估后,团队可以决定通过回到早期阶段进行调整,或继续推进到下一个阶段。
下图展示了带有各个阶段反馈检查点的机器学习项目生命周期,用绿色箭头表示:

图 2.6 – 带有反馈检查点的机器学习项目生命周期
让我们在这里更详细地看看:
-
数据摄取、清理和标注阶段的检查点:在完成第一阶段后,你已经开始按第二阶段的定义处理数据。你可能会发现实际数据不完整或不正确。你可以根据这些反馈改进对数据的理解,并可能需要重新定义项目的成功标准,或者在最坏的情况下,由于所需的数据不可用而停止项目。在许多场景中,团队会寻找额外的数据来源来填补第二阶段中识别的数据空白。
-
模型构建与调优阶段的检查点:在这个阶段,团队可能会发现用于训练模型的特征不足以获得预期的指标。此时,团队可能会决定花更多时间寻找新特征,或者重新审视原始数据,以确定是否需要更多数据。
-
模型验证阶段的检查点:在这个阶段,模型将针对一个模型从未见过的新数据集进行验证。此时指标较差可能会触发模型的调优,或者你也可以决定回去寻找更多特征,以提升模型的性能。
-
模型监控与验证阶段的检查点:一旦模型进入生产阶段,就必须持续监控,以验证模型是否仍然与现实世界和变化中的世界相关。你需要找出模型是否仍然有效,如果无效,如何使模型变得更有用。这个结果可能会触发生命周期中的其他任何阶段;正如在图 2.6中所看到的,你可能最终会使用新数据重新训练现有模型,或者完全换用不同的模型,甚至重新思考是否应该用机器学习来解决这个问题。没有确定的答案能告诉你最终会到哪个阶段,就像现实世界一样,它是不可预测的。然而,重要的是具备重新评估和重新审视的能力,并持续为业务创造价值。
你已经看到了机器学习项目生命周期的各个阶段以及从这些阶段获得的反馈检查点,决定是否继续推进到下一个阶段或返回上一个阶段。现在,让我们来看看每个阶段涉及的角色以及他们的合作点。
在项目生命周期中的协作
我们已经定义了构建模型的简化流程。让我们尝试定义一个多角色、多能力的团队如何协作完成这个模型。回顾上一章,构建模型需要不同团队成员的共同努力,每个团队成员拥有不同的能力。值得注意的是,在小型项目中,可能同一个人同时扮演不同的角色。例如,在一个小项目中,同一个人既可以是数据科学家,也可以是数据工程师。
以下图表展示了一个带有反馈点和角色重叠的 ML 项目生命周期图:

图 2.7 – 一个包含反馈检查点和团队角色的 ML 项目生命周期
你们组织中的 ML 项目在第一阶段需要数据科学家与业务领域专家(SMEs)之间的协作。设想一下,团队希望根据一张图片预测某种皮肤病的概率。
-
在这个阶段,数据科学家和医生(在此案例中的业务领域专家)之间需要进行协作,以定义问题和性能指标。没有这种合作,项目将无法成功。
-
在第二阶段——数据摄取和清洗阶段——数据工程师需要与业务领域专家合作,了解哪些数据是可用的,并如何正确地清洗和标注数据。业务领域专家在这个阶段带来的知识至关重要,因为这将负责创建一个有用的数据集,以供后续阶段使用。
-
在第三阶段,数据科学家、数据工程师和业务领域专家将协作处理第二阶段的基础数据,提取有用的特征。数据科学家和业务领域专家将提供有关可以提取哪些数据的指导,数据工程师将编写处理逻辑来实现这一目标。
-
在第四和第五阶段,大部分工作将由数据科学家完成,以根据给定标准构建和调整模型。然而,根据模型是否达到了定义的指标,团队可能会决定返回到任何一个先前的阶段,以提高模型的性能。
一旦模型构建完成,DevOps 团队的专家可以对模型进行打包、版本管理,并将其部署到正确的环境中。
- 最后阶段至关重要:团队利用可观察性功能来监控模型在生产环境中的性能。在监控了模型在实际环境中的表现后,团队可以根据反馈决定是否返回任何前阶段,以使模型对业务更有价值。
现在你已经很好地理解了我们所提到的挑战,以及如何通过机器学习生命周期克服这些挑战,接下来的阶段是要有一个支持这一生命周期的平台,同时为大图中的每个组件提供解决方案(请参见第一章,机器学习中的挑战),并具备自服务和自动化能力。与开源社区合作,开始这段旅程,还有什么比这更好的方式呢?
开源软件在机器学习项目中的角色
现在你已经清楚地了解了机器学习平台需要解决的问题,接下来我们来看一下为什么开源是最好的起点。我们应该从一些定义开始,以便打好基础,对吧?
免费的开源软件(OSS)是指用户有自由运行、复制、分发、研究、修改和改进软件的地方。
开源软件
想了解更多关于开源软件的信息,请参阅以下链接:
www.gnu.org/philosophy/free-sw.html
开源无处不在。Linux 是最常见的操作系统,运行在数据中心并支持全球云计算。Apache Spark 和相关的开源技术是大数据革命的基础,帮助众多组织实现其目标。基于开源的人工智能(AI)技术,如 TensorFlow 和 MLflow,处于 AI 发展的前沿,并被成百上千的组织所使用。开源的容器编排平台 Kubernetes 已经成为容器平台的事实标准。
计算领域的顶级玩家——如亚马逊、苹果、Facebook、谷歌、微软和红帽等——已经为主要开源项目做出贡献并拥有这些项目,而且新的参与者也不断加入。全球各地的企业和政府每天都依赖开源软件来支持关键任务和高可扩展的系统。
云计算领域最成功的开源项目之一就是Kubernetes。Kubernetes 成立于 2014 年中期,2015 年中期发布了它的 1.0 版本。自那时以来,它已成为容器编排的事实标准。
此外,云原生计算基金会(CNCF)是由Linux 基金会创建的,使命是让云计算无处不在。CNCF 通过汇集世界顶级工程师、开发者、终端用户和供应商来实现这一目标。它们还举办世界上最大的开源会议。基金会的创建使用了Kubernetes作为种子项目。正是通过这个项目,Kubernetes 设定了云原生的标准定义。截至本文撰写时,基金会已经有 741 个会员组织,130 个认证的 Kubernetes 发行版和平台,并且已经毕业了 16 个非常成功的开源项目。当然,其中包括Kubernetes,还有Operator Framework,你将在下一章了解更多关于它的内容。
在大数据和云计算爆炸式发展的之前,机器学习(ML)项目主要是学术性质的。它们很少走出高校的范围,但这并不意味着人工智能(AI)、机器学习(ML)和数据科学没有向前发展。学术界实际上已经创造了数百个开源的 Python 库,用于数学、科学和统计计算。这些库已经成为现代机器学习框架的基础。目前最流行的机器学习框架——TensorFlow、PyTorch、scikit-learn 和 Spark ML——都是开源的。如今最流行的数据科学和机器学习开发环境——Jupyter Notebook、JupyterLab、JupyterHub、Anaconda 等——同样都是开源的。
机器学习是一个不断发展的领域,它需要超越任何单一组织的大型社区的愿景。社区合作模式的工作流程能够促进机器学习项目所需的合作与创造力,而开源正是机器学习快速发展的重要原因之一。
现在你已经对开源软件(OSS)在人工智能和机器学习领域的重要性有了基本了解。接下来,让我们更详细地看看为什么你应该在 Kubernetes 上运行机器学习项目。
在 Kubernetes 上运行机器学习项目
为了构建可靠且可扩展的机器学习系统,你需要一个稳固的基础。Kubernetes为构建可扩展且可靠的分布式系统提供了基础,并提供了平台所需的自助服务功能。Kubernetes 能够将硬件基础设施抽象为一个单一的单元进行消费,这对于我们的平台具有巨大的益处。
另一个关键因素是基于 Kubernetes 的软件能够在任何地方运行,从小型本地数据中心到大型超大规模云平台(Amazon Web Services(AWS)、Google Cloud Platform(GCP)、Azure)。这个能力将为你提供在任何你想要的地方运行机器学习平台的可移植性。它带给平台使用者的统一性非常出色,因为团队可以在云上以极低的初始成本进行实验,然后根据企业的更广泛需求定制平台。
选择 Kubernetes 的第三个也是最后一个理由是其能够运行不同类型的工作负载。你可能还记得在前一章中提到的,成功的机器学习项目不仅需要机器学习,还需要基础设施自动化、数据生命周期管理、有状态组件等。Kubernetes 提供了一个一致的基础,可以运行各种类型的软件组件,从而为业务用例创建一个端到端(E2E)的解决方案。
以下截图展示了一个 ML 平台的各个层次。Kubernetes 提供了一个扩展和抽象层,ML 平台就是在这个层次上构建的。Kubernetes 提供了抽象底层基础设施的自由。正因如此,我们可以在各种云服务商和本地解决方案上运行。你将在本书中构建的 ML 平台支持 ML 项目三个更广泛领域的运营化和自助服务——前端(FE)、模型开发和 DevOps:

图 2.8 – 基于开源软件的 ML 平台
就这样:你的 ML 平台将基于开源软件,并使用 Kubernetes 作为托管基础。开源 Kubernetes 社区的力量将帮助你使用最好的技术,而这些技术会随着领域的不断成熟而不断发展。
总结
本章中,我们定义了 MLOps 这一术语,并提出了一个协作型的 ML 项目生命周期,能够提供早期反馈。你已经了解到,通过这个项目生命周期,团队可以持续向业务提供价值。你还了解了为何基于开源软件构建平台是合理的,以及社区驱动软件的好处。
本书的这一部分介绍了设置背景、学习为何需要平台以及探索平台预期解决的各种问题。下一章我们将探讨 Kubernetes 系统的一些基本概念,它是我们 ML 平台的核心。
进一步阅读
有关本章所涵盖主题的更多信息,请查阅以下资源:
- DevOps:打破开发与运维的壁垒
www.atlassian.com/devops
第二章:第三章: 探索 Kubernetes
现在你已经看到,Kubernetes 将成为你在本地计算机或云端的 minikube 实例的基础。这个是一个单节点的 Kubernetes 集群,你将使用它作为构建和运行 ML 平台的基础设施。
我们将按以下顺序覆盖以下主题:
-
探索 Kubernetes 的主要组件
-
通过 Kubernetes 实现云中立性
-
理解操作符(Operators)
-
设置本地 Kubernetes 环境
-
(可选)在谷歌云平台(GCP)上配置虚拟机(VM)
技术要求
本章包括一些动手操作。你将设置一个 Kubernetes 集群,为此,你需要一台符合以下硬件规格的机器:
-
至少四个核心的中央处理单元(CPU);推荐八个核心
-
至少 16 吉字节(GB)的内存;推荐 32 GB
-
至少 60 GB 可用空间的磁盘
这可以是物理机器,如笔记本电脑、服务器,或者是支持嵌套虚拟化的云端虚拟机(VM)。
探索 Kubernetes 主要组件
网络上有许多 Kubernetes 的定义。我们假设,作为 Kubernetes 用户,你已经有了自己偏好的定义。因此,在本节中,你将看到一些基本概念来刷新你对 Kubernetes 的知识。本节绝不是 Kubernetes 系统的参考资料或教程。
从第二章,《理解 MLOps》中,你已经看到 Kubernetes 为你的 ML 平台提供了以下能力:
-
提供声明式的软件组件运行方式:这一能力将帮助你的团队实现自主性。
-
提供硬件资源的抽象层:通过这个能力,你可以在各种硬件上运行 ML 平台,并提供按需的资源调度。
-
提供应用程序接口(API)以进行交互:这将使你能够将自动化引入到运行不同组件的 ML 平台中。
让我们从定义 Kubernetes 平台的主要组件开始:控制平面和工作节点。
控制平面
控制平面是一组组件,构成了 Kubernetes 的大脑。它由 API 服务器、键值数据库、调度器和一组控制器组成。让我们分别定义这些组件:
-
API 服务器:该组件提供了一组表征状态传输(REST)API,用于与 Kubernetes 系统交互。每个人都通过这个 API 与 Kubernetes 进行交互。作为开发人员或运维工程师,你使用 API,而 Kubernetes 内部组件通过 API 服务器进行通信,执行不同的活动。
-
etcd。Kubernetes 系统的其他组件不会直接与这个值存储交互——它只能通过 API 服务器访问。 -
调度器:调度器组件决定应用实例将在哪个节点上运行。调度器根据 Kubernetes 管理员定义的策略选择最合适的工作节点。
-
控制器:控制平面中运行着多个控制器。每个控制器都有一项特定任务;例如,节点控制器负责监控节点的状态。
以下图表显示了多个控制平面组件之间的交互:

图 3.1 – Kubernetes 控制平面组件
控制平面协调对象的创建、更新和删除。它监控并保持 Kubernetes 集群的健康状态。控制平面运行着保持集群运行的工作负载。那么,应用程序工作负载呢?
工作节点
正如其名称所示,工作节点是一组承载应用软件的节点。例如,所有 ML 平台组件将在工作节点上执行。然而,工作节点还运行一些 Kubernetes 组件,这些组件在控制平面与工作节点之间建立通信通道,并管理在工作节点上运行的应用程序。除应用程序外,这些是运行在工作节点上的关键组件:
-
Kube proxy:其主要作用是管理运行在节点上的应用程序的网络通信规则。
-
Kubelet:可以将 Kubelet 软件组件看作是运行在每个节点上的代理。这个代理的主要作用是与控制平面 API 服务器通信,并管理运行在节点上的应用程序。该代理还会捕获并通过 API 将节点和应用程序的状态反馈给控制平面。
-
容器运行时:容器运行时组件负责按照 Kubelet 的指示运行承载应用程序的容器。Docker 就是其中的一个例子;然而,Kubernetes 定义了容器运行时接口(CRI)。CRI 定义了 Kubernetes 使用的接口,Kubernetes 管理员可以选择与 CRI 兼容的任何容器运行时。
以下图表显示了多个工作节点组件之间的交互:

图 3.2 – Kubernetes 工作组件
工作节点,也称为计算节点,负责在集群中实际运行应用程序工作负载。运行应用程序工作负载需要你使用 Kubernetes 对象或资源与控制平面进行交互。
运行应用程序所需的 Kubernetes 对象
现在,让我们定义一组在 Kubernetes 系统上运行应用程序时常用的对象。当你为你的 ML 平台构建组件时,你将使用这些 Kubernetes 对象在 Kubernetes 上运行应用程序。以下是这些对象的列表:
-
命名空间:一个 Kubernetes 集群由多个团队和项目共享。命名空间提供了一种隔离 Kubernetes 资源的方式。通过这种隔离,不同的团队、不同的环境,甚至不同的应用程序可以共享同一个集群,同时保持不同的配置、网络策略、资源配额和访问控制。这就像在同一个 Kubernetes 集群内拥有一个逻辑上的子集群。
-
容器镜像:当你想在 Kubernetes 上运行一个应用程序时,你需要将应用程序打包成标准格式。这个打包格式包含了你的应用程序及其所有依赖项,称为容器镜像,而这个镜像的运行实例称为容器。它将你的应用程序及所有依赖项,包括操作系统资源和应用程序所需的资源,打包成一个共同的单元。
-
部署:这个 Kubernetes 对象代表了集群中应用程序的期望状态。一个部署对象包含了诸如你希望运行的容器镜像以及你需要多少个容器实例或副本等信息。Kubernetes 会定期将当前集群状态与部署对象中定义的期望状态进行对比。当 Kubernetes 发现当前状态与期望状态不符时,它会应用必要的更新来使集群达到期望状态。这些更新包括根据部署对象中定义的容器镜像启动新的容器、停止容器以及配置部署对象所需的网络和其他资源。
-
Pod:Pod 是 Kubernetes 中运行应用程序的基本单元。它也是最小的可调度部署单元。Pod 可以包含一个或多个容器。Pod 内的容器共享网络和磁盘资源。运行在同一个 Pod 中的容器会一起调度到同一个节点,并且能够彼此进行本地通信。
-
服务:Pod 之间是如何通信的呢?Pod 通过集群网络进行通信,每个 Pod 都有自己的互联网协议(IP)地址。然而,Pod 是可能会变动的。Kubernetes 可能会因为节点健康状况或调度变化而重启一个 Pod,当这种情况发生时,Pod 的 IP 地址会发生变化。此外,如果部署对象配置为运行多个相同 Pod 的副本,这意味着每个副本都会有自己的 IP 地址。
在 Kubernetes 中,服务将一组 Pod 暴露为一个抽象的网络服务。它提供一个一致的 IP 地址和域名系统(DNS)名称,可以对 Pod 进行流量路由并执行负载均衡。可以把服务看作是一个负载均衡的反向代理,指向你正在运行的 Pod。
- 配置映射和密钥:我们将应用程序打包成容器镜像并作为 pod 运行。相同的 pod 将在多个环境中部署,例如开发环境、测试环境和生产环境。然而,每个环境将有不同的配置,比如数据库位置等。将这种配置硬编码到容器镜像中不是正确的做法。一个原因是容器镜像可能会在多个环境中部署,而这些环境的设置是不同的。必须有一种方法可以在容器镜像之外定义配置,并在运行时将该配置注入到容器中!
配置映射和密钥提供了一种在 Kubernetes 中存储配置信息的方式。一旦定义了这些对象,它们可以作为文件或一组环境变量注入到运行中的 pod 中。
配置映射(ConfigMap)用于存储和访问配置信息。然而,对于敏感的配置,如密码和私钥,Kubernetes 提供了一种特殊的对象来处理这些内容,称为密钥(Secret)。与配置映射类似,密钥也可以作为文件或环境变量挂载到 pod 中。
下图展示了部署、pod、配置映射和服务之间的逻辑关系。部署对象提供了一个容器化应用程序的抽象,它隐藏了运行副本控制器和 pod 的复杂性。部署帮助你将应用程序作为单个 pod 或 pod 组运行,配置映射为你的 pod 提供环境特定的配置,而服务将部署中的 pod 作为一个单一的网络服务暴露出来:

图 3.3 – Kubernetes 中的存储配置
- 存储(持久卷和持久卷声明(PV 和 PVC)):Pod 是短暂的。一旦它们被销毁,所有本地资源都会丧失。通常,作为 pod 部署的应用程序可能需要访问存储,以读取和写入能够超越 pod 存活周期的持久数据。
Kubernetes 承诺成为许多硬件供应商和云提供商之上的基础设施抽象层。然而,不同云提供商和本地系统请求存储资源或配置磁盘的方式是不同的。这要求在不同的硬件供应商和云提供商之间以一致的方式请求存储资源。
Kubernetes 的解决方案是将存储资源分为两个 Kubernetes 对象。持久卷(PV)是定义物理存储卷的对象。它包含底层存储基础设施的详细信息。而持久卷声明(PVC)则是一个抽象的指针,指向一个 PV。PVC 表明所有者对特定 PV 的占用权。pod 的存储资源与 PVC 相关联,而不是直接与 PV 相关联;通过这种方式,底层存储定义从应用程序中抽象出来。
下图显示了 pod、PVC 和 PV 之间的关系。Pod 将 PVC 作为卷挂载;PVC 作为一个抽象层,用于为 pod 请求与 pod 关联的物理卷;PVC 绑定到提供磁盘具体信息的 PV:

图 3.4 - Kubernetes 中的存储供给(续)
- Ingress:服务允许从 Kubernetes 集群内访问 pod。对于需要从 Kubernetes 集群外部访问 pod 的场景,Ingress 是答案。Ingress 提供了一种方式,使您可以将特定服务暴露给从集群外部访问的方式。这使您可以映射指向服务的基于 HTTP 的 URL。Ingress 还可以在暴露的 URL 上使用 SSL,并可以配置终止集群内部流量的 SSL。这样,传输层将在整个到达 Ingress 的过程中加密,同时将流量转发到 pod 中的明文 HTTP。值得注意的是,如果需要,Kubernetes 还允许流量加密一直到 pod。
下图显示了 Ingress 如何使 pod 可以从 Kubernetes 集群外部访问:

图 3.5 - Kubernetes 集群中的 Ingress 对象
现在您已经刷新了对 Kubernetes 的理解,让我们看看 Kubernetes 如何让您在任何地方运行您的平台。
通过 Kubernetes 实现云无关性
我们正在构建的 ML 平台的一个关键方面是,它使组织能够在任何云或数据中心上运行。然而,每个云都有自己专有的 API 来管理资源和部署应用程序。例如,亚马逊网络服务(AWS)API 在提供服务器时使用 弹性计算云(EC2)实例(即服务器),而谷歌云的 API 则使用 Google 计算引擎(GCE)虚拟机(即服务器)。甚至资源的名称也不同!这就是 Kubernetes 发挥关键作用的地方。
Kubernetes 的广泛采用迫使主要云供应商提供与 Kubernetes 紧密集成的解决方案。这使任何人都可以在几分钟内在 AWS、GCP 或 Azure 上创建一个 Kubernetes 集群。
Kubernetes API 允许您管理云资源。使用标准 Kubernetes API,您可以在任何主要云提供商上部署应用程序,而无需了解云提供商的 API。Kubernetes API 已经成为在云中管理工作负载的抽象层。本书中将构建的 ML 平台将完全使用 Kubernetes API 来部署和运行应用程序。这包括构成 ML 平台的软件组件。
下图展示了 Kubernetes 如何帮助你实现云无关性。你通过 Kubernetes API 与 Kubernetes 交互,从而避免或减少直接与云提供商 API 的交互。换句话说,Kubernetes 提供了一种一致的方式来与环境交互,无论它运行在哪个云平台或数据中心:

图 3.6 – Kubernetes 作为云提供商 API 的适配层
另一个 Kubernetes 社区推出的重要概念是 操作员。你将使用 Kubernetes 操作员来部署大多数机器学习平台的组件。我们来深入了解一下。
了解操作员
在传统的信息技术(IT) 组织中,需要专门的团队来维护应用程序和其他软件组件,如数据库、缓存和消息组件。这些团队不断监控软件生态系统,并执行特定任务,例如备份数据库、升级和修补软件组件的新版本等。
操作员就像系统管理员或工程师一样,持续监控 Kubernetes 环境中运行的应用程序,并执行与特定组件相关的运维任务。换句话说,操作员是一个自动化的软件管理器,负责管理 Kubernetes 上应用程序的安装和生命周期。
简而言之,操作员负责基于你提供的配置来创建和更新 Kubernetes 对象(如部署、入口等),而不是你自己来执行这些任务。指示操作员执行特定任务的配置叫做 自定义资源 (CR),而 CR 的结构或模式由一个叫做 CR 定义 (CRD) 的对象来定义。
下图展示了操作员如何自动化应用程序的运维活动。在传统的方法中,开发人员构建和开发应用程序,然后由应用运维团队提供支持以运行该应用程序。Kubernetes 操作员的目标之一是自动化运维人员执行的任务:

图 3.7 – 操作员是一种自动化运维团队任务的软件
Kubernetes 操作员可能很复杂。有些操作员管理数据库实例,而有些则管理协同工作的 Pod 集群。一些操作员只拥有 1 或 2 个 CRD,而另一些则可能拥有超过 10 个 CRD。操作员生命周期管理器 (OLM) 简化了 Kubernetes 操作员的安装和管理。我们来更深入地了解一下。
在 OLM 中,安装 Operator 需要多个步骤:为 Operator 创建部署对象,配置运行 Operator 所需的权限(因为它需要观察 Kubernetes 集群中的变化),以及创建 CRD。为了减少安装 Operator 的复杂性,可能需要一个管理层来帮助完成这些任务。OLM 就是完成这个角色的工具。
OLM 标准化了与 Operators 的交互。它要求所有与 Operator 的交互都通过 Kubernetes API 完成。OLM 使得通过单一标准接口——Kubernetes API 来管理多个 Operator 的生命周期变得更加简单。我们的 ML 平台将使用几个 Operators,因此了解 OLM 和与之相关的对象是很有用的。让我们在这里更详细地了解它们:
-
ClusterServiceVersion:此对象定义了有关 Operator 的元数据。它包括 Operator 的名称和版本,以及安装信息和所需权限。它还描述了 Operator 所拥有和所需的 CRD。 -
Subscription:Subscription对象允许用户安装和更新 Operator。OLM 使用此对象来安装和配置 Operators、CRDs 以及相关的访问控制对象。 -
OperatorGroup:OperatorGroup提供了一种将您的 Operator 与特定命名空间集合关联的方法。OperatorGroup定义了一组命名空间,关联的 Operator 将对这些命名空间作出反应。如果我们在OperatorGroup定义中没有定义命名空间集合,那么 Operator 将在所有命名空间中全局运行。
在接下来的章节中,您将安装和配置本地的 Kubernetes 环境,并在 Kubernetes 集群上安装 OLM。
设置本地 Kubernetes 环境
现在,我们已经回顾了一些基础的 Kubernetes 概念,是时候开始实际操作了。在这一章节中,我们将准备并验证我们的本地 Kubernetes 集群。我们在这里设置的集群将用于在后续章节中托管 ML 平台。
安装 kubectl
kubectl 是一个命令行工具,帮助您在 Kubernetes 集群中运行命令。您可以通过这个工具创建 Kubernetes 对象、查看日志并监控操作进展。以下步骤将帮助您在机器上安装 kubectl。
在 Linux 上安装 kubectl
首先,让我们看看在运行 Linux 的机器上安装 kubectl 的过程。请按照以下步骤操作:
-
创建或 安全外壳(SSH)到您 Linux 计算机的终端会话。
-
下载
kubectl。本书中使用的 Kubernetes 版本为1.22.4。以下两行代码是一条命令:curl -LO https://dl.k8s.io/release/v1.22.4/bin/linux/amd64/kubectl -
通过运行以下命令安装
kubectlCLI:sudo install kubectl /usr/local/bin/kubectl -
通过运行以下命令验证它是否已安装:
kubectl version --client
您应该看到以下对 version 命令的响应:

图 3.8 – 在 Linux 中运行 kubectl version 命令的输出
现在你应该能够在 Linux 机器上运行 kubectl。
在 macOS 上安装 kubectl
首先,让我们了解在运行 macOS 的机器上安装 kubectl 的过程。请按照以下步骤操作:
-
在你的 Mac 电脑上创建或
SSH到一个终端会话。 -
下载
kubectlKubernetes CLI。本书中将使用 1.22.4 版本。
对于 Intel Mac,运行以下命令:
curl -LO https://dl.k8s.io/release/v1.22.4/bin/darwin/amd64/kubectl
对于 Apple M1 Mac,运行以下命令:
curl -LO https://dl.k8s.io/release/v1.22.4/bin/darwin/aa64/kubectl
-
运行以下命令安装
kubectlCLI:sudo install kubectl /usr/local/bin/kubectl -
通过运行以下命令验证是否已安装:
kubectl version --client
你应该看到以下对 version 命令的响应:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_03_009.jpg)
图 3.9 – macOS 中 kubectl version 命令的输出
现在你应该能够在 macOS 上运行 kubectl。
在 Windows 上安装 kubectl
接下来,让我们了解 Windows 系统上的步骤:
-
以 管理员 身份运行 PowerShell。
-
通过运行以下命令下载
kubectlKubernetes CLI 二进制文件。本书中将使用 1.22.4 版本:curl.exe -LO https://dl.k8s.io/release/v1.22.4/bin/windows/amd64/kubectl.exe -
通过运行以下命令,将
kubectl.exe文件复制到c:\kubectl:mkdir c:\kubectl copy kubectl.exe c:\kubectl -
通过运行以下命令将
c:\kubectl添加到PATH环境变量中,然后重新启动 PowerShell 终端:setx $ENV:PATH "$ENV:PATH;C:\kubectl" /M -
通过运行以下命令验证是否已安装:
kubectl version –client
你应该看到以下对 version 命令的响应:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_03_010.jpg)
图 3.10 – Windows 中 kubectl version 命令的输出
你刚刚安装了 kubectl Kubernetes CLI。下一步是安装 minikube,一个本地单节点 Kubernetes 集群。
安装 minikube
minikube 提供了一种轻松运行本地 Kubernetes 集群的方法。这是一个最小化的集群,旨在仅用于本地开发或实验。将 Kubernetes 用于生产环境超出了本书的范围。
与kubectl类似,接下来让我们了解如何在不同类型的操作系统上进行安装。
在 Linux 上安装 minikube
按照以下步骤在 Linux 上安装 minikube:
-
创建一个终端会话或通过
SSH连接到你的 Linux 计算机。 -
使用以下代码为
minikube安装podman:sudo dnf install podman -y -
从此位置下载
minikube。我们使用的是minikube的1.24.0版本:curl -LO https://storage.googleapis.com/minikube/releases/v1.24.0/minikube-linux-amd64 -
按照以下步骤安装
minikube工具:sudo install minikube-linux-amd64 /usr/local/bin/minikube -
验证
minikube版本,如下所示:minikube version
你应该看到以下响应:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_03_011.jpg)
图 3.11 – Linux 上 minikube version 命令的输出
你刚刚在 Linux 上安装了 kubectl 和 minikube。这两个命令行工具将帮助你设置一个本地 Kubernetes 集群。
在 macOS 上安装 minikube
虽然本书的首选操作系统是 Linux,但我们也提供了如何在 macOS 上安装 minikube 的步骤。很多开发者使用 macOS 系统,因此提供 Apple 操作系统的详细步骤也会很有帮助。接下来的步骤如下:
-
从 Docker 官网下载并安装 Docker Desktop,或者通过访问以下网页进行下载:
www.docker.com/products/docker-desktop。 -
一旦 Docker 安装完成,确保通过运行以下命令来验证其是否正确安装。运行此命令前,请确保 Docker 已经启动:
docker version
你应该看到以下响应。如果收到错误信息,请确保 Docker 已经启动:

图 3.12 – macOS 上执行 docker version 命令的输出
-
在 macOS 计算机上打开一个终端。
-
通过运行以下命令之一来下载
minikube。你将使用版本 1.24.0 的 Minikube:-
如果你使用的是 Intel Mac,请运行以下命令:
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.24.0/minikube-darwin-amd64 -
如果你有 M1 Mac(Apple Silicon),请运行以下命令:
curl -Lo minikube https://storage.googleapis.com/minikube/releases/v1.24.0/minikube-darwin-arm64
-
-
将下载的文件移动到
/usr/local/bin文件夹,并通过以下命令将下载的文件设为可执行文件:sudo mv minikube /usr/local/bin sudo chmod +x /usr/local/bin/minikube -
验证
minikube版本,方法如下:minikube version
你应该看到以下响应:

图 3.13 – minikube version 命令的输出
你刚刚在 macOS 上安装了 kubectl 和 minikube。这两个命令行工具将帮助你设置本地 Kubernetes 集群。
在 Windows 上安装 minikube
和 macOS 一样,许多开发者也使用 Windows。为 Microsoft 的强大操作系统 Windows 提供操作步骤是公平的。我们来探讨一下如何在 Windows 上使用 Hyper-V,Microsoft 的虚拟化层,来运行 minikube。请注意,Hyper-V 在除 Windows Home 外的所有 Windows 版本中均可用。按照以下步骤操作:
-
以 管理员 身份运行 PowerShell。
-
在 PowerShell 控制台中,运行以下命令以启用
Hyper-V:Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V --All
如果 Hyper-V 未启用,你应该看到以下响应。如果已经启用,该命令将仅打印状态。按 Y 继续:

图 3.14 – Windows 上启用 Hyper-V 命令的输出
如有需要,请重启计算机。
-
通过在浏览器中打开以下链接来下载
minikube安装程序:github.com/kubernetes/minikube/releases/download/v1.24.0/minikube-installer.exe。 -
运行已下载的安装程序,你应该会看到语言设置界面,如下图所示。点击 OK:

图 3.15 – minikube 安装程序的语言选择对话框
- 安装程序将显示以下欢迎界面。点击下一步 >,如下面的截图所示:

图 3.16 – minikube 安装程序向导
- 安装程序将显示以下许可协议界面。点击我同意:

图 3.17 – minikube 安装程序的许可协议界面
- 在此界面中,选择你希望安装
minikube的位置,然后点击安装,如下面的截图所示:

图 3.18 – minikube 安装程序的安装位置界面
- 安装可能需要几分钟时间。一旦安装成功,你应该看到以下界面。点击下一步 >:

图 3.19 – minikube 安装程序的成功安装界面
- 这是你
minikube设置过程中的最后一个界面。点击完成以结束设置:

图 3.20 – minikube 安装程序的最终界面
-
最后,在 PowerShell 控制台中,将
minikube的虚拟化驱动程序设置为hyperv。你可以通过运行以下命令来完成:minikube config set driver hyperv
你应该会看到以下响应:

图 3.21 – minikube 配置命令的输出
恭喜你——你已经在 Windows 机器上成功设置了minikube程序!
在前面的章节中,你已经安装了kubectl和minikube工具以设置 Kubernetes 集群。在接下来的章节中,你将设置一个 Kubernetes 集群。
设置本地 Kubernetes 集群
现在,我们将在你的本地机器上设置 Kubernetes 集群。如技术要求中所述,我们需要至少 4 个 CPU 核心或虚拟 CPU(vCPUs)、60GB 的可用磁盘空间,并且至少需要分配 16GB 内存给 Kubernetes 集群。我们推荐的配置是 8 个 CPU 和 64GB 内存,并配备 60GB 磁盘空间。如果你本地没有这些资源,你可以在云中提供一个 Linux 主机。我们将在下一节中描述如何在 Google Cloud 上提供主机。请按以下步骤继续:
-
通过以下命令设置
minikube的 CPU、磁盘和内存配置:minikube config set cpus 8 minikube config set memory 32GB minikube config set disk-size 60GB -
通过以下命令验证配置是否正确设置:
minikube config view
你应该会看到以下响应:

图 3.22 – minikube 配置命令的输出
-
现在,通过运行以下命令启动 Kubernetes 集群:
minikube start --kubernetes-version=1.22.4
你应该会看到以下响应:

图 3.23 – minikube start 命令的部分输出
一旦启动过程完成,Kubernetes 平台可用后,你应该看到类似于以下的成功消息:

图 3.24 – minikube 启动成功后的输出
-
通过在 Linux 或 macOS 上运行以下命令验证所有 pods 是否处于 运行中 状态。请注意,pods 可能需要几分钟才能处于 运行中 状态:
watch kubectl get pods --all-namespaces
或者,在 Windows PowerShell 中运行以下命令:
while (1) {kubectl get pods --all-namespaces; sleep 5}
你应该看到以下响应:

图 3.25 – 验证 Kubernetes pods 已成功启动
恭喜!你刚刚成功安装并验证了新的 Kubernetes 集群。接下来的步骤是安装可以在新 Kubernetes 集群上运行的组件。
安装 OLM
在验证所有 pods 已在本地 Kubernetes 集群中运行后,你将安装 OLM。在 Kubernetes 中安装 OLM 或任何其他应用程序的过程对于所有操作系统类型都是相同的。按照以下步骤进行:
-
运行以下命令安装 OLM 的 CRD:
kubectl apply -f https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.19.1/crds.yaml
你应该看到以下响应:

图 3.26 – 验证 OLM CR 已成功创建
-
运行以下命令在 Kubernetes 上安装 OLM:
kubectl apply -f https://github.com/operator-framework/operator-lifecycle-manager/releases/download/v0.19.1/olm.yaml
你应该看到以下响应:

图 3.27 – 在 Kubernetes 中创建 OLM 对象
-
通过在 Linux 或 macOS 上运行以下命令验证所有 OLM pods 是否处于 运行中 状态:
watch kubectl get pods -n olm
或者,在 Windows PowerShell 中运行以下命令:
while (1) {kubectl get pods -n olm; sleep 5}
你应该看到以下响应:

图 3.28 – 验证 OLM 的资源已成功创建
-
通过发出以下命令验证
catalogsource是否可用:kubectl get catalogsource -n olm
你应该看到以下响应:

图 3.29 – 验证 Operator 目录已安装
恭喜!你现在已经在本地运行了一个 Kubernetes 集群,并在其中安装了 OLM。你的集群现在准备好安装 Kubernetes Operators。一些人可能没有访问到满足 ML 平台运行所需最低硬件要求的机器,但不用担心——我们会为你提供帮助。接下来的部分将帮助你在 Google Cloud 中配置所需的虚拟机。
在 GCP 上配置虚拟机
最好能有一个本地环境,以便你能在其中进行本书的练习。然而,我们理解并非每个人的本地机器都具备所需的计算资源。那么,来使用云吧!你可以在云中按需配置适合本书练习的机器,而且是免费的。例如,Google Cloud 会为新账户提供 300 美元(USD)的信用额度。其他云服务提供商,如 AWS 和 Azure,也提供类似的免费套餐,具体选择哪个云服务商由你决定。然而,关于本书中所需的虚拟机配置,我们将使用 Google Cloud。
一旦你整理好账户信息,按照以下步骤在你的账户中配置虚拟机。只需记住,在完成会话后停止虚拟机实例,以避免在不使用机器时仍然被收费。
以下步骤将指导你通过 Google Cloud 配置虚拟机的过程:
-
首先,在
cloud.google.com注册一个新账户。 -
按照
cloud.google.com/sdk/docs/install上的步骤安装gcloud软件开发工具包(SDK)。 -
使用以下命令登录 Google Cloud。该命令将打开一个浏览器实例,在其中输入你的 Google Cloud 账户登录凭据:
gcloud auth login
你应该看到以下响应:

图 3.30 – 登录命令的输出
- 然后,它会带你到浏览器,你需要在浏览器中进行身份验证。浏览器完成身份验证后,你将在命令行看到以下输出:

图 3.31 – 成功登录 gcloud 账户后的输出
-
在 Google Cloud 中创建一个新项目,如下所示。你的虚拟机将属于此项目。请注意,项目名称在 GCP 中必须是全局唯一的,因此请根据个人喜好修改项目名称:
gcloud projects create mlops-kube --name="MLOps on Kubernetes"
你应该看到以下响应:

图 3.32 – 创建项目命令在 Google Cloud 中的输出
GCP 中的项目
项目 mlops-kube。为使该命令生效,请选择你自己的项目名称。你还需要在后续的命令中使用你选择的项目名称,而不是 mlops-kube 项目名称。
-
通过执行以下命令,确保你在正确的项目中:
gcloud config set project mlops-kube
你应该看到以下响应:

图 3.33 – 设置当前项目上下文命令的输出
-
根据你的所在位置设置正确的区域和可用区。你可以通过
gcloud compute zones list命令获取所有可用区的列表,如下所示:gcloud config set compute/region australia-southeast1
你应该看到以下响应:

图 3.34 – 设置 gcloud 区域后的输出
运行以下命令:
gcloud config set compute/zone australia-southeast1-a
你应该看到以下响应:

图 3.35 – 设置 gcloud 区域后的输出
-
启用计算引擎 API,如下所示。此步骤是通过 API 提供 Linux 虚拟机所必需的:
gcloud services enable compute.googleapis.com -
禁用 OS 登录,因为你只通过 SSH 连接,如下所示:
gcloud compute project-info add-metadata --metadata enable-oslogin=FALSE -
现在,通过运行以下命令在此项目中创建一个虚拟机(VM):
gcloud compute instances create mlopskube-cluster --project=mlops-kube --zone=australia-southeast1-a --machine-type=c2-standard-8 --network-interface=network-tier=PREMIUM,subnet=default --maintenance-policy=MIGRATE --service-account=702800110954-compute@developer.gserviceaccount.com --scopes=https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/trace.append --create-disk=auto-delete=yes,boot=yes,device-name=instance-1,image=projects/centos-cloud/global/images/centos-8-v20211105,mode=rw,size=80,type=projects/mlops-kube/zones/australia-southeast1-b/diskTypes/pd-balanced --no-shielded-secure-boot --shielded-vtpm --shielded-integrity-monitoring --reservation-affinity=any
命令的输出应该显示机器的详细信息,如下所示:

图 3.36 – 在 Google Cloud 上创建 VM 命令的输出
-
添加防火墙规则,允许通过端口
22进行 SSH 访问实例,如下所示。这是一个宽松的规则,不应在生产环境中使用:gcloud compute --project=mlops-kube firewall-rules create allow22 --direction=INGRESS --priority=1000 --network=default --action=ALLOW --rules=tcp:22 --source-ranges=0.0.0.0/0
你应该看到以下响应:

图 3.37 – 防火墙规则命令的输出
-
使用
gcloudSSH 功能连接到机器,如下所示。这将给你命令行,你可以执行前面章节中提到的 Kubernetes 命令:gcloud beta compute ssh --zone "australia-southeast1-a" "mlopskube-cluster" --project "mlops-kube" -
完成会话后,请删除实例,如下所示:
gcloud compute instances delete --zone "australia-southeast1-a" "mlopskube-cluster" --project "mlops-kube"
你应该看到以下响应:

图 3.38 – 在 Google Cloud 上删除机器
此时,你可以使用这个 gcloud 虚拟机作为 Kubernetes 集群的主机。按照之前的章节,你现在应该知道如何安装 kubectl 和 minikube,以及如何在这个虚拟机上设置本地 Kubernetes 集群。
总结
在本章中,你回顾了一些基础的 Kubernetes 概念,并了解了 Kubernetes 生态系统中的 Operator。如果你想深入了解 Kubernetes,Packt 出版社的《Kubernetes Workshop》是一个不错的起点。
你已经安装了设置本地 Kubernetes 集群所需的工具。你已经看过在其他环境(如 Linux、macOS 和 Windows)中进行设置的说明。你已经在 Google Cloud 上设置了一个虚拟机(VM),以防你不想使用本地计算机进行练习。你已配置 OLM 来管理 Kubernetes 集群上的操作员(Operators)。这些技术将构成我们机器学习平台的基础设施,接下来的章节你将开始构建这个平台。
第二部分:MLOps 平台的构建模块以及如何在 Kubernetes 上构建一个
本部分深入定义了 MLOps 解决方案的不同组件。各章节详细介绍了每个组件及其所服务的目的。本节还提供了一个 OSS 解决方案,可以在 MLOps 平台中扮演每个组件的角色。
本部分包括以下章节:
-
第四章**,机器学习平台的构成
-
第五章**,数据工程
-
第六章**,机器学习工程
-
第七章**,模型部署与自动化
第三章:第四章:机器学习平台的构成
在这一章及接下来的几章中,你将学习并安装构建在 Kubernetes 上的机器学习(ML)平台的各个组件。一个 ML 平台应当能够提供运行 ML 项目全生命周期所需的工具,如第二章《理解 MLOps》中所描述的那样。本章首先从技术无关的角度定义了 ML 平台的不同组件。在后续部分,你将看到一组开源软件,能够满足每个组件的要求。我们选择这种方式,是为了不将你束缚于特定的技术栈;相反,你可以根据自己的环境需要替换组件。
你将在本书中构建的解决方案将基于开源技术,并托管在你在第三章《探索 Kubernetes》中构建的 Kubernetes 平台上。
在本章中,你将学习以下主题:
-
定义自助平台
-
探索数据工程组件
-
探索 ML 模型生命周期组件
-
解决安全性、监控和自动化问题
-
探索 Open Data Hub
技术要求
本章包括一些实践设置。你将需要一个配置了操作符生命周期管理器(OLM)的运行中 Kubernetes 集群。如何构建这样的 Kubernetes 环境已在第三章《探索 Kubernetes》中介绍。在尝试本章的技术练习之前,请确保你有一个正在工作的 Kubernetes 集群。你可以选择使用与第三章《探索 Kubernetes》中描述的不同版本的 Kubernetes,只要该集群安装了 OLM。
定义自助服务平台
自助服务被定义为平台的一种能力,允许平台终端用户按需提供资源而无需其他人工干预。例如,数据科学家用户可能需要一个 Jupyter notebook 服务器实例,运行在一个具有 8 个 CPU 的主机容器上,以执行他的/她的工作。一个自助式的 ML 平台应当允许数据科学家通过一个终端用户友好的界面,提供容器并运行 Jupyter notebook 服务器实例。另一个自助服务的例子是,数据工程师请求提供一个新的 Apache Spark 集群实例,以运行他的/她的数据管道。最后一个例子是数据科学家希望将其 ML 模型打包并作为 REST 服务进行部署,以便应用程序可以使用该模型。
自助服务平台的一个好处是,它允许跨职能团队以最小的依赖关系共同工作。这种独立性带来了更好的团队动态、更少的摩擦和更高的团队速度。
然而,自服务模型需要治理。试想,每个数据科学家都请求 GPU,或者数据工程师请求数十 TB 的存储!自服务功能非常好,但如果没有适当的治理,也可能带来问题。为了避免此类问题,平台必须由平台团队进行管理,团队可以控制或限制最终用户的操作。资源配额就是这种限制的一个例子。团队和/或个人用户可以分配配额,并负责在分配的配额范围内管理自己的资源。幸运的是,Kubernetes 具有这种能力,我们的机器学习平台可以利用此能力为团队的资源应用限制。
作为治理的一部分,平台必须具备基于角色的访问控制。这是为了确保只有具有适当角色的用户才能访问他们管理的资源。例如,平台团队可以更改资源配额,而数据工程师只能启动新的 Spark 集群并运行数据管道。
自服务平台的另一个方面是工作负载的隔离。许多团队将共享同一个平台,尽管配额将使团队保持在预定义的边界内,但至关重要的是,平台必须具备隔离工作负载的能力,以确保在同一平台上运行的多个不相关的项目不会重叠。
探索数据工程组件
在本书的上下文中,数据工程是指从源系统中摄取原始数据,并生成可用于分析、业务报告和机器学习等场景的可靠数据的过程。数据工程师是构建软件来收集和处理原始数据,从而为数据分析师和数据科学家生成干净且有意义的数据集的人。这些数据集将成为组织机器学习计划的基础。
图 4.1 展示了典型的机器学习项目中数据工程阶段的各个步骤:

图 4.1 – 机器学习中的数据工程阶段
数据工程通常与特征工程有交集。虽然数据科学家决定哪些特征对机器学习用例更有用,但他或她可能会与数据工程师合作,以获取当前特征集中没有的数据点。这是数据工程师与数据科学家之间的主要合作点。数据工程师在数据工程阶段创建的数据集将成为机器学习阶段中的特征集。
一个支持团队进行特征工程的机器学习平台将包含以下组件和过程。
- 数据摄取:数据摄取是团队理解数据源并构建和部署收集来自一个或多个数据源的数据的软件的过程。数据工程师理解从源系统读取数据的影响。例如,在从源读取数据时,源系统的性能可能会受到影响。因此,机器学习平台必须具备工作流调度功能,以便在源系统不太活跃的时间安排数据收集。
一个机器学习平台使团队能够以多种方式从各种来源摄取数据。例如,某些数据源允许拉取数据,而其他数据源可能允许推送数据。数据可能来自关系型数据库、数据仓库、数据湖、数据池、数据流、API 调用,甚至是原始文件系统。平台还应具备理解不同协议的能力,例如,一个消息系统可能有多种协议,如高级消息队列协议(AMQP)、消息队列遥测传输(MQTT)和 Kafka。换句话说,机器学习平台应该具备从不同类型的数据源以各种方式收集不同形状和大小的数据的能力。图 4.2展示了平台应该能够摄取的各种数据来源:

图 4.2 – 数据摄取集成
-
数据转换:一旦数据从各种来源摄取,它需要从原始形式转化为对机器学习模型训练和其他应用场景更有用的形式。根据福布斯的一项调查,80%的数据科学家工作都与为模型训练准备数据相关;这通常是数据科学团队中被认为最枯燥的阶段。然而,如果数据没有转换为合适的形式,它将导致不那么有用和/或低效的机器学习模型。一个机器学习平台使团队能够轻松编写、构建和部署数据转换管道和作业。该平台抽象了运行和管理数据转换组件(如 Apache Spark 作业)的复杂性。平台不仅管理这些过程的执行,还管理运行这些组件所需的计算资源(如 CPU、内存和网络)的供应和清理。
-
存储:在特征工程过程中,您将在各个阶段读取和写入数据。您可能会创建数据集的临时表示以进行进一步处理,或者您可能会写入新数据集以供机器学习过程使用。在这些情况下,您将需要可以轻松访问并根据需要扩展的存储资源。一个机器学习平台提供按需存储,确保您的数据集能够以可靠的方式存储。
现在,让我们看看数据工程师将如何在他们的工作流中使用这些组件。
数据工程师工作流
前一节中提到的所有功能都由机器学习平台以自服务的方式提供。数据工程师在使用平台时通常会执行的工作流如下:
-
登录平台:在此步骤中,数据工程师进行平台身份验证。
-
配置开发环境:在此步骤中,数据工程师向平台请求开发环境所需的资源(如 CPU 数量、内存大小和特定的软件库)。平台随后会自动配置所请求的资源。
-
构建数据管道:在此步骤中,数据工程师编写用于数据摄取和数据转换的代码。然后,数据工程师将在隔离环境中运行代码,验证其有效性,并进行必要的重构和调优。
-
运行数据管道:在此步骤中,数据工程师按需安排代码运行。可以根据使用场景选择定期运行(如每小时或每日)或一次性运行。
从前面的步骤中可以看到,除了编写代码,其他所有步骤都是声明性的。数据工程师的重点将放在构建用于摄取和转换数据的代码上。流程的其他方面将由机器学习平台负责。这将提高团队的效率和工作速度。平台的声明式功能将帮助团队在整个组织内标准化流程,从而减少定制工具链的数量,并提升整体流程的安全性。
数据工程流程的主要输出是一个可用的、已转换并部分清理的数据集,可以用来开始构建和训练模型。
探索模型开发组件
一旦清理后的数据可用,数据科学家将对问题进行分析,尝试确定哪些模式对于该情况有帮助。关键是数据科学家的主要职责是从数据中找出模式。机器学习平台的模型开发组件会探索数据模式、构建和训练机器学习模型,并试验多种配置,找到最佳的配置和算法,以实现模型所需的性能。
在模型开发过程中,数据科学家或机器学习工程师基于多个算法构建多个模型。这些模型将使用数据工程流程中收集和准备的数据进行训练。数据科学家接着会调整几个超参数,通过模型测试得到不同的结果。然后,这些训练和测试的结果将与其他模型进行比较。这些实验过程将重复多次,直到达到预期的结果。
实验阶段将导致选择最合适的算法和配置。选定的模型将被标记以便打包和部署。
图 4.3 显示了机器学习项目中模型开发的各个阶段:

图 4.3 – 机器学习数据工程阶段
一个能够支持团队进行模型开发的机器学习平台将包含以下组件:
-
数据探索:我们人类在数据可视化时比仅仅查看原始数据集时更擅长发现模式。机器学习平台使你能够可视化数据。作为数据科学家,你需要与领域专家(SMEs)合作,后者拥有专业知识。假设你正在分析一组冠状病毒患者的数据集。如果你不是病毒学或医学领域的专家,你将需要与一个能够提供关于数据集、特征关系以及数据质量的见解的领域专家合作。机器学习平台允许你将自己创建的可视化图表分享给更广泛的团队,以便获得更好的反馈。平台还允许非技术人员以更图形化的方式查看数据,这有助于他们更好地理解数据。
-
实验:作为数据科学家,你将把数据分为训练集和测试集,然后开始为给定的指标构建模型。接下来,你将尝试多种机器学习算法,如决策树、XGBoost 和深度学习,并为每个算法应用不同的参数调优,例如深度学习模型中的层数或神经元数量。这就是我们所说的实验,平台使团队能够以自主的方式进行实验。请记住,对于每个实验,你可能需要不同的计算资源,如 GPU。因此,平台的自助服务配置能力至关重要。
-
跟踪:在进行多个实验时,你需要跟踪每个实验使用的参数以及它所取得的指标。一些算法可能需要不同的特征集,这意味着你还需要跟踪在训练中使用的数据集版本。这样做有两个原因。第一个原因是,你将需要保留实验历史,以便进行比较并挑选最佳组合。第二个原因是,你可能需要将结果与其他数据科学家共享。机器学习平台使你能够记录实验结果并无缝共享。
-
模型构建与调优:在实验阶段,你已经找到了最佳的算法和最佳参数。你已比较了模型的结果和相关指标,并选择了用于模型的算法和参数。在这个阶段,你将使用这些参数训练你的模型,并将其注册到模型注册表中:
- 模型注册表:作为数据科学家,当你对模型感到满意时,你将与团队一起部署它。然而,现实世界会发生变化,你需要为新数据集、不同指标或仅仅为了提高指标而更新模型。新版本的模型不断出现,ML 平台使你能够追踪模型版本。模型版本控制功能将帮助团队比较新旧模型版本的效率,并在需要时允许团队将生产中的新模型回滚至以前的版本。
-
存储:存储不仅在数据工程阶段重要,在模型开发过程中同样至关重要。在模型开发过程中,你会在多个阶段读取和写入数据。你将数据集分成测试集和训练集,你可能选择只写入一次数据,这样你可以在相同数据集上进行不同模型参数的实验。实验追踪模块和模型注册表都需要存储。ML 平台为你的数据集提供按需存储,确保数据可靠存储。
现在,让我们看看数据科学家如何在他们的工作流程中使用这些组件。
理解数据科学家的工作流程
上一节中提到的所有功能都由 ML 平台以自助方式提供。数据科学家的典型工作流程如下:
-
登录平台:数据科学家对平台进行身份验证。
-
开发环境的配置:在这一步骤中,数据科学家向平台请求开发环境的资源要求,如 CPU 数量、内存大小和特定的软件库。平台会自动为你配置所需资源。
-
探索性数据分析:在这个阶段,数据科学家进行多种数据转换和可视化技术,以理解数据中隐藏的模式。
-
尝试不同的算法:在这个阶段,数据科学家将完整数据集拆分为训练集和测试集。然后,数据科学家应用不同的 ML 算法和超参数,以实现所需的指标。数据科学家接着比较每次训练运行的参数,选择最适合给定用例的参数。
-
模型训练:数据科学家根据前一阶段找到的最优化参数训练模型,并将模型注册到模型注册表中。
-
运行模型部署管道:在此步骤中,数据科学家将模型打包以作为服务消费,并构建自动化部署流程。根据用例,它可以定期调度或一次性运行。
您可以在前面的步骤中看到,除了编写便于模型构建和训练的代码之外,所有其他步骤都是声明性的。数据科学家的重点将放在构建更多的数据科学和 ML 工程任务上。流程的所有其他方面将由 ML 平台处理。这将提高团队的效率和速度,更不用说数据科学家的幸福感了。平台的声明能力还将允许团队在整个组织中标准化流程,从而减少使用定制工具链,提高一致性并改善整个流程的安全性。
在接下来的部分中,您将探讨 ML 平台的常见服务。这些服务对于使平台达到生产就绪状态并在企业环境中更易于采用至关重要。
安全性、监控和自动化
在本节中,您将看到适用于我们到目前为止讨论的所有组件和阶段的 ML 平台的一些常见组件。这些组件帮助您在组织中实现平台的操作化:
-
数据管道执行:数据工程的结果是一个数据管道,用于摄取、清理和处理数据。您已经用缩减版数据构建了这个管道以供开发目的使用。现在,您需要用生产数据运行这些代码,或者您希望定期运行新数据,比如每周。ML 平台允许您获取代码并在不同环境中自动执行它。这是一大进步,因为平台不仅允许您运行代码,还将管理代码的所有依赖项的打包,使其可以在任何地方运行。如果您构建的代码使用 Apache Spark,平台应允许您自动化提供 Spark 集群和运行数据管道所需的所有其他组件的过程。
-
模型部署:一旦模型准备好供使用,它应该可以作为服务供消费。如果没有 ML 平台的自动化模型打包和部署能力,将模型打包并将其托管为服务的过程需要一些软件工程工作。这项工作需要与软件工程师和运维团队紧密合作,并可能需要花费数天甚至数周的时间才能完成。ML 平台自动化了这个过程,通常只需几秒到几分钟的时间。这个过程的结果是在环境中部署的 ML 模型,并可以作为服务访问,通常以 REST API 的形式提供。
模型的部署是一个方面;随着时间的推移,你可能还需要用新数据集重新训练模型。该平台还使团队能够使用第一次训练模型时所编写的相同训练代码来自动化重新训练过程。重新训练的模型将自动重新部署。这项功能大大提高了团队的效率,允许更高效地利用时间,如处理新的挑战,同时为业务提供价值。
-
监控:监控不仅仅是指拥有观察生产环境中组件动态的能力,例如监控模型响应时间,它还使团队能够在问题发生之前响应事件。一个好的监控平台在整个机器学习项目生命周期中提供可观测性,而不仅仅是生产环境中的监控。当你编写代码处理数据时,你可能需要调整来自多个系统的数据集之间的连接表达式。这是你在开发过程中需要的信息之一。该平台允许你在开发过程中深入了解细节。平台还提供监控底层 IT 基础设施的能力。例如,在模型训练阶段运行代码时,平台提供硬件资源利用率的度量。
-
安全性和治理:你正在构建的平台使团队能够独立工作。团队可以随时使用平台中的工具进行工作。然而,谁可以访问什么资源、谁可以使用哪些工具,成为许多组织面临的挑战。为此,平台必须具备访问控制功能,只允许授权用户访问。平台的安全组件通过标准协议,如OAuth2或OpenID Connect,对用户进行认证和授权。你将使用开源组件将认证功能引入平台。平台还使用 Kubernetes 命名空间功能,在共享相同集群的不同团队之间提供工作负载隔离。Kubernetes 还提供将硬件资源使用限制分配给各个团队的能力。这些功能将使团队能够在组织内的多个单位之间共享平台,同时提供明确的隔离边界和硬件资源配额。
-
源代码管理:当你构建数据管道或训练模型时,你会编写代码。该平台提供与源代码管理解决方案的集成功能。Git是集成平台的默认源代码管理解决方案。
现在,让我们继续介绍开放数据中心(ODH)。
引入 ODH
ODH 是一个开源项目,提供了构建我们 ML 平台所需的大部分组件。它包括一个 Kubernetes 操作符和一套精心挑选的开源软件组件,这些组件构成了 ML 平台的大部分。在本书中,我们将主要使用 ODH 操作符。平台中还有其他一些组件,它们并非 ODH 原生提供的。ODH 操作符的一个优点是,您可以根据实际需求,随时将默认组件替换为其他组件。
为了构建平台,您将使用以下组件。在接下来的几章中,您将学习每个组件的详细信息及其使用方法。目前,您只需了解这些组件的高层次用途:
-
ODH 操作符:一个 Kubernetes 操作符,用于管理 ML 平台中不同组件的生命周期。它控制并管理 ML 平台中使用的软件组件的安装和维护。
-
JupyterHub:管理 Jupyter Notebook 服务器实例及其相关资源。
-
Jupyter notebooks:一个集成开发环境(IDE),是平台中主要的数据工程和数据科学工作区。数据科学家和工程师将使用这些工作区编写和调试数据工程及 ML 工作流的代码。
-
Apache Spark:一个分布式并行数据处理引擎和框架,用于处理大规模数据集。它提供了广泛的数据摄取连接器,能够从各种数据源中获取数据。
-
Apache Airflow:一个工作流引擎,用于自动化执行和调度数据管道和模型部署。Airflow 协调数据管道中的不同组件。
-
Seldon Core:一个将 ML 模型打包并部署为 REST 服务的库。它还具有监控已部署模型的功能。它支持流行的 ML 框架,能够将使用 TensorFlow、scikit-learn、XGBoost 和 PyTorch 等框架构建的 ML 模型包装成 REST 服务。
-
Prometheus 和 Grafana:这两个组件为我们的平台提供监控功能。Prometheus 提供度量数据库,用于记录平台组件提供的遥测数据,而 Grafana 提供图形用户界面(GUI)来可视化捕获的度量数据。
-
Minio:一个与 Amazon S3 API 兼容的对象存储提供者。Minio 组件并非 ODH 工具链的一部分,但我们将扩展并配置 ODH 操作符以管理平台上的 Minio 组件。
-
MLFlow:一个用于追踪不同模型实验的组件,同时也作为平台的模型注册中心。MLFlow 组件并非 ODH 工具链的一部分,但我们将扩展 ODH 操作符以管理 MLFlow 组件。
您还将安装一个开源身份提供者组件。该组件的目标是为所有平台组件提供一个通用的单点登录功能。我们将使用 Keycloak 作为身份管理系统,但在您的情况下,可以使用基于 OAuth2 的系统来替换 Keycloak。Keycloak 不是 ODH 的一部分,我们将展示如何将其作为独立组件安装到您的 Kubernetes 集群中。
图 4.4 显示了作为 ML 平台主要组件的主要开源软件。ODH 可扩展性模型允许您根据需求添加或选择使用的产品。您可以用其他开源产品替换任何组件。不过,在本书的练习中,我们将使用此处列出的产品:

图 4.4 – ML 平台的主要组件
在下一节中,您将部署 ODH 运算符和 Keycloak 服务器到您的 Kubernetes 集群中。您还将安装并配置入口控制器,以接受来自集群外部的流量。
在 Kubernetes 上安装 ODH 运算符
在本节中,您将把 ODH 运算符安装到 Kubernetes 集群中。此时,您还不会启用平台的任何组件。要安装运算符,您首先需要注册运算符的目录源,然后才能安装它。
首先,让我们注册 ODH 运算符的目录。目录源包含元数据,通过这些元数据,OLM 可以发现运算符及其依赖项。ODH 运算符不在默认的 OLM 目录中,因此我们需要注册一个包含 ODH 元数据的新目录,以供 OLM 使用:
-
如果您使用的是
minikube,请验证您的 Kubernetes 集群是否正在运行:minikube status
您应该看到以下响应:

图 4.5 – 验证 Kubernetes 是否通过 minikube 正在运行
如果您的 Kubernetes 集群没有运行,请参考第三章,探索 Kubernetes,了解如何配置和启动 Kubernetes 集群。
-
通过执行以下命令来验证 OLM 是否已安装并正在运行:
kubectl get pods -n olm
您应该看到以下响应:

图 4.6 – 命令输出显示 OLM Pod 正在运行
确保所有 OLM Pod 都在运行。如果您遇到这种情况,请参考第三章,探索 Kubernetes,在 如何在集群中安装 OLM 部分。
- 克隆 Git 仓库并导航到仓库的根目录。该仓库包含你在本书范围内构建平台所需的所有源文件、脚本和清单: https://github.com/PacktPublishing/Machine-Learning-on-Kubernetes.git cd Machine-Learning-on-Kubernetes。
使用本书源代码中提供的 YAML 文件注册一个新的 catalog source 操作员:
kubectl create -f chapter4/catalog-source.yaml
-
几分钟后,验证操作员在你的集群中是否可用:
kubectl get packagemanifests -o wide -n olm | grep -I opendatahub
你应该看到以下响应:

图 4.7 – 验证 ODH 操作员是否可用
在 Windows PowerShell 上,你可能需要将 grep 命令替换为 findstr。
-
现在,创建 ODH 操作员的订阅。回想一下第三章,订阅对象通过 OLM 触发操作员的安装:
kubectl create -f chapter4/odh-subscription.yaml
你应该看到一个响应消息,显示订阅已创建。
-
在创建订阅后,OLM 会自动安装操作员及其所有组件。通过执行以下命令验证 ODH pod 是否在运行。可能需要几秒钟才能看到 pod 出现。如果 pod 未列出,等待几秒钟并重新运行相同的命令:
kubectl get pods -n operators
你应该看到以下响应:

图 4.8 – 验证 ODH pod 是否已启动并运行
你刚刚在 Kubernetes 集群上安装了 ODH 操作员。请注意,你并没有使用像 Deployments 这样的通用 Kubernetes 对象来运行你的操作员。OLM 允许你通过 Subscription 对象轻松管理操作员的安装。
在下一节中,你将安装 ingress 控制器,以允许流量进入 Kubernetes 集群。
在 Kubernetes 集群上启用 ingress 控制器
回想一下 第三章,探索 Kubernetes 中提到的,ingress 提供了一种方式,让你可以暴露特定的服务,使其可以从集群外部访问。Kubernetes 上有许多 ingress 提供商,我们将它留给你选择合适的 ingress 提供商来为你的集群提供服务。
如果你正在使用 minikube,需要按照以下步骤启用默认的 ingress:
-
通过执行以下命令,为你的集群启用基于 NGINX 的 ingress 控制器:
minikube addons enable ingress
你应该看到以下响应:

图 4.9 – 启用 minikube ingress 插件的输出
-
验证你的集群中是否有 ingress pod 在运行:
kubectl get pods -n ingress-nginx
你应该看到以下响应:

图 4.10 – 验证 Nginx ingress pod 是否处于运行状态
现在您已将外部流量启用到您的集群,下一步是为您的 ML 平台安装开源身份验证和授权组件。
在 Kubernetes 上安装 Keycloak
我们将使用 Keycloak (www.keycloak.org) 作为我们的身份提供者,并为您的平台添加身份验证和访问管理功能。Keycloak 支持行业标准的安全机制,如OAuth2和OpenID Connect。在本节中,您将在 Kubernetes 集群上安装 Keycloak 服务器,并登录到 Keycloak UI 以验证安装:
-
通过为
keycloak应用程序创建一个新命名空间开始:kubectl create ns keycloak
你应该看到以下响应:

图 4.11 – 为 Keycloak 创建新命名空间的输出
-
使用提供的 YAML 文件创建 Keycloak 清单:
kubectl create -f chapter4/keycloak.yaml --namespace keycloak -
验证
keycloakpod 是否正在运行。请注意,--namespace和-n标志在kubectl中是可以互换的:kubectl get pods -n keycloak
启动可能需要一些时间,因为它会从互联网上拉取容器镜像。第一次运行命令时,您可能会看到 Keycloak pod 正在运行,您应该看到以下响应:

图 4.12 – 验证 Keycloak pod 是否处于运行状态
在接下来的几个步骤中,您将定义并配置 Keycloak pod 的 ingress,使其能够从集群外部访问。
-
通过运行以下命令获取
minikube机器的 IP 地址:minikube ip
你应该看到以下响应:

图 4.13 – 您的 minikube 实例的 IP 地址
- 打开
chapter4/keycloak-ingress.yaml文件,并将KEYCLOAK_HOST字符串替换为keycloak.<YOUR_MINIKUBE_IP_ADDRESS>.nip.io字符串。所以,如果您的minikube的 IP 地址是192.168.61.72,那么字符串值将是keycloak.192.168.61.72.nip.io。
文件中有两个地方需要放入这个新字符串。文件将呈现 图 4.14 的样式。不要忘记保存此文件中的更改。

图 4.14 – 您的 minikube 实例的 IP 地址在 keycloak-ingress 文件中已更改
将修改后的文件应用到 Kubernetes 集群。这个 ingress 对象将为您创建所需的配置,使您能够从集群外部访问 Keycloak 服务器。运行以下命令以创建 ingress 对象:
kubectl create -f chapter4/keycloak-ingress.yaml --namespace keycloak
你应该看到以下响应:

图 4.15 – 修改后的 ingress 已应用
-
通过运行以下命令验证
ingress对象是否可用:kubectl get ingress --namespace keycloak
你应该看到以下响应:

图 4.16 – Ingress 对象已创建
- 现在你已经验证 Keycloak 正在运行,并通过
ingress对象暴露,打开你机器上的浏览器(minikube运行的地方),并访问以下网址。你需要根据步骤 5 中的说明替换正确的 IP 地址:https://keycloak.192.168.61.72.nip.io/auth/。
你会收到一个警告,提示 证书无效。这是因为 Keycloak 服务器默认使用自签名证书。你只需要点击浏览器显示的 高级 按钮,然后选择继续访问该网站。
你应该看到以下页面;点击 管理控制台 链接继续操作:

图 4.17 – Keycloak 登录页
- 在以下页面中使用凭据 admin/admin 登录。输入凭据后,点击 登录:

图 4.18 – Keycloak 登录页面
- 验证 Keycloak 的主管理页面是否如以下所示:

图 4.19 – Keycloak 管理页面
恭喜你!你已成功将 ODH 操作员和 Keycloak 安装到你的 Kubernetes 集群中。
总结
在本章中,你已经了解了 ML 平台的主要组件,以及开源社区项目如何为这些组件提供软件产品。使用开源软件不仅使大量用户可以免费使用软件,同时也促进了对组件的持续改进,并为软件不断发展与新增功能做出贡献。
你已经安装了设置 ML 平台所需的操作员,并且已安装 ingress 控制器以允许流量进入你的集群,同时也安装了 Keycloak,为你的平台提供身份与访问管理功能。
基础已经搭建好,我们可以深入探讨 ML 生命周期中的每个组件。在下一章中,你将学习如何在你的平台上设置 Spark 和 JupyterHub,这将使数据工程师能够构建和部署数据管道。
进一步阅读
- 数据准备是数据科学中最不令人愉快的任务:
www.forbes.com/sites/gilpress/2016/03/23/data-preparation-most-time-consuming-least-enjoyable-data-science-task-survey-says/?sh=1e5986216f63
第四章:第五章:数据工程
数据工程通常是指在组织中管理和组织数据及数据流的过程。它涉及数据收集、处理、版本控制、数据治理和分析。这是一个庞大的主题,围绕数据处理平台、数据湖、数据集市、数据仓库和数据流的开发与维护展开。数据工程是一个重要的实践,它为大数据和机器学习(ML)项目的成功做出了贡献。本章将介绍数据工程在机器学习中的具体应用。
许多机器学习教程/书籍从一个干净的数据集和 CSV 文件开始,构建您的模型。现实世界则不同。数据呈现出多种形态和规模,因此您需要有一个明确的策略来收集、处理并大规模准备数据。本章将讨论开源工具,这些工具可以为机器学习项目中的数据工程奠定基础。您将学习如何在 Kubernetes 平台上安装这些开源工具集,以及这些工具如何帮助您和您的团队提高效率和灵活性。
在本章中,您将学习以下内容:
-
配置 Keycloak 进行身份验证
-
配置 Open Data Hub 组件
-
理解并使用 JupyterHub IDE
-
理解 Apache Spark 的基础知识
-
理解 Open Data Hub 如何按需提供 Apache Spark 集群
-
从 Jupyter Notebook 编写并运行 Spark 应用程序
技术要求
本章包括一些动手设置和练习。您将需要一个已配置好操作生命周期管理器(Operator Lifecycle Manager)的 Kubernetes 集群。如何构建这样的 Kubernetes 环境已在第三章《探索 Kubernetes》中介绍。在进行本章的技术练习之前,请确保您已经在 Kubernetes 集群上搭建了运行中的 Kubernetes 集群,并安装了Open Data Hub(ODH)。ODH 的安装可参考第四章《机器学习平台的构成》。您可以在github.com/PacktPublishing/Machine-Learning-on-Kubernetes找到本书的所有相关代码。
配置 Keycloak 进行身份验证
在开始使用平台的任何组件之前,您需要配置与平台组件相关联的身份验证系统。如在第四章《机器学习平台的构成》中所述,您将使用 Keycloak,这是一款开源软件,用于提供身份验证服务。
首先,从 chapter5/realm-export.json 导入配置,该文件可在本书关联的代码库中找到。此文件包含将 OAuth2 功能关联到平台组件所需的配置。
虽然本书不是 Keycloak 的指南,但我们会提供一些基本定义,以帮助你理解 Keycloak 服务器的高级分类:
-
Realm:Keycloak 领域是一个管理属于同一域的用户、角色、组和客户端应用程序的对象。一个 Keycloak 服务器可以有多个领域,因此你可以有多个配置集,例如一个领域用于内部应用程序,另一个用于外部应用程序。
-
Clients:客户端是可以请求用户认证的实体。一个 Keycloak 客户端对象与一个领域相关联。我们平台中所有需要单点登录(SSO)的应用程序都将作为客户端注册到 Keycloak 服务器中。
-
用户和组:这两个术语不言而喻,在接下来的步骤中,你将创建一个新用户,并使用它登录到平台的不同软件中。
下一步是配置 Keycloak,为我们的 ML 平台组件提供 OAuth 功能。
导入 Keycloak 配置以供 ODH 组件使用
在本节中,你将把客户端和组配置导入到运行在 Kubernetes 集群上的 Keycloak 服务器中。以下步骤将把所有配置导入到 Keycloak 服务器的主领域:
- 使用用户名
admin和密码admin登录到你的 Keycloak 服务器。在左侧边栏的管理标题下点击导入链接:

图 5.1 – Keycloak 主领域
- 点击屏幕上的选择文件按钮,如下所示:

图 5.2 – Keycloak 导入配置页面
- 从弹出窗口中选择
chapter5/realm-export.json文件。之后,选择跳过,在如果资源已存在下拉选项中,然后点击导入:

图 5.3 – Keycloak 导入配置页面
- 验证记录是否已成功导入到你的 Keycloak 服务器:

图 5.4 – Keycloak 导入配置结果页面
- 验证是否已创建四个客户端,方法是点击左侧菜单中的Clients项。应该存在以下客户端 ID:aflow、mflow、grafana和jhub。aflow客户端用于平台的工作流引擎,是Apache Airflow的一个实例。mflow客户端用于模型注册和训练跟踪工具,是MLflow的一个实例。grafana客户端用于监控 UI,是Grafana的一个实例。最后,jhub客户端用于JupyterHub服务器实例。

图 5.5 – Keycloak 客户端页面
- 验证是否已创建名为 ml-group 的组,方法是在左侧面板点击 Groups 链接:

图 5.6 – Keycloak 组页面
你将使用该用户组来创建平台用户。
很棒!你刚刚为 ML 平台配置了多个 Keycloak 客户端。下一步是在 Keycloak 中创建一个用户,你将使用该用户进行本书余下部分的操作。需要注意的是,Keycloak 可以与企业目录或任何其他数据库集成,并将其用作用户源。请记住,我们在这里使用的领域配置非常基础,不建议用于生产环境。
创建 Keycloak 用户
在本节中,你将创建一个新用户,并将新创建的用户与前一节中导入的组关联。将用户与组关联将授予不同 ODH 软件所需的角色:
- 在 Keycloak 页面左侧,点击 Users 链接进入该页面。要添加新用户,点击右侧的 Add user 按钮:

图 5.7 – Keycloak 用户列表
- 添加用户名
mluser,确保 User Enabled 和 Email Verified 切换按钮设置为 ON。在 Groups 中,选择 ml-group 组,并填写 Email、First Name 和 Last Name 字段,如 图 5.8 所示,然后点击 Save 按钮:

图 5.8 – Keycloak 添加用户页面
- 点击 Credentials 标签页为你的用户设置密码:

图 5.9 – Keycloak 凭证页面
- 输入你选择的密码,然后禁用 Temporary 标志,点击 Set Password 按钮。
你刚刚在 Keycloak 中创建并配置了一个用户。现在你的 Keycloak 服务器已经可以被 ML 平台组件使用。下一步是探索平台中为所有角色提供主要编码环境的组件。
配置 ODH 组件
在第四章《机器学习平台的构成》中,你已经安装了 ODH 操作符。使用 ODH 操作符,你将配置一个 ODH 实例,该实例将自动安装 ML 平台的组件。ODH 执行 Kustomize 脚本来安装 ML 平台的组件。作为本书代码的一部分,我们提供了模板来安装和配置运行平台所需的所有组件。
你还可以通过 manifests 文件配置 ODH 操作员为你安装哪些组件。你可以将特定的配置传递给清单文件,并选择所需的组件。一本书的代码仓库中有一个这样的清单,路径为 manifests/kfdef/ml-platform.yaml。这个 YAML 文件为 ODH 操作员配置了所需的安装软件,使其成为平台的一部分。你需要对这个文件进行一些修改,接下来你将看到具体操作。
该文件定义了平台的组件以及这些组件获取设置的位置:
-
名称:定义组件的名称。
-
path属性定义了配置此组件所需文件的相对路径位置。 -
KEYCLOAK_URL和JUPYTERHUB_HOST需要根据你的配置进行更改。 -
manifests/jupytherhub/overlays文件夹在代码仓库中。 -
Repos:此配置部分特定于每个清单文件,并适用于清单中的所有组件。它定义了包含所有被清单文件引用文件的 Git 仓库的位置和版本。如果你希望清单引用你自己的安装文件,需在此处指定正确的 Git 仓库(包含你文件的仓库)。
图 5.10 展示了清单文件中定义 JupyterHub 组件的部分:

图 5.10 – ODH 清单文件中的组件
你将使用提供的清单文件来创建 ML 平台的实例。你也可以根据需要修改此文件,通过调整配置或添加、移除平台组件来进行个性化设置。但是,在书中的练习中,我们不建议你进行更改,除非有特别指示。
现在你已经看到了 ODH 清单文件,是时候充分利用它来创建你的第一个 Kubernetes 上的 ML 平台了。
安装 ODH
在我们安装平台的数据工程组件之前,我们首先需要创建一个 ODH 实例。ODH 实例是一个经过精心策划的相关工具集的集合,作为 ML 平台的组件。尽管 ML 平台可能包含 ODH 提供之外的组件,但可以公平地说,ODH 实例就是 ML 平台的一个实例。你也可以在同一个 Kubernetes 集群上运行多个 ODH 实例,只要它们运行在各自隔离的 Kubernetes 命名空间中。这在组织中的多个团队或部门共享单一 Kubernetes 集群时非常有用。
以下是你需要按照的步骤,以在 Kubernetes 集群上创建 ODH 实例:
-
使用以下命令在 Kubernetes 集群中创建一个新命名空间:
kubectl create ns ml-workshop
你应该看到以下响应:

图 5.11 – 你的 Kubernetes 集群中的新命名空间
-
确保 ODH 操作符正在运行,执行以下命令:
kubectl get pods -n operators
你应该看到以下响应。确保状态显示为Running:

图 5.12 – ODH 操作符的状态
-
获取你
minikube环境的 IP 地址。这个 IP 地址将用于为平台的不同组件创建入口,就像我们为 Keycloak 所做的那样。请注意,依据你的底层基础设施,每个minikube实例的 IP 可能会不同:minikube ip
这个命令应该会给出你minikube集群的 IP 地址。
-
打开
manifests/kfdef/ml-platform.yaml文件,并将以下参数的值更改为你minikube实例的 NIP(nip.io)域名。只需要替换域名中的 IP 地址部分。例如,KEYCLOAK_URL keycloak.<IP Address>.nip.io应该改为keycloak.192.168.61.72.nip.io。注意,这些参数在文件中可能会被多次引用。在完整的 Kubernetes 环境中,<IP Address>应该是你的 Kubernetes 集群的域名:-
KEYCLOAK_URL -
JUPYTERHUB_HOST -
AIRFLOW_HOST -
MINIO_HOST -
MLFLOW_HOST -
GRAFANA_HOST
-
-
使用以下命令将清单文件应用到你的 Kubernetes 集群:
kubectl create -f manifests/kfdef/ml-platform.yaml -n ml-workshop
你应该看到以下响应:

图 5.13 – 应用 ODH 组件清单后的结果
-
使用以下命令开始监控在
ml-workshop命名空间中创建的 Pods。安装所有组件需要一些时间。几分钟后,所有 Pods 将进入运行状态。在 Pods 创建过程中,你可能会看到一些 Pods 抛出错误。这是正常现象,因为某些 Pods 依赖于其他 Pods。耐心等待,所有组件最终会安装完毕,Pods 将进入运行状态:watch kubectl get pods -n ml-workshop
当所有 Pods 都在运行时,你应该看到以下响应:

图 5.14 – CLI 响应,展示 ODH 组件在 Kubernetes 集群上运行的情况
那么,这个命令是做什么的呢?kfdef ml-workshop命名空间:
kubectl get all -n ml-workshop
你应该看到 ODH 操作符在ml-workshop命名空间中创建的所有对象。
恭喜!你刚刚创建了一个新的 ODH 实例。现在你已经看过了从清单文件创建 ML 平台实例的过程,是时候看看数据工程师将用来开展工作的平台各个组件了。
使用 Podman 驱动的 Minikube
请注意,对于某些使用 podman 驱动程序的 minikube 设置(在 Linux 上),由于线程数的限制,Spark 操作符可能会失败。为了解决这个问题,你需要在 minikube 配置中使用 kvm2 驱动程序。你可以通过在 minikube start 命令中添加 --driver=kvm2 参数来实现。
理解和使用 JupyterHub
Jupyter Notebook 已成为编写机器学习项目代码的极受欢迎的工具。JupyterHub 是一款软件,它便于自助提供计算环境,包括启动预配置的 Jupyter Notebook 服务器并在 Kubernetes 平台上提供相关的计算资源。数据工程师和数据科学家等按需终端用户可以为自己提供专用的 Jupyter Notebook 实例。如果请求的用户已经有了自己运行的 Jupyter Notebook 实例,Hub 将直接将用户引导到现有实例,从而避免重复的环境。对终端用户而言,整个过程是无缝的。你将在本章的下一部分看到这一点。
当用户请求 JupyterHub 中的一个环境时,他们还可以选择预配置的硬件资源(如 CPU、内存和存储)大小。这为开发者、数据工程师和数据科学家提供了一种灵活的方式,使他们能够为特定任务分配合适的计算资源。这种资源的动态分配是通过底层的 Kubernetes 平台来实现的。
不同的用户可能需要不同的框架、库和编程环境的版本。一些数据科学家可能想使用 TensorFlow,而其他人可能想使用 scikit-learn 或 PyTorch。一些数据工程师可能更喜欢使用 pandas,而其他人可能需要在 PySpark 中运行数据管道。在 JupyterHub 中,他们可以为这些场景配置多个预定义的环境。用户在请求新环境时,可以选择一个预定义的配置。这些预定义的环境实际上是容器镜像。这意味着平台操作员或管理员可以准备多个预定义的容器镜像,作为终端用户的计算环境。此功能还支持环境的标准化。你有多少次在不同开发者计算机上处理不同版本库的情况?环境的标准化可以减少与库版本不一致相关的问题,并通常减少 它在我的机器上能运行 的问题。
图 5.15 展示了为 JupyterHub 环境配置的新环境的三步流程:
![图 5.15 – 创建 JupyterHub 新环境的工作流程]
](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_05_015.jpg)
图 5.15 – 创建 JupyterHub 新环境的工作流程
现在你知道了 JupyterHub 能做什么,我们来看看它是如何运作的。
验证 JupyterHub 安装
团队中的每个数据工程师都遵循一个简单而标准的环境配置工作流。不再需要手动安装和调试工作站配置。这对于自主团队来说非常好,肯定能提高团队的效率。
ODH 运维人员已经为你在之前的部分安装了 JupyterHub。现在,你将以数据工程师身份启动一个新的 Jupyter Notebook 环境,并编写数据管道:
-
使用以下命令获取在 Kubernetes 环境中创建的 ingress 对象。我们运行此命令以查找 JupyterHub 的 URL:
kubectl get ingress -n ml-workshop
你应该看到以下示例响应。请注意在 HOSTS 列中显示的 JupyterHub URL:

图 5.16 – 集群中的所有 ingress
- 从与 minikube 运行在同一台机器上的浏览器中,访问 JupyterHub URL。该 URL 看起来像是 https://jupyterhub.
.nip.io。此 URL 会将你带到 Keycloak 登录页面进行 SSO 认证。确保在这个 URL 中将 IP 地址替换为你 minikube 的 IP 地址:

图 5.17 – JupyterHub 的 SSO 挑战
- 输入
mluser作为用户名,然后输入你为该用户设置的密码,点击 Sign In。
你将看到 JupyterHub 服务器的登录页面,允许你选择你想使用的 notebook 容器镜像,以及你所需的预定义计算资源大小。
notebook 镜像部分包含了你通过 ODH 清单文件从代码库中的 manifests/jupyterhub-images 文件夹中配置的标准 notebooks。
容器大小下拉菜单允许你选择适合你需求的环境大小。此配置也通过 manifests/jupyterhub/jupyterhub/overlays/mlops/jupyterhub-singleuser-profiles-sizes-configmap.yaml 清单文件进行控制。
我们鼓励你查看这些文件,了解你可以为每个清单配置哪些设置。

图 5.18 – JupyterHub 登录页面
选择 Base Elyra Notebook Image 和 Default 容器大小,然后点击 Start server。
-
通过执行以下命令验证是否为你的用户创建了一个新的 Pod。Jupyter Notebook 实例的名称以
jupyter-nb-开头,并以用户名作为后缀。这使得每个用户的 notebook pod 拥有独特的名称:kubectl get pods -n ml-workshop | grep mluser
你应该看到以下响应:

图 5.19 – JupyterHub 创建的 Jupyter Notebook pod
- 恭喜!你现在正在 Kubernetes 平台上运行自己的自供给 Jupyter Notebook 服务器。

图 5.20 – Jupyter Notebook 登录页面
- 现在,让我们停止 notebook 服务器。点击 File > Hub Control Panel 菜单选项,进入以下所示的 Hub 控制面板 页面:

图 5.21 – 查看 Hub 控制面板的菜单选项
- 点击 Stop My Server 按钮。这是停止你 Jupyter Notebook 实例的方式。你可能稍后想要重新启动它以继续下一步。

图 5.22 – Hub 控制面板
-
通过发出以下命令验证新 pod 是否已为你的用户销毁:
kubectl get pods -n ml-workshop | grep mluser
此命令应该没有输出,因为 Jupyter Notebook pod 已被 JupyterHub 销毁。
我们将留给你自己探索在你环境中 notebook 的不同配置。你将在本章和接下来的几章中使用这个 Jupyter notebook 编写代码,所以如果你只是想继续阅读,你不会错过任何重要内容。
运行你的第一个 Jupyter notebook
现在你的 Jupyter notebook 已经运行,是时候编写 Hello World! 程序了。在本书的代码仓库中,我们提供了这样的一个程序,在接下来的步骤中,你将使用 Git 检出代码并运行该程序。在开始这些步骤之前,请确保你可以通过浏览器访问你的 Jupyter notebook,如前一节所述:
- 点击 Jupyter notebook 左侧菜单中的 Git 图标。该图标是从顶部数起的第三个。它会显示三个不同操作的按钮。点击 Clone a Repository 按钮:

图 5.23 – Jupyter notebook 中的 Git 操作
- 在 Clone a repo 弹出框中,输入本书的代码仓库位置,
github.com/PacktPublishing/Machine-Learning-on-Kubernetes.git,然后点击 CLONE。

图 5.24 – 在 Jupyter notebook 中克隆 Git 仓库
- 你会看到代码仓库已被克隆到 Jupyter notebook 的文件系统中。如 图 5.25 所示,导航到
chapter5/helloworld.ipynb文件并在 notebook 中打开它。点击顶部栏上的小播放图标运行该单元:

图 5.25 – 在你的 Jupyter 环境中的 notebook
-
Voilà!你刚刚在自己配置的 Kubernetes 上运行的 Jupyter Notebook 服务器中执行了 Python 代码。
-
通过选择 文件 > Hub 控制面板 菜单选项关闭你的笔记本。点击 停止我的服务器 按钮来关闭你的环境。请注意,ODH 会保存你的磁盘,下次启动笔记本时,所有保存的文件都会可用。
恭喜!现在,你可以在平台上运行你的代码。接下来,我们将回顾一下 Apache Spark 引擎的一些基础知识。
理解 Apache Spark 的基础知识
Apache Spark 是一个开源的数据处理引擎,旨在分布式大规模数据处理。这意味着,如果你处理的是较小的数据集,比如 10s 或者几百 GB,经过优化的传统数据库可能会提供更快的处理时间。Apache Spark 的主要特点是其能够在内存中进行中间计算,这使得 Apache Spark 比 Hadoop MapReduce 更加高效。
Apache Spark 的设计目标是速度、灵活性和易用性。Apache Spark 提供了超过 70 个高级数据处理操作符,使数据工程师可以轻松构建数据应用程序,因此使用 Apache Spark API 编写数据处理逻辑变得非常简单。灵活性意味着 Spark 作为一个统一的数据处理引擎,可以处理多种数据工作负载,如批处理应用程序、流处理应用程序、交互式查询,甚至是机器学习算法。
图 5.26 显示了 Apache Spark 组件:
![图 5.26 – Apache Spark 组件]
](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_05_027.jpg)
图 5.26 – Apache Spark 组件
理解 Apache Spark 作业执行
现在大多数数据工程师都知道 Apache Spark 是一个大规模并行数据处理引擎。它是 Apache 软件基金会最成功的项目之一。Spark 通常运行在多个 虚拟机(VMs)或裸金属服务器的集群上。然而,随着容器和 Kubernetes 的流行,Spark 增加了对在 Kubernetes 上运行 Spark 集群的支持。
在 Kubernetes 上运行 Spark 的两种最常见方式如下。第一种,也是原生方式,是通过 Kubernetes 引擎本身来协调 Kubernetes 工作节点。在这种方法中,Spark 集群实例始终运行,Spark 应用程序被提交到 Kubernetes API,然后 Kubernetes API 调度提交的应用程序。我们不会深入探讨这部分实现。第二种方法是通过 Kubernetes 运维工具(operators)。运维工具利用 Kubernetes CRD 来在 Kubernetes 中本地创建 Spark 对象。在这种方法中,Spark 集群是通过 Spark 运维工具动态创建的。与其将 Spark 应用程序提交到现有的集群,不如通过运维工具按需启动 Spark 集群。
Spark 集群采用管理/工作架构。Spark 集群管理器知道工作节点的位置,以及工作节点可用的资源。Spark 集群管理着将运行应用程序的工作节点集群的资源。每个工作节点都有一个或多个执行进程,通过执行进程运行分配的任务。
Spark 应用程序有两部分:驱动程序组件和数据处理逻辑。驱动程序组件负责执行数据处理操作的流程。驱动程序首先与集群管理器交互,找出哪些工作节点将运行应用程序逻辑。驱动程序将所有应用操作转换为任务,调度它们,并将任务直接分配给工作节点上的执行进程。一个执行进程可以运行多个与相同 Spark 上下文相关的任务。
如果你的应用程序需要收集计算结果并进行合并,驱动程序将负责此项工作。作为数据工程师,这一切操作都通过 SparkSession 对象进行了抽象,你只需要编写数据处理逻辑。我们提到过 Apache Spark 旨在简化操作吗?
图 5.27 显示了 Spark 驱动程序、Spark 集群管理器和 Spark 工作节点之间的关系:

图 5.27 – Apache Spark 组件关系
了解 ODH 如何按需提供 Apache Spark 集群
我们已经讨论了 ODH 如何帮助你创建一个动态灵活的开发环境,使用 Jupyter Notebook 编写代码,如数据管道。我们注意到,数据开发人员需要与 IT 部门互动,以便获得数据处理集群(如 Apache Spark)上的时间。这些互动降低了团队的敏捷性,而这是 ML 平台解决的一个问题。为了符合这一场景,ODH 提供了以下组件:
-
一个 Spark 操作符,用于启动 Apache Spark 集群。本书中,我们基于 ODH 和 radanalytics 提供的原始 Spark 操作符进行了分支,以适应 Kubernetes API 的最新变化。
-
JupyterHub 中的一项功能,当用户创建特定的笔记本环境时,向 Spark 操作符发出请求,要求创建一个新的 Spark 集群。
作为数据工程师,当你使用某些笔记本镜像启动新的笔记本环境时,JupyterHub 不仅会启动一个新的笔记本服务器,还会通过 Spark 操作符为你创建一个专用的 Apache Spark 集群。
创建 Spark 集群
让我们首先看看 Spark 操作符在 Kubernetes 集群中的工作方式。ODH 创建了 Spark 控制器。你可以在 chapter5/ml-platform.yaml 文件中查看配置,文件名为 radanalyticsio-spark-cluster,如 图 5.28 所示。你可以看到,这是另一组 Kubernetes YAML 文件,定义了本书代码库中的 manifests/radanalyticsio 文件夹。

图 5.28 – 安装 Spark 操作符的清单部分代码片段
当你需要启动一个 Apache Spark 集群时,可以通过创建一个名为SparkCluster的 Kubernetes 自定义资源来实现。收到请求后,Spark 操作符将根据所需的配置来配置一个新的 Spark 集群。以下步骤将向你展示如何在你的平台上配置 Spark 集群:
-
验证 Spark 操作符 Pod 是否正在运行:
kubectl get pods -n ml-workshop | grep spark-operator
你应该看到如下响应:

图 5.29 – Spark 操作符 Pod
- 使用
chapter5/simple-spark-cluster.yaml文件创建一个包含一个工作节点的简单 Spark 集群。你可以看到该文件请求一个包含一个主节点和一个工作节点的 Spark 集群。通过这个自定义资源,你可以设置多个 Spark 配置,正如我们将在下一节中看到的那样:

图 5.30 – Spark 自定义资源
在你的 Kubernetes 集群中通过运行以下命令创建这个 Spark 集群自定义资源。Spark 操作符会不断扫描 Kubernetes 平台上的此资源,并为每个指定的 Spark 集群自定义资源自动创建一个新的 Apache Spark 集群实例:
kubectl create -f chapter5/simple-spark-cluster.yaml -n ml-workshop
你应该看到如下响应:

图 5.31 – 创建 Spark 集群后的响应
-
验证 Spark 集群的 Pod 是否在你的集群中运行:
kubectl get pods -n ml-workshop | grep simple-spark
你应该看到如下响应。Spark 操作符已创建两个 Pod,一个用于 Spark 主节点,另一个用于工作节点。工作节点的 Pod 数量取决于SparkCluster资源中的instances参数值。首次启动时,Pod 可能需要一些时间才能进入运行状态:

图 5.32 – 正在运行的 Spark 集群 Pod 列表
现在,你已经了解了 Spark 操作符在 Kubernetes 集群中的工作原理。下一步是查看 JupyterHub 是如何配置以动态请求集群,并在为你配置新笔记本时提供 Spark 集群的。
了解 JupyterHub 如何创建 Spark 集群
简而言之,JupyterHub 执行了你在前一节中所做的操作。JupyterHub 在 Kubernetes 中创建一个SparkCluster资源,以便 Spark 操作符可以为你配置 Apache Spark 集群。这个SparkCluster资源配置是一个 Kubernetes ConfigMap文件,位于manifests/jupyterhub/jupyterhub/base/jupyterhub-spark-operator-configmap.yaml。请查找文件中的sparkClusterTemplate,如图 5.33所示。你可以看到它看起来与前一节中你创建的文件相似:

图 5.33 – JupyterHub 用于 Spark 资源的模板
你们中的一些人可能已经注意到这是一个模板,它需要填写模板中提到的特定变量的值。诸如{{ user }}和{{ worker_nodes }}等变量。回想一下,我们提到过 JupyterHub 在为你的笔记本配置容器时,会创建SparkCluster请求。JupyterHub 使用这个文件作为模板,并在创建笔记本时填充相应的值。JupyterHub 是如何决定创建 Spark 集群的呢?这个配置文件在manifests/jupyterhub/jupyterhub/overlays/spark3/jupyterhub-singleuser-profiles-configmap.yaml中,称为ConfigMap文件。它看起来像图 5.33所示的文件。
你可以看到,image字段指定了该配置文件将触发的容器镜像名称。因此,作为数据工程师,当你从 JupyterHub 登录页选择该笔记本镜像时,JupyterHub 将应用此配置文件。配置文件中的第二部分是env部分,它指定了将被推送到笔记本容器实例的环境变量。configuration对象定义了将应用于在resources键中提到的模板的值:

图 5.34 – JupyterHub 针对 Spark 资源的配置文件
正如你可能已经感受到的那样,在后台有很多工作在进行,旨在为你和你的团队提供一个流畅的体验,并且从开源的真正意义上讲,你可以配置所有内容,甚至如果你有任何修改或新功能,也可以回馈项目。
在下一节中,你将看到如何在运行这些组件的平台上编写和运行 Spark 应用程序是多么容易。
从 Jupyter Notebook 编写和运行 Spark 应用程序
在执行以下步骤之前,请确保你理解了我们在本章前面部分介绍的组件及其相互关系:
-
通过运行以下命令,验证 Spark 操作员 Pod 是否正在运行:
kubectl get pods -n ml-workshop | grep spark-operator
你应该会看到以下响应:

图 5.35 – Spark 操作员 Pod
-
通过运行以下命令,验证 JupyterHub Pod 是否正在运行:
kubectl get pods -n ml-workshop | grep jupyterhub
你应该会看到以下响应:

图 5.36 – JupyterHub Pod
-
在启动笔记本之前,让我们通过运行以下命令删除你在前面部分创建的 Spark 集群。这样可以演示 JupyterHub 将自动为你创建一个新的 Spark 集群实例:
kubectl delete sparkcluster simple-spark-cluster -n ml-workshop -
登录到你的 JupyterHub 服务器。参考本章前面提到的验证 JupyterHub 配置部分。你将看到服务器的登录页面。选择
manifests/jupyterhub/jupyterhub/overlays/spark3/jupyterhub-singleuser-profiles-configmap.yaml文件。 -
点击 启动服务器:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_05_038.jpg)
图 5.37 – 显示 Elyra Notebook 图像与 Spark 的 JupyterHub 登录页面
你刚刚启动的笔记本也会触发为你创建一个专用的 Spark 集群。启动笔记本可能需要一些时间,因为它需要等待 Spark 集群准备好。
此外,你可能已经注意到,在 jupyterhub-singleuser-profiles-configmap.yaml 文件中配置的镜像是 quay.io/ml-aml-workshop/elyra-spark:0.0.4,而我们选择的文件名称是 manifests/jupyterhub-images/elyra-notebook-spark3-imagestream.yaml。你会发现显示在 JupyterHub 登录页面上的描述性文本来自该文件的 annotations 部分。如果你想添加包含特定库的自定义镜像,只需在这里添加另一个文件,它就会对你的团队可用。JupyterHub 的这一功能使笔记本容器镜像的标准化成为可能,从而使团队中的每个人都能拥有相同的环境配置和相同的库集。
-
启动笔记本后,验证 Spark 集群是否已经为你配置好。请注意,这是为该笔记本用户专用的 Spark 集群,仅限于该用户使用:
kubectl get pods -n ml-workshop | grep mluser
你应该看到以下响应。该响应包含一个笔记本 Pod 和两个 Spark Pod;其中带有 -m 字符的是主节点,另一个是工作节点。注意,你的用户名(mluser)是如何与 Pod 名称关联的:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_05_039.jpg)
图 5.38 – Jupyter Notebook 和 Spark 集群 Pod
现在,你团队中的每个人都会获得自己的开发环境,并配备专用的 Spark 实例来编写和测试数据处理代码。
- Apache Spark 提供了一个用户界面,通过该界面你可以监控应用程序和数据处理作业。ODH 提供的 Spark 集群提供了这个图形用户界面,并且它可以通过
https://spark-cluster-mluser.192.168.61.72.nip.io访问。确保将 IP 地址更改为你自己的 minikube IP 地址。你可能还会注意到,登录 JupyterHub 使用的用户名mluser是 URL 的一部分。如果你使用了不同的用户名,可能需要相应地调整 URL。

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_05_040.jpg)
图 5.39 – Spark 用户界面
前面的用户界面提到你在集群中有一个工作节点,你可以点击该工作节点查看在其中运行的执行器。如果你想刷新你对 Spark 集群的了解,请参考本章前面的 理解 Apache Spark 基础知识 部分。
- 打开 notebook 中的
chapter5/hellospark.ipynb文件。这是一个非常简单的作业,计算给定数组的平方。记住,Spark 会自动调度作业并将其分发给执行器。这里的 notebook 是 Spark 驱动程序,它与 Spark 集群进行通信,所有这一切都通过SparkSession对象进行抽象。
在此 notebook 的第二个代码单元格中,你正在创建一个 SparkSession 对象。getOrCreateSparkSession 实用函数将连接到平台为你配置的 Spark 集群。
最后一格单元格是你数据处理逻辑所在的位置。在这个例子中,逻辑是获取数据并计算数组中每个元素的平方。一旦数据处理完成,collect方法会将响应带回到你 notebook 中运行的 Spark 应用程序的驱动程序。

图 5.40 – 一个包含简单 Spark 应用程序的 notebook
点击运行 > 运行所有单元格菜单选项,notebook 将连接到 Spark 集群,提交并执行你的作业。
- 在作业进行时,打开 Spark UI,网址是
spark-cluster-mluser.192.168.61.72.nip.io。记得根据你的设置调整 IP 地址,并点击页面中运行中的应用程序标题下的应用程序 ID表格。

图 5.41 – Apache Spark UI
- 进入 Spark 应用程序的详情页。请注意,应用程序标题Hello from ODH已在你的 notebook 中设置。点击应用程序详情 UI链接:

图 5.42 – Spark UI 显示已提交的 Spark 作业
你应该看到一个页面,显示你刚刚在 Spark 集群上通过 Jupyter notebook 执行的作业的详细指标:

图 5.43 – Spark UI 显示已提交的作业详情
- 完成工作后,转到文件 > Hub 控制面板菜单选项,并点击停止我的服务器按钮:

图 5.44 – Jupyter Notebook 控制面板
-
通过执行以下命令来验证 Spark 集群是否已被终止:
kubectl get pods -n ml-workshop | grep mluser
你不应该看到响应,因为这些 pods 已被集群中的 Spark 操作员终止。
你终于在一个按需的、运行在 Kubernetes 上的临时 Spark 集群中运行了一个基本的数据处理作业。请注意,你是通过在 Kubernetes 上运行的 Jupyter notebook 完成了这一切。
通过平台的这一功能,数据工程师可以直接从浏览器执行大规模的数据处理任务。这一功能还使他们能够轻松地协作,提供转换、清洗过的高质量数据,以支持你的 ML 项目。
总结
在这一章节中,你刚刚创建了你的第一个 ML 平台。你通过 ODH Kubernetes 操作员配置了 ODH 组件。你已经看到,数据工程师如何使用 JupyterHub 来配置 Jupyter notebook 和 Apache Spark 集群实例,同时平台自动提供环境的配置。你还看到,平台如何通过容器镜像实现操作环境的标准化,确保一致性和安全性。你也看到了,数据工程师如何从 Jupyter notebook 中运行 Apache Spark 作业。
所有这些功能使得数据工程师能够自主工作,并以自服务的方式操作。你已经看到,所有这些组件都是自主且按需提供的。平台的弹性和自服务特性将使团队在应对数据和 ML 领域不断变化的需求时更加高效和敏捷。
在下一章节中,你将看到数据科学家如何从平台中受益并提高效率。
第五章:第六章:机器学习工程
在本章中,我们将把讨论转向 机器学习(ML)工程生命周期中的模型构建和模型管理活动。您将学习 ML 平台在为数据科学家提供自服务解决方案中的角色,以便他们能够更高效地工作,并与数据团队和其他数据科学家进行合作。
本章的重点不在于构建模型,而是展示平台如何在不同环境和团队成员之间提供一致性和安全性。您将学习平台如何简化数据科学家在准备和维护数据科学工作空间方面的工作。
在本章中,您将学习以下主题:
-
理解机器学习工程?
-
使用自定义笔记本镜像
-
介绍 MLflow
-
使用 MLflow 作为实验跟踪系统
-
使用 MLflow 作为模型注册系统
技术要求
本章包含一些动手设置和练习。您需要一个配置了 操作员生命周期管理器(OLM)的运行中 Kubernetes 集群。构建这样的 Kubernetes 环境在 第三章《探索 Kubernetes》中已有介绍。在尝试本章的技术练习之前,请确保您已经有一个正常运行的 Kubernetes 集群,并且 开放数据中心(ODH)已安装在您的 Kubernetes 集群上。ODH 的安装方法在 第四章《机器学习平台的构造》中有详细说明。您可以在 github.com/PacktPublishing/Machine-Learning-on-Kubernetes 找到与本书相关的所有代码。
理解机器学习工程
机器学习工程是将软件工程原则和实践应用于机器学习项目的过程。在本书的上下文中,机器学习工程也是一种学科,它有助于将应用开发实践应用于数据科学生命周期。当你编写传统应用程序,如网站或银行系统时,有一系列流程和工具帮助你从一开始就编写高质量的代码。例如,智能 IDE、标准化环境、持续集成、自动化测试和静态代码分析等,都是一些常见的工具。自动化和持续部署实践使得组织能够在一天内多次部署应用程序,且不会出现停机时间。
机器学习工程是一个宽泛的术语,它将传统软件工程实践的好处带入到模型开发领域。然而,大多数数据科学家并非开发人员。他们可能不熟悉软件工程实践。此外,数据科学家使用的工具可能并不是执行机器学习工程任务的最佳工具。话虽如此,模型只是另一种软件。因此,我们也可以将现有的软件工程方法应用于机器学习模型。使用容器打包和部署机器学习模型就是一个例子。
一些团队可能会雇佣机器学习工程师来补充数据科学家的工作。虽然数据科学家的主要职责是构建能够解决业务问题的机器学习或深度学习模型,但机器学习工程师更多地关注软件工程方面的工作。数据工程师的一些职责包括:
-
模型优化(也包括确保构建的模型针对将托管模型的目标环境进行了优化)。
-
模型打包(使机器学习模型可移植、可交付、可执行并进行版本控制)。模型打包还可能包括模型服务和容器化。
-
监控(建立收集性能指标、日志记录、警报和异常检测(如漂移和离群点检测)基础设施)。
-
模型测试(包括促进和自动化 A/B 测试)。
-
模型部署。
-
构建和维护 MLOps 基础设施。
-
实现机器学习模型的持续集成和持续部署流水线。
-
自动化机器学习生命周期过程。
机器学习工程师还有其他职责,虽然这些职责没有列在前面,但这个列表应该能让您对如何区分数据科学与机器学习工程有个大致的了解。
您正在构建的机器学习平台将减少需要手动完成的机器学习工程任务的数量,直到即使是数据科学家也能独立完成大部分机器学习工程任务。
在接下来的章节中,您将看到数据科学家如何跟踪模型开发的迭代,以提高模型质量并与团队分享学习成果。您将看到团队如何将版本控制应用于机器学习模型,以及如何将其他软件工程实践应用于机器学习领域。
我们将在接下来的章节中继续我们的机器学习工程之旅,您将看到如何以标准化方式打包和部署模型,并了解如何自动化部署过程。
让我们从为数据科学团队构建标准开发环境开始。
使用自定义笔记本镜像
正如你在第五章《数据工程》中看到的那样,JupyterHub 允许你以自助服务的方式启动基于 Jupyter Notebook 的开发环境。你已启动 Base Elyra Notebook Image 容器镜像,并使用它编写了基于 Apache Spark 的数据处理代码。这种方法使你的团队能够使用一致或标准化的开发环境(例如,相同的 Python 版本和相同的库来构建代码),并对你的团队使用的已知软件集应用安全策略。然而,你可能还希望创建自己的自定义镜像,使用不同的库或不同的 ML 框架。平台允许你做到这一点。
在接下来的子章节中,你将构建并部署一个自定义容器镜像,以供你的团队使用。
构建自定义笔记本容器镜像
假设你的团队希望使用特定版本的 Scikit 库以及其他一些支持库,例如 joblib。然后,你希望你的团队在开发数据科学代码时使用此库:
- 打开本书代码库中提供的
Dockerfile,路径为chapter6/CustomNotebookDockerfile。该文件使用了 ODH 提供并使用的基础镜像,然后添加了所需的库。文件如 图 6.1 所示:

图 6.1 – 自定义笔记本镜像的 Dockerfile
请注意第一行,它指的是写作时最新的镜像。这个镜像由 ODH 使用。第 4 行和第 5 行安装了 requirements.txt 文件中定义的 Python 包。第 8 行安装了 requirements.txt 文件中未包含的依赖项。如果你希望将其他包添加到镜像中,只需在 requirements.txt 中插入一行即可。
-
使用前一步提供的文件构建镜像。运行以下命令:
docker build -t scikit-notebook:v1.1.0 -f chapter6/CustomNotebookDockerfile ./chapter6/.
你应该看到以下响应:

图 6.2 – 容器构建命令的输出
-
根据你的喜好标记构建好的镜像。你需要将此镜像推送到一个注册表,Kubernetes 集群可以从该注册表访问它。我们使用
quay.io作为首选的公共 Docker 仓库,你也可以在这里使用你偏好的仓库。请注意,在执行命令之前,你需要调整以下命令并更改quay.io/ml-on-k8s/部分:docker tag scikit-notebook:v1.1.0 quay.io/ml-on-k8s/scikit-notebook:v1.1.0
前面的命令没有输出。
-
将镜像推送到你选择的 Docker 仓库。使用以下命令,并确保按照 第 3 步 更改仓库位置。根据你的网络连接速度,推送该镜像到互联网仓库可能需要一些时间,请耐心等待:
docker push quay.io/ml-on-k8s/scikit-notebook:v1.1.0
你应该看到如下所示的命令输出,参见 图 6.3。等待推送完成。

图 6.3 – 将自定义 notebook 镜像推送到 Docker 仓库
现在,镜像已可以使用。接下来的步骤中,您将配置 ODH manifests 以使用此镜像。
- 打开
manifests/jupyterhub-images/base/customnotebook-imagestream.yaml文件。该文件如下所示:

图 6.4 – ImageStream 对象
ODH 中的 JupyterHub 使用一个名为 manifests/odh-common/base/imagestream-crd.yaml 的 CRD。
请注意第 7 行和第 8 行,我们定义了一些注释。JupyterHub 会读取所有 imagestream 对象,并使用这些注释在 JupyterHub 登陆页面上显示。JupyterHub 还会查看名为 dockerImageReference 的字段,以便在请求时加载这些容器镜像。
我们鼓励您将本书的代码仓库 fork 到您自己的 Git 账户中,并添加更多镜像。请记得在 manifests/kfdef/ml-platform.yaml 文件中更改 Git 仓库的位置。
-
为了让 JupyterHub 服务器看到新创建的镜像,您需要重启 JupyterHub pod。您可以通过以下命令找到该 pod 并删除它。几秒钟后,Kubernetes 将重启此 pod,您的新镜像将在 JupyterHub 登陆页面上显示:
kubectl get pods -n ml-workshop | grep jupyterhub
您应该看到如下响应。请注意,pod 名称将根据您的设置有所不同:

图 6.5 – 名称中包含 jupyterhub 的 Pods
-
通过运行以下命令删除 JupyterHub pod。请注意,您在此练习中不需要删除此 pod,因为我们的 manifest 文件中已经包含了自定义镜像。当您按照本节中的步骤添加新的客户 notebook 镜像时,此步骤才是必需的:
kubectl delete pod jupyterhub-7848ccd4b7-thnmm -n ml-workshop
您应该看到如下响应。请注意,pod 名称将根据您的设置有所不同:

图 6.6 – 删除 pod 命令的输出
- 登录到 JupyterHub 后,您将看到新的 notebook 镜像列在其中:

图 6.7 – 显示新 notebook 镜像的 JupyterHub 登陆页面
在下一节中,您将了解 MLflow,这是一款帮助团队记录和共享模型训练与调优实验结果的软件。
介绍 MLflow
简单来说,MLflow 旨在简化模型开发生命周期。数据科学家会花费大量时间寻找适合给定数据集的正确算法和超参数。作为数据科学家,你会尝试不同的参数和算法组合,然后回顾并比较结果,从中做出正确的选择。MLflow 使你能够记录、跟踪和比较这些参数、它们的结果以及相关的度量。捕获每个实验细节的 MLflow 组件称为 跟踪服务器。跟踪服务器捕获你的笔记本环境细节,例如 Python 库及其版本,以及实验生成的工件。
跟踪服务器允许你比较不同实验运行之间捕获的数据,例如性能指标(例如准确率)以及使用的超参数。你还可以与团队共享这些数据进行协作。
MLflow 跟踪服务器的第二个关键功能是模型注册。假设你已经为给定的数据集运行了十个不同的实验,每个实验都得到了一个模型。最终,只有一个模型会用于解决给定的问题。模型注册功能允许你为选择的模型打上三个阶段标签之一(Staging、Production 和 Archived)。模型注册有 API,允许你通过自动化作业访问这些模型。在生产环境中,版本控制模型注册中的模型将使你在需要时可以使用自动化工具回滚到模型的先前版本。
图 6.8 显示了 MLflow 软件的两个主要功能:

图 6.8 – MLflow 主要功能
现在你了解了 MLflow 的用途,让我们来看看组成 MLflow 的各个组件。
理解 MLflow 组件
让我们来看看 MLflow 系统的主要组件是什么,它是如何融入我们机器学习平台的生态系统中的。
MLflow 服务器
MLflow 以容器的形式部署,包含一个后端服务器、一个图形用户界面(GUI)和一个 API 用于与之交互。在本章的后续部分,你将使用 MLflow API 将实验数据存储到其中。你将使用 GUI 组件来可视化实验跟踪和模型注册部分。你可以在 manifests/mlflow/base/mlflow-dc.yaml 文件中找到此配置。
MLflow 后端存储
MLflow 服务器需要一个后端存储来存储关于实验的元数据。ODH 组件会自动配置一个 PostgreSQL 数据库,用作 MLflow 的后端存储。你可以在 manifests/mlflow/base/mlflow-postgres-statefulset.yaml 文件中找到此配置。
MLflow 存储
MLflow 服务器支持多种类型的存储,如 S3 和数据库。这些存储将作为持久存储,用于存放工件,如文件和模型文件。在我们的平台上,您将配置一个开源的 S3 兼容存储服务,称为 manifests/minio/base/minio-dc.yaml。
MLflow 认证
MLflow 在编写时没有现成的认证系统。在我们的平台中,我们在 MLflow GUI 前面配置了一个代理服务器,它将在将请求转发到 MLflow 服务器之前进行认证。我们使用的是开源组件 github.com/oauth2-proxy/oauth2-proxy 来实现这一目的。代理已配置为与平台的 Keycloak 服务进行 单点登录(SSO)认证。

图 6.9 – 平台中的 MLflow 及相关组件
如图 6.9所示,MLflow pod 中有两个容器:MLflow 服务器和 OAuth2 代理。OAuth2 代理已配置为使用您安装的 Keycloak 实例。
当您在第五章“数据工程”中创建了 ODH 的新实例时,它安装了许多平台组件,包括 MLflow 和 Minio。现在,让我们验证 MLflow 的安装。
验证 MLflow 安装
ODH 已为您安装了 MLflow 和相关组件。现在,您将使用 MLflow 图形用户界面(GUI)来熟悉该工具。您可以想象,所有团队成员都可以访问实验和模型,这将提高团队的协作:
-
使用以下命令在您的 Kubernetes 环境中创建入口对象。这是为了获取我们服务部署的端点的 URL:
kubectl get ingress -n ml-workshop
您应该看到以下响应:

图 6.10 – 您集群命名空间中的所有入口对象
- 打开 Minio 图形用户界面,这是我们的 S3 组件,验证是否有一个存储桶可供 MLflow 使用作为其存储。Minio 组件的 URL 将类似于
https://minio.192.168.61.72.nip.io,您需要根据您的环境调整 IP 地址。密码已在 manifests 文件中配置,默认为minio123。我们已将 Minio 添加到 manifests 中,展示了可以使用开源技术的选项,但将其适配生产环境超出了本书的范围。点击屏幕左侧的存储桶菜单项,您将看到可用的存储桶:

图 6.11 – Minio 存储桶列表
这些桶是如何创建的?在清单中,我们有一个 Kubernetes 作业来创建这些桶。你可以在 manifests/minio/base/minio-job.yaml 文件中找到该作业。该作业使用 Minio 命令行客户端 mc 来创建桶。你可以在该文件的 command 字段下找到这些命令。
MLflow 使用的 S3 配置已在 manifests/mlflow/base/mlflow-dc.yaml 文件中配置。
你可以看到如下设置:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_06_012.jpg)
图 6.12 – 配置 MLflow 使用 Minio
-
打开浏览器,将
jupyterhub入口的HOSTS值粘贴到浏览器中。对我来说,它是mlflow.192.168.61.72.nip.io。这个 URL 会将你带到 Keycloak 登录页面,这是如图所示的单点登录(SSO)服务器。确保将这个 URL 中的 IP 地址替换为你自己的。回想一下,MLflow 的认证部分是由你在manifests/mlflow/base/mlflow-dc.yaml中配置的代理管理的。 -
你可以看到如下的 OAuth 代理配置。由于
oauth-proxy和 MLflow 属于同一个 pod,我们所做的就是将流量从oauth-proxy路由到 MLflow 容器。这是通过–upstream属性进行设置的。你还可以看到oauth-proxy需要身份提供者服务器的名称,这里是 Keycloak,并且它被配置在–oidc-issuer属性下:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_06_013.jpg)
图 6.13 – 配置 MLflow 的 OAuth 代理
MLflow 的登录页面如下图所示 图 6.14。你会注意到顶部栏菜单有两个部分,一个是标有 实验,另一个是 模型。
- 在你看到此页面之前,SSO 配置将显示登录页面。输入用户 ID 为
mluser,密码为mluser以登录。用户名和密码是在 第四章 中配置的,机器学习平台的构成,在 创建 Keycloak 用户 部分。

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_06_014.jpg)
图 6.14 – MLflow 实验追踪页面
实验页面的左侧显示实验列表,右侧显示实验运行的详细信息。可以把实验看作是你正在进行的数据科学项目,比如消费者交易中的欺诈检测,备注部分则记录了运行实验时使用的参数、算法及其他信息的组合。
- 点击 模型 选项卡,查看模型注册表的登录页面。
模型选项卡包含注册表中模型的列表、它们的版本以及对应的阶段,显示了这些模型被部署的环境。

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_06_015.jpg)
图 6.15 – MLflow 模型注册页面
如果你能打开 MLflow 的 URL 并查看前述步骤中描述的页面,那么你就验证了 MLflow 已在你的平台中配置成功。下一步是编写一个笔记本,训练一个基本模型并将详细信息记录到你的 MLflow 服务器中。
使用 MLFlow 作为实验跟踪系统
在这一部分,你将看到 MLflow 库如何允许你将实验记录到 MLflow 服务器中。你在本章第一部分看到的自定义笔记本映像已经将 MLflow 库打包进容器中。请参考 chapter6/requirements.txt 文件,以确认 MLflow 库的确切版本。
在我们开始此活动之前,理解两个主要概念非常重要:实验 和 运行。
实验是一个逻辑名称,MLflow 会在其中记录和归类元数据,例如,实验可以是你项目的名称。假设你正在为你的零售客户构建一个预测信用卡欺诈的模型,这个名称就可以作为实验的名称。
运行是对实验的单次执行,MLflow 会跟踪它。每次运行都属于一个实验。每个运行可能有稍微不同的配置、不同的超参数,有时甚至使用不同的数据集。你将在 Jupyter 笔记本中调整这些实验参数。每次模型训练的执行通常被视为一次运行。
MLflow 记录实验详情的方式有两种。第一种,也是我们推荐的方式,是启用 MLflow 的自动记录功能,使其与 ML 库一起使用。它与 Scikit、TensorFlow、PyTorch、XGBoost 等库都有集成。第二种方式是手动记录所有内容。你将在接下来的步骤中看到这两种方法。
这些步骤将展示如何在执行 Jupyter 笔记本时,将实验运行或模型训练记录到 MLflow:
- 登录到 JupyterHub,并确保选择自定义容器,例如 Scikit v1.10 - Elyra 笔记本映像。
在点击 启动服务器 按钮之前,通过点击 添加更多变量 链接,添加一个环境变量。这个变量可能包含敏感信息,如密码。MLflow 需要这些信息来上传工件到 Minio S3 服务器。
登录页面将呈现出如 图 6.16 所示的截图:

图 6.16 – 带有环境变量的 JupyterHub
- 打开
chapter6/hellomlflow.ipynb笔记本。这个笔记本展示了如何将你的实验数据记录到 MLflow 服务器中。

图 6.17 – 带有 MLflow 集成的笔记本
请注意,在第一个代码单元中,您已导入了 MLflow 库。在第二个代码单元中,您通过set_tracking_uri方法设置了 MLflow 服务器的位置。请注意,因为您的笔记本和 MLflow 服务器都在 Kubernetes 上运行,所以我们仅仅将存储在HOST变量中的 Kubernetes 服务位置放在此方法中使用。
然后,您通过set_experiment方法设置实验名称。这是一个重要的变量,通过它,所有的实验运行将被存储在 MLflow 服务器中。
此单元格中的最后一个方法是sklearn.autolog,它是告诉 MLflow 我们正在使用 Scikit 库进行训练的一种方式,MLflow 将通过 Scikit API 记录数据。

图 6.18 – 带有 MLflow 配置的笔记本单元
在此笔记本的最后一个单元格中,您使用了一个简单的DecisionTreeClassifier来训练您的模型。请注意,这是一个相当简单的模型,旨在突出显示 MLflow 服务器的功能。
-
通过选择运行 > 运行所有单元菜单选项来运行笔记本。
-
登录 MLflow 服务器并点击实验名称
HelloMlFlow。MLflow 的 URL 将类似于mlflow.192.168.61.72.nip.io,其中的 IP 地址根据您的环境进行替换。正如本章前面提到的,您可以通过列出 Kubernetes 集群的ingress对象来获得此 URL。
您将看到如图 6.19所示的屏幕:

图 6.19 – MLflow 实验跟踪屏幕,显示实验运行
您会注意到右侧的表格中包含一条记录。这是您在第 6 步中执行的实验运行。如果您使用不同的参数多次执行笔记本,每次运行都会作为一行记录在此表格中。
- 点击表格的第一行。
您将看到您在上一步中选择的运行的详细信息。屏幕将显示如图 6.20所示的截图:

图 6.20 – MLflow 运行详情
让我们理解一下此屏幕上可用的信息:
4,您将看到我们为DecisionTreeClassifier使用的参数也记录在这里。一个这样的例子是max_depth参数,如图 6.21所示:

图 6.21 – MLflow 运行参数
- 截图中的
training_accuracy,如图 6.22所示:

图 6.22 – MLflow 运行指标
estimator_class),它定义了你使用的 ML 算法的类型。请注意,如果需要,你可以添加自己的标签。在接下来的部分中,我们将展示如何为你的运行关联一个自定义标签。图 6.23 展示了标签的示例:

图 6.23 – MLflow 运行标签
model.pkl文件。

图 6.24 – MLflow 运行工件
- 为了验证这些文件确实存储在 S3 服务器中,请登录 Minio 服务器,选择 Buckets,然后点击 MLflow 存储桶的 Browse 按钮。你会发现一个以运行名称命名的文件夹。这个名称显示在实验屏幕的左上角;查看前一个屏幕的左上角,你会看到一个由 32 个字母数字字符组成的标签。这个长数字就是你的 运行 ID,你可以在 S3 存储桶中看到一个由 32 个字母数字字符组成的文件夹标签,如下图所示。你可以点击此链接查看存储在 S3 存储桶中的工件:

图 6.25 – Minio 存储桶位置
你刚刚在 JupyterHub 中成功训练了一个模型,并在 MLflow 中跟踪了训练过程。
你已经看到了 MLflow 如何将数据与每次运行关联。你甚至可以通过从 步骤 6 中选择多个运行并点击 Compare 按钮,比较多个运行之间的数据。
向实验运行添加自定义数据
现在,让我们看看如何为每次运行添加更多数据。你将学习如何使用 MLflow API 将自定义数据与实验关联:
-
从启动 Jupyter 笔记本开始,方法和上一节一样。
-
打开
chapter6/hellomlflow-custom.ipynb笔记本。这个笔记本展示了如何将你的实验数据记录到 MLflow 服务器上。这个笔记本与之前的笔记本相似,唯一的区别是第6号单元格的代码,见图 6.26。这个代码单元包含了展示如何将数据与实验关联的函数:

图 6.26 – MLflow 自定义数据收集笔记本
让我们在接下来的几步中理解这些函数。第6号代码单元中的代码片段如下:
with mlflow.start_run(tags={ "mlflow.source.git.commit" : mlflow_util.get_git_revision_hash() , "mlflow.source.git.branch": mlflow_util.get_git_branch(), "code.repoURL": mlflow_util.get_git_remote() }) as run: model.fit(X, y) mlflow_util.record_libraries(mlflow) mlflow_util.log_metric(mlflow, "custom_mteric", 1.0) mlflow_util.log_param(mlflow, "docker_image_name", os.environ["JUPYTER_IMAGE"])
上述代码将包括一个名为 code.repoURL 的自定义标签。这样可以更轻松地追溯生成某次实验运行模型的原始源代码。
- 在调用
start_run函数时,你可以关联任何标签。以 mlflow 开头的标签键是保留用于内部使用的。你可以看到我们将 GIT 提交哈希与第一个属性关联。这将帮助我们跟踪哪些实验属于代码仓库中的哪个代码版本。
你会发现code.repoURL标签包含了 Git 仓库的位置。你可以根据需要添加任意数量的标签。通过进入 MLflow UI 并打开实验,你可以看到这些标签。请注意,笔记本使用了不同的实验名称,它被引用为HelloMlFlowCustom。
请注意code.repoURL位于标签部分:

图 6.27 – MLflow 自定义标签
- 我们使用的第二个函数是
record_libraries。这是一个封装函数,内部使用mlflow.log_artifact函数将文件与运行关联。这个工具函数捕获了pip freeze命令的输出,该命令列出了当前环境中的库。然后,该工具函数将其写入文件并将文件上传到 MLflow 实验。你可以查看这个函数以及其他所有函数,位于chapter6/mlflow_util.py文件中。
你可以看到在pip_freeze.txt中记录了pip freeze命令的输出:

图 6.28 – MLflow 自定义工件
log_metric函数记录指标名称及其相关值。请注意,指标的值应为数字。在示例代码中,我们只是放入了一个硬编码的值(1),然而,在实际应用中,这将是一个动态值,指向与每次实验运行相关的内容。你可以在页面的指标部分找到自定义的指标:

图 6.29 – MLflow 自定义指标
log_param函数与log_metric函数类似,但它可以接受任何类型的值与给定的参数名称。例如,我们记录了 Jupyter 笔记本使用的 Docker 镜像。请记住,这是你为数据科学团队构建的自定义镜像。你可以看到下面的docker_image_name参数,它包含了所需的值:

图 6.30 – MLflow 自定义参数
你已经使用 MLflow 跟踪、添加自定义标签和自定义工件到实验运行中。在下一部分中,你将看到 MLflow 作为模型注册组件的功能。让我们深入了解。
使用 MLFlow 作为模型注册系统
请记住,MLflow 具有模型注册功能。该注册表为你的模型提供版本控制功能。自动化工具可以从注册表中获取模型来进行部署,甚至在不同环境间回滚你的模型。在后续章节中,你将看到我们的平台中的自动化工具通过 API 从这个注册表中获取模型。现在,让我们来看一下如何使用注册表:
- 通过访问 UI 并点击模型链接,登录到 MLflow 服务器。你应该能看到以下屏幕。点击创建模型按钮:

图 6.31 – MLflow 注册新模型
- 在弹出窗口中输入模型名称,如下图所示,然后点击创建按钮。这个名称可以提到该模型所服务的项目名称:

图 6.32 – MLflow 模型名称提示
-
现在,你需要将模型文件附加到这个注册的名称上。回想一下前面的部分,你在实验中有多个运行。每次运行都定义了一组配置参数并将模型与之关联。选择你想注册模型的实验和运行。
-
你将看到如下屏幕。选择工件部分中的模型标签,你会注意到右侧有一个注册模型按钮。点击这个按钮:

图 6.33 – MLflow 显示注册模型按钮
- 从弹出窗口中,选择你在步骤 1 中创建的模型名称,然后点击注册。

图 6.34 – 在 MLflow 中注册模型时的模型名称对话框
- 前往
mlflowdemo:

图 6.35 – MLflow 显示已注册模型及其版本的列表
- 你将看到详细信息屏幕,在这里你可以附加模型的阶段,如阶段标签所示。你还可以编辑其他属性,我们将留给你去探索与此模型相关的数据:

图 6.36 – MLflow 显示用于将已注册模型提升到更高环境的按钮
恭喜你!你刚刚体验了使用 MLflow 作为模型注册表!你还看到了如何将模型版本提升到生命周期的不同阶段。
总结
在本章中,你对 ML 工程有了更深入的了解,并且了解了它与数据科学的区别。你还了解了一些 ML 工程师的职责。你必须注意,ML 工程学的定义和 ML 工程师的角色仍在不断发展,因为越来越多的技术正在浮现。我们本书中不会讨论的一项技术是在线 ML。
你还学会了如何创建自定义的笔记本镜像,并使用它来标准化笔记本环境。你已经在 Jupyter 笔记本中训练了一个模型,同时使用 MLflow 跟踪和比较模型开发的参数、训练结果和指标。你还了解了 MLflow 如何作为模型注册表使用,以及如何将模型版本提升到生命周期的不同阶段。
下一章将继续讲解机器学习工程领域,你将学习如何将机器学习模型打包并部署,以便作为 API 进行使用。然后,你将使用机器学习平台提供的工具来自动化打包和部署过程。
第六章:第七章:模型部署与自动化
在上一章中,你了解了平台如何使你能够以自主管理的方式构建和注册模型。在本章中,我们将扩展 机器学习 (ML) 工程领域,涵盖模型部署、监控和部署活动的自动化。
你将了解平台如何提供模型打包和部署功能,以及如何自动化这些过程。你将从注册表中获取模型,将其打包成容器,并将模型部署到平台上,作为 API 提供服务。接着,你将使用平台提供的工作流引擎自动化所有这些步骤。
一旦你的模型部署完成,它将在其训练数据上表现良好。然而,现实世界是不断变化的。你将看到平台如何让你观察模型的性能。本章将讨论监控模型性能的工具和技术。这些性能数据可以帮助决定模型是否需要基于新的数据集重新训练,或者是否该为给定问题构建一个新的模型。
在本章中,你将学习以下主题:
-
理解使用 Seldon Core 的模型推理
-
使用 Seldon Core 打包、运行和监控模型
-
理解 Apache Airflow
-
在 Airflow 中自动化 ML 模型部署
技术要求
本章包含一些动手设置和练习。你将需要一个运行中的 Kubernetes 集群,并且已配置 Operator Lifecycle Manager。如何构建这样的 Kubernetes 环境将在第三章中讨论,探索 Kubernetes。在尝试本章的技术练习之前,请确保你已经有一个可用的 Kubernetes 集群,并且 Open Data Hub (ODH) 已经安装在你的 Kubernetes 集群上。安装 ODH 的过程将在第四章中讨论,机器学习平台的结构。
理解使用 Seldon Core 的模型推理
在上一章中,你已经构建了模型。这些模型是由数据科学团队构建的,用于生产环境并处理预测请求。有多种方式可以将模型投入生产使用,比如将模型嵌入到面向客户的程序中,但最常见的方式是将模型作为 REST API 公开。然后,任何应用程序都可以使用这个 REST API。通常,运行和提供生产中的模型被称为 模型服务。
然而,一旦模型进入生产环境,它需要被监控其性能,并且需要更新以满足预期标准。托管的模型解决方案不仅可以让你提供模型服务,还能监控其性能并生成警报,用以触发模型的重新训练。
Seldon 是一家总部位于英国的公司,创建了一套用于管理模型生命周期的工具。Seldon Core 是一个开源框架,帮助将机器学习模型暴露为 REST API 以供使用。Seldon Core 会自动暴露 REST API 的监控统计信息,这些信息可以被平台的监控组件Prometheus使用。要在平台中将模型暴露为 REST API,您需要完成以下步骤:
-
为您的模型编写特定语言的包装器,将其暴露为服务。
-
将您的模型容器化。
-
使用 Seldon 部署 自定义资源(CR)在 Kubernetes 中定义并部署模型,利用模型的推理图。
接下来,我们将详细了解这三个步骤。
使用 Python 封装模型
让我们来看一下如何应用前面的步骤。在 第六章,机器学习工程 中,您已将实验细节和模型注册到 MLflow 服务器。回忆一下,模型文件被存储在 MLflow 的工件中,并命名为 model.pkl。
接下来,我们将以模型文件为基础,写一个简单的 Python 包装器。包装器的作用是使用 Seldon 库将模型便捷地暴露为 REST 服务。您可以在 chapter7/model_deploy_pipeline/model_build_push/Predictor.py 的代码中找到包装器的示例。这个包装器的关键组件是一个名为 predict 的函数,它会从 Seldon 框架创建的 HTTP 端点被调用。图 7.1 显示了一个使用 joblib 模型的简单 Python 包装器:

图 7.1 – 用于模型预测的 Python 语言包装器
predict 函数接收一个 numpy 数组(data_array)和一组列名(column_names),这些都是从 HTTP 请求中序列化过来的。该方法返回预测结果,结果可以是一个 numpy 数组,或者是一个值或字节的列表。对于语言包装器,还有许多其他方法可以使用,完整的方法列表可以在 docs.seldon.io/projects/seldon-core/en/v1.12.0/python/python_component.html#low-level-methods 中找到。请注意,在本书的后续章节中,您将看到更为详细的推理示例,其中会有额外的包装器用于数据转换。但在本章中,我们尽量保持简单。
语言包装器已准备好,下一步是将模型和语言包装器容器化。
容器化模型
你会把什么放入容器中?我们从一份清单开始。你需要模型和包装器文件。你还需要容器中可用的 Seldon Python 包。获取这些包后,你将使用 Seldon 服务来暴露模型。图 7.2展示了一个构建此类容器的Docker文件。该文件位于Chapter 7/model_deployment_pipeline/model_build_push/Dockerfile.py。

图 7.2 – 用于将模型打包为容器的 Docker 文件
现在,让我们理解 Docker 文件的内容:
-
第 1 行指示了你模型服务的基础容器镜像。我们选择了来自 Red Hat 的免费镜像,但你可以根据自己的需要选择。这张镜像可以是你公司基础镜像,包含标准版本的 Python 及相关软件。
-
在第 3 行,我们创建了一个
microservice目录,将所有相关的文件放入我们的容器中。 -
在第 4 行,我们构建容器所需的第一个文件是
base_requirements.txt。该文件包含 Seldon Core 系统的包和依赖项。你可以在chapter7/model_deployment_pipeline/model_build_push/base_requirements.txt中找到此文件。在该文件中,你会看到已添加了 Seldon Core 包和joblib包。
图 7.3展示了base_requirements.txt文件:

图 7.3 – 向容器中添加 Seldon 和 Joblib 文件
-
第 5 行使用
base_requirements.txt文件将 Python 包安装到容器中。 -
在第 7 行和第 8 行,当你训练模型时,可能会使用不同的包。在推理时,某些包可能会需要;例如,如果你在模型训练之前使用某个库进行了输入数据缩放,你可能需要相同的库来在推理时应用缩放。
在第六章,机器学习工程中,你将实验详情和模型注册到 MLflow 服务器。回想一下,模型文件与包含训练模型所用包的文件requirements.txt一起存储在工件中。使用 MLflow 生成的requirements.txt文件,你可以安装运行模型所需的包,或者你也可以选择将这些依赖项添加到自己的自定义文件中。图 7.4展示了在第六章,机器学习工程中提到的 MLflow 快照。你可以在这里看到与model.pkl文件并排的requirements.txt文件。

图 7.4 – MLflow 运行工件
第 10 行:你将语言包装器文件和模型文件添加到容器中。
第 11 行:在这里,您使用 seldon-core-microservice 服务器启动推理服务器。请注意,参数已经在这里传递,接下来您将看到如何传递这些参数:
-
MODEL_NAME:这是包含模型的语言包装器中的 Python 类名称。
-
MODEL。 -
GRPC_PORT:Google 远程过程调用(gRPC)端点将监听模型推理的端口。
-
METRICS_PORT:暴露服务性能数据的端口。请注意,这里指的是服务的性能数据,而非模型的数据。
-
HTTP_NAME:您将通过 HTTP 提供模型的 HTTP 端口。
现在,我们有一个以 Docker 文件形式的容器规格。接下来,我们将看到如何使用 Seldon 控制器在 Kubernetes 平台上部署容器。
使用 Seldon 控制器部署模型
我们的机器学习平台提供了一个 Seldon 控制器,它作为一个 Pod 运行,并协助部署您在前一部分构建的容器。请注意,我们平台中的控制器是现有 Seldon 操作符的扩展。在撰写本文时,Seldon 操作符不兼容 Kubernetes 版本 1.22,因此我们扩展了现有操作符,以便与最新及未来版本的 Kubernetes 平台兼容。
请参阅 第四章,机器学习平台的构成,在那里您学习了如何安装 ODH 以及它如何在 Kubernetes 集群上工作。以同样的方式,Seldon 控制器也是由 ODH 操作符安装的。manifests/ml-platform.yaml 文件包含了安装 Seldon 控制器的配置。图 7.5 展示了这些设置:

图 7.5 – 清单文件的 MLFlow 部分
让我们验证 Seldon 控制器是否在集群中正确运行:
kubectl get pods –n ml-workshop | grep –i seldon
您应该看到以下响应:

图 7.6 – Seldon 控制器 Pod
Seldon 控制器 Pod 是由 ODH 操作符安装的,这些操作符会监视 Seldon 部署 CR。该资源的架构由 Seldon 部署 manifests/odhseldon/cluster/base/seldon-operator-crd-seldondeployments.yaml 定义。一旦创建 Seldon 部署 CR,控制器将部署与该 CR 关联的 Pod。图 7.7 展示了这种关系:

图 7.7 – 用于部署 Seldon 服务的平台组件
让我们看看 Seldon 部署 CR 的不同组件。您可以在 chapter7/manual_model_deployment/SeldonDeploy.yaml 中找到一个简单的示例。
Seldon 部署 CR 包含了 Seldon 控制器部署模型到 Kubernetes 集群所需的所有信息。Seldon 部署 CR 主要有三个部分:
apiVersion、kind以及其他与 Kubernetes 相关的信息。你将像定义其他 Kubernetes 对象一样定义 Seldon 部署的标签和名称。你可以在以下截图中看到它包含了对象的标签和注释:

图 7.8 – Seldon 部署 – 与 Kubernetes 相关的信息
- 包含该信息的
chapter7/manual_model_deployment/SeldonDeploy.yaml文件。
请注意,containers 为 image 对象提供了一个数组,因此你可以向其中添加更多的镜像。image 键将包含你的容器位置。env 数组定义了将可用于 pod 的环境变量。回想一下,在前一部分的 Docker 文件中,这些变量已被使用。MODEL_NAME 的值为 Predictor,即你用作封装器的类名。SERVICE_TYPE 的值为 MODEL,表示此容器提供的服务类型。
最后一部分包含 hpaSpec,Seldon 控制器会将其转换为 maxReplicas 设置为 1,因此不会创建新的 pods,但你可以为每次部署控制此值。如果 CPU 使用率超过 80%(对于以下示例中的 pods),可扩展性将启动;然而,由于 maxReplica 设置为 1,因此不会创建新的 pods。

图 7.9 – Seldon 部署 – Seldon 服务容器
graph键为你的服务构建推理图。推理图将有多个节点,你将定义每个节点使用的容器。你会看到有一个children键,它接受一个对象数组,你通过该数组定义推理图。在此示例中,graph只有一个节点,children键没有与之关联的信息;然而,在后续章节中,你将看到如何构建具有更多节点的推理图。
图表下方的其余字段定义了推理图的第一个节点。name 字段的值与 containers 部分中你所定义的名称相对应。请注意,这是 Seldon 知道在推理图的该节点处提供服务的容器的关键。
另一个重要部分是 logger 部分。Seldon 可以自动将请求和响应转发到 logger 部分下提到的 URL。转发请求和响应的功能可以用于多种场景,例如出于审计/法律原因存储负载,或者应用数据漂移算法触发重新训练,或其他用途。请注意,Seldon 也可以在需要时将请求转发到 Kafka,但这超出了本书的讨论范围。

图 7.10 – Seldon 部署 – 推理图
一旦你使用常规的 kubectl 命令创建 Seldon 部署 CR,Seldon 控制器将部署 pods,并且模型将作为服务供消费。
接下来,我们将继续打包并部署你在第六章中构建的基础模型,机器学习工程。
使用 Seldon Core 打包、运行和监控模型
在本节中,你将打包并构建容器,使用你在第六章中构建的模型文件。然后,你将使用 Seldon 部署来部署并访问该模型。稍后在本书中,你将自动化这一过程,但手动完成,如本节所做的那样,将进一步加深你对组件及其工作原理的理解。
在开始此练习之前,请确保你已经在公共 Docker 注册表上创建了一个帐户。我们将使用免费的 quay.io 作为我们的注册表,但你也可以使用你偏好的其他注册表:
-
让我们首先验证 MLflow 和 Minio(我们的 S3 服务器)是否在集群中运行:
kubectl get pods -n ml-workshop | grep -iE 'mlflow|minio'
你应该看到以下响应:

图 7.11 – MLflow 和 Minio 正在平台上运行
-
获取 MLflow 的 ingress 列表,并通过以下输出中提供的
mlflowURL 登录 MLflow:kubectl get ingresses.networking.k8s.io -n ml-workshop
你应该看到以下响应:

图 7.12 – 你的 Kubernetes 集群中的 ingress
- 一旦进入 MLflow UI,导航到你在第六章中记录的实验,机器学习工程。该实验的名称是HelloMIFlow。

图 7.13 – MlFlow 实验跟踪
- 从右侧面板选择第一个运行,进入该运行的详细页面。从
model.pkl文件中,你会看到右侧有一个小的下载箭头图标。点击该图标下载requirements.txt文件。

图 7.14 – MLflow 实验跟踪 – 运行详情
-
进入你克隆的代码仓库所在文件夹。如果还没克隆,请在本地机器上克隆
github.com/PacktPublishing/Machine-Learning-on-Kubernetes.git仓库。 -
然后,进入
chapter7/model_deploy_pipeline/model_build_push文件夹,并将上一步骤中下载的两个文件复制到此文件夹。最终,这个文件夹将包含以下文件:

图 7.15 – 示例文件,用于将模型打包为容器
注意
最后两个文件就是你刚才复制的文件。所有其他文件来自你克隆的代码仓库。
好奇的人会注意到,你从 MLFlow 服务器下载的requirements.txt文件包含了在运行笔记本进行模型训练时所需的包。并不是所有这些包(例如mlflow)都需要用来执行保存的模型。为了简单起见,我们将它们全部添加到我们的容器中。
-
现在,让我们在本地机器上构建容器:
docker build -t hellomlflow-manual:1.0.0 .
你应该看到以下响应:

图 7.16 – 将模型打包为容器
-
下一步是标记容器并将其推送到你选择的仓库。在将镜像推送到仓库之前,你需要拥有一个镜像注册表账户。如果没有,可以在
hub.docker.com或quay.io创建一个账户。创建好注册表后,你可以运行以下命令来标记并推送镜像:docker tag hellomlflow-manual:1.0.0 <DOCKER_REGISTRY>/hellomlflow-manual:1.0.0 docker push <DOCKER_REGISTRY> /hellomlflow-manual:1.0.0
你应该看到以下响应。你会注意到,在以下截图中,我们将quay.io/ml-on-k8s称为我们的注册表:

图 7.17 – 将模型推送到公共仓库
- 现在你的容器已经在一个注册表中可用,你需要使用 Seldon 部署 CR 将其部署为服务。打开
chapter7/manual_model_deployment/SeldonDeploy.yaml文件,并调整镜像的位置。
你可以看到我修改后的文件,它显示了第 16 行(根据我的镜像位置)如下:

图 7.18 – 带有镜像位置的 Seldon 部署 CR
-
我们通过部署
chapter7/manual_model_deployment/SeldonDeploy.yaml文件来将模型作为服务部署。运行以下命令:kubectl create -f chapter7/manual_model_deployment/SeldonDeploy.yaml -n ml-workshop
你应该看到以下响应:

图 7.19 – 创建 Seldon 部署 CR
-
验证容器是否处于运行状态。运行以下命令:
kubectl get pod -n ml-workshop | grep model-test-predictor
你会注意到,你在 SeldonDeploy.yaml 文件的 graph 部分中填写的名称(model-test-predictor)是容器名称的一部分。
你应该看到以下响应:

图 7.20 – 在 Seldon 部署 CR 后验证 pod
-
很好!你已经有一个模型作为服务在运行。现在,让我们看看 Seldon 控制器为我们创建的 pod 中包含什么内容。运行以下命令来获取 pod 中容器的列表:
export POD_NAME=$(kubectl get pod -o=custom-columns=NAME:.metadata.name -n ml-workshop | grep model-test-predictor) kubectl get pods $POD_NAME -o jsonpath='{.spec.containers[*].name}' -n ml-workshop
你应该看到以下响应:

图 7.21 – Seldon pod 中的容器
你会看到有两个容器。一个是 model-test-predictor,它是我们构建的镜像,另一个是 seldon-container-engine,它是 Seldon 服务器。
model-test-predictor 容器包含模型,并使用语言包装器通过 HTTP 和 gRPC 暴露模型。你可以使用以下命令查看日志以及从 model-test-predictor 暴露了哪些端口:
kubectl logs -f $POD_NAME -n ml-workshop -c model-test-predictor
你应该看到以下响应(以及其他日志):

图 7.22 – 显示端口的容器日志
你可以看到,服务器已准备好在 9000 端口接收 HTTP 请求,在 6005 端口接收指标服务器请求。这个指标服务器将通过 /prometheus 端点暴露基于 Prometheus 的监控数据。你可以在以下日志部分看到这一点:

图 7.23 – 显示 Prometheus 端点的容器日志
第二个容器是 seldon-container-engine,它负责推理图的编排,并将负载转发到你在 Seldon 部署 CR 的 logger 部分配置的服务。
-
在此步骤中,你将了解 Seldon 部署 CR 为你创建了哪些 Kubernetes 对象。查看这些对象的一种简单方法是运行以下命令。此命令依赖于 Seldon 控制器使用标签键
seldon-deployment-id对它所创建的对象进行标记,并且值是你的 Seldon 部署 CR 的名称,即model-test:kubectl get all -l seldon-deployment-id=model-test -n ml-workshop
你应该看到以下响应:

图 7.24 – Seldon 控制器创建的 Kubernetes 对象
你可以看到,Seldon 控制器根据你在 Seldon 部署 CR 中提供的配置,为你创建了 Deployment 对象、服务和水平 Pod 自动扩展器(HPA)对象。最终,部署会创建 pod 和为你的 pod 创建副本集。Seldon 控制器使我们在 Kubernetes 平台上部署模型变得更加简单。
-
你可能已经注意到,Seldon 部署 CR 没有创建入口对象。让我们创建入口对象,以便通过运行以下命令从集群外部调用我们的模型。入口对象是通过
chapter7/manual_model_deployment/Ingress.yaml中的文件创建的。确保根据你的配置调整host值,正如你在前面的章节中所做的那样。你还会注意到,入口将流量转发到端口8000。Seldon 提供了对该端口的监听器,负责协调推理调用。此服务可在名为seldon-container-engine的容器中使用:kubectl create -f chapter7/manual_model_deployment/Ingress.yaml -n ml-workshop
你应该看到以下响应:

图 7.25 – 为我们的服务创建入口对象
通过执行以下命令来验证入口是否已创建:
kubectl get ingress -n ml-workshop | grep model-test
你应该看到以下响应:

图 7.26 – 验证我们服务的入口
-
由于我们的 Seldon 部署 CR 已引用了日志记录器 URL,你将部署一个简单的 HTTP 回显服务器,它只会打印收到的请求。这将帮助我们验证负载是否已转发到 Seldon 部署 CR 中
logger部分配置的 URL。可以通过以下命令创建一个非常简单的回显服务器:kubectl create -f chapter7/manual_model_deployment/http-echo-service.yaml -n ml-workshop
你应该看到以下响应:

图 7.27 – 创建一个简单的 HTTP 回显服务器以验证负载日志记录
通过执行以下命令来验证 Pod 是否已创建:
kubectl get pods -n ml-workshop | grep logger
你应该看到以下响应:

图 7.28 – 验证一个简单的 HTTP 回显服务器
- 让我们调用模型进行预测。我们在上一章中开发的模型并不是非常有用,但它将帮助我们理解并验证整体的模型打包和部署过程。
回想一下 第六章,机器学习工程,hellomlflow笔记本的模型输入形状是 (4,2),输出形状是 (4,)。

图 7.29 – 模型的输入和输出
因此,如果我们要向模型发送数据,它将是一个整数对数组,例如[2,1]。当你调用模型时,输入数据必须位于一个名为data的字段下,并使用ndarray格式。输入数据将如下所示。这是 Seldon 服务期望发送给它的数据格式:

图 7.30 – 作为 HTTP 负载的模型输入
- 接下来是模型的 REST 端点。它将是你在 第 13 步 中创建的 ingress 和标准的 Seldon URL。最终形式如下:http://<INGRESS_LOCATION>/api/v1.0/predictions。
这将转换为我的情况下的model-test.192.168.61.72.nip.io/api/v1.0/predictions。
现在,你已经有了负载和要发送此请求的 URL。
- 在此步骤中,你将调用你的模型。我们使用的是常用的命令行选项来进行此调用;不过,你也可以选择使用其他软件,如 Postman,来进行此 HTTP 调用。
你将使用 POST HTTP 动词进行调用,并提供服务的地址。你需要传递 Content-Type 头部来标明 JSON 内容,并使用 curl 程序的 data-raw 标志来传递请求体:
curl -vvvv -X POST 'http://<INGRESS_LOCATION>/api/v1.0/predictions' \--header 'Content-Type: application/json' \--data-raw '{ "data": { "ndarray": [[2,1]] }}'
最终的请求应如下所示。在发出此请求之前,请确保根据你的 ingress 位置修改 URL:
curl -vvvv -X POST 'http://model-test.192.168.61.72.nip.io/api/v1.0/predictions' \--header 'Content-Type: application/json' \--data-raw '{ "data": { "ndarray": [[2,1]] }}'
你应该看到以下响应。注意,命令的输出显示了与我们模型相同形状的数组,即(4,),并且它位于以下截图中的ndarray键下:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_07_031.jpg)
图 7.31 – 模型推理调用的输出负载
-
现在,让我们验证模型负载是否已记录到我们的 echo 服务器上。你正在验证 Seldon 捕获输入和输出并将其发送到目标位置以进行进一步处理(例如漂移检测或审计日志记录)的能力:
export LOGGER_POD_NAME=$(kubectl get pod -o=custom-columns=NAME:.metadata.name -n ml-workshop | grep logger) kubectl logs -f $LOGGER_POD_NAME -n ml-workshop
你会看到输入和输出负载有单独的记录。你可以使用 ce-requestid 键来关联日志中的两个记录。以下截图显示了推理调用的捕获输入负载的主要字段:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_07_032.jpg)
图 7.32 – 捕获的输入负载转发到 echo pod
以下截图显示了推理调用输出负载的主要字段:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_07_033.jpg)
图 7.33 – 捕获的输出负载转发到 echo pod
- 现在,让我们验证 Seldon 引擎是否已捕获服务监控数据,并且我们可以使用并记录这些数据。请注意,Prometheus 的工作方式是通过重复抓取,因此这些数据处于当前状态,Prometheus 服务器负责调用此 URL 并将其记录在其数据库中。
该信息的 URL 格式如下。Ingress 是你在 第 13 步 中创建的相同:
http://<INGRESS_LOCATION>/prometheus
这将转换为我 ingress 的以下内容:
http://model-test.192.168.61.72.nip.io/prometheus
打开浏览器并访问其中的 URL。你应该看到以下响应:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_07_034.jpg)
图 7.34 – 以 Prometheus 格式访问监控数据
你会发现收集了很多信息,包括响应时间、每个状态码的 HTTP 响应数量(200、400、500 等)、数据捕获、服务器性能以及暴露 Go 运行时指标。我们鼓励你深入了解这些参数,以便理解可用的数据。在后续章节中,你将看到如何收集和绘制这些数据,以便可视化模型推理服务器的性能。
你在这个练习中做得非常棒。本节的目的是展示使用 Seldon Core 部署模型的步骤和组件。在接下来的章节中,你将介绍平台的工作流组件 Airflow,并且接下来的几章中,所有这些步骤将通过 ML 平台中的组件进行自动化。
介绍 Apache Airflow
Apache Airflow 是一款开源软件,旨在以编程方式创建、执行、调度和监控工作流。工作流是一个任务序列,可以包括数据管道、ML 工作流、部署管道,甚至基础设施任务。它由 Airbnb 开发,作为一个工作流管理系统,后来在 Apache 软件基金会的孵化项目中开源。
虽然大多数工作流引擎使用 XML 来定义工作流,但 Airflow 使用 Python 作为定义工作流的核心语言。工作流中的任务也都是用 Python 编写的。
Airflow 具有许多功能,但本书只介绍 Airflow 的基础部分。本节绝不是 Airflow 的详细指南。我们的重点是介绍 ML 平台的软件组件。让我们从 DAG 开始。
理解 DAG
工作流可以简单地定义为一系列任务。在 Airflow 中,任务序列遵循一种称为有向无环图(DAG)的数据结构。如果你还记得计算机科学中的数据结构,DAG 由节点和单向顶点组成,这些节点和顶点按一定方式组织,以确保没有循环或环路。因此,Airflow 中的工作流被称为 DAG。
图 7.35 显示了一个典型的数据管道工作流示例:

图 7.35 – 典型的数据管道工作流
图 7.36 中的示例工作流由表示任务的框组成。任务的执行顺序由箭头的方向决定:

图 7.36 – 带有并行执行的示例工作流
另一个工作流示例显示在图 7.36中。在这个示例中,有些任务是并行执行的。生成报告任务将在两个转换数据任务完成后才会开始执行。这被称为执行依赖性,它是 Airflow 解决的问题之一。任务只有在上游任务完成后才能执行。
只要图中没有循环,你可以根据需求配置工作流,如图 7.37所示:

图 7.37 – 带有循环的示例工作流
在图 7.37中的例子中,清理数据任务永远不会执行,因为它依赖于存储数据任务,而后者也不会执行。Airflow 只允许非循环图。
如图所示,DAG 是一系列任务,Airflow 中有三种常见的任务类型:
-
操作符:预定义的任务,你可以用来执行某些操作。它们可以串联起来形成一个管道或工作流。你的 DAG 主要由操作符组成,甚至完全由操作符组成。
-
传感器:操作符的子类型,用于基于外部事件触发一系列其他操作符。
-
@task。这允许你将常规的 Python 函数作为任务运行。
Airflow 操作符是可扩展的,这意味着社区已经创建了许多预定义的操作符,你可以直接使用。在接下来的练习中,你将主要使用的操作符之一是Notebook 操作符。这个操作符允许你将任何 Jupyter 笔记本作为 DAG 中的任务来运行。
那么,使用 DAG 执行任务序列的优势是什么呢?仅仅编写一个能够按顺序执行其他脚本的脚本就足够了吗?答案在于 Airflow 所提供的特性,我们将继续探讨这些特性。
探索 Airflow 特性
与cron作业和脚本相比,Airflow 带来的优势可以通过其特性详细说明。让我们首先来看一下这些特性:
- 故障和错误管理:在任务失败时,Airflow 能够优雅地处理错误和失败。可以配置任务在失败时自动重试,并且可以配置重试次数。
就执行顺序而言,典型工作流中有两种任务依赖关系,它们在 Airflow 中比编写脚本更容易管理。
-
数据依赖性:某些任务可能需要先处理其他任务,因为它们需要其他任务生成的数据。这可以在 Airflow 中进行管理。此外,Airflow 允许将一个任务的输出中的小量元数据作为输入传递给另一个任务。
-
执行依赖性:你可能能够在小型工作流中编写执行依赖性脚本。然而,想象一下用 Bash 编写一个有一百个任务的工作流,其中一些任务可以并行运行,而其他任务只能顺序执行。我想这会是一个相当令人生畏的任务。Airflow 通过创建 DAG 帮助简化了这一过程。
-
可扩展性:Airflow 可以水平扩展到多个机器或容器。工作流中的任务可以在不同节点上执行,同时由一个共同的调度器集中协调。
-
git仓库包含你的 DAG。这使你能够实现 DAG 的持续集成。
下一步是了解 Airflow 的不同组件。
理解 Airflow 组件
Airflow 包含多个作为独立服务运行的组件。图 7.38 显示了 Airflow 组件及其相互作用:

图 7.38 – Airflow 组件
Airflow 中有三个核心服务。Airflow Web 提供用户界面,用户可以在其中直观地监控和与 DAG 及任务交互。Airflow Scheduler 是负责调度 Airflow Worker 任务的服务。调度不仅仅是根据预定时间执行任务,还包括按照特定顺序执行任务,考虑到执行依赖关系和失败管理。Airflow Worker 是执行任务的服务。这也是 Airflow 的主要可扩展性点。运行的 Airflow Worker 越多,可以并行执行的任务就越多。
DAG 仓库是文件系统中的一个目录,存储并由调度器检索用 Python 编写的 DAG 文件。我们平台中配置的 Airflow 实例包括一个侧车容器,它将 DAG 仓库与远程 git 仓库同步。这简化了 DAG 的部署,只需将 Python 文件推送到 Git。
本书中我们不会深入探讨 Airflow。目标是让你学到足够的内容,以便能够使用最少的 Python 编码在 Airflow 中创建管道。你将使用 Elyra 笔记本管道构建器功能,以图形方式构建 Airflow 管道。如果你想了解更多关于 Airflow 的内容,以及如何在 Python 中以编程方式构建管道,建议你从 Apache Airflow 的丰富文档开始,网址是 airflow.apache.org/docs/apache-airflow/stable/concepts/overview.html。
现在你对 Airflow 有了基本了解,是时候看看它的实际操作了。在 第四章,《机器学习平台的架构》中,你安装了一个新的 ODH 实例。这个过程也为你安装了 Airflow 服务。现在,让我们验证这个安装。
验证 Airflow 安装
要验证 Airflow 是否在你的集群中正常运行,你需要执行以下步骤:
-
通过执行以下命令检查所有 Airflow pod 是否都在运行:
kubectl get pods -n ml-workshop | grep airflow
你应该会看到三个 Airflow 服务 pod 处于运行状态,如 图 7.39 所示的截图。验证所有 pod 是否都处于 Running 状态:

图 7.39 – 运行状态下的 Airflow pod
-
通过查看
ap-airflow2的入口主机,获取 Airflow Web 的 URL。你可以通过执行以下命令来完成:kubectl get ingress -n ml-workshop | grep airflow
你应该看到类似于 图 7.39 的结果。注意 ap-airflow2 ingress 的主机值。你的环境中的 IP 地址可能不同:

图 7.40 – ml-workshop 命名空间中的 Airflow ingress
- 访问
airflow.192.168.49.2.nip.io。请注意,域名是ap-airflow2ingress 的主机值。你应该能看到 Airflow Web UI,如 图 7.41 所示:

图 7.41 – Apache Airflow 的主页
如果你能够加载 Airflow 着陆页,则说明 Airflow 安装有效。你还应该注意到,在列出 DAG 的表格中,已经有现有的 DAG 正处于失败状态。这些是已经存在的 DAG 文件,位于默认配置的 DAG 仓库 github.com/airflow-dags/dags/ 中。你需要为你的实验创建自己的 DAG 仓库。下一部分将提供如何执行此操作的详细信息。
配置 Airflow DAG 仓库
DAG 仓库是一个 Git 仓库,Airflow 从中获取表示管道或工作流的 DAG 文件。要配置 Airflow 指向你自己的 DAG 仓库,你需要创建一个 Git 仓库,并将 Airflow 调度器和 Airflow Web 指向该 Git 仓库。你将使用 GitHub 创建这个仓库。以下步骤将指导你完成此过程:
-
通过访问
github.com创建一个 GitHub 仓库。这要求你已经有一个 GitHub 帐号。为了本次练习,我们将这个仓库命名为airflow-dags。请记下你新创建的 Git 仓库的 URL,它应类似于:github.com/your-user-name/airflow-dags.git。我们假设你已经知道如何在 GitHub 上创建新的仓库。 -
通过编辑
kfdef(Kubeflow 定义)对象来编辑你的 ODH 实例。你可以通过执行以下命令来完成:kubectl edit kfdef opendatahub-ml-workshop -n ml-workshop
你应该会看到一个 vim 编辑器,显示 kfdef 清单文件,如 图 7.42 所示。按 i 开始编辑。

图 7.42 – vim 编辑器显示定义 Airflow 实例的部分
- 替换
DAG_REPO参数的值为你在 步骤 1 中创建的 Git 仓库的 URL。编辑后的文件应如 图 7.43 中的截图所示。按 Esc,然后 :,输入wq并按 Enter 保存你对kfdef对象所做的更改。

图 7.43 – 编辑后 DAG_REPO 参数的值
更改将被 ODH 操作符拾取,并应用到受影响的 Kubernetes 部署对象,在这种情况下,是 Airflow Web 和 Airflow 调度器部署。这个过程需要几分钟才能完成。
-
通过检查 Airflow 部署来验证更改。你可以运行以下命令来查看部署对象的应用清单:
kubectl get deployment app-aflow-airflow-scheduler -o yaml -n ml-workshop | grep value:.*airflow-dags.git
这应该返回一行,包含你 GitHub 仓库的 URL。
- 由于这个仓库是新的且为空,当你打开 Airflow Web UI 时,你应该看不到任何 DAG 文件。要验证 Airflow web 应用程序,导航到你的 Airflow URL,或刷新现有的浏览器标签,你应该看到一个空的 Airflow DAG 列表,类似于图 7.44中的截图:

图 7.44 – 空的 Airflow DAG 列表
现在你已经验证了 Airflow 安装并将 DAG 仓库更新为自己的 git 仓库,是时候充分利用 Airflow 了。
配置 Airflow 运行时镜像
Airflow 流水线,或 DAG,可以通过编写 Python 文件并使用 Airflow 库来创建。然而,也可以从 Elyra 笔记本中图形化创建 DAG。在本节中,你将从 Elyra 创建一个 Airflow DAG,将其推送到 DAG 仓库,并在 Airflow 中执行它。
为了进一步验证 Airflow 设置并测试配置,你需要运行一个简单的 Hello world 流水线。按照步骤创建一个包含两个任务的流水线。你将创建 Python 文件、一个流水线,并配置整个过程使用的运行时镜像:
- 如果你没有运行的笔记本环境,请通过导航到 JupyterHub,点击启动我的服务器,并选择要运行的笔记本镜像,如图 7.45所示,这时我们使用Base Elyra 笔记本镜像,因为我们不需要任何特殊库。

图 7.45 – 显示已选择 Base Elyra 笔记本镜像的 JupyterHub 登陆页面
-
在你的 Elyra 浏览器中,导航到
Machine-Learning-on-Kubernetes/chapter7/model_deploy_pipeline/目录。 -
打开一个新的流水线编辑器。你可以通过选择菜单项
untitled.pipeline来实现。

图 7.46 – Elyra 笔记本
-
右键点击
untitled.pipeline文件并将其重命名为hello_world.pipeline。 -
创建两个内容相同的 Python 文件,包含以下行:
print('Hello airflow!')。你可以通过选择菜单项hello.py和world.py来实现。你的目录结构应类似于图 7.47中的截图:

图 7.47 – 显示 hello.pipeline 文件的 Elyra 目录结构
- 通过将
hello.py文件拖动到管道编辑器窗口中,创建一个包含两个任务的管道。对world.py执行相同操作。通过将任务框右侧的小圆圈拖动到另一个框中,连接这些任务。最终的管道拓扑应该类似于图 7.48中的插图。通过点击顶部工具栏中的保存图标来保存管道。

图 7.48 – 任务拓扑
- 在我们运行这个管道之前,需要配置每个任务。因为每个任务将作为容器在 Kubernetes 上运行,我们需要指定该任务将使用哪个容器镜像。选择左侧工具栏上的运行时镜像图标。然后,点击+按钮添加一个新的运行时镜像,如图 7.49所示:

图 7.49 – 在 Elyra 中添加新运行时镜像
- 在添加新运行时镜像对话框中,添加Kaniko 容器构建器镜像的详细信息,如图 7.50所示,然后点击保存并关闭按钮。
这个容器镜像(quay.io/repository/ml-on-k8s/kaniko-container-builder)包含了在 Kubernetes 中构建 Docker 文件并将镜像推送到镜像仓库所需的工具。这个镜像也可以从 MLflow 模型仓库中拉取机器学习模型和元数据。你将在下一部分使用这个镜像构建容器,以托管你的机器学习模型。此容器镜像是为本书的目的创建的。你可以使用任何能够在 Kubernetes 上运行的容器镜像作为管道任务的运行时镜像。

图 7.50 – 为 Kaniko 构建器添加新的运行时镜像对话框
- 添加另一个名为Airflow Python Runner的运行时镜像。容器镜像位于
quay.io/repository/ml-on-k8s/airflow-python-runner。该镜像可以运行任何 Python 3.8 脚本,并与 Kubernetes 和 Spark 操作符进行交互。你将在下一部分使用此镜像将容器镜像部署到 Kubernetes。请参阅图 7.51中的添加新运行时镜像对话框字段值,然后点击保存并关闭按钮:

图 7.51 – 为 Airflow Python Runner 添加新运行时镜像对话框
- 从远程仓库将镜像拉取到你的 Kubernetes 集群的本地 Docker 守护进程中。这将有助于通过使用已经拉取到本地 Docker 实例的运行时镜像,加快 Airflow 任务的启动速度。
你可以通过在运行 Minikube 的同一台机器上运行以下命令来实现这一点。此命令允许你将 Docker 客户端连接到 Minikube 虚拟机(VM)内部的 Docker 守护进程:
eval $(minikube docker-env)
-
通过在与你的 Minikube 运行的同一台机器上运行以下命令来拉取Kaniko Container Builder镜像。这将从quay.io拉取镜像到你 Minikube 内的 Docker 守护进程:
docker pull quay.io/ml-on-k8s/kaniko-container-builder:1.0.0 -
通过在与你的 Minikube 运行的同一台机器上运行以下命令来拉取Airflow Python Runner镜像:
docker pull quay.io/ml-on-k8s/airflow-python-runner:0.0.11 -
分配
hello.py任务。你可以通过右键点击任务框并选择属性上下文菜单项来完成此操作。任务的属性将在管道编辑器的右侧窗格中显示,如图 7.52所示。使用运行时镜像下拉框,选择Kaniko Container Builder。

图 7.52 – 在管道编辑器中设置任务的运行时镜像
注意
如果你在下拉列表中没有看到新添加的运行时镜像,你需要关闭并重新打开管道编辑器。这将刷新运行时镜像的列表。
- 分配
world.py任务。这与步骤 10相似,但针对的是world.py任务。参见图 7.53中的运行时镜像值:

图 7.53 – 在管道编辑器中设置任务的运行时镜像
- 你刚刚创建了一个 Airflow 管道,其中包含两个任务,每个任务使用不同的运行时。但在我们运行这个管道之前,我们需要告诉 Elyra Airflow 的位置。为此,请点击 Elyra 左侧工具栏中的运行时图标,如图 7.54所示:

图 7.54 – 运行时工具栏
-
点击
ml-workshop。这是你所有 ML 平台工作负载的命名空间。 -
github-username/airflow-dags格式。将github-username替换为你的 GitHub 用户名。 -
minio。 -
minio123。
填写所有字段后,点击保存并关闭按钮。

图 7.55 – 添加新的 Apache Airflow 运行时配置
- 通过点击管道编辑器顶部工具栏中的播放按钮来运行 Airflow 管道。这将弹出运行管道对话框。选择Apache Airflow 运行时作为运行时平台,并选择MyAirflow作为运行时配置,然后点击确定。参见图 7.56:

图 7.56 – 运行管道对话框
此操作会生成一个 Airflow DAG 文件并将文件推送到配置为 DAG 仓库的 GitHub 仓库。你可以通过检查你的 GitHub 仓库中是否有新推送的文件来验证这一点。
- 打开 Airflow 网站。你应该能看到新创建的 DAG,如图 7.57所示。如果看不到,刷新 Airflow 页面几次。有时候 DAG 在 UI 中出现需要几秒钟。

图 7.57 – Airflow 显示正在运行的 DAG
DAG 应该会在几分钟内成功运行。如果失败,你需要检查步骤,确保设置了正确的值,并且没有遗漏任何步骤。
你刚刚使用 Elyra 的图形管道编辑器创建了一个基本的 Airflow DAG。生成的 DAG 默认配置为仅运行一次,通过@once注解表示。在实际应用中,你可能不希望直接从 Elyra 运行 DAG。你可能想要对 DAG 文件进行额外的自定义。在这种情况下,使用导出功能而不是点击播放按钮来运行 DAG。这将把管道导出为一个 DAG 文件,你可以进一步自定义,比如设置计划任务。然后,你可以将自定义的 DAG 文件推送到 DAG 仓库并提交给 Airflow。
你刚刚验证了 Airflow 设置,添加了 Airflow 运行时配置,并将 Elyra 与 Airflow 集成。现在是时候构建一个真正的部署管道了!
在 Airflow 中自动化 ML 模型部署
你已经在前面的章节中了解了如何手动将 ML 模型打包成运行在 Kubernetes 上的 HTTP 服务。你还了解了如何在 Airflow 中创建和运行基础管道。在本节中,你将通过创建一个 Airflow DAG 来自动化模型部署过程,从而将这些新知识结合起来。你将创建一个简单的 Airflow 管道,用于将 ML 模型从 MLflow 模型注册中心打包并部署到 Kubernetes。
使用管道编辑器创建管道
类似于上一节,你将使用 Elyra 的管道编辑器来创建模型构建和部署的 DAG:
-
如果你没有运行中的 Elyra 环境,可以通过导航到 JupyterHub,点击启动我的服务器,并选择一个笔记本镜像来启动笔记本环境,如图 7.45所示。我们这次使用基础 Elyra 笔记本镜像,因为这次我们不需要任何特殊的库。
-
在 Elyra 浏览器中,导航到
Machine-Learning-on-Kubernetes/chapter7/model_deploy_pipeline/目录。 -
打开一个新的管道编辑器。你可以通过选择菜单项
untitled.pipeline来完成此操作。 -
右键点击
untitled.pipeline文件,将其重命名为model_deploy.pipeline。你的目录结构应当像图 7.58中截图所示:

图 7.58 – Elyra 显示空的管道编辑器
- 你将构建一个包含两个任务的管道。第一个任务将从 MLflow 模型注册中心拉取模型工件,使用 Seldon Core 将模型打包成容器,并将容器镜像推送到镜像仓库。要创建第一个任务,拖动并将
build_push_image.py文件从model_build_push目录拖放到管道编辑器的工作区。此操作将在管道编辑器窗口中创建一个新任务,如图 7.59所示:

图 7.59 – Elyra 管道编辑器显示 build_push_image 任务
- 第二个任务将从镜像仓库拉取容器镜像,并将其部署到 Kubernetes。通过将
model_deploy directory中的deploy_model.py文件拖放到管道编辑器工作区来创建第二个任务。这将会在管道编辑器中创建一个第二个任务,如 图 7.60 所示:

图 7.60 – Elyra 管道编辑器显示 deploy_model 任务
- 通过拖动
build_push_image.py任务右侧的小圆圈到deploy_model.py任务框,连接这两个任务。任务拓扑应如下图所示 图 7.61。注意红框中箭头的方向。

图 7.61 – DAG 任务拓扑
- 通过右击框并选择 属性 来配置
build_push_image.py任务。一个属性面板将出现在编辑器的右侧,如 图 7.62 所示。选择 Kaniko Container Builder 作为此任务的运行时镜像。

图 7.62 – 显示属性面板的管道编辑器,展示 Kaniko Builder 运行时
-
通过点击
Dockerfile来向build_push_image.py添加文件依赖 – 这是将要构建的 Docker 文件,用于生成包含 ML 模型和预测器 Python 文件的容器镜像。 -
Predictor.py– 这是 Seldon 用来定义推理图的 Python 文件。你在前面的章节中已经看到过这个文件。 -
Base_requirements.txt– 这是一个常规文本文件,包含运行此模型所需的 Python 包列表。它由 Docker 文件中的pip install命令使用。 -
到目前为止,你应该对整个管道的工作原理有一个大致的了解。因为管道需要将容器镜像推送到注册中心,你将需要一个容器注册中心来存放你的 ML 模型容器。在你选择的容器注册中心中创建一个新的仓库。在本书的练习中,我们将使用
mlflowdemo。 -
一旦你创建了镜像仓库,设置
build_push_image.py任务,如 图 7.63 所示。以下是你需要设置的六个变量:-
MODEL_NAME是在 MLflow 中注册的 ML 模型名称。在前面的章节中,你使用了名称mlflowdemo。将此变量的值设置为mlflowdemo。 -
MODEL_VERSION是在 MLflow 中注册的 ML 模型的版本号。将此变量的值设置为1。 -
CONTAINER_REGISTRY是容器注册表的 API 端点。对于 Docker Hub,地址为index.docker.io/v1。将此变量的值设置为https://index.docker.io/v1/。 -
CONTAINER_REGISTRY_USER是将镜像推送到镜像注册表的用户的用户名。将其设置为你的 Docker Hub 用户名。 -
CONTAINER_REGISTRY_PASSWORD是你 Docker Hub 用户的密码。在生产环境中,你不应该这样做。你可以使用密钥管理工具来存储你的 Docker Hub 密码。然而,为了简单起见,在本练习中,你将把 Docker Hub 密码作为环境变量。 -
CONTAINER_DETAILS是镜像将被推送到的仓库名称,包括镜像名称和标签。这包括 Docker Hub 用户名,格式为your-username/mlflowdemo:latestv。
-
通过点击管道编辑器顶部工具栏的保存图标来保存更改:

图 7.63 – build_push_image.py 任务的示例环境变量
-
配置
deploy_model.py任务,设置运行时镜像、文件依赖和环境变量,如图 7.64所示。你需要设置四个环境变量,具体如下:-
MODEL_NAME是在 MLflow 中注册的 ML 模型名称。你在前面的章节中使用了mlflowdemo这个名称。将此变量的值设置为mlflowdemo。 -
MODEL_VERSION是在 MLflow 中注册的 ML 模型的版本号。将此变量的值设置为1。 -
CONTAINER_DETAILS是镜像将被推送到的仓库名称,包括镜像名称和标签。这包括 Docker Hub 用户名,格式为your-username/mlflowdemo:latest。 -
CLUSTER_DOMAIN_NAME是你 Kubernetes 集群的 DNS 名称,在本例中是 Minikube 的 IP 地址,即<Minikube IP>.nip.io。例如,如果minikube ip命令的响应是192.168.49.2,那么集群域名就是192.168.49.2.nip.io。此项用于配置 ML 模型 HTTP 服务的入口,以便它可以在 Kubernetes 集群外部访问。
-
通过点击管道编辑器顶部工具栏的保存图标来保存更改。

图 7.64 – deploy_model.py 任务的属性
- 现在你已经准备好运行管道了。点击管道编辑器顶部工具栏的播放按钮。这将弹出运行管道对话框,如图 7.65所示。在运行平台下选择Apache Airflow 运行时,在运行配置下选择MyAirflow。点击确定按钮。这将生成 Airflow DAG Python 文件并将其推送到 Git 仓库。

图 7.65 – 运行管道对话框
- 一旦 DAG 成功生成并推送到
git仓库,你应该会看到一个对话框,如 图 7.66 所示。点击 确定。

图 7.66 – DAG 提交确认对话框
-
导航到 Airflow 的 GUI。你应该能在 DAG 表格中看到一个新的 DAG,标签为 model_deploy-some-number,它应该会很快开始运行,如 图 7.67 所示。任务的薄荷绿色表示它正在运行,深绿色表示它已经成功。
注意
如果你没有看到新的 DAG,请刷新页面直到看到它。Airflow 可能需要几秒钟的时间与 Git 仓库同步。

图 7.67 – Airflow GUI 显示 model_deploy DAG
- 同时,你可以通过点击 DAG 名称并选择 图形视图 标签来探索 DAG。它应该会显示你在 Elyra 流水线编辑器中设计的任务拓扑,如 图 7.68 所示。你还可以通过选择 <> 代码 标签进一步探索 DAG。这将显示生成的 DAG 源代码。

图 7.68 – Airflow 中 model_deploy DAG 的图形视图
-
几分钟后,任务应该会成功,你应该能看到 图形视图 中所有任务的轮廓变成深绿色。你还可以通过查看 Kubernetes 中的 pod 来探索这些任务。运行以下命令,你应该能看到两个 完成 状态的 pod,如 图 7.69 所示。这些 pod 是流水线中两个已经成功执行的任务:
kubectl get pods -n ml-workshop\
你应该看到以下响应:

图 7.69 – Kubernetes pod 完成状态
你刚刚使用 Seldon Core、Elyra 的流水线编辑器、Airflow 编排并部署到 Kubernetes,成功创建了一个完整的 ML 模型构建和部署流水线。
Seldon Core 和 Airflow 是功能强大的工具,它们有更多的功能,我们在本书中没有涉及,也不会完全覆盖。我们已经为你提供了开始进一步探索这些工具的基本知识和技能,作为你 ML 平台的一部分。
总结
恭喜!你已经走到了这一步!
到目前为止,你已经使用过 JupyterHub、Elyra、Apache Spark、MLflow、Apache Airflow、Seldon Core 和 Kubernetes。你已经学会了这些工具如何解决 MLOps 所要解决的问题。而且,你已经看到所有这些工具都在 Kubernetes 上良好运作。
我们还想在平台上向你展示更多的内容。然而,我们能写的内容有限,因为你已经看到的每个工具的功能足以填满一本书。
在下一章,我们将退一步,从整体上回顾到目前为止所构建的内容。接下来,你将开始在一个示例用例中全面使用平台。在接下来的章节中,你将扮演不同的角色,例如数据科学家、机器学习工程师、数据工程师以及运维工程师。
第三部分:如何使用 MLOps 平台并使用新平台构建一个完整的端到端项目
本节将展示如何使用上一节构建的平台来构建一个完整的机器学习项目。本节中的章节将对我们的平台进行实战测试。本节将定义一个完整的机器学习生命周期,然后处理数据,构建并部署模型。
本节包括以下章节:
-
第八章**,使用平台构建一个完整的机器学习项目
-
第九章**,构建你的数据管道
-
第十章**,构建、部署和监控你的模型
-
第十一章**,在 Kubernetes 上进行机器学习
第七章:第八章:使用平台构建完整的机器学习项目
到目前为止,你已经了解了平台的一些组件及其工作原理。本章将从宏观层面理解平台,全面的视角将帮助你看到这些组件如何为你的机器学习(ML)需求编织出完整的解决方案。
在本章的后半部分,你将看到如何通过一个简单的示例启动机器学习项目,以及团队和平台如何帮助实现你的目标。
在本章中,你将学习以下内容:
-
审视机器学习平台的完整图景
-
理解业务问题
-
数据收集、处理和清洗
-
执行探索性数据分析
-
理解特征工程
-
构建和评估机器学习模型
-
可复现性
审视机器学习平台的完整图景
在前几章中,你已经在 Kubernetes 上构建了一个完整的机器学习平台。你安装、配置并探索了平台的不同组件。在开始使用平台之前,让我们退后一步,从工具的角度来看你构建的平台。图 8.1 展示了平台的完整逻辑架构:

图 8.1 – 逻辑平台架构
图 8.1 中的图示也展示了每个平台组件的交互。整个平台运行在 Kubernetes 内,并完全由 Kfdef 文件进行管理。还需要注意的是,ODH 操作符允许你添加或删除工具,或将一种工具替换为另一种工具。例如,你可以使用 Argo CD 来进行模型部署,而不是使用 Airflow。Keycloak 也不属于 ODH 项目的一部分。然而,组件必须通过单点登录机制进行保护,而 Keycloak 是一种非常适合为平台添加单点登录功能的开源工具。
从图表的顶部开始,你可以看到最终用户与 Jupyter notebooks、Spark、Airflow 和 MLflow 的用户界面进行交互。你在前面的章节中已经见识并体验了这些交互。部署的机器学习模型随后可以通过 REST API 调用供应用进行推理使用。
在图表的中间,你可以看到各个组件之间的交互以及它们之间执行的交互类型。Jupyter 服务器和 Airflow 作业可以向托管的 Spark 集群提交 Spark 应用程序。Airflow 与 MLflow 模型注册表进行交互,而 Jupyter notebooks 可以与 MLflow 进行交互,记录实验运行。Airflow 还会创建 Seldon 部署对象,Seldon 控制器将其转换为运行的 Pods,并将机器学习模型暴露为 REST 服务。一个组件与其他平台组件的交互方式没有限制。
在图表的底部,ODH 操作员管理和操作平台组件。ODH 操作员负责这些组件的安装和更新。Spark、JupyterHub 和 Seldon 控制器也是 Kubernetes 操作员,分别管理 Spark 集群、Jupyter notebook 服务器和 Seldon 部署实例。
最后,ODH 操作员还管理 Prometheus 和 Grafana 实例。Prometheus 用于收集每个组件的指标,包括 Seldon 部署的统计数据。Grafana 可以可视化这些指标,并可以配置为触发警报。
ODH 项目仍在发展中。未来可能会有一些组件的增减变化。一些官方支持的组件可能会随着时间的推移被其他组件替代。因此,理解架构和 ODH 操作员的工作原理是非常重要的,这样你才能保持它的最新状态。
在接下来的章节中,我们将退后一步,深入了解机器学习项目,从识别适合机器学习解决方案的机会开始。你将会跟随一个场景,逐步创建一个完整的机器学习项目。
理解业务问题
和任何软件项目一样,首先要做的是明确你试图解决的业务问题。我们为本书选择了一个虚构的场景,以便简化内容,集中关注过程。你也可以将这种方法应用于更复杂的项目。
假设你在一家航空公司预订公司工作,担任首席数据分析师。公司业务团队报告称,许多客户抱怨航班延误,导致公司客户体验差,电话客服花费大量时间向客户解释细节。业务方希望你提供一个解决方案,识别哪些航空公司、航班和时段的延误概率较低,以便网站可以优先展示这些航空公司,从而减少客户的延误。
让我们稍作休息,分析一下如何解决这个问题。我们需要在这里使用机器学习吗?如果我们拿历史数据,将航空公司分为 延误 和 准时 两类,并将每家航空公司放入正确的类别,那么在客户搜索航空公司时,这个属性就可以作为参考,帮助他们找到准时性更好的航空公司。一组数据分析师将分析数据并给出评级。工作完成!
在探索这组数据时,业务方提到每个航空公司一个桶的数据可能无法提供解决方案所需的粒度。他们希望从航司级别之外的其他因素进行性能评估,例如起点和终点机场,以及一天中的时间。因此,航空公司 A 的悉尼至墨尔本航班可能会进入准时桶,而同一航空公司从东京飞往大阪的航班则可能进入延误桶。这一下就扩大了问题的范围。如果你需要在这种粒度上分析数据,处理和分配正确的类别会花费大量时间,而且你可能需要频繁地分析这些数据。
你是否已经开始考虑如何自动化这个过程?业务方接着提到天气在这个问题中起着至关重要的作用,气象局的预报数据需要被提取并预处理,以进行分析。你意识到,如果依赖人工团队来完成这个任务,会非常缓慢且复杂,且无法提供业务方所需的解决方案。于是你向业务方提到,你需要调查现有的数据,这些数据可以用来预测某个航班的正确类别。你和业务方一致认为,目标是在航班计划时间前 10 天预测延误,并且至少达到 75%的准确率,以改善客户体验。你还将讨论模型的响应时间要求,并了解该模型如何在整体业务流程中使用。
你刚刚定义了这个项目的成功标准。你已将信息传达给业务方,说明你的团队将分析可用数据以评估其是否适用于项目,然后再规划下一步的工作。你已要求业务方指派一位主题专家(SME)来协助此阶段的数据探索。
总结来说,你已经概述了业务目标和项目范围。你还定义了评估标准,用以衡量项目的成功。至关重要的是,你需要在机器学习生命周期的每个阶段都记录业务价值。
一旦定义了评估标准,下一步就是开始查看可用的数据。对于此用例,数据可以在www.kaggle.com/usdot/flight-delays?select=flights.csv找到。
数据收集、处理和清洗
在这个阶段,你将开始从已识别的数据源收集原始数据。你将编写数据管道,以准备和清理原始数据,供分析使用。
理解数据来源、位置和格式
你已开始与领域专家合作,访问一部分航班数据。你将了解数据格式以及访问这些数据所需的集成过程。数据可能是 CSV 格式,或者它可能存储在某些关系型数据库管理系统(RDBMS)中。了解这些数据如何为你的项目所用,以及这些数据最终是如何被维护的至关重要。
从识别易于获取的数据开始。领域专家提到,包含航班信息、计划和实际出发时间、计划和实际到达时间的航班记录数据可以轻松获得。这些信息存储在你们组织的对象存储中。这可以作为一个良好的起点。
理解数据处理与清理
从原始数据源收集的数据可能存在许多问题。收集的数据可能存在重复、缺失值和/或无效记录。例如,你可能会发现某一列的string类型数据中竟然包含了数字。你将与领域专家合作,找出处理这些异常数据的方法。
你将如何处理缺失数据?从现有的数据集中选择一个估算值来填充缺失数据。或者,如果缺失值很多且你无法找到任何方式来填补这些缺失值,你可能决定完全删除该列。
实现数据验证检查,确保清理后的数据集具有一致性,并且适当处理此处描述的数据质量问题。假设年龄列的值为250。虽然我们都希望能活这么长或者更长,但显然这个数据是无效的。在这个阶段,你将发现数据中的不一致性,并思考如何处理它。
你可能会发现航班的到达和出发时间是以当地时区表示的,你可能选择新增一个列,将这些时间转换为 UTC 格式,以便于进行比较。
数据清理可以发生在数据工程阶段和模型开发阶段。与领域或业务逻辑相关的数据异常可以在数据工程阶段发现并处理,而数据增强和数据编码则是在模型开发阶段完成的。这是因为数据科学家或机器学习工程师最了解模型训练所需的数据格式,而数据工程师则与业务领域专家的合作更为紧密。
实现数据验证的一种方法是在数据工程阶段通过 Apache Spark。Spark 提供了一组内置函数,可以用于数据清理。以下代码示例展示了如何在从数据源读取数据时,过滤掉无效行或包含格式错误数据的行:
dataframe = spark.read.option("header", True).option("mode", 'DROPMALFORMED').csv('flights.csv')
另一个示例是 fillna() 函数。它用于用其他值替换空值。以下示例展示了如何用零替换数据框中的所有空值:
dataframe = dataframe.fillna(value=0)
在模型开发方面,有几种技术可以使用 pandas 来执行相同的操作,以处理数据框架。你将在接下来的章节中看到这些技术的实际应用。
一旦你执行了数据清理流程并创建了一个可以用于下一阶段的中间数据集,接下来的步骤是看看现有数据是否有助于实现业务目标。
执行探索性数据分析
在此阶段,你需要分析数据,以评估其是否适合给定的问题。数据分析对于构建机器学习模型至关重要。在创建机器学习模型之前,你需要理解数据的背景。分析大量的公司数据并将其转化为有用的结果是极其困难的,而且没有单一的方法来实现这一点。找出哪些数据是有意义的,哪些数据对业务至关重要,是构建机器学习模型的基础。
这是一项初步分析,不能保证模型会带来预期的结果。然而,它提供了一个机会,可以在更高层次上理解数据,并在需要时进行调整。
理解样本数据
当你获得一组数据时,你首先通过简单地查看数据来理解它。接着,你会深入了解业务问题,尝试确定哪些模式对当前情况有帮助。很多时候,你将需要与具有相关领域知识的专家进行合作。
在此阶段,你可以选择将数据转换为表格形式,以便更好地理解它。根据数据值对列进行分类。理解数据集中的每个变量,并找出这些值是连续的,还是表示某一类别。然后,你将使用描述性统计来总结列,以了解列中包含的值。这些统计数据可以是均值、中央値,或任何有助于你理解数据的指标。
了解数据方差。例如,你的数据中只有 5%的航班记录是延误的,剩下的航班都是准时的。这个数据集对于你期望的结果会有帮助吗?你需要获取一个更好的数据集,代表更平衡的分布。如果数据集严重不平衡,你可以选择通过减少多数类别的样本来对数据集进行下采样。
人类擅长可视化数据,因此,为了更好地理解数据,你需要使用图表将列可视化。有一系列不同的图表可以帮助你可视化数据。我们在这里展示的平台将帮助你编写代码,使用流行的库,如 Matplotlib 或 Seaborn,来可视化数据。在你选择使用图表可视化数据之前,思考一下你希望从图表中获得什么信息,以及它如何帮助你理解数据。
作为示例,我们定义了以下小节中给出的三种基本图表及其特性。
箱型图
箱线图(www.khanacademy.org/math/statistics-probability/summarizing-quantitative-data/box-whisker-plots/a/box-plot-review)是一个可视化并理解数据方差的极好方法。箱线图将结果以四分位数的形式展示,每个四分位数包含数据集的 25%的值;这些值会被绘制出来,显示数据的分布情况。图 8.2 显示了一个示例箱线图。注意黑点是一个离群值:

图 8.2 – 箱线图
箱线图的第一个组成部分是数据集的最小值。接下来是下四分位数,即最小的 25%的值。之后,我们得到的是数据集的中位数,即 50%的位置。接下来是上四分位数,即最大的 25%的值。最上面是数据集的最大值。最后,我们有离群值。离群值是极端的数据点——无论是高端还是低端——它们可能会对分析结果产生影响。
直方图
直方图表示数值数据的分布情况。要创建直方图,首先将数值范围划分为多个区间,这些区间称为箱子(bins)。在确定了用于存储数据的箱子数量后,数据会被放入适当的箱子中。直方图图表显示了按预定义的箱子进行分布的数据。图 8.3 显示了一个示例直方图。注意箱子位于图表的x轴上。以下图表显示了仅在两个箱子中的分布。你可以看到,分布偏向第一个箱子。

图 8.3 – 直方图
密度图
直方图的一个缺点是它们对箱子边界和箱子数量比较敏感。箱子的定义会影响分布的形状。如果你的数据包含更多离散的值(如性别或邮政编码),直方图可能会更适合。否则,可以考虑使用密度图,它是直方图的平滑版本。图 8.4 显示了一个示例密度图:

图 8.4 – 密度图
一旦你完成了探索性数据分析,你可以选择回去从现有的资源中收集更多数据,或者寻找新的数据源。如果你在此阶段确信你收集到的数据能够帮助你实现业务目标,那么你就可以进入下一阶段——特征工程。
理解特征工程
机器学习的核心是数据。无论我们的算法有多先进,如果数据不准确或不足,我们的模型就无法达到预期效果。特征工程将输入数据转换为与模型目标紧密对接的特征,并将数据转化为有助于模型训练的格式。
有时,某些数据对于给定的训练问题可能无用。我们如何确保算法仅使用正确的信息集?那些单独不实用的字段如何在我们将函数应用于一组字段时变得特别有用?
使数据对算法有用的行为称为特征工程。大多数情况下,数据科学家的工作是为给定问题找到正确的数据集。特征工程需要掌握特定领域的技术,您将与业务 SME 合作,以更好地理解数据。
特征工程不仅仅是从现有数据中找到正确的特征,而且您可能需要从现有数据中创建新特征。这些特征被称为工程特征。
想象一下,在您的航班数据集中,有提到scheduled_departure_time和departure_time的字段。这两个字段将告诉您航班是否晚点。然而,您的业务希望对航班是否晚点进行分类。您和业务部门同意将延误分类为以下三个类别:
-
准时
-
短延误
-
长延误
短延误捕捉最多 30 分钟延误起飞的航班。所有其他延误航班将根据延误列中的长延误值进行分类。您需要将此列或特征添加到数据集中。
您可能最终会放弃对给定问题无用的列。您认为Cancellation Reason列对预测航班延误有用吗?如果不是,您可以选择放弃此列。
您还将表示您的数据,使其易于 ML 算法消化。许多 ML 算法操作的是数值值;然而,并非所有数据都以数值格式存在。您将应用诸如独热编码之类的技术,将列转换为数值格式。
通常,ML 算法在值范围在–1和1之间的情况下表现良好,因为它收敛更快,训练时间更短。即使您有数值数据,将其转换为这个范围可能也会有益,这个过程称为缩放。在此阶段,您可以编写代码来缩放数据集。
数据增强
在某些情况下,您可能希望出于几个原因在数据集中创建额外的记录。一个原因是当您没有足够的数据来训练有意义的模型时,另一个原因是当您有意地想影响模型的行为,以支持一个答案而不是另一个答案,如纠正过拟合。这种创建合成数据的过程称为数据增强。
所有与数据收集、处理、清理、数据分析、特征工程和数据增强相关的活动都可以通过使用 Jupyter 笔记本和可能的 Apache Spark 平台来完成。
一旦你清理、分析并转换了数据,下一阶段就是构建和训练机器学习模型。
构建和评估机器学习模型
恭喜!你现在准备好训练你的模型了。你将首先评估哪些算法集适合当前问题。是回归问题还是分类问题?你如何评估模型是否达到了业务描述中的 75%正确预测率?
选择评估标准
我们从准确度作为模型评估标准开始。这记录了预测值与测试数据集中标签相同的次数。然而,如果数据集的方差不够,模型可能会对每个样本猜测大多数类别,这实际上没有学习到关于少数类的任何信息。
你决定使用混淆矩阵来查看每个类别的准确度。假设你有 1,000 条数据记录,其中 50 条被标记为延迟,其余 950 条标记为准时。现在,如果模型正确预测了 950 条数据中有920条是准时,并且 50 条数据中有12条是延迟,那么混淆矩阵将如下所示,如图 8.5所示:

图 8.5 – 混淆矩阵
对于不平衡的数据集,建议选择召回率、精确率或 F-score 等指标,以全面了解情况。在这种情况下,精确率为 31%(12/38),召回率为 24%(12/50),而准确度为 93.2%(932/1000),但在你的场景中,准确度可能会导致误导。
构建模型
你将开始将数据拆分为训练集、验证集和测试集。考虑一种情景,你将数据拆分为这些集合并训练一个模型;我们称之为实验 1。现在,你想使用不同的超参数重新训练模型,并再次拆分数据用于这一新的迭代并训练模型;我们称之为实验 2。如果两个实验中的数据拆分不一致,你能比较两个实验的结果吗?确保数据拆分是可重复的,这对于比较训练过程中的不同运行至关重要。
你将尝试不同的算法或算法的集成,来评估数据验证集的表现,并检查预测的质量。在这个阶段,每次你对模型进行新的调整(例如,超参数或不同的算法)时,你都将衡量并记录与业务专家在理解业务问题阶段共同设定的评估指标。
模型阶段的大多数步骤是迭代的。根据实验的结果,你可能会意识到模型的表现没有达到预期。在这种情况下,你可能想回到生命周期的前面步骤,比如特征工程。或者,你可能希望重新做一次数据分析,以确保你正确理解数据。在训练过程中,你将重新审视商业目标和数据,以找到正确的平衡。你可能会决定需要来自新源的额外数据点来增强训练数据。在这个阶段,强烈建议你将结果展示给业务相关方。这种沟通会在初期阶段向业务展示模型的价值,收集早期反馈,并为团队提供必要时调整方向的机会。
下一阶段是部署模型以进行推理。
部署模型
一旦你训练好了模型,下一阶段就是在 MLflow 中为模型版本控制,并将其部署到可以用来对传入请求进行预测的环境中。模型的版本控制将使你能够跟踪模型,并在需要时回滚到旧版本。
本书中,我们将使用在线模型推理的方法。模型已经通过平台的 Seldon 组件进行了容器化,并作为 REST API 暴露。每次调用这个 REST API 都会产生一个预测。运行在 Kubernetes 上的无状态容器将能够处理数十万次请求,因为容器本身具备扩展能力。
另一种方式是将传入的请求按批处理。假设有数十万条标记数据记录,并且你想测试这些记录上模型的表现。在这种情况下,单独进行 REST API 调用可能并不是正确的做法。相反,批量推理提供了一种异步的方法,可以为数百万条记录进行预测。Seldon 具备对数据批量推理的能力,但这超出了本书的范围。
你为航班延误预测所暴露的 REST API 可以被网页应用程序利用,从而进一步提升客户体验。
可重现性
现在,你已经知道了一个机器学习生命周期的样子,以及平台如何在你每一步旅程中提供帮助。作为个人,你可能能够在一个单一的笔记本中编写数据管道和模型训练与调优的每个步骤。然而,在团队中,可能会有人负责生命周期的不同部分,这样做可能会造成问题。假设某人想运行模型训练部分,但整个过程彼此相互关联。使用这种方法,团队可能无法扩展。
一种更好且更具可扩展性的方法是,为项目生命周期的各个阶段(如数据处理和模型训练)编写不同的笔记本,并使用工作流引擎将它们关联起来。使用 Kubernetes 平台,所有阶段将通过容器执行,并为您的项目在不同运行之间提供一致的环境。该平台提供了 Airflow,这是一个可以用于创建和执行工作流的引擎。
总结
在本章简短的内容中,我们希望回顾并向您展示平台和模型生命周期的整体图景。我们鼓励您参考第二章,理解 MLOps,在该章中我们展示了一个典型的机器学习生命周期,进行更为详细的讨论。请回想一下跨多个团队合作的重要性,以及在理解可用数据上投入更多时间将如何导致一个能够提供预期商业价值的模型。
现在您已经了解了项目的各个阶段将如何进行。在接下来的两章中,您将使用本书中展示的机器学习平台实现航班延误预测服务,并执行我们在本章中描述的每一个阶段。我们的目的是向您展示平台如何满足项目的每个阶段,以及您如何在组织中实施该平台。
第八章:第九章:构建您的数据管道
在上一章中,您了解了通过推荐具有更高准时率的航班来改善用户体验的示例业务目标。您已经与业务 领域专家(SME)合作,了解了可用数据。在本章中,您将看到平台如何帮助您从各种来源收集和处理数据。您将看到如何按需创建 Spark 集群,以及如何使用平台在共享环境中隔离工作负载。新的航班数据可能会频繁更新,您将看到平台如何帮助您自动执行数据管道。
本章中,您将学习以下内容:
-
自动化配置用于开发的 Spark 集群
-
编写 Spark 数据管道
-
使用 Spark UI 监控您的任务
-
使用 Airflow 构建并执行数据管道
技术要求
本章包括一些动手设置和练习。您将需要一个已启动的 Kubernetes 集群,并且已配置 Operator Lifecycle Manager(OLM)。构建此类 Kubernetes 环境的过程可以参考 第三章,探索 Kubernetes。在进行本章的技术练习之前,请确保您拥有一个可工作的 Kubernetes 集群,并且 Open Data Hub(ODH)已安装在您的 Kubernetes 集群上。安装 ODH 的过程可以参考 第四章,机器学习平台的构成。
自动化配置用于开发的 Spark 集群
在本节中,您将学习平台如何使您的团队按需配置 Apache Spark 集群。这种按需配置新的 Apache Spark 集群的能力使得您的组织能够在共享的 Kubernetes 集群上运行多个由多个团队使用的隔离项目,而不会发生重叠。
该组件的核心是平台中可用的 Spark 操作符。Spark Kubernetes 操作符允许您声明性地启动 Spark 集群。您可以在本书的 Git 仓库中的 manifests/radanalyticsio 文件夹找到必要的配置文件。该操作符的详细内容超出了本书的范围,但我们会向您展示该机制是如何工作的。
Spark 操作符定义了一个 Kubernetes 自定义资源定义(CRD),它提供了您可以向 Spark 操作符发出的请求的架构。在此架构中,您可以定义许多内容,例如集群的工作节点数量以及分配给集群主节点和工作节点的资源。
通过此文件,您可以定义以下选项。请注意,这不是一个详尽无遗的列表。完整列表请参阅该开源项目的文档:github.com/radanalyticsio/spark-operator:
-
customImage部分定义了提供 Spark 软件的容器名称。 -
master部分定义了 Spark 主实例的数量以及分配给主 Pod 的资源。 -
worker部分定义了 Spark 工作实例的数量以及分配给工作 Pod 的资源。 -
sparkConfiguration部分使你能够添加任何特定的 Spark 配置,例如广播连接的阈值。 -
env部分使你能够添加 Spark 所使用的变量,例如SPARK_WORKER_CORES。 -
sparkWebUI部分启用标志,并指示操作员为 Spark UI 创建一个 Kubernetes Ingress。在接下来的部分中,你将使用这个 UI 来调查你的 Spark 代码。
你可以在 manifests/radanalyticsio/spark/cluster/base/simple-cluster.yaml 找到其中一个文件,并且它在以下截图中展示。图 9.1 显示了 simple-cluster.yaml 文件的一个部分:

图 9.1 – Spark 操作符使用的简单 Spark 自定义资源
现在,你已经了解了在平台上配置 Spark 集群的基本过程。然而,在接下来的章节中,你会看到,当你选择Elyra Notebook Image with Spark笔记本镜像时,Spark 集群会自动为你配置。这是因为在平台中,JupyterHub 被配置为在你选择特定笔记本时提交 Spark 集群自定义资源(CR)。此配置通过两个文件提供。
第一个文件是 manifests/jupyterhub/jupyterhub/overlays/spark3/jupyterhub-singleusers-profiles-configmap.yaml,它定义了一个名为 Spark Notebook 的配置文件。在这一部分中,平台会在 images 键下配置容器镜像的名称,因此每当 JupyterHub 启动该镜像的新实例时,它会应用这些设置。configuration 和 resources 部分指向将在该镜像实例创建时一起创建的资源。图 9.2 显示了 jupyterhub-singleusers-profiles-configmap.yaml 文件的一个部分:

图 9.2 – jupyterhub-singleusers-profiles-configmap.yaml 文件的一个部分
注意,resources 中有一个属性值为 sparkClusterTemplate,这将引出我们第二个文件的内容。
第二个文件 manifests/jupyterhub/jupyterhub/base/jupyterhub-spark-operator-configmap.yaml 包含 sparkClusterTemplate,它定义了 Spark CR。请注意,jupyterhub-singleusers-profiles-configmap.yaml 文件中可用的参数将在此处使用。图 9.3 显示了 jupyterhub-spark-operator-configmap.yaml 文件的一个部分:

图 9.3 – jupyterhub-spark-operator-configmap.yaml 文件的一个部分
在本节中,你已经看到平台如何将不同组件连接在一起,使你的团队和组织的工作变得更轻松,你可以根据需要更改和配置这些组件,这就是开源软件的真正力量。
让我们编写一个数据管道来处理我们的航班数据。
编写一个 Spark 数据管道
在本节中,你将构建一个真实的数据管道,用于收集和处理数据集。处理的目标是格式化、清理和转换数据,使其成为可用于模型训练的状态。在编写数据管道之前,我们首先来了解数据。
准备环境
为了执行以下练习,我们首先需要设置一些内容。你需要设置一个 PostgreSQL 数据库来存储历史航班数据。并且你需要将文件上传到 MinIO 的 S3 桶中。我们同时使用关系型数据库和 S3 桶,以更好地展示如何从不同的数据源收集数据。
我们已准备好一个 Postgres 数据库容器镜像,你可以在 Kubernetes 集群上运行。该容器镜像可以在quay.io/repository/ml-on-k8s/flights-data获取。它运行一个 PostgreSQL 数据库,并在名为flights的表中预加载了航班数据。
按照以下步骤运行此容器,验证数据库表,并将 CSV 文件上传到 MinIO:
-
在与 minikube 运行的机器上运行以下命令以启动 Postgres 数据库容器:
kubectl create -f chapter9/deployment-pg-flights-data.yaml -n ml-workshop
你应该看到一条消息,告诉你deployment对象已被创建。
-
通过运行以下命令,通过服务公开此部署的 Pods:
kubectl create -f chapter9/service-pg-flights-data.yaml -n ml-workshop
你应该看到一条消息,表示服务对象已创建。
-
探索数据库的内容。你可以通过进入 Pod,运行 Postgres 客户端
psql并执行 SQL 脚本来实现。执行以下命令以连接到 Postgres Pod 并运行 Postgres 客户端界面:POD_NAME=$(kubectl get pods -n ml-workshop –l app=pg-flights-data) -
连接到 Pod。你可以通过执行以下命令来实现:
kubectl exec -it $POD_NAME -n ml-workshop -- bash -
运行 Postgres 客户端 CLI,
psql,并验证表格。运行以下命令从命令行登录到 Postgres 数据库:psql –U postgres
这将运行客户端 CLI 并连接到默认数据库。
-
验证表格是否存在。应该有一个名为
flights的表。运行以下命令从psqlshell 验证表格的正确性:select count(1) from flights;
这应该给你提供flights表中的记录数量,超过 580 万,如图 9.4所示:

图 9.4 – 来自航班表的记录数量
-
将其余的数据上传到 MinIO 的 S3 桶中。打开与 minikube 运行的机器相同的浏览器窗口,并导航到。使用用户名
minio和密码minio123。记得将<minikube_ip>替换为你的 minikube 实例的 IP 地址。 -
导航到
airport-data并点击创建存储桶按钮,如图 9.5所示:

图 9.5 – MinIO 创建存储桶对话框
- 在存储桶内,从
chapter9/data/文件夹上传两个 CSV 文件到airport-data存储桶,如图 9.6所示:

图 9.6 – 机场和航空公司数据文件
在现实世界中,你不需要执行上述步骤。数据源应该已经存在,你只需要知道在哪里获取它们。然而,为了进行接下来的练习,我们必须将数据加载到环境中,以便在后续步骤中使用。
现在你已经将数据加载到平台上。让我们进一步探索和理解这些数据。
理解数据
理解数据包括以下活动。理解所有涉及的数据集的特性非常重要,以便制定管道策略和设计:
-
知道数据将从哪里收集。数据可能来自多种来源。它可能来自关系数据库、对象存储、NoSQL 数据库、图形数据库、数据流、S3 存储桶、HDFS、文件系统或 FTP。掌握这些信息后,你将能够准备数据管道所需的连接。在你的案例中,你需要从 PostgreSQL 数据库和 S3 存储桶中收集数据。
-
理解数据的格式。数据可以有多种形式和结构。无论是 CSV 文件、SQL 表、Kafka 流、MQ 流、Parquet 文件、Avro 文件,还是 Excel 文件,你都需要具备能够读取这种格式的工具。理解数据格式有助于你准备读取这些数据集所需的工具或库。
-
清理不重要或无关的数据。了解哪些数据是重要的,哪些是无关的,有助于你更高效地设计管道。例如,如果你有一个包含
airline_name和airline_id字段的数据集,你可能希望在最终输出中去掉airline_name,只保留airline_id。这意味着减少一个字段的编码,从而提高模型训练的性能。 -
理解不同数据集之间的关系。识别标识符字段或主键,理解连接键和聚合级别。你需要了解这些,以便你可以将数据结构扁平化,使数据科学家更容易使用你的数据集。
-
知道处理后的数据存储在哪里。你需要知道将处理后的数据写入哪里,以便准备连接要求并理解接口。
鉴于前述活动,你需要一种方法来访问和探索数据源。接下来的章节将展示如何在 Jupyter 笔记本中读取数据库表。
从数据库读取数据
使用 Jupyter 笔记本,我们来查看数据。请按照以下步骤开始数据探索,从读取 PostgreSQL 数据库中的数据开始。
整个数据探索笔记本可以在本书的 Git 仓库中找到,路径为chapter9/explore_data.ipynb。我们建议您使用此笔记本进行额外的数据探索。可以通过简单地显示字段、统计某一列中相同值的出现次数,以及寻找数据源之间的关系来进行探索:
- 通过访问
https://jupyterhub.<minikube_ip>.nip.io来启动 Jupyter 笔记本。如果系统提示登录凭据,您需要使用已创建的 Keycloak 用户进行登录。用户名是mluser,密码是mluser。启动Elyra Notebook Image with Spark笔记本,如图 9.7所示。由于我们将读取一个包含 580 万条记录的大型数据集,因此请选择Large容器大小。确保您的环境中有足够的容量来运行大型容器。如果容量不足,请尝试使用中型容器。

图 9.7 – JupyterHub 启动页面
- 创建一个 Python 3 笔记本。您将使用此笔记本来探索数据。可以通过选择文件 | 新建 | 笔记本菜单选项来创建。然后,选择Python 3作为内核,如图 9.8所示:

图 9.8 – Elyra 笔记本的内核选择对话框
- 您可以通过查看数据库中的
flights表格来开始。访问数据库的最基本方式是通过 PostgreSQL Python 客户端库。在练习中使用psycopg2。您也可以选择其他客户端库来连接 PostgreSQL 数据库。图 9.9中的代码片段是最基本的示例:

图 9.9 – 使用 psycopg2 连接 PostgreSQL 的基本示例
- 另一种更优雅的访问数据的方式是通过pandas或PySpark。pandas 和 PySpark 都允许您通过数据框架利用函数式编程方法访问数据,而不是像步骤 3中那样的过程式方法。pandas 和 Spark 之间的区别在于,Spark 查询可以通过分布式方式执行,利用多个机器或 Pods 来执行查询。这对于大数据集来说非常理想。然而,pandas 提供的可视化效果比 Spark 更具美观性,因此 pandas 更适合探索小型数据集。图 9.10展示了如何通过 pandas 访问数据库的代码片段:

图 9.10 – 使用 pandas 连接 PostgreSQL 的基本示例
- 如果你需要转换一个巨大的数据集,PySpark 将是理想的选择。例如,假设你需要转换并聚合一个有 1 亿条记录的表。你需要将这项工作分配给多台机器,以便更快地得到结果。这时 Spark 发挥了重要作用。图 9.11中的代码片段展示了如何通过 PySpark 读取 PostgreSQL 表:

图 9.11 – 通过 PySpark 读取 PostgreSQL 表
由于 Spark 的分布式架构,你在从任何关系型数据库读取表时,需要提供分区信息,特别是分区的数量和分区列。当你提供分区信息时,每个分区将在 Spark 的术语中成为一个任务,每个任务可以由单个 CPU 核心独立执行。如果没有提供分区信息,Spark 将尝试将整个表当作一个单一分区来处理。你不希望这样做,因为该表有 580 万个记录,可能无法在单个 Spark 工作节点的内存中容纳。
你还需要提供一些关于 Spark 集群的信息,比如主节点的 URL 以及运行 Spark 应用所需的包。在图 9.12的示例中,我们包括了org.postgresql:postgresql:42.3.3包。这个包是 Spark 连接数据库所需的 PostgreSQL JDBC 驱动程序。Spark 将在应用启动时自动从 Maven 下载此包。
从 S3 桶中读取数据
现在你已经了解了从 Jupyter 笔记本访问 PostgreSQL 数据库的不同方式,让我们来探索其余的数据。虽然数据库中的flights表包含航班信息,但我们也有作为 CSV 文件提供的airport和airline信息,这些文件托管在 MinIO 的 S3 桶中。
Spark 可以通过hadoop-aws库与任何 S3 服务器进行通信。图 9.12展示了如何从笔记本使用 Spark 访问 S3 桶中的 CSV 文件:

图 9.12 – 从笔记本中读取 S3 桶的 Spark 代码
请注意,我们添加了几个 Spark 提交参数。这是为了告诉 Spark 引擎 S3 服务器在哪里以及使用哪个驱动程序库。
在你探索完数据集后,你应该已经了解了以下关于数据的事实:
-
flights表包含 5,819,079 条记录。
-
airports.csv文件中有 322 个机场。 -
airlines.csv文件中有 22 家航空公司。 -
机场和航空公司之间没有直接的关系。
-
flights表使用来自airportCSV 文件中的IATA_CODE机场信息作为某个航班的起始和目的机场。 -
flights表使用来自airlinesCSV 文件中的IATA_CODE航空公司信息来指示某个航班由哪家航空公司提供服务。 -
所有的机场都位于美国。这意味着“国家”列对机器学习(ML)训练来说是无用的。
-
flights表有SCHEDULED_DEPARTURE、DEPARTURE_TIME和DEPARTURE_DELAY字段,这些字段可以用来判断航班是否延误,并且我们可以用它们生成一个label列来进行 ML 训练。
基于这些事实,我们可以说,我们可以使用机场和航空公司数据,为原始flights数据添加额外的机场和航空公司信息。这个过程通常称为增强,可以通过数据框连接来实现。我们还可以利用行数信息来优化我们的 Spark 代码。
现在你已经理解了数据,可以开始设计和构建你的数据管道了。
设计和构建数据管道
理解数据是一回事,设计管道是另一回事。从你在前面部分探索的数据中,你学到了几个事实。我们将基于这些事实来决定如何构建我们的数据管道。
目标是生成一个包含所有可能对 ML 训练有用的关键信息的单一扁平数据集。我们说“所有关键信息”,因为在实际进行 ML 训练之前,我们无法确定哪些字段或特征是重要的。作为数据工程师,你可以根据对数据的理解,并借助领域专家(SME)的帮助,做出一个有根据的猜测,来判断哪些字段重要,哪些不重要。在 ML 生命周期中,数据科学家可能会回头找你,要求增加更多字段、删除某些字段或对数据进行某些转换。
以生成单一数据集为目标,我们需要用机场和航空公司数据来增强航班数据。为了将原始航班数据与机场和航空公司数据结合,我们需要执行数据框的join操作。我们还需要注意,航班数据有数百万条记录,而机场和航空公司数据不到 50 条。我们可以利用这些信息来优化 Spark 的join算法。
为数据框连接准备笔记本
首先,创建一个新的笔记本来执行连接操作,然后将这个笔记本作为管道的一个阶段。接下来的步骤将展示如何操作:
-
创建一个新的笔记本,命名为
merge_data.ipynb。 -
使用 Spark 从 Postgres 和 S3 桶中获取数据。运用你在前面部分学到的知识。图 9.13展示了笔记本中数据读取的部分。我们还提供了一个实用的 Python 文件,
chapter9/spark_util.py,它封装了 Spark 上下文的创建,使你的笔记本更加易读。图 9.13中的代码片段展示了如何使用这个工具:

图 9.13 – 准备数据框的 Spark 代码
注意这里有一个新的import语句,用于broadcast()。你将在下一步中使用这个函数进行优化。
- 在 Spark 中执行数据框连接,如图 9.14所示。你需要连接在步骤 2中准备的所有三个数据框。从我们在前一节中的理解,机场和航空公司数据应该通过
IATA_CODE作为主键进行合并。但首先,让我们先连接航空公司数据。注意连接后的结果模式;与原始模式相比,底部多了两个额外的列。这些新列来自airlines.csv文件:

图 9.14 – Spark 代码实现基本数据框连接
- 连接机场数据稍微有点复杂,因为你必须进行两次连接:一次连接到
origin_airport,另一次连接到destination_airport。如果我们按照步骤 3中的相同方法进行操作,连接将成功,并且列会被添加到模式中。问题是,很难判断哪些机场字段代表目的地机场,哪些字段代表出发地机场。图 9.15显示了字段名是如何重复的:

图 9.15 – 连接后重复的列
- 最简单的解决方法是创建带有前缀字段名的新数据框(
ORIG_用于出发机场,DEST_用于目的地机场)。你也可以对航空公司字段做同样的操作。图 9.16展示了如何实现:

图 9.16 – 向字段名添加前缀
- 将
df_airports数据框替换为df_o_airports和df_d_airports,如图 9.17所示。现在,你得到了一个更易读的数据框:

图 9.17 – 带有前缀数据框的更新连接语句
在join语句中有一点需要注意,就是broadcast()函数。在前一节中,我们讨论了了解数据集大小的重要性,这样你可以优化代码。broadcast()函数给 Spark 引擎一个提示,告诉它该数据框应该被广播,并且join操作必须使用广播join算法。这意味着,在执行之前,Spark 会将df_airlines、df_o_airports和df_d_airports数据框的副本分发给每个 Spark 执行器,以便它们可以与每个分区的记录进行连接。为了使广播join有效,你需要选择较小的数据框进行广播。如果你想了解更多信息,请参考 Spark 的性能调优文档,网址:spark.apache.org/docs/latest/sql-performance-tuning.html。
你刚刚学习了如何使用 PySpark 连接数据框。由于 PySpark 语句是惰性求值的,因此 join 操作的实际执行尚未发生。这就是为什么 printSchema() 执行速度很快的原因。Spark 只会在实际需要数据时执行处理。一个典型的场景就是当你将实际数据持久化到存储中时。
持久化数据框
要获取连接结果,你需要将数据框转化为物理数据。你将把数据框写入 S3 存储,以便数据管道的下一个阶段可以读取它。图 9.18 显示了一个代码片段,该代码片段将连接后的航班数据框写入 MinIO 中的 CSV 文件:

图 9.18 – 将数据框写入 S3 存储桶
执行此操作需要一些时间,因为这是处理 580 万条记录的实际过程所在。此操作正在运行时,你可以查看 Spark 集群中发生的情况。当你启动笔记本时,它在 Kubernetes 中创建了一个 Spark 集群,并将用户 mluser 分配给你。Spark GUI 在 https://spark-cluster-mluser.<minikube_ip>.nip.io 上公开。请访问此网址以监控 Spark 应用程序并检查应用程序作业的状态。你应该会看到一个名为 Enrich flights data 的正在运行的应用程序。点击该应用程序名称,将带你到一个更详细的视图,显示正在处理的作业,如 图 9.19 所示:

图 9.19 – Spark 应用程序 UI
图 9.19 显示了来自数据库的 flights 数据的详细信息,包括列重命名、数据框连接以及将输出写入 S3 存储桶。这对于数据框的每个分区执行,表示按“日”划分的 flights 数据框,总共有 31 个分区。Spark 还创建了 31 个并行处理任务。每个任务都被安排在 Spark 执行器 上运行。在 图 9.19 中,详细信息显示,在过去 1.2 分钟的处理时间里,31 个任务中有 13 个成功完成,当前有四个任务正在运行。
你也可能会在某些情况下发现任务失败。Spark 会自动将失败的任务重新调度到另一个执行器。默认情况下,如果同一任务连续失败四次,整个应用程序将被终止并标记为失败。任务失败有多种原因,其中一些包括网络中断或资源拥塞,如内存不足异常或超时。这就是为什么理解数据非常重要,因为你可以调整分区逻辑。这里有一个基本规则需要注意:分区数量越多,分区大小越小。较小的分区大小会减少内存溢出异常的可能性,但也会增加调度时的 CPU 开销。Spark 的机制比这要复杂得多,但理解分区、任务、作业和执行器之间的关系是个不错的起点。
数据工程的工作中,实际上近一半的时间都花费在优化数据管道上。优化 Spark 应用程序有很多技术,包括代码优化、分区和执行器大小调整。我们在本书中不会详细讨论这个话题。不过,如果你想了解更多相关内容,可以随时参考 Spark 的性能调优文档。

图 9.20 – 包含 Parquet 文件的 S3 存储桶
在 Spark 应用程序完成后,数据应以多个文件的形式写入 S3,每个文件表示一个分区,格式为 Parquet,如 图 9.20 所示。Parquet 文件格式是一种列式数据格式,意味着数据是按列组织的,而不是像传统的 CSV 文件那样按行组织。Parquet 的主要优势是你可以选择性地读取需要的列,而无需扫描整个数据集。这使得 Parquet 非常适合分析、报告以及数据清理,这也是你接下来需要做的。
你可以在本书的 Git 仓库中的 chapter9 文件夹里找到完整的 merge_data.ipynb 笔记本。然而,我们强烈建议你从头开始创建自己的笔记本,以最大化学习体验。
清理数据集
现在你已经拥有了一个扁平化并且丰富的 flights 数据集。下一步是清理数据,删除不需要的字段,丢弃不需要的行,同化字段值,推导新字段,并且可能还需要转换某些字段。
首先,创建一个新的笔记本,使用这个笔记本读取我们生成的 Parquet 文件,并将其写成清理后的数据集。以下步骤将带你完成这个过程:
-
创建一个新的笔记本,命名为
clean_data.ipynb。 -
从
flights-data/flightsS3 桶中加载flights数据的 Parquet 文件,如图 9.21所示。验证模式和行数。行数应该略少于原始数据集。这是因为在之前的join操作中,使用的是内连接,而原始flights数据中存在没有机场或航空公司引用的记录。

图 9.21 – 从 S3 读取 Parquet 数据
- 删除不需要或重复的字段,去除整个数据集中值相同的字段,并创建一个派生的布尔字段
DELAYED,对于延迟的航班值为1,对于未延迟的航班值为0。假设只有当航班延迟超过 15 分钟时,我们才认为它是延迟的。你可以根据需求随时更改这一点。让我们慢慢来,首先删除不需要的列,如图 9.22所示:

图 9.22 – 删除不需要的列
我们不需要AI_IATA_CODE、ORIG_IATA_CODE和DEST_IATA_CODE,因为它们分别与airline、origin_airport和destination_airport列相同。
- 查找数据集中所有值相同的列是一个昂贵的操作。这意味着你需要统计每列在 500 万条记录中的不同值。幸运的是,Spark 提供了
approx_count_distinct()函数,它的运行速度非常快。图 9.23中的代码片段展示了如何找到值相同的列:

图 9.23 – 删除所有行中值相同的列
- 最后,创建
label字段来判断航班是否延迟。数据科学家可以将此字段作为训练的标签。然而,数据科学家也可以根据选择的算法使用类似的范围,比如departure_delay。因此,我们将departure_delay字段与基于 15 分钟延迟阈值的新布尔字段一起保留。我们将这个新字段称为DELAYED:

图 9.24 – 创建 DELAYED 列
图 9.24展示了创建派生列的代码片段。通过运行一个简单的查询并使用show()函数来测试列创建逻辑。
- 现在,将物理数据写入同一 S3 桶中的
flights-clean路径。我们还希望将输出以 Parquet 格式写入(见图 9.25):

图 9.25 – 将最终数据框写入 S3
作为数据工程师,你需要与数据科学家达成一致关于输出格式。一些数据科学家可能希望得到一个巨大的 CSV 文件数据集,而不是多个 Parquet 文件。在我们的案例中,假设数据科学家更倾向于读取多个 Parquet 文件。
- 第 6 步 可能会花费相当长的时间。你可以访问 Spark UI 来监控应用程序的执行情况。
你可以在本书的 Git 仓库中的 chapter9 文件夹下找到完整的 clean_data.ipynb 笔记本。但是,我们强烈建议你从头开始创建自己的笔记本,以最大化学习体验。
使用 Spark UI 监控数据管道
在运行 Spark 应用程序时,你可能想深入了解 Spark 实际在做什么,以便优化你的管道。Spark UI 提供了非常有用的信息。主节点的着陆页显示了工作节点和应用程序的列表,如 图 9.26 所示:

图 9.26 – Spark 集群着陆页
着陆页还显示了历史应用程序运行记录。你可以通过点击某个已完成的应用程序 ID 查看已完成应用程序的一些详细信息。然而,在监控应用程序时,我们更关注正在运行的应用程序。让我们进一步了解 UI 中的信息。
探索 workers 页面
Worker 是 Spark 集群中的机器。它们的主要责任是运行执行器。在我们的案例中,工作节点 是带有工作 Java 虚拟机 (JVM) 的 Kubernetes Pods。每个 Worker 可以托管一个或多个执行器。然而,在 Kubernetes 上运行 Spark Worker 时,这并不是一个好主意,因此你应该配置执行器,使得每个 Worker 只运行一个执行器:

图 9.27 – Spark Worker 视图
点击 UI 中的一个 worker 将带你到 worker UI,在那里你可以看到该 worker 已经运行或正在运行的所有执行器。你还可以看到哪些应用程序拥有这些执行器。你可以看到分配给它的 CPU 或内存量,甚至可以查看每个执行器的日志。
探索 Executors 页面
执行器是运行在工作节点中的进程。它们的主要责任是执行任务。执行器实际上就是在工作节点上运行的 Java 或 JVM 进程。工作 JVM 进程管理同一主机内的执行器实例。访问 http://spark-cluster-mluser.<minikube_ip>.nip.io/proxy/<application_id>/executors/ 将带你到 Executors 页面,该页面将列出当前应用程序的所有执行器,如 图 9.28 所示:

图 9.28 – Spark Executors 页面
在这一页,你会找到对微调和优化应用程序非常重要的有用指标。例如,你可以看到资源使用情况、垃圾回收时间和数据交换(Shuffles)。Shuffles 是在多个执行器之间交换数据,通常会在你执行聚合函数时发生。你希望将其保持在尽可能小的范围内。
探索应用程序页面
Spark 中的应用程序是拥有 Spark 上下文的任何进程。它可以是一个正在运行的 Java、Scala 或 Python 应用程序,这些应用程序创建了一个 Spark 会话或 Spark 上下文,并将其提交给 Spark 主 URL。应用程序不一定要在 Spark 集群中运行,它可以位于网络的任何地方,只要它能连接到 Spark 主节点。然而,还有一种模式,其中应用程序(也称为驱动程序应用程序)在 Spark 执行器之一内执行。在我们的例子中,驱动程序应用程序是运行在 Spark 集群外的 Jupyter 笔记本。这就是为什么在图 9.28中你看到的是一个执行器,称为driver,而不是实际的执行器 ID。
从着陆页面点击正在运行的应用程序名称会带你进入应用程序 UI 页面。该页面显示属于当前应用程序的所有作业。作业是修改数据框的操作。每个作业由一个或多个任务组成。任务是操作与数据框分区的配对。这是分配给执行器的工作单元。在计算机科学中,这相当于一个闭包。这些任务作为二进制文件通过网络传输到工作节点,供执行器执行。图 9.29展示了应用程序 UI 页面:

图 9.29 – Spark 应用程序 UI
在图 9.29中的示例中,你可以看到活跃作业5有五个任务,其中四个任务正在运行。任务级别的并行度取决于分配给应用程序的 CPU 核心数。你还可以进一步深入查看特定的作业。如果你访问 http://spark-cluster-mluser.<minikube_ip>.nip.io/proxy/<application_id>/jobs/job/?id=<job_id>,你应该能看到该作业的各个阶段以及每个阶段的 DAG(有向无环图)。

图 9.30 – Spark 作业详细页面
Spark 的图形用户界面在执行诊断和微调复杂的数据处理应用程序时非常有用。Spark 也有很好的文档,我们建议你访问以下链接查看 Spark 的文档:spark.apache.org/docs/3.0.0。
现在,你已经创建了一个用于丰富flights数据的笔记本,以及另一个用于清理数据集以为机器学习项目生命周期的下一个阶段做准备的笔记本,让我们看看如何自动化这些笔记本的执行。
使用 Airflow 构建并执行数据管道
在上一部分中,您已经构建了数据管道以摄取和处理数据。假设新的flights数据每周更新一次,您需要反复处理这些新数据。一个方法是手动运行数据管道;然而,随着数据管道数量的增长,这种方法可能无法扩展。数据工程师的时间应该更多地用在编写新的管道,而不是反复运行旧的管道。第二个问题是安全性。您可能是基于样本数据编写的数据管道,而您的团队可能没有访问生产数据的权限来执行该数据管道。
自动化提供了解决这两个问题的方案。您可以按需调度数据管道的运行,而数据工程师则可以从事更有趣的工作。您的自动化管道可以连接到生产数据,而无需开发团队的参与,从而提高安全性。
机器学习平台包含 Airflow,可以自动化数据管道的执行和调度。请参考第七章,模型部署与自动化,了解 Airflow 简介以及可视化编辑器如何帮助数据工程师从相同的 IDE 中构建数据管道,正是他们用来编写数据管道的工具。集成提供了数据工程团队可以自助独立工作的能力,从而进一步提高了团队的效率。
在下一部分中,您将自动化上一部分中构建的项目数据管道。
理解数据管道 DAG
首先让我们了解执行您已构建的数据管道的过程。一旦您拥有了正确的信息,自动化过程就会变得简单。
当您开始在 JupyterHub 中编写数据管道时,您从 JupyterHub 登陆页面开始使用带 Spark 的 Elyra Notebook 镜像。在笔记本中,您连接到 Apache Spark 集群并开始编写数据管道。机器学习平台知道对于带 Spark 的 Elyra Notebook 镜像,它需要启动一个新的 Spark 集群,以便在笔记本中使用。一旦完成工作,您可以关闭 Jupyter 环境,这将导致机器学习平台关闭 Apache Spark 集群。
以下是执行flights数据管道过程中的三个主要阶段:
-
启动 Spark 集群。
-
运行数据管道笔记本。
-
停止 Spark 集群。
图 9.31 显示了 DAG 的各个阶段:

图 9.31 – 用于航班项目的 Airflow DAG
这些阶段中的每一个都将由 Airflow 作为独立的步骤执行。Airflow 启动一个 Kubernetes Pod 来运行这些阶段中的每一个,同时您提供运行每个阶段所需的 Pod 镜像。Pod 运行 Airflow 管道中为该阶段定义的代码。
让我们来看一下 DAG 中每个阶段的职责。
启动 Spark 集群
在这个阶段,一个新的 Spark 集群将被配置。这个集群将专门用于运行一个 Airflow DAG。自动化的作用是向 Kubernetes 提交一个新 Spark 集群的请求作为 CR。然后,Spark 操作符将提供该集群,可用于 DAG 中的下一个步骤。
一旦 Airflow 引擎提交了创建 Spark 集群的请求,它将进入运行第二阶段。
运行数据管道
在这个阶段,你之前在本章编写的笔记本(merge_data 和 clean_data)将通过 Airflow DAG 执行。回顾 第七章,模型部署与自动化,Airflow 使用不同的操作符来运行自动化管道的各个阶段(请注意,Airflow 操作符与 Kubernetes 操作符不同)。Airflow 提供了一个笔记本操作符来运行 Jupyter 笔记本。
自动化的作用是通过笔记本操作符运行你的数据管道笔记本。当数据管道完成执行你的代码后,Airflow 引擎将进入下一个阶段。
停止 Spark 集群
在这个阶段,一个 Spark 集群将被销毁。自动化的作用是删除在 DAG 的第一阶段创建的 Spark 集群 CR。然后,Spark 操作符将终止用于执行前一个阶段数据管道的集群。
接下来是定义容器镜像,这些镜像将被 Airflow 用来执行每个阶段的任务。
注册容器镜像以执行你的 DAG
你刚刚构建了用于运行数据管道的自动化 DAG,并且该 DAG 的每个阶段都将通过为每个阶段运行一个独立的 Pod 来执行:
- 要注册容器镜像,首先打开 JupyterHub IDE,点击左侧菜单栏中的 Runtime Images 选项。你将看到以下屏幕:

图 9.32 – 在你的 JupyterHub IDE 中注册容器运行时镜像
- 点击右上角的 + 图标注册一个新容器。你将看到以下屏幕:

图 9.33 – 在你的 JupyterHub IDE 中注册容器运行时镜像的详细信息
对于 flights 数据管道 DAG,你将需要以下两个容器:
-
第一个容器镜像将使 Airflow 能够运行 Python 代码。填写屏幕(见 图 9.33)中的以下细节,并点击标有
AirFlow Python Runner的按钮 -
一个带 Python 运行时的容器 -
quay.io/ml-on-k8s/airflow-python-runner:0.0.11 -
镜像拉取策略: IfNotPresent
-
第二个容器镜像将使 Airflow 运行数据管道笔记本。请按如下所示填写 图 9.33 中显示的屏幕,并点击标有 保存并关闭 的按钮:
-
AirFlow PySpark Runner -
一个包含笔记本和 pyspark 的容器,用于启用 PySpark 代码的执行 -
quay.io/ml-on-k8s/elyra-spark:0.0.4 -
镜像拉取策略:IfNotPresent
在下一节中,您将使用 Airflow 构建并执行这三个阶段。
构建并运行 DAG
在本节中,您将使用 ML 平台构建并部署 DAG。您将首先使用拖放编辑器构建 DAG,然后修改生成的代码以进一步自定义 DAG。
使用可视化编辑器构建 Airflow DAG
在本节中,您将为数据处理流程构建 DAG。您将看到 JupyterHub 如何通过拖放功能帮助您构建 DAG:
-
从登录 JupyterHub 开始。
-
通过选择 文件 | 新建 | PipelineEditor 菜单选项创建一个新管道。您将获得一个新的空管道:

图 9.34 – 一个空的 Airflow DAG
- 如前面的截图所示,您可以从编辑器左侧的文件浏览器中开始拖动所需的文件到您的管道中。对于我们的
flightsDAG,第一步是启动一个新的 Spark 集群。您将在浏览器中看到一个名为pipeline-helpers/start-spark-cluster的文件。将其从浏览器中拖动并放入您的管道中:

图 9.35 – 使用拖放构建 DAG 阶段
-
通过添加所需的文件来完成您的管道。
flights数据的完整 DAG 在下一步中提供。 -
我们已经为您添加了一个预构建的示例,您可以用它作为参考。请前往名为
Chapter 9/的文件夹,打开flights.pipeline文件。您将看到有三个阶段用于处理flights数据:

图 9.36 – JupyterHub IDE 中的 DAG 视图
- 点击 DAG 中名为 start-spark-cluster 的第一个元素。右键点击此元素并选择 属性:

图 9.37 – 选择 DAG 中第一个阶段的属性
- 在右侧窗口中,您可以看到此阶段的属性:

图 9.38 – start-spark.py 阶段的属性
以下列表描述了每个属性:
-
start-spark-cluster.py文件将在此阶段由 Airflow 执行。 -
运行时镜像部分定义了将在上一步中提到的文件执行时使用的镜像。这是你在之前部分注册的容器镜像。对于 Python 阶段,你将使用 AirFlow Python Runner 容器镜像。
-
spark-cluster.yaml定义了 Spark 集群的配置。spark_util.py文件是我们创建的辅助工具文件,用于与 Spark 集群进行通信。请注意,DAG 中与此阶段相关的文件将被打包在 DAG 中,并在 Airflow 执行该阶段时可供使用。所有这些文件都可以在仓库中找到。 -
在这种情况下,
start-spark-cluster.py将访问这些环境变量。可以将这些变量视为用于管理文件行为的配置。例如,SPARK_CLUSTER变量用于命名创建的 Spark 集群。WORKER_NODES定义了将作为 Spark 工作节点创建的工作 Pod 数量。因此,对于较大的作业,你可以选择更改此参数以增加节点数量。打开start-spark-cluster.py文件,你会看到这两个环境变量正被读取。图 9.39 显示了文件:

图 9.39 – start-spark.py 文件读取环境变量
spark_util.py 文件打印了 Spark 集群的位置;可以将其视为集群正在监听的网络名称。其他阶段(例如数据管道笔记本)可以使用此名称连接到 Spark 集群。Airflow 还提供了其他选项,用于在各阶段之间共享数据,你可以探索并决定最适合你用例的选项。
- 单击名为 merge_data.ipynb 的 DAG 第二个元素。右键单击此元素并选择 属性。你会看到,对于此阶段,运行时镜像已更改为 AirFlow PySpark Runner。你会注意到,与此阶段相关的文件是 Jupyter 笔记本文件。这就是这种集成的真正灵活性,它会照常运行你的代码,无论在哪种环境中。

图 9.40 – DAG 中的 Spark 笔记本阶段
将第二个笔记本 clean_data.ipynb 添加为 DAG 的下一个阶段,设置与 merge_data.ipynb 类似。我们将数据管道拆分为多个笔记本,以便于维护和代码管理。
- 此 DAG 的最后一个阶段是停止 Spark 集群。注意,此阶段的 运行时镜像 再次是 AirFlow Python Runner,因为该代码是基于 Python 的。

图 9.41 – stop-spark-cluster.py 阶段的属性
- 如果对
flights.pipeline文件进行了任何更改,请确保保存该文件。
现在,你已完成第一个 DAG。重要的是,作为数据工程师,你自己构建了 DAG,并且你所构建的数据管道代码在管道中得到了实际应用。这种能力将提升工作效率,使你的数据工程团队更加自主和自给自足。
在下一阶段,你将在平台上运行此 DAG。
运行并验证 DAG
在本节中,你将运行前一节中构建的 DAG。我们假设你已经完成了第七章,《模型部署与自动化》一节中的步骤:
- 在 JupyterHub IDE 中加载
flights.pipeline文件并点击运行管道图标。该图标是图标栏上的一个播放按钮。你将看到以下运行管道界面:

图 9.42 – Airflow DAG 提交对话框
为管道命名,选择 MyAirflow。
-
提供信息后,点击确定。
-
你将看到以下界面,验证管道已经成功提交到平台上的 Airflow 引擎:

图 9.43 – Airflow DAG 提交确认
- 打开 Airflow 用户界面。你可以通过
https://airflow.<IP 地址>.nip.io访问该界面。IP 地址是你 minikube 环境的地址。你会看到在 Airflow 图形用户界面中显示了该管道:

图 9.44 – Airflow 图形用户界面中的 DAG 列表
- 点击 DAG,然后点击图形视图链接。你将看到已执行 DAG 的详细信息。这与前一节中构建的图形相同,包含了三个阶段。
请注意,屏幕显示可能会因你的 DAG 执行阶段而有所不同:

图 9.45 – DAG 执行状态
在本节中,你已经看到数据工程师如何构建数据管道(merge_data 笔记本),然后能够使用 Airflow(flights.pipeline)从 JupyterHub IDE 打包并部署它。该平台提供了一个集成解决方案,用于在大规模环境下构建、测试和运行数据管道。
该 IDE 提供了构建 Airflow DAG 的基础。如果你想改变 DAG,使用 Airflow 引擎的高级功能怎么办?在下一节中,你将看到如何修改 IDE 生成的 DAG 代码,以便处理更复杂的用例。
通过编辑代码增强 DAG
你可能注意到你构建的 DAG 只运行了一次。如果你希望它按周期运行怎么办?在本节中,你将通过改变运行频率使 DAG 每天运行一次,从而增强你的 DAG:
- 在 JupyterHub IDE 中打开
flights.pipeline文件。你将看到以下熟悉的界面:

图 9.46 – flights.pipeline 文件
- 点击顶部栏中的 导出管道 图标,您将看到一个导出管道的对话框。点击 确定 按钮:

图 9.47 – 导出管道对话框
- 您将收到一条消息,显示管道导出成功,并且会创建一个新的文件
flights.py。通过从左侧面板中选择该文件打开它。您应该看到生成的 DAG 的完整代码:

图 9.48 – 导出后的 DAG 代码
-
您将看到您的 DAG 代码,使用 Python 编写。从这里,您可以根据需要修改代码。对于本练习,我们要更改 DAG 执行的频率。在代码中找到 DAG 对象;它大约在 第 11 行:
dag = DAG( "flights-0310132300", default_args=args, schedule_interval="@once", start_date=days_ago(1), description="Created with Elyra 2.2.4 pipeline editor using flights.pipeline.", is_paused_upon_creation=False, ) -
更改 DAG 对象的计划。将
schedule_interval="@once"的值更改为schedule_interval="@daily"。 -
更改后,DAG 代码将如下所示:
dag = DAG( "flights-0310132300", default_args=args, schedule_interval="@daily", start_date=days_ago(1), description="Created with Elyra 2.2.4 pipeline editor using flights.pipeline.", is_paused_upon_creation=False, ) -
在 IDE 中保存文件,并将文件推送到您的 DAG 的 Git 仓库。这是您在第七章《模型部署与自动化》中配置的 Git 仓库,在配置 Airflow 时使用的。
-
现在,加载 Airflow GUI,您将能够看到新的 DAG,其中 Schedule 列包含 @daily 标签。这意味着作业将每天运行:

图 9.49 – 显示每日计划的 Airflow DAG 列表
恭喜!您已成功构建数据管道并使用 DAG 自动化了管道的执行。这个抽象的一个重要部分是由平台管理的 Apache Spark 集群的生命周期。由于 IDE、自动化(Airflow)和数据处理引擎(Apache Spark)都由平台管理,您的团队将拥有更高的开发效率。
摘要
呼!这是又一章马拉松式的内容,您已经构建了用于预测航班准时表现的数据处理管道。您已经看到,您所构建的平台使您能够使用 Apache Spark 编写复杂的数据管道,而无需担心 Spark 集群的配置和维护。事实上,您在没有 IT 团队特定帮助的情况下完成了所有练习。您已经使用平台提供的技术自动化了数据管道的执行,并且已经看到从 IDE 中集成了 Airflow 管道,那个您用于编写 Spark 数据管道的同一个 IDE。
记住,本书的主要目的是帮助您提供一个平台,让数据和机器学习团队能够以自服务和独立的方式工作,您刚刚实现了这一目标。您和您的团队拥有数据工程的完整生命周期,并负责安排管道的执行。
在下一章中,你将看到如何将相同的原则应用到数据科学生命周期中,以及团队如何利用这个平台构建和自动化该项目的数据科学组件。
第九章:第十章:构建、部署和监控你的模型
在上一章,你已经构建了数据管道,并创建了一个基础的航班数据集,供你的数据科学团队使用。在本章中,你的数据科学团队将使用该航班数据集来构建 机器学习 (ML) 模型。该模型将用于预测航班的准点表现。
在这一章,你将看到平台如何帮助你可视化和实验数据,以构建正确的模型。你将学习如何调整超参数,并比较不同训练过程的结果。你还将看到如何使用平台提供的组件注册和管理模型版本。你将把模型作为 REST 服务进行部署,并开始使用平台提供的组件来监控已部署的模型。
请记住,本书并不是关于数据科学的,重点在于使团队能够自主高效地工作。你可能会看到一些概念和步骤在前面的章节中被重复。这是故意的,目的是向你展示前面章节中提供的概念如何帮助你构建完整的生命周期。
保持目标清晰,你将学习以下内容:
-
使用 JupyterHub 可视化和探索数据
-
使用 JupyterHub 构建和调整你的模型
-
使用 MLflow 跟踪模型实验和版本管理
-
通过 Seldon 和 Airflow 将你的模型部署为服务
-
使用 Prometheus 和 Grafana 监控你的模型
技术要求
本章包含一些实践设置和练习。你需要一个已配置Operator Lifecycle Manager(OLM)的运行中的 Kubernetes 集群。如何构建这样的 Kubernetes 环境,已在第三章,探索 Kubernetes 中进行了介绍。在尝试本章的技术练习之前,请确保你拥有一个工作中的 Kubernetes 集群,并且Open Data Hub(ODH)已安装在你的 Kubernetes 集群上。ODH 的安装内容已在第四章,机器学习平台的构成 中进行了详细讲解。
使用 JupyterHub 可视化和探索数据
回顾一下第九章,构建你的数据管道,数据工程师与业务领域专家(SME)合作,准备了可用于预测航班准点表现的航班数据。
在本节中,你将了解数据工程团队产生的数据。这是数据科学家的职责,他们负责构建模型。你将看到平台如何使数据科学团队和数据工程团队进行协作,以及数据科学家如何利用平台为给定问题构建模型。
让我们使用平台进行一些基础数据探索。请记住,本书的重点是帮助你的团队高效工作。重点不在于数据科学或数据工程,而是在于构建和使用平台:
- 启动 JupyterHub,但这次请选择与数据科学生命周期相关的图像。SciKit 是平台上可用的一个图像。现在不要点击 启动服务器 按钮。

图 10.1 – JupyterHub 登录页面
- 在 JupyterHub 登录页面,添加一个
AWS_SECRET_ACCESS_KEY变量,并将其填入 S3 环境的密码。此练习中的密钥值为minio123。请注意,我们使用了 中等 容器大小以适应数据集。现在,点击 启动服务器 按钮来启动你的 JupyterHub IDE。

图 10.2 – JupyterHub 登录页面
-
在你的 JupyterHub IDE 中打开
chapter10/visualize.ipynb文件笔记本。 -
第一步是读取数据工程团队提供的数据。请注意,数据已经在同一平台上可用,这提高了团队的工作效率。笔记本中的 单元格 2 使用了
PyArrow库将数据读取为 pandas 数据框。你将从数据团队放置数据的flights-data存储桶中读取数据。你可以看到数据读取代码如下:

图 10.3 – 第十章/可视化笔记本中的单元格 2
- 第一步是查看数据。尝试理解它,并熟悉其中的内容是理想的做法。你可以在 单元格 3 中看到,已经使用了 DataFrame 的
head函数来查看前几行。你会注意到字段名和其中的数据,看看是否能够理解一条记录。注意,有些字段是NaN,有些是None。这提示数据集可能还未准备好用于构建模型。以下屏幕截图显示了部分输出,预计你需要在自己的环境中运行此代码以获取完整的结果:

图 10.4 – 第十章/可视化笔记本中的单元格 3
- 下一步是进行简单的验证,看看有多少数据可用,是否能够读取所有记录。你可以在 单元格 4 中看到,已经使用了 DataFrame 的
count函数。以下屏幕截图显示了部分输出,预计你需要在自己的环境中运行此代码以获取完整的结果:

图 10.5 – 第十章/可视化笔记本中的单元格 4
-
单元格 5 和 单元格 6 使用了 DataFrame 的形状功能,列的功能也一目了然。
-
第 7 单元格使用了 DataFrame 的
describe函数来生成数据集的一些基本统计信息。你可以使用这个方法验证是否有一些数据不合逻辑。例如,taxi_in时间的最大值可能过高。在这种情况下,你需要与主题专家(SME)合作,澄清并根据需要调整记录。以下截图显示了部分输出,建议你在自己的环境中运行这段代码以查看完整内容:

图 10.6 – 第十章/可视化笔记本的第 7 单元格
- 接下来,你想查看数据是否有空值。你在步骤 3中已经看到数据中存在一些
NaN和None值,并发现有许多列存在缺失数据的问题。以下截图显示了部分输出,建议你在自己的环境中运行这段代码以查看完整内容:

图 10.7 – 第十章/可视化笔记本的第 8 单元格
- 你将使用 Dataframe 的
isnull函数来查找有多少记录缺失数据。通过运行df.isnull().sum().sort_values(ascending = False)代码输出,你会发现数据分为两组。输出的前六行显示了缺失数据率非常高的列,对于这些列,你可以与数据工程团队和主题专家(SME)沟通,找出可以从中获取数据的资源。对于本示例,我们将删除这些列。

图 10.8 – 第十章/可视化笔记本的第 9 单元格
- 在第二组数据中,从
wheels_on列开始,你可以选择删除没有数据的行,或者尝试使用适当的统计函数填充数据。例如,缺失的taxi_in列可以用同一机场和相同时间的均值来填补。此策略需与团队讨论。对于本次练习,我们将直接删除这些行。

图 10.9 – 第十章/可视化笔记本的第 9 单元格
- 通常,调查某一列缺失数据的示例行是一个不错的主意。你可能会在数据中发现某些模式,这对进一步理解数据可能非常有用。你选择查看
tail_number字段没有值的行,并查看是否能发现任何模式。以下截图显示了部分输出,建议你在自己的环境中运行这段代码以查看完整内容:

图 10.10 – 第十章/可视化笔记本的第 10 单元格
- 然后,你将运行 Dataframe 的
info函数,以找出列的数据类型。很多时候,列的数据类型并不是你所预期的。然后你将与 SME 和数据团队合作,改善数据质量。以下截图展示了部分输出,预计你会在自己的环境中运行这段代码以获取完整信息:

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_10_011.jpg)
图 10.11 – 第十章/可视化笔记本中的单元格 11
- 可视化是理解数据的一个特别重要的工具。你可以使用任何你熟悉的库。例如,在这个笔记本的最后一个单元格中,你构建了一个图表来找出
DELAYED列的数据分布。假设 99%的记录中,DELAYED列为0。如果是这样,数据可能不足以预测航班的准点表现,你需要与 SME 和数据团队合作,获取更多的数据。对于这个练习,我们将使用现有的数据分布。

](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_10_012.jpg)
图 10.12 – 第十章/可视化笔记本中的单元格 12
现在我们对航班数据有了更好的理解,让我们开始构建模型。在现实世界中,你需要投入更多的时间来理解数据。本书的重点是展示如何执行模型开发生命周期,因此我们将示例保持在最低限度。
使用 JupyterHub 构建和调优模型
作为一名数据科学家,你将希望尝试不同的模型和参数,以找到最合适的模型。在你开始构建模型之前,请回忆一下在第八章《使用平台构建完整的机器学习项目》中提到的,你需要定义评估标准,而且准确率(accuracy)可能对于很多使用案例来说是一个具有误导性的标准。
对于航班使用案例,假设你的团队和 SME(主题专家)一致同意使用精准度(PRECISION)指标。请注意,精准度衡量的是提供的数据集中正确正向识别的比例。
让我们开始编写我们的模型,看看平台如何使数据科学家高效地完成工作:
-
在你的 JupyterHub 环境中打开
chapter10/experiments.ipynb文件笔记本。 -
在单元格 2中,添加 MLflow 的连接信息。请回想一下,MLflow 是平台中的组件,用于记录模型实验并充当模型注册中心。在代码中,你将配置
EXPERIMENT_NAME,它为你的实验运行提供一个名称。该单元格的最后一行提到了 MLflow 如何记录实验运行。autolog功能使得 MLflow 在训练过程中注册自动回调,以记录参数以供以后使用。
你还需要提供 S3 桶的配置,该桶将由 MLflow 用于存储你的实验结果:

图 10.13 – 第 2 单元,位于第十章/实验笔记本中
- 第 3 单元 从数据工程团队读取可用数据,第 4 单元 再次提供了多列缺失数据的信息。在这本笔记本中,你将使用这些信息来删除你认为不实用的列。以下是部分输出的屏幕截图,建议在你的环境中运行此代码以获取完整的图像:

图 10.14 – 第 3 单元,位于第十章/实验笔记本中
- 第 5 单元 删除了两组列。第一组是那些在大多数行中没有数据的列。你根据前一步骤选择了这些列。在这里,我们简单地删除了这些列;然而,强烈建议你与数据团队合作,找出这种异常的原因,并尽可能获取更多数据。你正在删除的列包括
"cancellation_reason","late_aircraft_delay","weather_delay","airline_delay","security_delay", 和"air_system_delay", 并显示在以下截图中:

图 10.15 – 第 5 单元,位于第十章/实验笔记本中
第二个 drop 语句删除了 tail_number 列。在实际场景中,你需要与专家讨论这一列是否对航班延误起到任何作用。

图 10.16 – 第 5 单元,位于第十章/实验笔记本中
- 第 6 单元 使用 Dataframe 的
dropna函数删除数据不可用的行。回想一下,从 步骤 3 中,与总行数相比,这些列缺失数据的行数较少。air_time,arrival_delay和elapsed_time是 步骤 5 中的示例列。我们采用了这种方法来保持简单;更好的方法是找到获取缺失数据的途径或从现有值创建此数据。

图 10.17 – 第 6 单元,位于第十章/实验笔记本中
- 在 第 7 单元,你正在删除那些未来航班没有数据的列。回想一下,该模型旨在预测未来航班的准时性。然而,诸如
departure_time和arrival_time这样的列包含实际的出发和到达时间。对于预测未来航班,你在预测时将无法使用这些数据,因此在训练模型时需要删除这些列。

图 10.18 – 第 7 单元,位于第十章/实验笔记本中
- 在数据集中,计划的出发和到达时间以 HHMM 格式表示,其中 HH 是小时,MM 是分钟。在 第 8 单元格 中,作为数据科学家,你可以选择将这个数据拆分成两列,一列表示小时,另一列表示分钟。这样做可能简化数据集,并提高模型性能,如果期望的分类与拆分数据之间存在某种相关性。你可以凭直觉做出这个选择,或者与领域专家讨论这个选项。
你选择将 scheduled_departure 和 scheduled_arrival 列拆分:

图 10.19 – 第 8 单元格,章节 10/实验笔记本
- 在 第 9 单元格 中,你删除了更多列。第一组包含需要将时间拆分为小时和分钟的列,例如
scheduled_arrival:

图 10.20 – 第 9 单元格,章节 10/实验笔记本
第二组包含在其他列中表示的列。例如,origin_airport 列包含机场的键值,而 ORIG_AIRPORT 列则是一个描述性名称。这两列表示相同的信息:

图 10.21 – 第 9 单元格,章节 10/实验笔记本
- 在 第 10 单元格 中,你再次通过
head语句查看数据集。你已经注意到某些数据是字符串格式,比如airline列:

图 10.22 – 第 10 单元格,章节 10/实验笔记本
你选择对数据进行编码,将其转换为数字。有许多可用的技术,如 OrdinalEncoder。该编码器将分类值编码为整数数组。在 第 12 单元格 中,你对选定的字段,如 airline 和 origin_airport,应用了类别编码:

图 10.23 – 第 12 单元格,章节 10/实验笔记本
这意味着这些字段的输入字符串数据将被转换为整数。这对于训练是有益的;然而,在推理时,调用者可能并不知道你刚刚进行的编码。一种方法是保存这个编码器,并在推理时使用它将值从字符串转换为整数。因此,你的推理管道将包含两个步骤。第一步是应用编码,第二步是使用保存的模型预测响应。在 第 12 单元格 的最后四行,你已经保存了编码器,并需要将其注册到 MLflow 中:

图 10.24 – 第 12 单元格,章节 10/实验笔记本
- 在第 13 单元格中,您使用
head语句对数据进行了验证。请注意,airline列(您已对其应用类别编码的列之一)已发生变化。例如,比较第 10 单元格和第 13 单元格中的airline列的值,可以发现airline列的值已更改为1。这确认了编码已成功应用到数据集:

图 10.25 – 第 13 单元格,chapter10/experiments 笔记本
-
在第 14 单元格中,您使用
dftype语句验证了数据集中每列的数据类型。许多算法需要数据以数值格式呈现,依据可用的模型,您可能需要将所有字段转换为数值格式。 -
在第 15 单元格中,您将数据划分为训练集和测试集。您将使用
X_Train和y_train集来训练模型,并使用X_Test和y_test来验证模型的性能。您可以执行交叉验证,进一步评估模型在未见数据上的表现。我们假设您作为数据科学家已经了解这些概念,因此不再提供更多细节。

图 10.26 – 第 15 单元格,chapter10/experiments 笔记本
- 在第 16 单元格中,您可视化了数据集的数据分布。以下截图捕获了部分输出,预计您将在自己的环境中运行此代码以获得完整的输出:

图 10.27 – 第 16 单元格,chapter10/experiments 笔记本
从前面的图表中,您可以看到数据偏向准时航班。这可能会影响模型的表现。幸运的是,SciKit库中的RandomForestClassifier对象提供了一个class_weight参数。它可以接受一个 Python 字典对象,您可以在其中为各个标签提供所需的权重。例如,您可以为DELAYED列中表示准时航班的0值分配较小的权重。class_weight的另一种值可以是balanced,它将指导算法根据标签出现频率的逆比例对标签加权。简单来说,对于我们的情况,balanced值将比0更重视DELAYED列中的1值。
- 在第 19 单元格中,您定义了一个随机森林分类模型,在第 20 单元格中,您对该模型进行了训练。您会注意到,我们仅定义了最基本的超参数,并使用
GridSearchCV来找到适合给定数据集的最佳估计器。我们在该单元格的注释中列出了另外一组超参数,建议您尝试不同的组合。

图 10.28 – 第 19 单元格,来自 chapter10/experiments 笔记本
图 10.29展示了通过执行model.fit()函数来进行模型训练:
![图 10.29 – 第 20 单元格,来自 chapter10/experiments 笔记本]
](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_10_029.jpg)
图 10.29 – 第 20 单元格,来自 chapter10/experiments 笔记本
训练将需要一些时间,因此对于第 20 单元格,即您正在训练模型的单元格,请耐心等待。
- 在第 21 单元格中,您使用了
predict方法来捕获模型对测试数据的预测。请注意,rf_best_model模型是GridSearchCV对象的输出:
![图 10.30 – 第 21 单元格,来自 chapter10/experiments 笔记本]
](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_10_030.jpg)
图 10.30 – 第 21 单元格,来自 chapter10/experiments 笔记本
- 在第 22 单元格中,您使用了
confusion_matrix函数来计算矩阵并验证模型的性能:
![图 10.31 – 第 22 单元格,来自 chapter10/experiments 笔记本]
](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_10_031.jpg)
图 10.31 – 第 22 单元格,来自 chapter10/experiments 笔记本
- 在第 23 单元格中,您使用了
precision_score函数来计算模型在测试数据集上的recallscore。您可以看到,您在第一次实验运行中达到了 72%的精度,这对于第一次实验运行来说是不错的。您可以通过平台运行更多实验并提升模型的指标:
![图 10.32 – 第 23 单元格,来自 chapter10/experiments 笔记本]
](https://github.com/OpenDocCN/freelearn-devops-pt6-zh/raw/master/docs/ml-k8s/img/B18332_10_032.jpg)
图 10.32 – 第 23 单元格,来自 chapter10/experiments 笔记本
您已经完成了一个带有多个参数的实验运行,并使用了RandomForestClassifier模型。在此阶段,您可能想检查 MLflow,查看网格搜索执行的所有运行、捕获的参数以及模型性能数据。
通常,数据科学家会尝试多种算法,以找到适合给定问题的最佳算法。您可以执行并优化代码,并使用 MLflow 比较不同的算法。
让我们看看 MLflow 为我们记录了什么。
使用 MLflow 跟踪模型实验和版本控制
在本节中,您将使用 MLflow 来跟踪您的实验并对您的模型进行版本控制。此小节是对第六章的回顾,机器学习工程,我们在其中详细讨论了 MLflow。
跟踪模型实验
在本节中,您将看到 MLflow 为您的实验记录的数据。请注意,您刚刚注册了 MLflow 并调用了autolog函数,MLflow 会自动记录您的所有数据。这是您平台的一项强大功能,您可以通过它比较多个实验并与团队成员共享您的发现。
以下步骤展示了如何在 MLflow 中执行实验跟踪:
-
登录到平台的 MLflow UI。
-
在左侧,你会看到 Experiments 部分,里面包含你名为 FlightsDelay-mluser 的实验。点击它,你将看到以下界面。右侧显示所有的运行。回想一下,我们使用了 GridSearchCV,因此会有多个运行:

图 10.33 – MLflow 中的模型追踪详情
- 点击
autolog功能,MLflow 将自动捕获大部分指标。选择所有四个运行并点击比较按钮。
图 10.34 显示了每次运行的比较以及与运行相关的超参数:

图 10.34 – 在 MLflow 中比较模型
- 点击
FlightsDelayOrdinalEncoder.pkl旁边的运行:

图 10.35 – MLflow 捕获的文件和数据
在本节中,你已经看到 MLflow 捕获了训练运行的所有指标,并通过提供比较功能帮助你选择了合适的模型。
下一阶段是对模型进行版本控制。
对模型进行版本控制
在对模型性能进行一些思考并与其他团队成员共享数据后,你已经选择了可以用于此项目的模型。在本节中,你将对模型进行版本控制。请参考第六章,机器学习工程,我们在其中详细讨论了模型版本控制。
以下步骤将指导你如何对模型进行版本控制:
-
进入 MLflow,点击左侧的 FlightDelay-mluser 实验。
-
然后,在屏幕右侧,点击你运行的+图标。你将看到以下界面:

图 10.36 – MLflow 捕获的文件和数据
- 点击 model 文件夹下的 artifacts,一个蓝色按钮将显示,按钮上标有 注册模型:

图 10.37 – 在 MLflow 中对模型进行版本控制
- 点击
flights-ontime:

图 10.38 – 在 MLflow 中注册模型
作为数据科学家,你已经将用于预测航班延误的模型注册到了模型注册表。下一步是部署你的模型。
将模型部署为服务
在本节中,你将把模型部署为一个 REST 服务。你将看到,使用第七章中提到的详细信息,模型部署与自动化,团队可以将模型打包并作为服务部署。该服务将由你的模型用户使用。我们强烈建议你在继续本节之前,复习一下第七章中的知识,模型部署与自动化。
在第七章《模型部署与自动化》中,你已经使用 Predictor 类部署了模型,将模型暴露为 REST 服务。你将在这里使用相同的类,然而,在航班项目中,你在用于模型训练之前对数据应用了类别编码。这意味着你需要在推理时对输入数据应用相同的编码。回想一下,在本章早些时候,你将文件保存为FlightsDelayOrdinalEncoder.pkl,并且它可在 MLflow 仓库中找到。
下一步是编写一个简单的类,用于将转换应用到输入数据。一旦定义了这个类,你将使用 Seldon 定义推理管道,然后将模型打包为容器。因此,推理管道将包含两个阶段;第一阶段是应用编码,第二阶段是使用模型进行分类预测。
听起来很难吗?你会看到平台已经抽象化了大部分细节,你只需要提供一些配置参数,就能将模型打包并作为服务部署。
让我们先来看一下 Transformer 类,它将加载 FlightsDelayOrdinalEncoder.pkl 文件,并将编码应用到输入数据。打开 chapter10/model_deploy_pipeline/model_build_push/Transformer.py 文件。你会看到 __init__ 函数加载编码器文件,transform_input 函数使用标准的 transform 函数对输入数据进行转换。这与模型训练时使用的函数相同。图 10.39 显示了代码文件:

图 10.39 – Transformer 类
第二个工件是定义模型推理图。回顾第七章《模型部署与自动化》,你已经使用SeldonDeploy.yaml文件定义了一个容器和推理图中的一个阶段。在本节中,你将扩展推理图,以适应推理管道中的转换和预测部分。自然地,当你在图中定义一个新组件时,你还需要定义相应的容器,作为图节点的服务。
请注意,你可以选择在Predict.py中执行转换逻辑,以保持简单。然而,我们希望展示 Seldon 如何构建复杂的图形,每个图形都可以是容器的一个单独实例。这种方法为以弹性方式运行生产模型带来了多样性。
现在,让我们来看一下chapter10/model_deploy_pipeline/model_deploy/SeldonDeploy.yaml文件。该文件已从第七章《模型部署与自动化》中复制过来,并对其进行了以下修改。
第一个变化是构建推理图。你需要先应用转换操作,然后运行模型预测。图 10.40 显示了这个图。注意,图的根元素是 TRANSFORMER 类型,名称为 transformer,并且图中有一个 children 节点。children 节点将在根节点之后执行。这个设置允许你根据模型要求拥有不同的图。这个示例中的子节点是实际的预测:

图 10.40 – Seldon 部署 YAML
对 chapter10/model_deploy_pipeline/model_deploy/SeldonDeploy.yaml 文件的第二个变化是为根节点和子节点注册容器。图中的 name 字段是将容器与图节点关联的字段。因此,我们将有两个容器实例,一个是 transformer,另一个是 predictor。transformer 实例将执行 Transformer.py 文件,而 predictor 实例将执行 Predictor.py 文件。我们所做的工作是创建一个包含所有这些文件的单一容器镜像,因此我们的容器镜像是相同的。你可以查看 chapter10/model_deploy_pipeline/model_build_push/Dockerfile.py 文件,在其中将所有文件打包成容器镜像。图 10.41 突出了 SeldonDeploy.yaml 中配置容器的部分。
注意,第一个容器的名称是 transformer。MODEL_NAME 变量表示 Python 文件的名称,而 SERVICE_TYPE 变量表示 Seldon 调用的回调类型。回想一下,Transformer.py 有一个 transform_input 方法,SERVICE_TYPE 引导 Seldon 系统调用正确的函数。对 predictor 容器实例同样适用,注意 MODEL_NAME 和 SERVICE_TYPE 对于 predictor 实例是不同的:

图 10.41 – Seldon 部署 YAML
就这样!对于你们中的一些人来说,这可能有些令人不知所措,但一旦你为项目定义了结构,这些文件就可以标准化,数据科学家将不需要为每个项目更改它们。你已经看到,ML 平台不仅允许你构建模型,还能将它们打包,从而实现自给自足。
下一步是编写一个简单的 Airflow 管道来部署你的模型。在开始这一部分之前,我们建议你复习一下使用 Airflow 部署模型的知识,详见第七章,模型部署与自动化。你构建的管道无需更改,你只需更改几个配置参数,为管道提供正确的模型名称和版本。
我们已经为您预构建了此管道,因此,打开 chapter10/model_deploy_pipeline/flights_model.pipeline 文件。打开此文件并验证它是否与 第七章**,模型部署与自动化 中提到的两个阶段相同。第一个阶段构建并推送容器镜像到容器注册表,第二个阶段使用 Seldon 部署模型。
图 10.42 显示了构建和推送容器镜像时使用的参数的第一阶段。运行时镜像和文件依赖项与前面显示的值相同。请注意环境变量部分,您会看到相同的变量名,但值不同:

图 10.42 – Flights 模型部署管道
我们来逐一看看它们:
-
MODEL_NAME的值为flights-ontime。这是您在将模型注册到 MLflow 时分配给该模型的名称。 -
MODEL_VERSION的值为1。这是您希望部署的模型版本。该版本会被记录在 MLflow 系统中。 -
CONTAINER_DETAILS的值为flights-ontime。这是您在将模型注册到 MLflow 时分配给该模型的名称。 -
CONTAINER_REGISTRY是容器注册表 API 端点。对于 DockerHub,这个端点是index.docker.io/v1。将此变量的值设置为index.docker.io/v1/。在本示例中,我们使用了 quay.io 作为注册表。这是您可以使用的另一个免费的注册表。 -
CONTAINER_REGISTRY_USER是将镜像推送到镜像注册表的用户的用户名。将此设置为您的 DockerHub 用户名或 Quay 用户名。 -
CONTAINER_REGISTRY_PASSWORD是容器注册表用户的密码。在生产环境中,您不应该这样做。您可以使用秘密管理工具来存储您的密码。
CONTAINER_DETAILS 还是镜像将被推送到的仓库的名称,以及镜像名称和镜像标签。图 10.43 显示了第二阶段,使用 Seldon 部署容器镜像时使用的参数。MODEL_NAME,MODEL_VERSION,CONTAINER_DETAILS 和 CLUSTER_DOMAIN。您已经看到了前面一段中提到的所有变量,但 CLUSTER_DOMAIN 是您 Kubernetes 集群的 DNS 名称。在本例中,minikube 的 IP 地址是 <Minikube IP>.nip.io。

图 10.43 – Flights 模型部署管道
保存并部署此 DAG 到你的 Airflow 环境,当 Airflow DAG 执行完成时,模型将可供使用。通过登录 Airflow 并检查 DAG 的状态来验证此 DAG 是否已正确执行。图 10.44 显示了你已验证 DAG 状态的 Airflow 用户界面。请注意,我们已将 DAG 保存为 flights-model-deploy;如果你选择了其他名称,DAG 名称将相应地反映出来。

图 10.44 – 飞行管道的 Airflow DAG
回想一下,MLflow 会为每个实验关联一个运行 ID。你将其中一个实验注册到模型注册表中,以便可以进行部署。参考图 10.34,该图展示了此模型的运行 ID 截图。
这个模型运行将与已部署的模型相关联,因此你的团队可以将环境中运行的模型追踪到单个运行。这一功能提供了追溯不同环境中运行的模型版本的能力。运行以下命令查看模型创建的资源:
kubectl get service,ingress,SeldonDeployment -n ml-workshop | grep bf32
你应该得到以下响应。如你所见,Kubernetes 服务和 Ingress 的运行 ID 在此示例中以 bf32 开头。请注意,在你的情况下,它将具有不同的值,你需要在前面的命令中调整运行 ID:

图 10.45 – 平台创建的 Kubernetes 对象
现在,模型已部署;你可以通过对模型进行 RESTful 调用来测试它。
调用你的模型
回想一下,模型是通过 Kubernetes Ingress 暴露的,而 Ingress 是通过自动化创建的。为了测试模型是否作为 RESTful API 正常运行,请按照以下步骤操作:
-
运行以下命令以获取
ingress对象。注意,ingress对象的名称将在你的设置中有所不同:kubectl get ingress <INGRESS_NAME> –n ml-workshop -
现在,发起 HTTP 调用到你的模型可用于推理的位置。运行以下命令。
chapter10/inference文件夹包含航班数据的负载,模型将返回预测航班延误的概率。 -
首先,切换到
chapter10/inference文件夹:cd chapter10/inference -
然后,运行
curl命令将负载发送到模型。请根据你的设置更改 HTTP 地址:curl -vvvvk --header "content-type: application/json" -X POST -d @data.json https://flights-ontime.192.168.39.216.nip.io/api/v1.0/predictions; done
Windows 用户可以选择使用优秀的 Postman 应用程序(www.postman.com/)来发起 HTTP 调用。
- 打开
chapter10/inference/data.json文件,查看我们发送给模型的有效载荷。你会注意到,json有效载荷中有两个部分。第一部分是带有names键的部分,用于捕获你在训练模型时使用的特征列。注意这里没有DELAYED列,因为模型将预测DELAYED列的概率。第二部分是带有ndarrray键的部分,包含了特征列的值。需要注意的是,类别列的值是以原始形式存在的,推理管道会在执行模型之前将其转换为类别值。图 10.46 显示了以下文件:

图 10.46 – 航班模型推理的示例有效载荷
现在你已经成功地通过 HTTP 进行推理调用,让我们看看监控系统是如何捕获这些信息的。
监控你的模型
在最后一节中,你将看到平台如何自动开始捕获模型的典型性能指标。平台还帮助你可视化推理的性能。平台使用 Seldon 打包模型,Seldon 会暴露默认的指标以供捕获。Seldon 还允许你为特定模型编写自定义指标;不过这部分内容不在本书的范围之内。
让我们首先理解一下指标捕获和可视化是如何工作的。
理解监控组件
指标捕获的工作方式是,模型由 Seldon 封装。然后 Seldon 将这些指标暴露到一个定义良好的 URL 端点,详细信息请参见 第七章,模型部署与自动化。Prometheus 收集这些信息并将其存储在其数据库中。平台的 Grafana 连接到 Prometheus,并帮助你可视化记录的指标。
图 10.47 总结了模型与监控组件之间的关系:

图 10.47 – ML 平台监控组件
让我们理解一下这个图中的每个组件:
-
开放数据中心(ODH)操作员:这是我们平台的基础操作员。它的作用是为平台提供所有不同的组件。在本书的多个章节中,我们已经讨论过这个操作员,因此在本节中不再详细描述。
-
manifests/prometheus/base/subscription.yaml。以下代码片段展示了它如何使用 OLM 机制来安装 Prometheus 操作员:

图 10.48 – Prometheus 操作员的订阅
manifests/prometheus/base/prometheus.yaml。以下代码片段展示了该文件:

图 10.49 – Prometheus 服务器配置
manifests/prometheus/base/prometheus.yaml。以下代码片段展示了该文件。请注意,配置使用了端口8000,这是 Seldon 用于暴露指标信息的端口。selector对象定义了 Prometheus 用来决定从哪些 Pod 抓取数据的过滤器:

图 10.50 – Prometheus 服务器监控 Seldon Pod
manifests/grafana/base/deployment.yaml文件。
在本节中,你已经看到平台如何提供和连接不同的组件,为你提供一个可视化框架,以满足你的可观察性需求。
接下来是配置 Grafana。
配置 Grafana 和仪表板
在本节中,你将配置 Grafana 连接到 Prometheus,并构建一个仪表板来可视化模型的指标。什么是仪表板?它是你的模型的图表、表格和其他可视化元素的集合。你将为航班模型创建一个仪表板。
请注意,这只是一次性配置,不需要为每个模型重复此过程。这意味着一旦你有了仪表板,就可以在多个模型中使用它。你的团队可能会创建一些标准仪表板,一旦新的模型被部署,平台会自动找到它并将其用于监控。
让我们开始配置 Grafana 实例:
- 使用 https://grafna.192.128.36.219.nip.io 登录 Grafana。请注意,你需要根据你的设置更改 IP 地址。在登录页面,点击 使用 KeyCloak 登录 按钮,它位于登录窗口的底部:

图 10.51 – Grafana 登录页面
- 首先,你需要添加一个数据源。数据源是一个系统,它将提供 Grafana 帮助你可视化的数据。Prometheus 是数据提供者,它从你的模型中抓取指标数据。选择左侧菜单中的 配置 | 数据源 选项:

图 10.52 – Grafana 数据源菜单选项
- 点击 添加数据源 按钮,如下图所示:

图 10.53 – 添加新的 Grafana 数据源
- 选择数据源类型,在你的案例中为 Prometheus。你可能会注意到,Grafana 可以与多种数据源进行通信,包括 InfluxDB 和 YYYY 等。

图 10.54 – 添加新的 Prometheus Grafana 数据源
-
现在,你需要添加 Prometheus 服务器的详细信息。Grafana 将使用这些详细信息连接并从 Prometheus 服务器获取数据。在提到的屏幕中添加以下属性:
-
Prometheus
-
-
然后点击保存并测试按钮。URL 是由平台创建的 Prometheus 服务的地址。由于 Grafana pod 将通过 Kubernetes 内部网络与 Prometheus pod 通信,因此这个 URL 对你的设置也是相同的:

图 10.55 – Prometheus Grafana 数据源的配置
你可以通过执行以下命令来查看prometheus服务的详细信息:
kubectl get service –n ml-platform | grep prometheus
- 在你配置 Grafana 连接到 Prometheus 后,下一步是构建仪表盘。如前所述,仪表盘是一组可视化内容,每个可视化内容都由查询支持。Grafana 会运行这些查询并为你绘制数据。构建仪表盘超出了本书的范围,但我们提供了一个你可以使用的仪表盘。请从左侧菜单中选择导入选项:

图 10.56 – 在 Grafana 中添加新仪表盘
- 打开
chapter10/grafana-dashboard/sample-seldon-dashboard.json文件,将其粘贴到通过面板 JSON 导入文本框中。点击加载按钮导入仪表盘:

图 10.57 – 在 Grafana 中导入 Seldon 仪表盘
- 设置导入的仪表盘名称,并点击
Flights Prediction Analytics,如下面的截图所示:

图 10.58 – 在 Grafana 中导入 Seldon 仪表盘
- 导入仪表盘后,Grafana 将立即开始显示仪表盘。你可以看到一些指标,比如响应时间、成功率以及其他与已部署模型相关的指标。你可能需要多次调用你的模型,才能开始填充这个仪表盘。请参考本章前面提到的调用你的模型部分,了解如何调用已部署的模型。

图 10.59 – Seldon 模型仪表盘
你可以看到仪表盘捕获了由你包装在 Seldon 中的模型所发出的指标。随着更多模型的部署,它们将出现在这个仪表盘中,你可以通过仪表盘顶部的过滤器来筛选模型。
现在,你的航班准时预测服务已经可以使用。接下来,你将与产品开发团队和网站团队合作,帮助他们集成这个功能,为客户提供更好的服务。你的工作并不会在此结束;你需要持续监控模型的表现,并通过新的数据和/或进一步优化模型来提升服务。该平台将帮助你加速这个循环,并不断改进对客户的服务。
总结
这是另一个长章节,涵盖了航班准点率项目的模型开发和部署生命周期。你已经看到平台如何帮助你和你的团队在 EDA、模型实验与追踪、模型注册以及模型部署方面实现自主。
在下一章,我们将退后一步,总结整体平台的旅程,并讲解如何将其作为适合你行业的解决方案。你可以利用这些概念和工具为你的团队构建一个平台,帮助你的业务实现 AI 的强大力量。
第十章:第十一章:在 Kubernetes 上进行机器学习
在各章中,您已经学习了传统软件开发过程与机器学习(ML)之间的差异。您了解了 ML 生命周期,并意识到它与传统软件开发生命周期有很大的不同。我们向您展示了如何使用开源软件在 Kubernetes 上构建完整的 ML 平台。我们还展示了 ML 项目的生命周期,通过相关活动,您已经体验了项目生命周期每个阶段的执行方式。
在本章中,我们将展示一些关键概念,帮助您进一步理解该主题。以下是本章将涉及的内容:
-
确定 ML 平台的应用场景
-
机器学习的操作化
-
在 Kubernetes 上运行
这些内容将帮助您决定何时以及如何使用我们在本书中介绍的 ML 平台,并帮助您为在生产环境中运行和维护平台设置合适的组织结构。
确定 ML 平台的应用场景
正如前面几章所讨论的,理解什么是 ML 以及它与数据分析和数据科学等相关学科的区别至关重要。数据科学可能是 ML 的前置条件。在研究和探索阶段,您不确定 ML 算法是否能解决问题时,数据科学非常重要。在前面的章节中,您使用了数据科学方法,如问题定义、业务指标隔离和算法对比。虽然数据科学是必不可少的,但也有一些 ML 的应用场景不需要那么多数据科学活动。一个例子就是使用 AutoML 框架,我们将在下一节中讨论。
确定 ML 是否能最好地解决问题并选择 ML 平台,实际上是一个“先有鸡还是先有蛋”的问题。因为,为了确定 ML 算法能否最好地解决某个业务问题,需要进行一些数据科学工作,比如数据探索,而这又需要一个平台来支持。如果您处于这种情况,最好的选择是选择一个像Open Data Hub(ODH)这样的开源平台,正如我们在本书中所介绍的那样。因为它完全开源,您可以在没有任何商业协议和许可证的情况下开始安装和使用平台,而且您已经看到这个平台的强大功能。一旦您有了平台,便可以利用它启动研究和数据探索,直到您能够得出结论,是否 ML 是解决业务问题的正确方法。然后,您可以继续使用该平台进行项目生命周期的剩余部分,或者在没有任何平台费用的情况下放弃它。
在某些情况下,你可能已经知道商业问题可以通过机器学习来解决,因为你曾在其他地方看到过类似的实现。在这种情况下,选择我们所介绍的机器学习平台也是一个不错的选择。然而,你也可能面临没有强大数据科学团队的情况。你可能只有一些数据工程师和机器学习工程师,他们了解模型开发的过程,但对自己的数据科学技能不太自信。这时,AutoML 就成为一个值得考虑的选项。
考虑 AutoML
简单来说,AutoML 是指自动生成机器学习模型,几乎不需要数据科学的工作。更详细地说,它涉及自动算法选择、自动超参数调优和自动模型评估。
AutoML 技术作为框架或软件库出现,可以从给定的数据集生成机器学习模型。截至本书写作时,市场上已经有多种 AutoML 框架可用。以下列表展示了一些当前流行的 AutoML 框架,还有许多其他未列出的 AutoML 框架,我们鼓励你去探索它们:
-
BigML – 一个端到端的 AutoML 企业平台,商业化销售。
-
MLJAR – 一个开源的 AutoML 框架。
-
H2O.ai – 一个开源的全功能机器学习平台,包含一个 AutoML 框架。
-
TPOT – 将自己视为数据科学家的助手。它是由宾夕法尼亚大学计算遗传学实验室开发的开源 AutoML 框架。
-
MLBox – 一个开源的 AutoML Python 库。
-
Ludwig – 一个零代码机器学习模型开发工具箱,包含 AutoML。
-
Auto-sklearn – 基于 scikit-learn 机器学习库的开源 AutoML 工具包。
-
Auto-PyTorch – 一个开源的 AutoML 框架,具有自动神经网络架构搜索功能。它可以自动优化神经网络架构。
-
AutoKeras – 基于 Keras 机器学习库的开源 AutoML 框架。
还需要注意的是,这些框架和库中的一些可以在我们的机器学习平台内,或者与任何机器学习平台一起使用。
商业平台
机器学习平台的商业供应商,包括云服务提供商,也在他们的产品组合中提供 AutoML 产品和服务。谷歌有 Google Cloud AutoML,微软有 Azure Machine Learning,亚马逊有 Sagemaker Autopilot,IBM 有 Watson Studio,其中包含 AutoML 和 AutoAI 组件。然而,这些供应商将他们的 AutoML 产品和服务作为机器学习平台产品的一部分出售,这意味着你必须使用他们的机器学习平台才能利用 AutoML 功能。
ODH
你已经看到 ODH 如何让你选择安装哪些组件,它还允许你通过更新kfdef清单文件将一个组件替换为另一个。这增加了你选择将哪些组件作为平台一部分的灵活性。例如,假设你只需要 JupyterHub 和 MLflow,以便你的数据科学团队开始探索使用 ML 解决业务问题的可能性。那么,你可以选择仅安装这些组件。这样可以节省计算资源,从而减少云计算账单。
无论你选择哪种 ML 平台,明确操作化 ML 平台的路径同样至关重要。这包括找到合适的人来在生产环境中运行平台,并将 ML 生命周期中的角色映射到现有组织中。这还包括建立一些流程和沟通渠道,这也是我们接下来要讨论的话题。
操作化 ML
正如之前章节所讨论的,如果你的模型被部署并在生产环境中使用,你就能在业务中充分利用 ML 的全部好处。操作化不仅仅是部署 ML 模型。还需要解决其他一些问题,才能在生产中成功运行支持 ML 的应用程序。让我们深入探讨一下。
设定业务预期
确保业务相关方理解使用 ML 模型预测做出商业决策的风险极为重要。你不希望你的组织因为 ML 而失败。Zillow,这家在其产品Zestimate上投入大量资金的房地产公司,由于错误的房地产价格估算,损失了 5 亿美元。他们最终以 ML 模型设定的价格购买了房产,但这些房产最终以远低于购入价格的价格出售。
ML 模型并不完美;它们会犯错。业务必须接受这个事实,不能完全依赖 ML 模型的预测而忽视其他数据源。如果业务未能接受这一事实,可能会导致由于错误预期而造成的不可挽回的损失。这些损失包括声誉损失、业务信任丧失,甚至是监管罚款和处罚。
另一个案例是,一些算法,特别是深度学习,无法解释其决策过程。必须向业务方传达这一点,因为在某些情况下,可能需要一个可解释的算法以符合监管要求。有些监管机构可能要求你解释业务决策背后的原因。例如,假设一个机器学习模型判定一个新银行客户不是高风险客户,但这个客户最终被某些监管机构列入了黑名单或受到制裁;金融机构可能需要在调查和事后分析过程中向监管机构解释这个决策背后的原因。更糟糕的是,组织可能会因此被罚款数百万美元。
避免向业务方做出过度承诺。IBM Watson 曾提出机器学习能够通过分析来自多个医疗机构的诊断数据来诊断癌症,并可能在未来帮助或甚至替代医生,进行更可靠的癌症诊断。这一想法吸引了大量关注,许多组织也投入了大量资金。然而,事实证明,这项任务非常困难。它不仅导致了损失,还在某种程度上损害了品牌形象。
总结来说,在决定是否使用机器学习模型来预测业务决策之前,确保业务方理解如果模型表现不如预期,可能带来的风险和后果。设定正确的期望值。明确哪些是可能的,哪些是困难的。一些机器学习模型可能在某些业务流程中替代人工,但并非所有机器学习模型都能达到超人的能力。
处理脏数据的实际情况
你用于模型训练的数据通常是经过准备并在受控环境中测试过的数据集。然而,在实际环境中情况并非如此。模型部署到生产环境后,你必须预期会接收到脏数据。你可能会收到结构错误的数据,而且大部分数据是新的,在训练时模型从未见过。为了确保模型适用于生产环境,要避免过拟合,并使用与生产环境中看到的数据尽可能接近的数据集进行彻底测试。如果可能,使用数据增强技术或甚至是制造的数据来模拟生产场景。例如,一个在诊断使用胸部 X 光片的患者时表现良好的模型,可能在一个诊所效果很好,但在另一个使用旧设备的诊所中可能就无法工作。这个案例背后有一个真实的故事,原因是 X 光扫描仪生成的图像中显示了机器传感器上的灰尘颗粒。
总结来说,要避免过拟合。确保数据清洗过程作为推理管道的一部分。通过拥有来自不同来源的合适数据集,为最糟糕的输入数据做准备。当模型没有返回预期结果时,要做好准备。
处理错误结果
假设你有一个信用卡欺诈检测模型,它将一笔常规交易标记为欺诈交易。这可能有很多原因,例如模型可能没有意识到圣诞节期间的消费高于正常水平。你需要能够调查此类场景,因此,至关重要的是要有日志记录功能。这将使你能够回溯模型在生产环境中对特定问题的回答。你将需要此功能来调查模型问题。
当这种情况发生时,你必须准备好面对模型返回错误信息的后果。但同样,你还必须能够通过定期用新的数据集更新模型来解决未来的错误结果。你还必须能够跟踪模型随时间变化的表现。在上一章中你已经看到过如何进行监控。模型表现随时间的变化也被称为漂移。漂移有两种类型。数据漂移发生在模型开始接收它未经过训练的新类型数据时。例如,一个保险欺诈检测模型在正常工作时,突然开始看到包含新保险产品的数据,而该模型之前未见过该产品。这种情况下,模型将无法产生可靠的结果。换句话说,你的模型表现已经退化。另一个例子是,你的模型是基于某一特定的人群或年龄组进行训练的,随后突然出现了一个新年龄组。类似地,机器学习模型返回不可靠结果的概率会更高。概念漂移指的是输入数据和标签之间的功能关系发生了变化。例如,在一个欺诈检测模型中,原本不被视为欺诈的交易,现在根据新的法规被标记为欺诈或异常。这意味着模型将产生更多的假阴性结果,从而使模型变得不可靠。
在这些情况下,你必须为解决这些问题制定一个流程。你必须有一个流程来决定何时手动重新训练模型,或者在模型检测到漂移时自动重新训练模型。你可能还希望在输入数据中实现异常检测。这样可以确保模型只在输入数据合理的情况下给出结果。这也能避免对模型的滥用或攻击。这些自动化要求可以作为你持续集成和部署流水线的一部分进行集成。
维护持续交付
你已经看到了如何手动在平台上运行模型构建和模型部署。你也看到过如何使用 Airflow 自动化部署工作流。尽管团队中的数据科学家或 ML 工程师可以手动执行或触发这些操作,但在现实世界中,你仍然需要有人或一个团队来维护这些管道,以确保它们始终在工作。你可能希望有一个专门的团队来维护执行管道的底层平台,或者你可以将这个责任分配给数据工程团队。无论你选择哪种方式,重要的是必须有人负责确保部署管道始终正常运行。
尽管 ODH 操作员完全管理 ML 平台,但你仍然需要有人负责维护它。确保 Kubernetes 操作员是最新的,并在必要时应用安全补丁。
对于某些关键工作负载,你可能无法自动将其部署到生产环境中。在将更新发布到生产环境中的模型之前,需要手动批准。在这种情况下,你需要通过将此过程嵌入到平台中或通过与手动批准流程的相互协议来建立此审批工作流。不过,目标是确保有人负责维护持续交付服务。
总结一下,持续交付必须始终有效,以便模型开发生命周期能够有更快速的反馈周期。此外,如果检测到漂移,你将始终有一个随时可用的交付管道,可以发布一个更新的模型版本。
管理安全
安全性是将 ML 项目投入生产时需要关注的另一个关键领域。你在前几章中已经看到,ML 平台可以通过使用OpenID Connect(OIDC)或OAuth2,一种标准的认证机制,来进行安全保护。不同的平台组件可以利用相同的认证机制,提供更加无缝的用户体验。你已经使用了一个开源工具 Keycloak,它是一个行业标准的身份与访问管理(IAM)系统的实现,主要支持 OIDC、安全声明标记语言(SAML)等。Seldon Core API 允许将 REST 暴露的 ML 模型保护在相同的认证机制后面。有关更多细节,请参阅 Seldon Core 文档。
总结来说,ML 平台必须通过认证机制进行保护,最好使用 OIDC。这也允许实现单点登录(SSO)。此外,你还需要保护已部署的模型,确保只有目标用户才能访问你的 ML 模型。最后,必须有人负责维护平台所使用的 Keycloak 实例,并且有人或团队负责管理平台资源的访问权限。
遵守合规性政策
在一些商业环境中,合规性是运营的核心。金融机构有一个专门的部门来管理合规性。这些合规规则通常来自监管机构,负责监督金融机构的运营。根据你的机器学习平台将要使用和托管的国家,监管政策可能会禁止你将数据从本地数据中心迁出,或者可能要求对静态数据进行加密。
好消息是,你的平台足够灵活,可以配置为符合这些合规性措施。得益于 Kubernetes,它可以在本地或任何云提供商上运行。你也可以在云中运行机器学习平台,同时将存储保存在本地,或利用混合云策略。
另一个问题是,平台中的每个组件都是可替换和可插拔的。例如,你可以使用一个现有的、经过监管机构批准的 OIDC 提供者,而不是使用专用的 Keycloak 实例。
合规性通常可能成为推进机器学习项目的障碍。如果你打算使用商业平台而不是本书中构建的那个,始终在做出决定之前考虑合规性或监管要求。某些云中的商业平台可能无法遵守数据主权要求,尤其是在主要云提供商尚未在当地拥有数据中心的国家。
换句话说,在规划你的机器学习平台架构时,始终考虑合规性要求。
应用治理
在考虑了前述因素之后,另一个需要明确的问题是如何使你的机器学习平台具备可操作性,即治理。在这一部分,你将设计组织结构、角色和责任、协作模式以及上报点。作者提倡一个跨职能的团队,具有非常高的协作水平。然而,这在现实世界中并不总是可能的。有些组织拥有非常明确的等级制度和职能孤岛,不愿意改变现有的工作方式。如果你所在的组织是这种类型,你可能会在实施我们所介绍的机器学习平台时遇到一些障碍。
该平台的主要特点之一是它是一个自助式平台。它允许数据科学家、机器学习工程师和数据工程师启动他们的笔记本服务器和 Spark 集群。然而,这也会导致云账单或运营成本的不可预测性。如果你是该项目的数据架构师,你的部分工作就是说服领导团队和平台团队信任他们的数据科学家和机器学习工程师。
理想情况下,围绕 ML 项目设计组织结构的最佳方式是拥有一个平台团队。该团队负责运行 ML 平台。然后,这个团队作为服务提供商,向数据和应用团队提供服务,这些团队也被称为流对齐团队,并采用软件即服务(SaaS)模式。平台团队的目标是确保流对齐团队可以在平台上尽可能顺利、快速地完成他们的工作。数据科学团队和数据工程团队可以是流对齐团队,它们是平台的主要用户,也是平台团队的主要客户。DevSecOps 或 DevOps 团队可能会在同一个组织单元内,因为平台团队为流对齐团队提供 DevOps 服务。图 11.1 展示了你可以实施的一个组织结构示例,用于使用 Team Topologies 标注来运行 ML 项目:

图 11.1 – 示例 ML 项目团队结构
在图 11.1中,总共有三个流对齐团队,即数据科学团队、数据工程团队和软件工程团队。所有三个流对齐团队都在相互协作,目标是交付一个在生产环境中运行的 ML 驱动的应用程序。还有三个平台团队。云基础设施团队为另外两个平台团队提供云平台即服务(PaaS):ML 平台团队和 MLOps 团队。ML 平台团队和 MLOps 团队为所有三个流对齐团队提供 ML PaaS 和 MLOps 即服务。紫色框表示一个支持团队。这里是 SME 和产品负责人所在的地方。这个团队为所有流对齐团队提供支持和帮助。
你必须注意,这只是一个示例;你也可以将 ML 平台团队和 MLOps 团队合并,或者将数据科学团队和数据工程团队合并,这完全没问题。
如果你想了解更多关于这种组织设计标注的内容,你可能想阅读关于 Team Topologies 的资料。
我们可以总结如下:
-
使用你在图 2.7中看到的 ML 生命周期图,结合 第二章《理解 MLOps》中的内容,来映射当前团队的组织结构。
-
清晰地传达角色和责任。
-
设置协作渠道和反馈点,例如设计冲刺会议和聊天组。
假设你无法打破团队之间的壁垒;可以设立定期会议,促进壁垒之间的沟通,并建立更加流畅的交接流程。然而,如果你想充分发挥 ML 平台的潜力,我们强烈建议你组建一个跨职能的自组织团队来交付你的 ML 项目。
在 Kubernetes 上运行
使用 ODH 操作符,ML 平台真正释放了 Kubernetes 作为 ML 平台基础设施层的全部潜力。操作符生命周期管理(OLM)框架使 ODH 操作符能够简化 ML 平台的操作和维护。几乎所有操作工作都以 Kubernetes 原生方式进行,您甚至可以通过几次点击启动多个 ML 平台。Kubernetes 和 OLM 还允许您实施 平台即代码(PaC)方法,从而使您能够实施 GitOps 实践。
本书中展示的 ML 平台与原生 Kubernetes 实例或其他任何类型的 Kubernetes 平台,甚至是基于 Kubernetes 的平台,都能很好地配合使用。事实上,原始的 ODH 仓库主要是为 Red Hat OpenShift 设计和构建的。
避免供应商锁定
Kubernetes 保护您免受供应商锁定的困扰。由于额外的容器化和容器编排层,所有工作负载都不会直接运行在基础设施层上,而是通过容器运行。这使得 ML 平台可以托管在任何具有能力的基础设施上。无论是本地部署还是云端,操作都是一样的。这也使得您可以在需要时无缝切换到不同的云供应商。这是使用此 ML 平台与云供应商提供的商业平台相比的一个优势。您不受供应商锁定的限制。
例如,如果您选择 Azure ML 作为您的平台,您将被限制使用 Azure 作为基础设施提供商。您将无法在不更改平台和部署架构的情况下将整个 ML 项目迁移到另一个云供应商。换句话说,切换到不同的云供应商的成本非常高,基本上您会被原供应商“锁定”。
考虑其他 Kubernetes 平台
这个 ML 平台不必仅仅在原生 Kubernetes 平台上运行。如前一部分所述,原始的 ODH 是为在 Red Hat OpenShift 上运行而设计的,而在本书中,您已成功使其在 minikube(一个单节点原生 Kubernetes)上运行。
市面上还有许多其他 Kubernetes 平台,包括主要云供应商提供的。以下是按非特定顺序列出的最常见的一些,但在撰写本文时,其他新兴的基于 Kubernetes 的平台刚刚进入市场,或者正在处于 Beta 测试或开发阶段:
-
Kubernetes
-
Red Hat OpenShift 容器平台(OCP)
-
Google Kubernetes 引擎(GKE)
-
亚马逊 弹性 Kubernetes 引擎(EKS)
-
Azure Kubernetes 服务(AKS)
-
VMware Tanzu
-
Docker 企业版(Docker EE)
尽管我们在 Kubernetes 和 Red Hat OpenShift 上测试了该平台,但您在 minikube 上构建的 ML 平台也可以在上述任何 Kubernetes 平台以及其他平台上构建。但是,未来会如何呢?ODH 的发展方向是什么?
路线图
ODH 是一个活跃的开源项目,主要由全球最大的开源公司 Red Hat 维护。ODH 将不断更新,带来越来越多的功能。然而,由于 ML 和 MLOps 领域相对较新且仍在发展中,看到项目随时间发生显著变化和调整并不奇怪。
在本书编写时,ODH 的下一个版本包括以下更改(如图 11.2所示):

图 11.2 – ODH 的下一个发布版本
ODH 还有一些你尚未探索的功能,它们更多地面向数据工程和数据分析领域。一个例子是使用 Trino 和 Superset 进行数据虚拟化和可视化。如果你想了解这些功能,可以通过更新kfdef文件,将 Trino 和 Superset 作为你机器学习平台的组件,来在你构建的同一平台上进行探索。你可以在 ODH 的 GitHub 项目中找到这些kfdef文件的示例。
你可以在以下网址查看 ODH 的未来路线图:opendatahub.io/docs/roadmap/future.html。
未来,可能会有另一个开源机器学习平台项目在市场上出现。保持开放的心态,永远不要停止探索其他开源项目。
总结
你在本书中学到的关于机器学习(ML)、数据科学与数据工程、MLOps 和机器学习生命周期的知识同样适用于其他任何机器学习平台。你不仅获得了关于在 Kubernetes 中运行机器学习项目的重要见解和知识,还积累了从零开始构建平台的经验。在接下来的章节中,你能够获得实践经验,并同时扮演数据工程师、数据科学家和 MLOps 工程师的角色。
在撰写本书的过程中,我们意识到这个主题非常广泛,深入探讨书中涉及的每个主题可能对某些读者来说信息量过大。虽然我们已触及机器学习平台的大部分组件,但关于每个组件仍有很多内容值得学习,特别是关于 Seldon Core、Apache Spark 和 Apache Airflow 的知识。为了进一步了解这些应用,我们建议查阅它们的官方文档页面。
机器学习(ML)、人工智能(AI)和 MLOps 仍在发展之中。另一方面,尽管 Kubernetes 已经有近 8 年历史,但对于大多数企业组织来说,它仍然相对较新。因此,这一领域的大多数专业人士仍在学习的同时,也在建立新的标准。
保持对最新的机器学习(ML)和 Kubernetes 趋势的关注。你已经掌握了足够的知识,可以独立推进在这一领域的学习。
深入阅读
-
Seldon 核心文档:
docs.seldon.io/projects/seldon-core/en/latest/index.html -
团队拓扑:
teamtopologies.com -
开放数据中心:
opendatahub.io

订阅我们的在线数字图书馆,全面访问超过 7,000 本书籍和视频,并使用行业领先的工具帮助你规划个人发展,推动职业生涯。更多信息,请访问我们的网站。
第十一章:为什么要订阅?
-
通过来自 4000 多位行业专业人士的实用电子书和视频,减少学习时间,增加编码时间
-
通过特别为你打造的学习计划提升学习效果
-
每月获得一本免费的电子书或视频
-
完全可搜索,便于快速访问重要信息
-
复制、粘贴、打印和书签内容
你知道 Packt 提供所有书籍的电子书版本吗?PDF 和 ePub 文件可供下载。你可以在packt.com升级为电子书版本,作为纸质书客户,你还可以享受电子书折扣。如需更多详情,请通过 customercare@packtpub.com 联系我们。
在www.packt.com,你还可以阅读一系列免费的技术文章,注册多种免费的新闻通讯,并享受 Packt 书籍和电子书的独家折扣和优惠。
你可能喜欢的其他书籍
如果你喜欢这本书,可能还会对 Packt 出版的其他书籍感兴趣:

Kubernetes 工作坊
Zachary Arnold, Sahil Dua, Wei Huang, Faisal Masood, Melony Qin, Mohammed Abu Taleb
ISBN: 978-1-83882-075-6
-
掌握 Kubernetes 基础知识及其术语
-
在同一 Pod 中分享或存储不同容器中的数据
-
从镜像定义清单中创建容器镜像
-
为部署构建一个 Kubernetes 感知的持续集成(CI)流水线
-
使用 Kubernetes 入口(ingress)为你的应用吸引流量
-
构建和部署自己的准入控制器
Kubernetes 圣经
Nassim Kebbani, Piotr Tylenda, Russ McKendrick
ISBN: 978-1-83882-769-4
-
使用 Kubernetes 管理容器化应用
-
理解 Kubernetes 架构以及每个组件的职责
-
在 Amazon Elastic Kubernetes Service、Google Kubernetes Engine 和 Microsoft Azure Kubernetes Service 上设置 Kubernetes
-
使用 Helm charts 部署云应用,如 Prometheus 和 Elasticsearch
-
探索 Pod 调度和集群自动扩展的高级技术
-
理解 Kubernetes 中可能的流量路由方法
Packt 正在寻找像你这样的作者
如果你有兴趣成为 Packt 的作者,请访问authors.packtpub.com并立即申请。我们与成千上万的开发者和技术专业人士合作,帮助他们将见解分享给全球技术社区。你可以提交一个通用申请,申请我们正在招聘作者的特定热门话题,或提交你自己的想法。
分享你的想法
现在你已经完成了《Kubernetes 上的机器学习》,我们很想听听你的想法!如果你是在亚马逊购买的此书,请点击这里直接进入亚马逊的评论页面,分享你的反馈或在你购买书籍的网站上留下评论。
你的评论对我们和技术社区非常重要,将帮助我们确保提供优质的内容。


浙公网安备 33010602011771号