Python-DevOps-实用指南-全-

Python DevOps 实用指南(全)

原文:annas-archive.org/md5/0228db3442938136abc9262d5596d201

译者:飞龙

协议:CC BY-NC-SA 4.0

序言

欢迎阅读本书!让我们来谈谈本书的内容以及你将从中学到的东西。本书涉及两件事:DevOps 和 Python。它讲述了这两者是如何相互作用的——无论你称它们为实体、哲学、框架,或者其他任何名称。

本书将帮助你在技术层面上理解 Python,同时也在概念层面上,了解 Python 与其他许多语言的不同之处,以及它为何在程序员和其他提供 IT 解决方案的人群中如此受欢迎。

同时,它还将为你提供关于 DevOps 在现代 IT 基础设施中有多么重要和有用的视角,以及如何使用 Python 来实现 DevOps 的概念。

你将学会如何将难题变得简单,如何以一致和可持续的方式解决问题。你还将学会如何将 Python 代码片段插入到你的工作负载中,以优化你的问题解决过程。

本书不仅仅是一些技术描述和过程,它还将帮助你改善工作流程和工作过程,无论你使用什么工具。

本书适合谁

如果你对 DevOps 或开发有任何关注,你会发现本书很有用。但有几类特定的人物可能会特别发现这本书有价值:

  • 想要探索 DevOps 的开发者:由于本书使用了大量的 DevOps 代码,它非常适合那些可能想要探索 DevOps 的开发者。

  • 学习 Python 的 DevOps 工程师:本书将帮助学习 Python 的 DevOps 工程师,并可能希望在 DevOps 中尝试实施一些 Python 解决方案。

  • 喜欢寻找解决方案的人:如果你是一个想要找到 IT 问题解决方案的人,且没有特定职位,但有工作要做,那么这本书适合你。

本书涵盖内容

第一章介绍 DevOps 原则,将帮助你理解 DevOps 背后的概念,以及它们在提升工作负载生产力方面的重要性。

第二章谈论 Python,涵盖了 DevOps 背后的核心哲学原则,以及这些原则如何定义你在创建解决方案时采取的方式。

第三章立即开始在 Python 中使用 DevOps 的最简单方法,快速介绍了 Python 及其背后的原则,以及这些原则如何与 DevOps 的原则对接。

第四章资源配置,探讨了使用 Python 来增强 DevOps 工作负载的最简便方法。

第五章操作资源,讲解了如何使用 Python 作为一种方式,以可持续和准确的方式配置资源,支持你的 DevOps 工作负载。

第六章使用 Python 的安全性和 DevSecOps,讨论了如何使用 Python 修改已存在的资源,自动化更新并大规模修改复制资源。

第七章自动化任务,探讨了如何使用 Python 自动化常见的 DevOps 任务,通过节省重复任务的时间,提高用户的生产力。

第八章理解事件驱动架构,介绍了如何使用 Python 将不同系统与基于事件驱动的概念连接到系统架构中。

第九章使用 Python 进行 CI/CD 流水线,讨论了 Python 在最常见的 DevOps 任务——持续集成/持续部署(CI/CD)中的应用,以及如何增强这些 CI/CD 流水线。

第十章一些世界最大公司的常见 DevOps 用例,讨论了在一些世界最大公司以及主要云平台提供的工作负载中,Python 在 DevOps 用例中的应用。

第十一章MLOps 和 DataOps,提供了关于机器学习和大数据领域的 DevOps 的内容,以及 Python 如何帮助增强这些工作负载。

第十二章Python 如何与 IaC 概念集成,探讨了如何使用 Python 库和框架,通过基础设施即代码(IaC)来配置资源,以标准化的方式构建和修改 DevOps 工作负载。

第十三章将 DevOps 提升到新高度的工具,探讨了高级 DevOps 概念和工具,以及如何将其集成到你的工作负载中。

为了从本书中获得最大收益

在本书中,我们经常介绍工具和示例,展示如何使用这些工具提高 DevOps 工作负载的生产力。你至少需要本书中提到的 Python 版本,以使用书中描述的所有功能。在一个云平台上完成的大多数任务可以在其他平台上的等效服务上完成。

书中涵盖的 软件/硬件 操作系统 要求
Python 3.9 或更高版本 Windows、macOS 或 Linux
亚马逊云 服务 (AWS)
Google Cloud Platform (GCP)
Microsoft Azure
Google Colab
Grafana

对于云平台,你需要设置相应服务的账户和账单。

如果你使用的是本书的数字版,我们建议你亲自输入代码或从本书的 GitHub 仓库访问代码(下一个部分将提供链接)。这样做可以帮助你避免任何与复制和粘贴代码相关的潜在错误。

下载示例代码文件

您可以从 GitHub 下载本书的示例代码文件,链接为github.com/PacktPublishing/Hands-On-Python-for-DevOps。如果代码有更新,GitHub 仓库中的内容将会同步更新。

我们的丰富书籍和视频目录中还提供了其他代码包,您可以访问github.com/PacktPublishing/查看!

使用的约定

本书中使用了许多文本约定。

文本中的代码:表示文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。例如:“如果您参考下面的图示,数据包大小存储在packet_sizes数组中,数据包的时间戳存储在timestamps变量中。”

一段代码如下所示:

def packet_handler(packet):print(packet)packet_sizes.append(len(packet))timestamps.append(packet.time)

所有命令行输入或输出如下所示:

pip install sphinx

粗体:表示一个新术语、重要的单词或屏幕上看到的单词。例如,菜单或对话框中的单词通常以粗体显示。示例如下:“参考前面的图示,当您点击上方显示的运行按钮时,您将启动一个 Flask 服务器(一个可以返回某种响应的 URL)。”

提示或重要说明

如下所示:

联系我们

我们随时欢迎读者的反馈。

一般反馈:如果您对本书的任何方面有疑问,请通过电子邮件联系我们:customercare@packtpub.com,并在邮件主题中注明书名。

勘误:虽然我们已经尽力确保内容的准确性,但难免会有错误。如果您发现本书中的错误,我们将非常感谢您向我们报告。请访问www.packtpub.com/support/errata并填写表单。

盗版:如果您在互联网上发现任何非法的我们作品的复制品,请提供给我们相关的网址或网站名称。请通过版权@packt.com与我们联系,并附上该材料的链接。

如果您有兴趣成为作者:如果您在某个领域有专长并有意撰写或参与编写书籍,请访问authors.packtpub.com

分享您的想法

一旦您阅读了《DevOps 中的 Python 实战》,我们非常希望听到您的想法!请点击这里直接前往亚马逊书评页面并分享您的反馈

您的评论对我们和技术社区非常重要,能够帮助我们确保提供高质量的内容。

下载本书的免费 PDF 版本

感谢您购买本书!

您喜欢随时随地阅读,但又无法携带纸质书籍吗?

您的电子书购买是否与您选择的设备不兼容?

别担心,现在购买每本 Packt 书籍,您都能免费获得该书的无 DRM 版本 PDF。

随时随地,在任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制并粘贴代码到您的应用程序中。

福利不仅如此,您还可以独家获取折扣、新闻通讯以及每天发送到您邮箱的精彩免费内容

按照这些简单的步骤获取福利:

  1. 扫描二维码或访问以下链接

packt.link/free-ebook/9781835081167

  1. 提交您的购买证明

  2. 就是这样!我们会直接将您的免费 PDF 和其他福利发送到您的邮箱

第一部分:DevOps 简介以及 Python 在 DevOps 中的作用

本部分将介绍 DevOps 和 Python 的基础知识以及它们之间的关系。还将涵盖一些可以提高 DevOps 工作负载的技巧和窍门。

本部分包括以下章节:

  • 第一章介绍 DevOps 原则

  • 第二章谈论 Python

  • 第三章立即开始在 Python 中使用 DevOps 的最简单方法

  • 第四章资源配置

第一章:介绍 DevOps 原则

遵循原则,但不被其束缚。

– 李小龙

DevOps 有许多定义,其中大多数侧重于文化和程序。如果您已经购买了这本书,作为您进入 DevOps 领域的一部分旅程,您可能至少听说过其中的 100 种定义。由于这本书更多地关注 DevOps 的实践和实际操作,我们会尽量将这些抽象概念和定义保持在最低限度,或者尽可能通过行动而非言语来解释它们。

然而,由于这是一本关于 DevOps 的书,我必须对此做出回应:

DevOps 是一系列旨在设定一种支持自动化重复工作和持续交付产品的文化的原则和实践,同时融合软件开发和 IT 运维方面的 产品交付。

不错。可能不完整,但这就是事物的本质,也许这正是使这个定义适当的原因。任何 DevOps 工程师都会告诉你,工作永远不完美。它的原则在许多方面与日本哲学中的 Ikigai(生死志) 相似。它赋予工程师一个目标;一个改进系统的途径,这给他们带来了和剑客磨练技艺或艺术家绘画杰作时相同的兴奋感。满足,但同时又不满足。禅。

除了哲学性的沉思,我相信 DevOps 原则对任何现代软件团队都是至关重要的。在这些团队中工作,最好从原则入手,因为它们有助于解释 DevOps 中使用的工具如何形成、软件团队为何以这种方式构建以及如何促进 DevOps 原则。如果我必须用一个词总结:时间。

在本章中,您将学习定义 DevOps 作为一种理念和思维方式的基本原则。将其视为一种思想练习和技术练习同样重要。本章将为您提供所需的背景,以便理解为什么 DevOps 原则和工具存在以及它们背后的基本哲学。

在本章中,我们将涵盖以下主题:

  • 探索自动化

  • 理解日志记录与监控

  • 事件和事件响应

  • 理解高可用性

  • 深入探讨基础设施即代码

探索自动化

我们将从为什么自动化在生活中普遍需要开始,然后逐步过渡到一个更具体的定义,这与 DevOps 和其他技术团队活动相关。自动化是为懒人而生,但许多人没有意识到,要真正变得懒惰,你必须付出多少努力和学习。要实现自动化,它需要一种思维方式、一种态度、一种对现状的挫败感。

自动化及其与世界的关系

在 Tim Ferris 的书籍《每周工作四小时》中,他专门有一章讲述了自动化工作流程,强调了自动化原则有助于清理生活、去除或自动化不必要的任务或干扰。DevOps 希望在你的职业生涯中做类似的事情。自动化是释放我们时间做其他事情的基础。

人类一直试图进一步自动化的一个领域就是交通工具。我们从步行、骑马、汽车、飞机,到自驾版本的这些交通工具演变而来。这背后的原因与 DevOps 成为一种流行文化的原因相同:节省时间。

从运维工程师的角度来看自动化是如何演变的

你可能听过那个关于构建工程师的著名故事,他将自己的工作完全自动化到了秒(如果你没查过,值得一读)。他做的是自动化任何需要他在服务器环境中关注超过 90 秒的任务(如果你问我,这家伙有很扎实的 DevOps 原则)。这些任务包括:如果他迟到,自动发送短信给妻子;根据客户数据库管理员发送的特定电子邮件自动回滚数据库服务器;以及通过安全外壳连接到咖啡机,自动为他冲咖啡,进一步证明了我的观点:大多数事情都可以自动化。

如果你不想,你不需要将工作区或生活自动化到这种程度,但这里有一个你应该从中学到的教训:使用自动化来节省时间,避免自己被麻烦困扰,因为 a) 你的时间很宝贵,b) 如果你正确设置一次,自动化任务每次都能完美完成。

让我们跟随一位年轻的软件工程师 John 的人生历程。假设 John 是一名 Flask 开发者。John 刚刚加入了他的第一个大软件团队,他们正在生产一个已经上线的项目,且有开发和测试环境。John 在整个编程过程中只接触过 localhost:5000,对其他的内容一无所知(很多初级程序员也是如此)。John 知道使用 Git 进行版本控制,并且知道你推送到上面的源代码会……传到某个地方。然后它会出现在应用程序中。以下是 John 探索这一过程的经历(然后因此感到无聊):

  • John 获得了对代码仓库的访问权限,并在本地设置了代码。虽然这不是他从未做过的事情,但他开始贡献代码。

  • 一个月后,负责管理 John 所在项目部署的运维人员离开了。John 被问是否能接管部署工作,直到他们找到替代人选。John 年轻且天真,同意了。

  • 两个月后,仍然没有替代者,约翰已经弄明白了部署服务器,如 Nginx 或 Apache,是如何工作的,如何将代码复制到服务器环境并以能连接到公共互联网的方式部署它(结果证明,它其实就是伪装的localhost,谁知道呢?)。他甚至可能已经被允许独立修改 DNS 记录。

  • 四个月后,约翰感到疲惫,他花了一半的时间将代码拉到服务器、解决合并冲突、重启服务器和调试服务器。服务器就像一群山羊,而他只是那只喂养许多嘴巴的手。对他来说,推动新功能并完成预先分配的任务变得越来越困难。这时他开始怀疑是否有更好的方法。

  • 他学习了 Bash 脚本和运行手册。他了解到,可以在代码更新时,向代码库和服务器添加触发器来执行某些任务。他还了解了当常见错误开始频繁出现时,可以运行的操作手册。

  • 六个月后,约翰几乎自动化了应用程序的每个部署和维护环节。它自动运行了。这个过程让约翰成为了一个更好的程序员,因为他现在在编写代码时,会考虑到部署和自动化的挑战。

  • 八个月后,约翰没有任何事情可做。他已经自动化了所有相关任务,不再需要那个 HR 从未回应过的 Ops 人员。他现在是一名 DevOps 工程师。

  • 他的经理问他为什么工作日志看起来很空。约翰告诉他,DevOps 任务是根据难度和复杂度来衡量的,而不是工作小时数。经理感到困惑。

  • 现在,在这个阶段,会发生两种情况中的一种:要么经理听取建议,约翰将企业引导向 DevOps 思想,使其转型为现代 IT 公司(尽管有些 IT 公司已经过时,虽然听起来有点奇怪),要么他离开,去到一个欣赏他才华的地方,如果他正确地推销自己,离开会很容易。

这可能看起来像是幻想,但它正是许多 DevOps 工程师在无能的火焰中锻造出来的方式。然而,这个故事更像是对整体公司的一种类比,讨论它们是否会转变为使用 DevOps 原则。那些转变的公司变得更加灵活,能够交付新功能,并将资源用于有意义的事情,而不仅仅是用于维持现状。

自动化源自一种不想一次又一次地做相同的事情(通常会做得更差)的愿望。这个概念是 DevOps 的核心,因为那些进行自动化的人意识到在重复性任务中保持一致性是多么重要,并且它是节省时间甚至可能挽救生命的关键。

但为了让任务能够可靠地以相同的方式反复执行,必须对其进行观察,以确保它保持在正确的轨道上。这就是日志记录和监控的作用所在。

理解日志记录和监控

转到一个更具体的话题,DevOps 的一个核心原则是记录和监控实例、端点、服务,以及你能追踪和追踪的其他内容。这是必要的,因为无论你做什么,无论你的代码有多干净,服务器配置有多好,总会有某些事情失败、出错,或者莫名其妙地完全停止工作。这是必然的。这是生活的一个事实。事实上,这就是墨菲定律:

任何可能出错的事情都会在最糟糕的时刻出错。

熟悉这个事实对 DevOps 工程师来说非常重要。一旦你意识到这一点,你就能处理它。日志记录和监控的作用在于,当事情确实出错时,你需要合适的数据来应对这个事件,有时甚至是自动响应。

本节的其余部分围绕日志记录、监控和警报展开。这些方面中的每一个在确保 DevOps 工作负载顺利进行方面都扮演着重要角色。

日志记录

如果你没有技术背景或是刚接触日志记录原则,可以这样理解日志记录:

每天放学后,一个男孩会去找一个卖火柴的老太太,给她钱买一盒火柴。但是,他并不拿回火柴盒。一天,男孩照常走着,看到老太太快要开口说话了,于是他说:“我知道你可能在想,为什么我给你钱买火柴盒,却不拿火柴盒。你想让我告诉你原因吗?”老太太回答:“不,我只是想告诉你,火柴的价格已经 涨了。”

在这个例子中,老太太是日志记录者,男孩是查看日志的人。老太太并不关心原因,她只是收集数据,当数据发生变化时,她收集变化后的数据。男孩每天检查日志,按部就班,直到日志发生变化。一旦日志发生变化,男孩根据自己的判断决定是否采取相应的措施。

在后续章节中,你将学习如何分析日志(通常使用 Python),以及如何对日志作出合适的响应。但目前,你需要知道的是,良好的账务管理/日志记录已经帮助建立了帝国,因为历史和我们从中学到的教训非常重要。它们给我们提供了视角和应对未来事件所需的适当教训。

监控

当你看到这一节的标题《理解日志记录和监控》时,你可能会想,二者有什么区别?嗯,这是合理的。我花了一段时间才弄明白。我认为,这归结为几个方面:

  1. 监控关注的是特定的指标(通常由日志生成),以及该指标是否超过某个阈值。然而,日志记录仅仅是收集数据,而没有从中产生任何洞察或信息。

  2. 监控是主动的,专注于当前正在监控的实例或对象的状态,而日志记录是被动的,更多关注于大量历史数据的收集。

在许多方面,这就像事务型数据库与数据仓库之间的区别。一个处理当前数据,而另一个则是存储历史数据以发现趋势。两者几乎是不可分割的,因此通常会一起讨论。现在你已经记录和监控了所有数据,可能会问自己,这些数据的意义何在?接下来的部分将帮助解答这个问题。

警报

你无法谈论日志记录和监控而不提及警报的概念。日志度量通过监控服务进行监控。该服务查看从日志中产生的数据,并将其与为该度量设定的阈值进行比较。如果阈值在持续、定义的时间段内被突破,就会触发警报或警钟。

大多数时候,这些警报或警钟要么连接到通知系统,能够通知相关人员警报状态的提升,要么连接到响应系统,可以自动触发对事件的响应。

现在你已经了解了通过日志记录和监控获得的观察力和洞察力的力量,是时候学习如何运用这种力量了。让我们来看看当我们通过日志记录和监控发现重要且令人担忧的洞察时,我们应该采取什么行动。

事件和响应

我再一次提一下墨菲定律,因为我认为这句话非常重要:

任何可能出错的事情都会在最糟糕的时刻出错。

处理事件和响应涉及大量工作或没有工作。这取决于你有多准备,以及事件或问题的独特性。事件和响应涵盖了从自动化、成本控制到网络安全的广泛内容。

DevOps 工程师如何响应事件取决于许多因素。在处理客户和顾客时,当需要响应时,使用服务级别目标SLO)。然而,这通常是在生产环境中,并且需要定义服务级别指标SLI)。这还涉及创建错误预算,以确定何时添加新功能以及何时进行系统维护。较低优先级的开发环境用于对潜在的生产案例和事件响应策略的有效性进行压力测试。这些目标将在理解高 可用性部分中进一步探讨。

如果你在 DevOps 的网站可靠性工程SRE)方面工作,那么事件将是你的主要工作内容。这个角色的工作描述中有很大一部分包括建立正确的指标,以便你可以响应情况。如今,许多 SRE 团队都设置了全球范围内的活跃人员,可以根据其活跃时区监控站点。对事件本身的响应由事件响应团队完成,我将在下一节详细介绍。

事件响应的另一部分是理解事件的原因,恢复所需的时间以及未来可能做得更好的地方。这由事后分析来完成,通常帮助创建一份清晰、公正的报告,可以帮助应对未来的事件。事件响应团队负责创建此文档。

如何应对事件(生活和 DevOps 中)

事件总是会发生,负责处理这些事件的人需要处理它们。消防员必须对抗火灾,医生必须治疗病人,DevOps 工程师必须处理管理和部署其管理的站点可能发生的各种事件。

现在,在生活中,你如何处理影响你的生活或工作的事件?我在 Ian Stuart Abernathy 的书《心理力量》中读到了一个方法,后来发现在我所遇到的 DevOps 课程和专家中随处可见:具体(Specific)、可衡量(Measurable)、可实现(Achievable)、现实(Realistic)和时限(Time-bound)SMART)。如果一个问题的解决方案必须遵循所有这些原则,那么它将有很大的成功机会。你可以将这些应用到自己的生活和 DevOps 旅程中。毕竟,这都是问题解决的一部分。

简要定义 SMART 原则,我们逐个来看每个组成部分:

  • 具体性:确切地知道正在发生什么

  • 可衡量性:衡量其影响

  • 可实现性:考虑你为缓解目标是什么

  • 现实性:对你的期望和你能做到的事情要实际

  • 有时效性:时间非常宝贵,不要浪费

以下是一些 DevOps 工程师可能需要处理的常见事件:

  • 生产网站或应用程序崩溃了

  • 有一大批流量激增,表明可能是分布式拒绝服务攻击

  • 有一大批流量激增,表明有大量新用户需要扩展资源

  • 在代码流水线中构建最新代码时出现错误

  • 有人删除了生产数据库(是真的,这种事情可能发生)

处理事件首先需要根据可以提供的响应类型以及是否已预见并为此类事件做准备来划分事件。如果响应是手动的,那么时间就不是一个因素。通常,这种情况发生在事件不影响工作负载但仍然需要处理时,比如潜在的异常或数据泄露。相关方需要被告知,以便他们可以就此事做出知情决定。自动响应适用于那些你知道会时不时发生的常见错误或事件,并且有适当的响应措施。例如,如果你需要增加计算能力或增加更多服务器来应对流量增加,或者如果某个指标出现异常需要重启实例(这在 Kubernetes 中时常发生)。

我们处理这些事件的目的是为了为我们管理的任何应用程序或网站提供最大可用性。追求最大可用性的做法将在下一节的站点可靠性工程中介绍。

站点可靠性工程

因此,站点可靠性工程SRE)被许多人视为一种 DevOps 形式,而被其他人视为与 DevOps 分开的概念。我将这一部分放在这里,因为无论你对这个话题有什么看法,作为 DevOps 工程师,你将不得不面对站点可靠性、如何保持其稳定性以及如何维持客户信任的相关概念。

作为一个概念,SRE 比整个 DevOps 思想更加僵化和不灵活。它是过去数据中心技术员的进化,他们几乎一生都在数据中心工作,维护服务器机架和配置,以确保他们的服务器持续提供所需的产品。那就是他们的工作:不是创造新东西,而是找到方法来维持旧的基础设施。

SRE(站点可靠性工程)类似于此,但工程师已不再身处数据中心,而是坐在办公室或自己家中的远程工作桌前。他们仍然生活在离数据中心或管理的资源所在云区域相对较近的地方,但与他们的前辈相比有几个不同之处:

  1. 他们的团队可能分散在各个地区,而不是集中在一个地方。

  2. 他们现在的重点是我们所称的预测性维护,即他们不会等到问题发生才做出响应。

事件响应团队

这种新的 SRE 趋势也促成了事件响应团队的产生,这些团队可以迅速从 DevOps 团队内部组建,来监控和处理事件。在处理事件的同时,它们还会与利益相关者进行沟通,确保他们了解情况,并找出事件的根本原因。这些团队还会生成报告,帮助 DevOps 团队应对和缓解未来可能发生的类似情况。在一个几分钟的停机可能会导致数百万美元损失和损害的世界里,事件响应团队已经成为任何 DevOps 工程师世界中的重要组成部分。

通常,事件响应团队由以下成员组成:

  • 事件指挥官IC):事件指挥官负责领导事件响应,并负责事件后的响应计划

  • 沟通领导CL):沟通领导是团队中负责向利益相关者沟通事件及事件缓解进展的公共成员

  • 运营领导OL):有时与事件指挥官同义,OL 通过查看日志、错误和指标来领导事件的技术解决,并找到使网站或应用恢复上线的方法

  • 团队成员:由各自领导协调的 CL 和 OL 下的团队成员,以执行他们所需要的任务

图 1.1 – 一个典型的事件响应团队结构

图 1.1 – 一个典型的事件响应团队结构

如你在图 1.1中看到的,事件响应团队的结构相当简单,通常在发生此类事件时非常有效地缓解问题。但是事件发生之后会发生什么呢?另一个事件?这也是一种可能性,事实上,它之所以成为可能,正是我们需要从当前事件中获得洞察的原因。我们通过死后分析来实现这一点。

死后分析

事件发生了。它影响了业务价值和应用用户,然后它消失或解决了。但谁能说它不会再发生?在它有机会再次发生之前,能做些什么来缓解它?死后分析就是所有这些问题的答案。任何优秀的 DevOps 团队都会在事件发生后进行死后分析。这项死后分析将由处理该事件的事件响应团队主导。

死后分析听起来有些令人毛骨悚然,但它们是恢复过程和工作负载与 DevOps 团队改进的关键部分。它们让 DevOps 团队了解发生的事件以及它是如何发生的,并且对响应团队做出的回应进行剖析。像这样的练习为未来更快的响应时间、学习经验和团队成长打下了坚实的基础。

在事后总结中,常常强调的一个方面是必须无责怪性地进行,也就是说,不能将事件发生的责任归咎于个人。如果发生了事件,应该修改的是过程,而不是人。这样的方法能够创造一个开放的环境,确保事后总结的结果是事实性的、客观的,并且不带偏见。

那么,你可能会问自己,为什么要经历这一切?原因通常是合同性的和强制性的。在现代技术环境中,这些事情是必要的,也是预期的,目的是为最终用户提供价值和可用性。那么让我们来准确理解一下什么是可用性。

理解高可用性

我不会再说一次“墨菲定律”,但要明白它在这里同样适用。事情会出错,它们会崩溃。永远不要忘记这一点。DevOps 作为一个概念和文化之所以如此受欢迎,其中一个原因就是它的方法能够提供一个高度可用的产品,几乎没有停机时间、维护时间,也几乎不受应用程序崩溃错误的影响。

DevOps 能够在其高可用性使命中取得成功的一个原因是,它能够理解失败、应对失败并从失败中恢复。以下是亚马逊首席技术官 Werner Vogel 的名言:

一切都会失败,而且是时时刻刻都在失败。

事实上,这正是 AWS 为 DevOps 运维提供的最佳实践指南、教程和文档的基础,这一点是正确的。有时,事情会因为某个错误而失败;有时,它们会因为完全超出我们控制范围的情况而失败;还有时,事情失败是没有任何原因的。但关键是,事情会失败,而当它们失败时,DevOps 工程师需要处理这些失败。此外,他们还需要尽可能快地处理这些问题,并且尽量减少对客户的干扰。

对于那些可能从未参与过大型项目的人,或者至少没有站在执行者面前的人,我有个小建议:要求具体信息。这是 DevOps、敏捷以及任何其他功能性战略的基本原则,对于项目所有利益相关者和参与者之间的任何工作关系都至关重要。如果你告诉别人你具体需要什么,并且给出衡量这些需求的指标,那么生产出来的结果就会更容易。因此,在 DevOps 中,有一些指标和测量方式有助于界定服务的可用性要求以及维护这些服务的协议。

有许多与高可用性相关的缩略语、指标和指标。在本节中将对这些内容进行探讨,它们将有助于准确界定工作负载中高可用性的含义。

SLIs、SLOs 和 SLAs

服务协议、服务条款、合同以及许多其他类型的协议都是为了让两方达成协议并且必须遵守。你需要合同的场景包括一方支付另一方服务费用、双方交换服务、当一方同意另一方制定的用户协议时(你读过这样的协议吗?),以及许多其他原因。

让我们逐一解析这些内容:

  • 服务级别指标SLIs):这些是可以用来数值化定义产品提供的服务水平的指标。例如,如果你运营一个网站,你可以使用正常运行时间(网站可用服务的时间量)作为 SLI。

  • 服务级别目标SLOs):这些为前述的 SLI 提供了一个具体的数值。这个数值是 DevOps 团队必须为客户达到的目标。回到前面的 SLI 定义示例:如果正常运行时间是 SLI,那么一个月正常运行 99%的时间就是 SLO。通常一个月有 30 天,即 720 小时,因此该网站在该月应该有至少 712.8 小时的正常运行时间,允许的停机时间为 7.2 小时。

  • 服务级别协议SLAs):这些是执行 SLO 的合同。在 SLA 中,会为某个 SLI 定义一个 SLO(希望你现在理解了),该 SLO 必须由 DevOps 团队实现。如果这个 SLA 没有得到满足,那么与 DevOps 团队签订合同的那一方有权获得某些赔偿。以这个示例为结尾,如果该网站的 SLA 有一个 99%的正常运行时间的 SLO,那么这是协议中定义的,DevOps 团队需要满足这个指标。然而,大多数 SLA 都有多个 SLO。

简单来说,SLI(通过测量)-> SLO(通过定义)-> SLA。

AWS 团队喜欢展示的一个显著示例是亚马逊安全存储服务S3)的 11 个 9(99.999999999%)的耐用性(其他云对象存储服务也提供类似的服务)。这意味着每个 S3 桶每 10,000 年才会丢失一个对象。它的标准层 SLA 也有 99.9%的可用性。这相当于在 30 天的日历月内停机 44 分钟。

现在,这三个缩写与可用性相关,但属于附属关系。接下来的两个缩写将更加专注于合同和目标层面上的可用性实际含义。

RTO 和 RPO

这两个缩写比其他三个更侧重于可用性。恢复时间目标RTOs)和恢复点目标RPOs)被用作衡量可用性的标准。如果一个应用程序未能在其 RTO 或 RPO 范围内运行,则未能履行其可用性保证。RTO 和 RPO 主要关注在灾难发生后恢复操作。世界上有一些金融、医疗等关键系统,如果它们的底层系统停机几分钟,就无法正常运作。考虑到一切都会失败的格言,这种灾难或失败并不不现实。

当服务需要保持持续运行时,会设置一个 RTO。RTO 中的时间指的是服务在恢复并重新上线之前,能够容忍的最大离线时间。RTO 的完成在 SLA 中定义为系统下线后重新可用的最大时间。为了遵守 DevOps 的 SLA,他们必须在该时间框架内恢复系统。

现在,你可能会觉得这很简单:只是把东西关了再开,没错吧?实际上,在许多情况下,这样做确实可以解决问题,但记住,这不仅仅是完成工作,而是在规定的时间内完成工作。

在大多数情况下,当服务器宕机时,重新启动服务器就能解决问题。但这个过程需要多长时间呢?如果你的 RTO 是五分钟,而你花了六分钟来重启服务器,那么你就违反了 RTO(在许多关键的企业系统中,RTO 甚至更短)。这就是为什么,当你初次定义 RTO 时,应该做两件事:提出比实际需求更多的时间,并考虑自动化。

现代的服务水平协议(SLA)达到 99%(每月 7 小时)甚至 99.9%(每月 44 分钟),是通过消除人为干预(特别是犹豫)来实现的。服务通过持续监控其健康状况自动恢复,因此当某个实例出现不健康的迹象时,可以进行修复或替换。这个概念促成了 Kubernetes 的流行,它在生产环境中拥有市场上最好的恢复和健康检查概念。

RPO 与数据相关,定义了一个特定的日期或时间(点),可以从中恢复数据库或实例中的数据。RPO 是当前时间与备份或恢复点之间最大可容忍的时间差。例如,一个较小的内部应用程序中的用户数据库可以有一天的 RPO。但一个关键业务应用程序的 RPO 可能只有几分钟(如果有的话)。

RPO 通过不断备份和复制数据库来保持。你使用的大多数应用中的数据库并非主数据库,而是只读副本,通常放置在不同的地理区域。这减轻了主数据库的负载,使其可以专门用于写操作。如果数据库出现故障,通常可以通过将一个只读副本提升为新的主数据库来迅速恢复。只读副本将包含所有必要的数据,因此一致性通常不是问题。在数据中心发生灾难时,类似的备份和恢复选项对于恢复系统功能变得至关重要。

基于这些目标和协议,我们可以提出一些能够影响团队行为的度量标准,就像我们接下来的话题一样。

错误预算

在遵循 DevOps 原则的团队中,错误预算成为团队未来发展方向的一个重要部分。错误预算通过以下公式计算:错误预算 = 1 - SLA(**以小数形式表示)

这基本上意味着错误预算是 SLA 剩余的百分比。因此,如果 SLA 为 99%,则错误预算为 1%。它是我们的停机时间与正常运行时间之比。在这种情况下,每月的错误预算大约为 7.2 小时。根据此预算,我们可以根据团队目标定义团队的进展:

  • 如果团队的目标是可靠性,那么目标应该是收紧错误预算。这样做将有助于团队提供更高的 SLO,并获得客户更多的信任。如果将 SLO 从 99%收紧到 99.9%,则表示容忍的停机时间从 7.2 小时减少到 44 分钟,因此你需要确保能够履行这一承诺。反过来,如果无法履行这样的 SLO,就不应该在任何协议中做出承诺。

  • 如果团队的目标是开发新特性,那么绝不应以降低 SLO 为代价。如果每个月消耗大量错误预算,则团队应当从开发新特性转向提高系统的可靠性。

所有这些统计数据的存在是为了帮助我们拥有可用于保持高可用性的度量标准。但我们并不是直接使用它们,而是将它们配置为自动使用。

如何实现高可用性的自动化?

现在你已经了解了游戏规则,你需要弄清楚如何在这些规则内工作并履行对客户的承诺。为了实现这一目标,你只需要完成 SLA 中设定的事项。对于小规模的工作来说,这并不特别困难,但我们不是来做小事的。

有一些每个 DevOps 工程师都需要了解的基本知识,以实现高可用性:

  • 使用虚拟机上的期望状态配置以防止状态漂移

  • 如何在灾难发生时正确备份数据并快速恢复

  • 如何实现最小停机时间的服务器和实例恢复自动化

  • 如何正确监控工作负载,察觉错误或中断的迹象

  • 如何成功,即使你失败了

听起来很简单,不是吗?嗯,从某种角度来看确实如此。所有这些事情是相互关联的,编织在 DevOps 的框架中,彼此依赖。从失败中恢复成功是生活中最重要的技能之一,不仅仅是在 DevOps 中。

DevOps 社区通过开发工具,使得在代码中维持工作负载的必要状态,这一失败并恢复到成功状态的概念得到了更进一步的深化。

深入了解基础设施即代码

最后,在一本关于 Python 的书中,我们进入了有关代码的章节。到目前为止,我已经给了你很多关于需要完成什么的说明,但要完成我们想要的东西,尤其是在这本书中,我们必须拥有一种方法、一种工具、一种武器,即代码。

现在,“代码”这个词吓坏了许多科技行业的人,甚至是开发者。对自己所使用的每一项工作的基础感到害怕,确实有些奇怪。但有时候,这就是现实。如果你,亲爱的读者,是这样的人,首先,购买这本书本身就是一件勇敢的事情,其次,你所做的只是在拒绝自己解决世界上所有问题的机会。说真的。

现在,原因在于,代码几乎在每种情况下都是首选武器。它是所有自动化问题、监控问题、响应问题、合同问题,甚至可能是我不了解的其他问题的解决方案。并且很多时候,只需要少量代码。

重要提示

记住这一点:业余爱好者不写代码,新手写很多代码,专家写代码的方式让人觉得他们根本没写什么代码,所以在本书中,你会看到很多代码。

让我进一步解释。为了维持 DevOps 所需的服务一致性,你需要某种恒定的东西;一种资源可以回退并用来维持自己标准的东西。你可以为此编写代码。

除此之外,你还需要能够自动化重复性任务和那些需要比人类反应更快的任务。你需要释放自己的时间,同时又不浪费客户的时间。你可以为此编写代码。

你还需要灵活,并且能够在环境变化的情况下动态创建资源,同时具备无缝切换到备份、容错和备用方案的能力。你可以为此编写代码。

基础设施即代码 (IaC) 对于最后这一部分尤其有用。事实上,你可以利用它来封装并制定其他两者的内容。IaC 是协调者。它为云服务提供了一个比喻性的购物清单,列出了它想要的内容以及所需的配置,作为交换,它获得了按照编码要求配置的精确环境。

IaC 是一个 得到你想要的 系统,这一点需要提醒,因为与所有涉及计算机的事情一样,它将 完全 按照你的要求去做,这意味着你需要在使用这些框架时非常具体和精确。

让我们来看一个小示例,用来展示使用一些简单的伪代码(没有那些烦人的语法)来演示 IaC 背后的概念。

伪代码

本章不会涉及到实际的 IaC 代码(你可以在专门讲解 IaC 的章节找到相关内容),我只会简要概述 IaC 背后的概念,并使用一些伪代码定义。这些将帮助你理解单个 IaC 定义在资源安全中的工作原理。

一个创建虚拟机的伪代码示例——将其拆解成最简单的部分,看起来应该是如下所示:

  • 模块名称(通常是对正在部署服务的描述)

    • 虚拟机名称(例如 VM1

    • 分配的资源(规格或虚拟机类型)(例如 1GB 内存)

    • 内部网络和 IP 地址(在VPC1中)

    • 标签(例如 "Department": "Accounting"

这个示例将创建一个名为 VM1 的虚拟机,配备 1GB 内存,并放置在一个名为 VPC1 的 VPC 或等效网络中,标签键为 Department,值为 Accounting。一旦启动,事情将按预期发生。哦,我需要 2GB 内存,怎么办?

很简单,只需更改你的代码:

  • 模块名称(通常是对正在部署服务的描述)

    • 虚拟机名称(例如 VM1

    • 分配的资源(规格或虚拟机类型)(现在是 2GB 内存)

    • 内部网络和 IP 地址(在VPC1中)

    • 标签(例如 "Department": "Accounting"

就是这么简单。你可以看到为什么它这么受欢迎。它足够稳定可靠,同时又足够灵活,可以重用。现在,以下是一些其他提示,可以帮助你理解大多数 IaC 模板是如何工作的:

  • 如果你重命名了虚拟机,它会使用新名称重新部署

  • 如果你重命名了模块,大多数模板默认会拆除并停用旧模块中的虚拟机,并从头创建一个新的虚拟机

  • 更改网络或 VPC 会逻辑上将虚拟机移至另一个网络,并遵循该网络的网络规则

  • 大多数模板允许你循环或迭代多个虚拟机

IaC,哇,真是一个好概念。这是一个非常有趣且非常流行的解决方案,用于解决常见问题。它可以解决很多 DevOps 中的头痛问题,并应该成为每个 DevOps 工程师的武器库中的一部分。

总结

DevOps 的概念令人兴奋、广阔,并且充满创意的空间。它是一个几乎完全由你掌控的领域。有效的 DevOps 需要有效的结构,并且要能适应这些结构以应对挑战,正如我们在 探索 自动化 章节中学到的那样。

但请记住,凡是可能出错的事情都会出错,所以要为成功做好规划,但也要准备好面对失败这一常见的情况。在失败的情况下——正如我们在监控和事件响应部分所学到的——恢复的能力是关键,而且恢复的速度往往也非常重要。如果要从中恢复的事件是新的,它必须被报告并理解,这样才能在未来减少此类事件的发生。

最后,正如我们在深入了解基础设施即代码中所提到的,代码是你的朋友。对待朋友要友善,和它们一起玩。你将在本书中学到如何做到这一点。

第二章:说到 Python

语言是世界和平的关键。如果我们都能说彼此的语言,也许战争的祸害将永远结束。

– 蝙蝠侠

Python 编程语言是基于一套原则构建的,这些原则旨在简化编程。与其他编程语言相比,这种简化的代价是牺牲了许多速度和性能,但也因此产生了一种易于访问和构建的流行语言,拥有庞大的内置函数库。所有这些都使得 Python 非常多才多艺,能够在多种情况下使用,堪称编程界的瑞士军刀。如果你愿意,可以说它是 DevOps 这样的多样化学科的完美工具。

初学者推荐 Python 作为学习语言,因为它相对简单,容易上手,并且在行业中使用广泛(如果不是,为什么我要写这本书呢?)。Python 还是一个非常适合爱好者的灵活编程语言,原因和之前一样,还有它在操作系统自动化、物联网、机器学习以及其他特定兴趣领域中的库支持。在专业领域,Python 面临着很多市场竞争,但这在很大程度上是因为——在那个层次上——较小的差异、遗留系统和可用技能都很重要。

而且这完全没问题。我们不需要 Python 占据整个市场份额——那样会非常无聊且不符合创新的直觉。事实上,我鼓励你在回到 Python 之前,尝试其他语言及其概念,因为那样能帮助你发现 Python 带给你的许多便利,并帮助你更好地理解 Python 提供的抽象。

Python 是简单的语言,它也是简洁的语言。通常,你可以用一行 Python 代码完成其他语言中需要 10 行的代码。

我所陈述的所有理由并不是 Python 在开发和 DevOps 中如此受欢迎的唯一原因。事实上,Python 流行的最重要原因之一是:

{}

对,就是那个。那不是打印错误。它代表了 JSON/字典格式,用于在几乎所有现代主要系统上跨越互联网传递数据。Python 处理它比任何其他语言都要好,并且使得操作起来比其他语言更简单。基础的 Python 库通常足以充分释放 JSON 的潜力,而在许多其他语言中,可能需要额外的库或自定义函数。

现在,你可能会问:“我不能安装那些库吗?这有什么大不了的?”嗯,理解其中的重要性来自于与这类数据打交道,并且理解并非每一种编程语言都会特别强调这两个括号的重要性,以及它们在现代 DevOps/云计算中可能带来的问题。

在本章中,我将提供 Python 的基础复习,并给你一些在 DevOps 领域实用的 Python 知识。这里不会涵盖所有 Python 编程语言的内容,因为那是一个庞大的话题,并且不是本书的重点。我们将只关注 Python 编程语言中对我们工作有用的部分。

所以,让我们列出我们在这里将要涵盖的内容:

  • 通过其创造者的哲学思想来理解 Python 的基础

  • Python 如何支持 DevOps 实践

  • 一些支持这些观点的例子

Python 101

Python 是——正如我之前所说——一种很容易上手的语言。它的设计旨在让普通人也能阅读,而且其代码逻辑也容易理解。正是因为这个事实,从安装 Python 到在操作系统中配置它,可能是所有主要编程语言中最顺利的安装过程。入门的门槛几乎为零。

所以,如果你想声明一个变量和做其他基本操作,从下图开始并搞清楚:

图 2.1 – 声明和操作变量

图 2.1 – 声明和操作变量

本节将专注于 Python 的哲学,因为这将是你在 DevOps 中使用 Python 旅程中的关键。理解了这些基本哲学后,你将明白为什么 Python 和 DevOps 是如此完美的搭配。

事实上,我们可以在import this中找到这种相似性,你会看到 19 行“Python 之禅”。你说是奇数行?这是一个有趣的故事。

所以,如果你以前没见过,我将把它列出来,以便后代留存:

美丽总比丑陋 更好。

显式优于隐式。

简单优于复杂。

复杂优于复杂化。

平坦优于嵌套。

稀疏总比密集 更好。

可读性很重要。

特例不足以打破 规则。

尽管实用性 胜过纯粹性。

错误不应该 默默地通过。

除非 明确禁止。

面对模糊时,拒绝 猜测的诱惑。

应该有一种——最好只有一种——明显的方式 去做这件事。

虽然那条路一开始可能不明显,除非 你是荷兰人。

现在比永远 更好。

尽管“永远”通常比现在就更好。*

如果实现很难解释,那就是 一个坏主意。

如果实现容易解释,那可能是 一个好主意。

命名空间是一个非常棒的主意——让我们做更多 这样的事情!

(Tim Peters, 1999,《Python 之禅》, peps.python.org/pep-0020/#the-zen-of-python)

我现在给你讲这些,是为了举例说明这些原则是如何成为成熟的 Python 语言的一部分的。我将以一对一对的形式来做这件事。除了最后一条。这些规则及其实现将为你提供编写良好 Python 代码所需的适当界限。

美丑/显式隐式

让我们从美开始。人们说美在于观者的眼中。这也是为什么,当你看到缩进不当的代码时,你就开始理解正确缩进代码的美感。下面是用 JavaScript 和 Python 正确写的相同代码:

  • JavaScript:

    const value = 5; for (let i = 0; i <= value; i++) {console.log(i);}
    
  • Python:

    value = 5
    for i in range(value+1):
    print(i)
    

顺便说一下,JavaScript 代码是有效的。它和 Python 代码做的是一样的事情。如果你愿意,你可以把所有的 JavaScript 脚本写在一行中(而且在构建 JS 前端时,有时为了节省空间你确实会这么做)。但是,哪一个你能读得更好?哪一个将信息更好地呈现给你?Python 强制使用这种语法,去掉了分号,取而代之的是通过缩进来分隔代码行,这使得代码在客观上更 优美

但是,有些东西缺失了。你可能理解了代码是清晰和简洁的,但你可能不理解代码。这时我们必须在代码的定义及其变量的定义上变得显式。Python 鼓励在每个代码块中使用注释,并且在给变量赋值时采用明确的结构。snake_case 用于变量,且大写蛇形命名法(UPPER_SNAKE_CASE)用于常量。让我们按照这些指导原则重新编写我们的 Python 代码:

""" Initial constant that doesn't change """INITIAL_VALUE = 5""" Loop through the range of the constant """for current_value in range(INITIAL_VALUE+1):""" Print current loop value """print(current_value)

你不需要为每一行都这样做;我只是比平常更明确一些,为了以后参考。但这是定义变量和注释的基本方式。不再使用那种 ijk 的做法。要友善,并且要定义清晰。

定义简化事物,这就是我们在下一节将要讨论的内容。

简单-复杂-复杂化

必须尽可能保持简洁。这是规则,因为……那样更容易。然而,保持简洁是困难的。有时几乎不可能。随着应用或解决方案的规模变大,复杂性也会增加。我们不希望的是让代码变得复杂化。

复杂和复杂化有什么区别?当代码是为了可持续地动态且可理解地处理所有可能的场景时,它是复杂的。当(在一个复杂的解决方案中)代码是以一种根据静态、非常具体的参数(硬编码)来处理所有可能的情况的方式编写时,它就是复杂化的,且即使是写代码的人,也会觉得它很难理解。

在我的职业生涯中,我看过很多这样的代码;我刚开始时也写过很多。这是一个学习过程,如果你没有培养好的习惯,你就会陷入坏习惯,或者会退回到为更复杂问题找到一个更简单的解决方案。

曾经,在回顾一个旧的JSONResponse函数时,我感到困惑,忍不住想,为什么有人会以这种方式编写代码,直到我发现编写此代码的人并没有之前的网页开发经验,而是一名数据工程师。于是,他们回归了自己对简洁性的理解:使用数据科学库,即便是用于后端开发。

现在,这大大拖慢了应用程序的速度,当然,必须进行重构,但是——因为本书中我们不指责任何个人——我们不能怪罪开发者。我们必须责怪他们依赖的习惯和他们追求的简洁性,这最终导致了复杂的代码,而稍微复杂一点但简洁的解决方案本可以生成更好的代码。

平坦—嵌套/稀疏—密集

特别是关于“平坦优于嵌套”的部分,是那些著名的单行 Python 代码背后的原因。简单的代码不应该需要跨越 20 到 30 行,尤其是当它能在几行内完成时。在许多语言中,做不到几行代码,但在 Python 中可以。

让我们测试一下这个概念,打印这个数组的每个值:my_list = [1,2,3,4,5]

平坦稀疏 嵌套密集
print(*my_list) for element in my_list: print(element)

表格 2.1 – 平坦稀疏与嵌套密集

这是一个非常小的例子,但它是 Python 语言中许多类似例子中的一个。我建议你浏览 Python 自带的库列表;这是一份非常有趣的读物,能够帮助你产生很多想法。

很多时候,这种平坦稀疏的概念大大减少了编写代码的量。反过来,这使得代码变得更具可读性,仅仅是因为减少了阅读代码所需的时间。

让我们深入探讨可读性和概念的纯粹性。

可读性—特例—实用性—纯粹性—错误

Python 旨在成为一种普通人也能在某种程度上阅读和理解的语言。它不需要任何特别的语法,甚至那些单行代码也能轻松理解。可读性很重要,没有任何特殊情况足够特殊到违反这一原则。我已经通过前面的例子表达了这两种哲学,所以在这里无需重复。

实用性优于纯粹性是一个相当简单的概念。通常,过于严格地遵循最佳实践只会浪费时间。有时候,最好的做法是先做,再解释。但是,在这种情况下,确保你的大胆做法不会导致系统崩溃。在这种情况下,try-catch 错误处理是你最好的朋友。当你需要时,它还可以帮助你安静地传递错误。

两者之间的平衡——进展与验证——会产生经过验证和测试的代码,但也会产生实际交付给最终用户的代码。这种平衡对任何成功的项目至关重要。在实际工作中,你必须务实,但你也必须意识到其他人在行动和估算时可能不那么务实。

在任何方向采取行动,无论是务实还是纯粹,都需要有一个方向感。它需要决定某个事情或某种方法,并坚持下去。

模糊性/单向/荷兰式

任何曾经和客户合作过的人都知道,模糊的需求是多么令人沮丧和气馁。“做这个,做那个,我们需要这个”——你只会听到这些,没有来自对方的理解,也没有尊重过程如何运作。他们心中有一个目标,而不在乎你怎么去达成。对于机器来说,这没问题(我们也会学到一些机器的做法),但是对于人类的工作(尤其是编码工作)来说,这不是方法。你需要清楚地知道需求是什么,才能精准地去完成。

很多时候,即使是客户自己也不知道他们到底想要什么;他们有一个模糊的想法,想要去实现,但除此之外没有更多的内容。这种模糊性需要在项目开始时解决,并且绝对不应扩展到代码中。一旦某些东西被定义,就有一种方法可以做到最快、最安全或最方便(具体取决于需求)。这就是你需要找到的方式。

但是,如何找到这种方式呢?对于非荷兰人来说并不显而易见(这是对荷兰程序员 Guido Van Rossum 的一个提及,他是 Python 的原作者)。所以,如果你是荷兰人,那就没问题。如果你不是,阅读这个故事(它比普通的代码更符合这些原则):

三位朋友被困在一艘没有食物和水的船上。这些朋友身上只有一盏看似空的灯。一个朋友决定擦拭这盏灯,结果召唤出了一位神灵。神灵为每个朋友实现了一个愿望,因为他们是一起召唤 他的。

第一个朋友许下了他的愿望:“我希望被送到我的妻子和孩子那里。”愿望得到了满足,朋友消失了,被送回了他的家人身边。第二个朋友希望被送回他家乡的房子里,这个愿望同样得到了实现。第三个朋友是个孤独的人,他没有地方去,也没有人可以去找,于是当轮到他时,他说:“我希望我的朋友们 在我身边。”

这个故事很老了,但大多数人理解它的方式是,朋友们被第三个朋友的愿望强行带回船上:这是一个经典的小心你许下的愿望的故事。然而,一个工程师可以从这个故事中得出其他可能的情境。也许第三个愿望带回了不止这两个人(如果他有超过两个朋友的话);也许没有带回任何人(如果另外两个人不算朋友,那将是个悲伤的转折),或者它甚至可能引发一场关于什么是朋友的争论。

但大多数编程语言就像精灵。它完全按照你说的去做。如果你表达模糊,留给它解释的空间可能会让你付出代价,所以要小心,只许愿你真正想要的东西。人们(比如我们之前的客户)就像人类一样。有时他们知道自己想要什么,有时却不清楚。但为了成功,他们需要精确地知道自己想要什么,既要在目标的背景下(回家),也要在约束规则的背景下(如果他们怀疑第三个朋友的意图,他们本可以让他先走)。这真是个难题,不是吗?

这里的关键——而这也是 DevOps 和敏捷方法论所提倡的——就是持续改进。不断寻找那个最佳的方法。如果情况发生变化,就调整方法以适应新的情境。这种策略在编码、DevOps、机器学习以及几乎所有技术领域中都至关重要。迭代方法论有助于将即使是最模糊的目标转化为一个大胆的使命声明,并能够提供统一的方向。

荷兰人是非常直接的民族;只有他们能发明出像 Python 一样直截了当的语言。说到直截了当,你现在应该阅读下一部分了……或者永远不读,如果你现在没时间(看到我在这里做了什么了吗?)。

现在或永远

这是另一对原则,更多是关于写作方法,而不是写作本身。现在永远更好,但永远又比现在更好,这种说法看起来有些自相矛盾,但它们描述了编写代码和通过代码传递价值的本质。

现在并不意味着此刻。它是指不久的将来,而在这个不久的将来,我们写的代码已经交付了价值。这与永远不发布代码,或在不现实的长期时间框架内发布代码不同,后者可能导致编写的代码变得无关紧要。正如史蒂夫·乔布斯曾经说过的:

真正的 艺术家发布作品

然而,现在也从来不是一个好时机。过早发布某个东西,既没有考虑过它,也没有理解和规划,这可能会导致灾难。这里的基本教训是:三思而后行。如果你跳进了火山口,显然你没有做好应尽的准备。

现在被视为不合时宜的原因之一是,因为现在没有什么好主意。现在永远没有什么好主意;你得等大脑想出一个来。你用力过猛,试图推动一些难以解释的东西——那就是一个坏主意。这就解释了为什么体育经理在交易截止日期时做出许多愚蠢的交易。

难-坏/易-好

如果你很难解释某件事,那很可能是个坏主意。没有太多可以解释的——这就是常识。一个复杂的愿景其实根本算不上愿景。它需要被简化、提炼,并且形成大多数人能理解的东西(或者至少是朝着这个方向努力)。

一个复杂的想法其实只是一个还未简化到最有用、最简单组件的想法。正如老话所说,20%的努力通常能产生 80%的结果。要创造出好的想法,我们只需要集中精力去实现那 20%的部分。

命名空间

孤独的 zen,lib1lib2,它们都包含一个名为 example 的方法。如何解决这个问题,使得这两个方法都能被导入到一个 Python 文件中?你只需要将其中一个或两个方法的名称更改为唯一的命名空间:

  • 不带命名空间的代码:

    from lib1 import example
    from lib2 import example
     This is bound to cause conflicts """
    
  • 带命名空间的代码:

    from lib1 import example as ex1
    from lib2 import example as ex2
    #This won't cause conflicts
    

这确实是个很棒的主意。

通过这些原理,你可以看到 Python 如何演变成今天的样子,以及它如何与其他编程语言区分开来。这些变化也帮助 Python 成为与 DevOps 原理一致的语言。那么,让我们现在来看一下 Python 和 DevOps 原理之间的结合,以及它们如何互相促进。

Python 为 DevOps 提供的功能

在上一节中,我们重点讲解了 Python 的基本原理。现在,我们将探讨遵循这些原理对 DevOps 实践和 DevOps 工程师的意义。DevOps 和 Python 背后的原理有更多相似之处而非差异。它们都强调灵活性、自动化和简洁性。这使得 Python 和 DevOps 在 DevOps 领域中成为完美的组合。即使是那些可能没有最强编码能力的 DevOps 专业人士,Python 也易于学习和使用,并且几乎与所有工具和平台都可以集成,因为几乎所有这些平台都有原生的 Python 支持和库。

我之前提到过,Python 在 DevOps 中如此普及的原因是它能比几乎任何其他语言更好地处理存在于大括号 ({}) 之间的数据。Python 为 DevOps 提供的功能众多,未来章节中会进一步详细讲解。现在,我们将简要概述其中的一些功能。

操作系统

Python 有原生库,可以与它当前运行的任何服务器的操作系统交互。这些库允许程序化访问各种操作系统进程。当你在云端处理虚拟机时(例如使用Amazon EC2),这尤其有用。你可以做以下事情:

  • 在操作系统中设置环境变量

  • 获取文件或目录的信息

  • 操作、创建或删除文件和目录

  • 终止或创建进程和线程

  • 创建临时文件和文件位置

  • 运行 Bash 脚本

操作系统很不错,但它们在理想资源使用情况下,保持在期望状态可能会变得困难。对于这个挑战,我们有一个常见的解决方案——容器化。

容器化

容器是使用Docker库创建的。容器的创建、销毁和修改可以通过 Python 自动化和编排。它提供了一种程序化的方式来维护和修改容器状态。一些应用包括:

  • 与 Docker API 交互,执行命令,比如获取操作系统中现有的 Docker 容器或镜像列表

  • 从 Docker 镜像列表自动生成Docker Compose 文件

  • 构建 Docker 镜像

  • 使用Kubernetes 库编排容器

  • 测试和验证 Docker 镜像

你可能会想,容器的意义何在?可能是因为你从未厌倦过关于操作系统和框架的不断讨论,以及哪些更优(事实上,你甚至可能鼓励过这种废话)。但容器的存在,是为了那些厌倦了这种辩论,想要为所有特定操作需求提供隔离环境的人。所以他们用容器创建了一个,且有人聪明地将它们称为微服务。

微服务

有时,容器和微服务可以互换使用,但在现代 DevOps 中,这不一定是这样。是的,正是容器让微服务成为可能,但在这些容器上编写微服务的整体代码是高效的,它在性能上最大化了投入产出比。以下是 Python 在微服务中使用的一些原因:

  • Python 容器中强大的本地库支持 —— 如jsonasynciosubprocess等库

  • 出色的本地代码模块,简化了对数据的某些迭代和操作性操作,如 collection 模块

  • 能够正确地本地处理通常用于微服务的半结构化和多样化的 JSON 数据

为了使这些微服务能够有效且一致地相互交互,我们需要一些重复,一些一致的重复。我要找的词是……自动化……不,那是机器人……签名……不,那是等这本书成为畅销书后的事……自动化,对,正是这个词。自动化。

自动化可能是 Python 对于 DevOps 工程师的主要卖点,因为它拥有令人难以置信的自动化库和支持功能。大多数从系统管理员转型为 DevOps 的人员更喜欢他们宝贵的Bash 脚本,这种脚本在某些环境中确实有其用武之地,但 Python 更加强大、更加灵活,而且得到了社区和行业中大多数公司的更好支持。在这种情况下,Python 在自动化方面的一些应用包括:

  • 各种针对云端部署的软件开发工具包SDKs),包括AWSAzureGoogle Cloud和其他云服务提供商

  • 支持自动构建和测试应用程序

  • 支持监控应用程序并发送通知

  • 支持从网页、数据库及其他各种数据源中解析和抓取必要的数据

既然我们已经谈过了,让我们做点实际的事情。轻轻跑步,结合 Python 和 DevOps。

一些简单的 Python DevOps 任务

迄今为止,我向你们传授了 DevOps 和 Python 的优点,但到目前为止,我展示的却很少有两者如何协同工作的内容。现在,我们进入这一部分。在这里,我将展示几个如何使用 Python 自动化一些 DevOps 常规任务的例子,这些任务是一些工程师可能每天都要执行的。这两个例子来自 AWS,尽管它们同样适用于其他大云平台,并且如果你拥有正确的 API,也可以应用于大多数数据中心服务器。

本章及所有后续章节的代码都存储在这个仓库中:github.com/PacktPublishing/Hands-On-Python-for-DevOps

自动关闭服务器

经常会遇到某些服务器只需要在工作时间运行,工作时间过后则需要关闭的情况。现在,这种特定场景有很多前提条件,包括使用的平台、服务器运行的账户以及如何衡量工作时间……但是,对于这个场景,我们只是简单地在 AWS 账户中通过一个运行 Python 脚本并利用 boto3 库AWS Lambda 函数微服务来关闭我们的 EC2 服务器。听起来很复杂吗?让我们分解一下。

在我的 AWS 账户中,我有两个正在运行的 EC2 实例。它们每秒钟的运行都会让我花费一些钱。然而,在工作时间我需要它们。它们是这样的:

图 2.2 – 运行中的实例

图 2.2 – 运行中的实例

起了个有创意的名字,我知道。但是它们确实在运行,并且总有一天,我希望它们不再运行。为了实现这一点,我需要找到某种方法来停止它们。我可以一个一个地停止它们,但那样很麻烦。如果这两个实例变成了 1000 个实例,我还会这样做吗?不会。那么,我们需要找到另一种方法。

我们可以尝试命令行界面CLI),但这是一本编码书,而不是 CLI 书,所以我们不这么做。不过,如果你有兴趣,也可以尝试。让我们看看我们的老朋友 Python,并且也可以使用一个服务,允许你随时调用的函数,称为 AWS Lambda。下面是创建 Lambda 函数并用它来启动和停止 EC2 实例的步骤:

  1. 让我们创建一个名为stopper的函数,使用最新的 Python 运行时(本书中使用的是 3.10):

图 2.3 – 创建 Lambda 函数

图 2.3 – 创建 Lambda 函数

  1. 接下来,你需要为 Lambda 函数创建一个执行角色,或者为其分配一个现有的角色。这一点稍后会很重要。不过现在,你可以选择你偏好的方式。默认情况下,点击 boto3 库,它非常适合资源交互。

  2. 在我们能够启动或停止任何实例之前,我们需要列出它们。你需要先加载并转储返回函数一次,以处理datetime数据类型。现在,让我们先初始化 EC2 的 boto3 客户端,尝试列出当前可用的所有实例:

图 2.4 – 描述实例的初始代码

图 2.4 – 描述实例的初始代码

运行此代码进行测试时,你会遇到类似于以下的异常:

图 2.5 – 授权异常

图 2.5 – 授权异常

这是因为 Lambda 函数也有一个身份与访问管理IAM)角色,而这个角色没有描述实例所需的权限。因此,我们需要设置可能需要的权限。

  1. 如下图所示,在配置|权限下,你将找到分配给 Lambda 函数的角色:

图 2.6 – 查找权限角色

图 2.6 – 查找权限角色

  1. 在角色页面,转到添加权限,然后选择附加策略

图 2.7 – 附加权限

图 2.7 – 附加权限

让我们赋予 Lambda 函数对 EC2 服务的完全访问权限,因为我们还需要它来停止实例。如果你觉得这权限过大,也可以创建自定义角色:

图 2.8 – 附加适当的权限

图 2.8 – 附加适当的权限

  1. 让我们重新运行一次,看看结果:

图 2.9 – 成功运行的代码

图 2.9 – 成功运行的代码

你将看到实例的展示,以及关于它们是否正在运行的信息。

  1. 现在,让我们进入关闭正在运行的实例的部分。添加代码以在实例中进行筛选,找出正在运行的实例,并获取它们的 ID 列表,稍后我们将使用这些 ID 来引用我们希望停止的实例:

图 2.10 – 添加停止实例的代码

图 2.10 – 添加停止实例的代码

足够简单易懂,尤其是如果我们遵循可读性和明确性原则的话。

实例现在处于关闭状态,很快就会停止:

图 2.11 – 关闭实例

图 2.11 – 关闭实例

  1. 现在我们已经做了一次,让我们通过使用名为 EventBridge 的服务进一步自动化它,EventBridge 可以每天触发该函数。前往Amazon EventBridge并创建 EventBridge 定时事件:

图 2.12 – 在 EventBridge 上设置定时任务

图 2.12 – 在 EventBridge 上设置定时任务

  1. 选择我们的 Lambda 函数作为事件目标:

图 2.13 – 为 EventBridge 选择 Lambda 进行调用

图 2.13 – 为 EventBridge 选择 Lambda 进行调用

现在,你可以创建 EventBridge 定时事件,该事件将在每天晚上 8 点触发,列出并关闭你的 EC2 实例。

所以,按照这些步骤,你现在拥有了必要的工具,可以根据你想要的定时计划安排实例的关闭。

自动拉取 Docker 镜像列表

拉取 Docker 镜像可能会很繁琐,尤其是当需要拉取多个镜像时。那么,现在我们将看看如何使用 Python 库同时拉取多个 Docker 镜像:

  1. 首先,使用以下命令在虚拟环境中安装 Docker Python 库:

    pip install docker
    
  2. 然后,编写一个名为docker_pull.py的脚本,循环遍历镜像名称列表,并通过利用 Docker 库拉取它们:

图 2.14 – 拉取 Docker 镜像的代码

图 2.14 – 拉取 Docker 镜像的代码

  1. 完成此操作后,使用以下命令运行文件:

    docker images command to check out the Docker containers that you may have locally:
    

图 2.15 – Docker 镜像列表

图 2.15 – Docker 镜像列表

所以,这为你提供了一种相当巧妙的方式,以你想要的标签获取 Docker 容器。

摘要

Python 不仅仅是写代码;它是一种编码哲学——这哲学使得 Python 在 DevOps 工程师中变得极为流行。Python 的 Zen(哲学)影响了语言的开发方式,至今我们仍能在 Python 和 DevOps 中看到这种影响。Python 的哲学为 DevOps 领域提供了符合其哲学的编程语言。

Python 有很多用途,可以促进许多 DevOps 任务的完成。所以,希望本章能为你提供一些关于 Python 为何是 DevOps 好伴侣的见解。在下一章,你将看到这种伴侣关系的实际应用,并学习如何以真正实践的方式运用你的知识。

第三章:立即开始在 Python 中使用 DevOps 的最简单方式

事情不会自己发生。它们是被创造出来的。

—— 约翰·F·肯尼迪

在过去的几章中,你可能一直在思考,所有这些原则和理念倒是不错,但我想亲自动手!如果你想这样做,那这一章就适合你。在本章中,你将学习如何使用 Python 及其库来为你的工作负载服务。

现在,我并不是建议你从当前使用的工具转向基于 Python 的替代品。事实上,我们即将讨论的大多数工具和技术,旨在支持现有的基础设施和方法,而不是替代它们。

本章旨在让你充分了解 Python 编程语言为 DevOps 提供的各种可能性,以及如何将其集成到你现有的系统和基础设施中。

在本章中,我们将学习在 API 调用的不同方面中,如何使用 Python 的一些简单实现:

  • 进行 API 调用以及不同的 API 调用方式

  • Python 如何帮助分析、构建和优化你工作负载的网络资源

技术要求

如果你想充分利用本章内容,有几个技术要求可能需要满足:

  • 一个 GitHub 账户

  • 一个 Replit 账户(与 GitHub 单点登录)

  • 一个 Hugging Face 账户

  • 一个 Google 账户

  • 任何带有互联网连接和命令行界面的计算设备

  • 能够容忍我的写作风格

好吧,如果你能拿到这些,那你就准备好开始本章的旅程了。我们开始吧。

引入 API 调用

要定义 API 调用,让我们从 应用程序编程接口API)开始。API 是一种软件接口,提供了你应用程序访问其他应用程序功能和过程的能力。可以这样理解:当用户尝试从一个应用程序获取信息时,他们是通过 用户界面UI)来实现的。API 对于软件有类似的功能,所以你可以把 API 看作是软件的 UI。

现在,API 调用是出于多种原因进行的:

  • 你不想为一个大功能自己编写底层逻辑(相信我,很多时候你并不想)。

  • API 提供了你通常无法访问的资源(例如,通过云提供商的 API 创建虚拟机)

  • 你只是想把一些信息引入到你的应用程序中(公共 API 非常适合这个用途)

你用于编码的任何库,技术上来说都是一个 API。你把这个库拉进来,然后调用它为你的应用执行某个功能。所以,你可以理解为什么 API 的定义有时会令人困惑。但重点是:更多的东西是 API,而不是不是 API。你在应用或网站中看到的一切,都是通过 API 实现的。

那么,让我们深入探讨一些如何在 DevOps 中利用 API 获得好处的例子。

练习 1 – 调用 Hugging Face Transformer API

我选择这个练习是因为它是免费的,它将向你介绍许多 API 背后的核心工具和概念,并且 Hugging Face API 相当流行,你将亲身体验这些 API。我们将使用的 API 具体是一个将文本提示转换为图像的 transformer。这是一个很棒的 API,可以帮助你了解和学习 API 的工作原理。对于这节课,我使用的是 Google Colab 笔记本,它是由 Google 托管的 Jupyter Notebook。它非常有用,当你想为某些代码部分重新创建运行时环境时,可以使用它。它就像是一个你可以分成更小部分的测试区域。如果你愿意,我们可以创建一个笔记本来进一步探索 Hugging Face API:

  1. 要打开 Colab 笔记本,你可以访问 colab.research.google.com,然后创建一个新的笔记本。最终结果应该是这样:

图 3.1 – 使用 Google Colab 创建的初始笔记本

图 3.1 – 使用 Google Colab 创建的初始笔记本

  1. 我们需要做的第一件事是安装正确的库。这些库包含了我们调用 API 所需的函数和模块。如果你愿意,你可以直接在笔记本中安装它们。我们将安装 huggingface_hubtransformers[agents] 库。以下是安装命令:

    !pip install huggingface_hub transformers[agents]
    

    当你将此命令输入到单元格中并按下播放按钮时,它将在你的运行环境中安装所需的库:

图 3.2 – 安装所需的库

图 3.2 – 安装所需的库

  1. 下一步是使用 API 密钥登录 huggingface_hub

    这就是 API 密钥的概念来源。API 密钥就像是软件的登录凭证。大多数公司只有通过购买 API 密钥才允许完全访问其 API。像 Hugging Face 这样的许多开源项目也使用 API 密钥来推广和跟踪用户交互,有时还会为用户提供高级版本,如果他们需要的话。

  2. 要获取 Hugging Face API 密钥,首先必须访问 huggingface.co 网站并注册账户,或者如果你已经注册过,直接登录。完成后,进入个人资料页面,点击 设置 标签,然后进入 访问令牌 标签。在这里,你可以生成一个访问令牌进行使用:

图 3.3 – 为 Hugging Face API 生成访问令牌

图 3.3 – 为 Hugging Face API 生成访问令牌

  1. 你可以复制这个令牌,并在下一部分代码中使用它。在这里,你导入 Hugging Face 登录模块以调用登录 API,并输入你的密钥来使用 API:

    from huggingface_hub import login
    login("<your_key_here>")
    

    如果你正确加载了它,你会看到此消息。如果是这样,恭喜你,成功调用了登录 API:

图 3.4 – 成功登录并初始化

图 3.4 – 成功登录并初始化

现在来到有趣的部分。我们将使用 Hugging Face Transformer API 将一行文本转换成图像。但首先,我们必须使用HfAgent API 导入一个 Hugging Face 代理(看到模式了吗?):

from transformers import HfAgentagent = HfAgent("https://api-inference.huggingface.co/models/ bigcode/starcoderbase")

我们在这里使用starcoderbase模型。一旦你运行它并获取了代理,你只需简单地输入提示即可生成一幅图像:

agent.run("Draw me a picture of `prompt`", prompt="rainbow butterflies")

但请记住,如果你不想等待半个小时来获取你的图像,可以转到runtime标签并选择 GPU 运行时:

图 3.5 – 选择 GPU 以加快图像处理速度

图 3.5 – 选择 GPU 以加快图像处理速度

  1. 最终产品将让你感到震惊和满意。你将得到如下内容:

图 3.6 – 你的最终结果(很漂亮,不是吗?)

图 3.6 – 你的最终结果(很漂亮,不是吗?)

因此,我们完成了这个练习,并成功调用了一个 API,给我们带来了一个视觉上令人满意的结论。还有什么比这更让人期待的呢?现在,如果其他人也能见证你的劳动成果就好了!

好了,这就是调用 API 的全部意义所在。API 旨在供目标受众使用,因此现在,我们将看看如何分发我们的 API。

练习 2 – 创建和发布一个供消费的 API

部署应用程序是 DevOps 工程师可能会遇到的最频繁的任务之一。拥有一个良好且快速的部署非常重要,但在此之前,首先要有一个部署。在许多方面,部署较小和较大的应用程序是相似的。它们之间主要的不同之处在于你必须为保持较大应用程序的可用性而采取的措施。我们在本节不会讨论这一点。我们将尝试为加法 API 准备一个 API,就像我说的那样,让我们保持简单并开始在一个新的Replit Repl中进行编码。

  1. replit.com注册一个账号。你可以为几乎每个应用程序框架和代码库创建小型虚拟环境。注册后,你可以通过点击创建 Repl按钮来创建一个Repl,这是一个小型虚拟服务器:

图 3.7 – 创建 Repl 的按钮

图 3.7 – 创建 Repl 的按钮

  1. 一旦完成,搜索并创建一个带有Flask模板的 Repl。名称不重要:

图 3.8 – 初始化 Flask Repl

图 3.8 – 初始化 Flask Repl

这将为您提供一个包含预初始化和安装了基本 Flask 库的样板 Flask 代码的 IDE:

图 3.9 – 初始 Flask 框架

图 3.9 – 初始 Flask 框架

  1. 关于前述的图,当你点击"/"时已经定义。因此,如果你在新标签页中打开该网址,你将会得到如下内容:

图 3.10 – 初始 Flask 网页

图 3.10 – 初始 Flask 网页

这个函数只是返回一个网页上的字符串。通常,API 是以 JSON 格式编写的。所以,我们将它转换为 JSON 格式。在 Flask 中,这是非常简单的。你只需在返回类型中传递一个字典变量:

图 3.11 – 在 Flask 中编写简单的 JSON API

图 3.11 – 在 Flask 中编写简单的 JSON API

完成后,你将得到一个 JSON 格式的返回值:

图 3.12 – JSON API 结果

图 3.12 – JSON API 结果

  1. 这个 API 目前只返回静态值。要让它接受用户输入,只需在 URL 中添加request参数。让我们修改应用程序,使其接受两个参数,num1num2,它们将被加在一起,并在 JSON 返回值中显示它们的和:

图 3.13 – Flask API 代码,用于将两个数字相加

图 3.13 – Flask API 代码,用于将两个数字相加

最终结果需要一个 URL,格式为<your_url_here>/?num1=<number>&num2=<number>。结果将类似于以下内容:

图 3.14 – Flask API 调用以将两个数字相加

图 3.14 – Flask API 调用以将两个数字相加

所以,现在你已经学会了如何在 Python 中创建一个 API 来将两个数字相加并部署该 API。这是一个非常大的进步。编程世界中唯一会呈指数级复杂化的事情就是业务逻辑。安全性和网络同样重要,但它们通常遵循一定的公式。只要你能将你的逻辑部署到最终用户,你就可以了。

现在,你已经掌握了 API 的技巧,接下来我们将深入探讨如何将 API 交付给最终用户。我们将讨论网络。网络是 DevOps 和应用程序开发中不可或缺的一部分,以至于有时候它根本没有被提及。那么,接下来让我们看看一些我们可以在网络方面使用 Python 的有用方法。

网络

不,这不是在说如何扩展你的 LinkedIn 人脉,尽管我也推荐你去做这个。计算机网络对于当前每个应用程序的正常运行至关重要,因为它们是将持续价值交付给用户并保持他们与环境连接的唯一方式。如今,几乎每个设备都连接到网络,这也是为什么理解设备的网络和网络的网络(有个名字叫互联网,你听说过吗?)非常重要的原因。

接下来,我将演示两个如何使用 Python 进行网络分析和数据收集的例子。

练习 1 – 使用 Scapy 捕获数据包并可视化数据包大小随时间的变化

Scapy是一个 Python 库,可用于复制、模拟和操作通过计算机网络发送的数据包。Scapy 是任何开发者或 DevOps 专业人士工具箱中的一款非常有用的工具。

在这个练习中,我们将使用 Scapy 收集数据包列表,并获取它们的时间戳和数据包大小。然后,我们将这些信息映射到我们使用matplotlib 库制作的图表上。您可以使用前面提到的 Google Colab 来进行这个练习。那么,让我们初始化笔记本并开始编写代码:

  1. 首先,我们需要安装 matplotlibscapy 库:

    !pip install scapy matplotlib
    
  2. 现在,让我们编写代码,使用 Scapy 的 sniff 模块获取时间戳上数据包大小的列表:

    from scapy.all import sniff
    # Lists to store packet sizes and timestamps
    packet_sizes = []
    timestamps = []
    #Handle packets and get the necessary data
    def packet_handler(packet):
    print(packet)
    packet_sizes.append(len(packet))
    timestamps.append(packet.time)
    # Start packet sniffing on the default network interface
    sniff(prn=packet_handler, count=100)
    

    您将获得最近通过您的网络的 100 个数据包的长度列表,并附带时间戳和流量类型。如果您参考以下示意图,数据包大小存储在 packet_sizes 数组中,数据包的时间戳存储在 timestamps 变量中:

图 3.15 – 在您的计算设备中嗅探数据包

图 3.15 – 在您的计算设备中嗅探数据包

  1. 现在让我们编写代码,使用 matplotlib 绘制随时间变化的数据包大小:

    # Create a plot
    plt.figure(figsize=(16, 8))
    plt.plot(timestamps, packet_sizes, marker='o')
    plt.xlabel("Time")
    plt.ylabel("Packet Size")
    plt.title("Packet Size over Time")
    plt.grid(True)
    plt.show()
    

    这将给我们一个图表,x 轴为时间,y 轴为数据包大小:

图 3.16 – 随时间变化的数据包大小图表

图 3.16 – 随时间变化的数据包大小图表

上面的图表显示了网络活动模式,看起来涉及多个相关的数据包。所以,您可以看到网络分析库已经开始派上用场了。

所以,现在我们已经追踪了我们的网络活动,并通过 Python 生成了数据洞察。让我们看看另一个网络实现,这次重点关注您的设备(或您运行工作负载的设备)所拥有的路由规则。

练习 2 – 为您的设备生成路由表

路由表定义了某些网络流量在您的设备中所经过的路径。这些表几乎存在于每个设备中,并且定义了设备访问计算机网络的路径。您可以使用netifaces Python 库来生成路由表,显示设备中所有可用的路径和目的地。在这种情况下,netifaces 库用于收集操作系统的网络接口(因此得名 netifaces)。然后,您将解析这些信息并以表格形式展示。您仍然可以使用 Google Colab 来完成此操作,尽管为了获得更有趣的结果,您可以尝试在本地运行代码。

  1. 让我们开始为您的设备生成路由表的步骤。如果您一直跟随到现在,您已经知道第一步是安装库:

    !pip install netifaces
    
  2. 接下来,编写代码来生成路由表:

    #import library
    import netifaces
    #begin function
    def generate_routing_table():
    routing_table = []
    #Loop through network interfaces
    for interface in netifaces.interfaces():
         #initialize current address of interface
    Interface_addresses =netifaces.ifaddresses(interface)
    #Check for, then loop through the addresses
    if netifaces.AF_INET in addresses:
    for entry in  	interface_addresses[netifaces.AF_INET]:
    	#Create routing entry wherefound
    if 'netmask' in entry and 'addr' in 	entry:
    routing_entry = {
    'interface': interface,
    'destination': entry['addr'],
    'netmask': entry['netmask']
    }
    #Append route to routing table
    routing_table.append(routing_entry)
    return routing_table
    #Call function
    routing_table = generate_routing_table()
    #Display routing table
    for entry in routing_table:
    print(f"Interface: {entry['interface']}")
    print(f"Destination: {entry['destination']}")
    print(f"Netmask: {entry['netmask']}")
    print("-" * 30)
    

    这段代码很多,但相当容易理解。它还为您提供了有关网络流量从网络接口到哪里去的详细信息。如果您按照我建议的在 Colab 上试过,您会得到类似这样的结果:

图 3.17 – Colab 上的路由表

图 3.17 – Colab 上的路由表

如果你在个人计算机上操作,可能会得到如下所示的结果:

图 3.18 – 个人计算机上的路由表

图 3.18 – 个人计算机上的路由表

这里增加了一些额外的负担。

但这就是要点,这只是你可以使用 Python 来促进 DevOps 网络部分的一些方法。

总结

在这一章中,你学到了一些关于本书动手部分的内容。你了解了 API 和计算机网络,这几乎意味着在 Python DevOps 实现方面,你已经走了一半的路。

在这一章中,你不仅了解了这些重要的 DevOps 概念,还学到了如何在你的 DevOps 流程中实现它们。如果对你有帮助,你可以直接将这段代码应用到你的 DevOps 工作负载中。

你所学到的这些基础知识将帮助你在几乎任何你可能遇到的 DevOps 工作负载中进行增强、监控和故障诊断。在下一章中,我们将讨论如何在 DevOps 工作负载中创建资源,以及 Python 在这一过程中如何提供帮助和发挥作用。

第四章:提供资源

生活是混乱的,危险的,充满了惊喜。建筑物应该反映这一点。

– 弗兰克·盖里(著名建筑师)

DevOps 是资源的游戏。是将你拥有的资源放到正确位置的游戏。听起来很容易,但实际上并非如此。资源的获取基于 DevOps 工程师收到的多个标准和要求。如果你想要最优地提供资源,那么你必须理解背后的逻辑和推理,以及底层基础设施使用策略背后的战略。

如果你想要以简单的英语来表达这一切:只拿你所需的。

所以,这将是本章的一个基本概念之一:权益调整。权益调整是找到你的应用程序或工作负载的最佳资源大小的艺术。很多时候只是试错(通常是你自己的,但如果可能的话,是别人的),相信我说这一点,现代 DevOps 景观中,以编程方式进行比手动方式要容易得多。

但说起来容易做起来难,因为有时你的资源承载的负载远大于你配置的资源的大小,特别是如果你的应用程序变得流行起来。你成为了自己成功的受害者。或者你配置了最大容量的资源,但那种容量只是偶尔需要,而你不可能及时增加你的资源。

这引出了本章的第二个基本概念:扩展。扩展你的资源的增减是 DevOps 的重要方面之一,而删除资源与提供资源同样重要。这几乎总是以编程方式完成的,我们将看看 Python 如何帮助我们做到这一点的几种方式。

如果你掌握了这些概念以及如何有效地使用它们,你可以为你和你的组织节省大量的时间、金钱和资源。此外,你将能够以满足组织和客户需求的方式应对需求激增。

在本章中,我们将探讨以下内容:

  • 如何使用 Python 配置虚拟资源

  • 如何使用各种云的 Python SDK,并通过它们来提供资源

  • 扩展工作原理,扩展的类型以及选择正确的扩展类型

  • 如何使用资源的容器化可以帮助权益调整和更容易的提供资源(以及 Python 在其中的角色)

技术要求

这里是完成本章练习所需满足的要求列表:

  • 安装 Python,并安装了 boto3、Kubernetes 和 Docker 库

  • 一个 AWS 账户

  • 熟悉如何使用 Jupyter Notebook

  • 如果你使用 Windows,使用Windows Subsystem for LinuxWSL)来本地使用 Docker

  • 一个 GitHub 账户,以及 Git 和仓库的基本知识

  • 本书代码库的访问地址:github.com/PacktPublishing/Hands-On-Python-for-DevOps

  • 对虚拟化和 Kubernetes 的基本理解

Python SDK(以及为什么大家都在使用它们)

从头开始讲。SDKs(即 软件开发工具包)是由平台发布的官方编程库和 CLI,它们允许开发者开发能够利用该平台的工具和应用程序。这些 SDK 通常使用非常流行的语言编写,以覆盖尽可能多的开发者。

三大云平台(大多数 DevOps 工作都在这些平台上完成)在其 SDK 中共享以下编程语言:Java.NETC++GoJavaScript/TypeScript/Node.jsPython。如果你从事这些工作——而你从事其中一项的可能性远大于不从事——你需要选择一门编程语言。

那么,问题就变成了,为什么是 Python?而且,为什么我们要在本书的四个章节后才提出这个问题?我来告诉你。Python 恰好是那种松散和结构化之间的平衡,它是实现许多 DevOps 原则所必需的。

严格类型化的语言,如 Java、.NET 和 C++,对于开发来说是不错的选择,但对于现代 DevOps 工作负载所需的灵活性,它们将产生糟糕的结果。话虽如此,大多数云平台都是建立在这些语言上的。但在这些语言上进行操作完全是另一回事。把这些语言看作是提供坚固性的骨架,而把 Python 看作是提供灵活性的关节——它们应该存在于任何需要灵活性的地方。

然后,在另一个极端,你会看到那组 JavaScript 三重奏。即使这些语言得到了主要云平台的大量支持,它们有时仍然不适合这个用途,原因在于它们固有的局限性和语法怪癖。这些语言本来并不是为了这样原生工作,而且它们是单线程的,难以并发操作。

在这一领域,Python 的主要竞争对手,有时也是合作伙伴的是 Go。让我告诉你,Go 很棒。大多数云端工具,比如 DockerKubernetes,都是用 Go 编写的,而那些不是用 Go 编写的,通常是用 Python 编写的。但是,Go 真的可以与 Python 一较高下,它在 DevOps 中的实用性不容小觑。我之所以这么说,是因为在本章中,我将处理很多用 Go 编写的框架,比如 Terraform 和 Docker。

了解了所有这些信息后,让我们终于将注意力重新集中到 Python 上。Python 是非常宽松的。它具有不需要严格数据类型的变量赋值,这对于松耦合的服务非常有用,而松耦合服务是一种非常常见的架构选择。它拥有一个庞大的社区,几乎总是现代基础设施提供商首先提供的 SDK。如本节前面所提到的,Python 本质上可以与任何用任何语言编写的框架建立共生关系。如果有一个流行的框架或工具,它的 Python 版本很可能会得到良好的维护并及时更新。

这是快速了解 Python SDK 的重要性和受欢迎程度,现在,我们将看到一个示例,展示 Python SDK 如何用于配置资源。

使用 Python 的 boto3 库创建 AWS EC2 实例

Boto3 – 这是你如果在 AWS 和 Python 中工作过,可能经常听到的名字。它是包含几乎所有当前可用的 AWS 服务的 SDK,适用于 Python。

在这个示例中,我们将使用 Boto3 在脚本中创建一个 EC2 实例,供你在 AWS 账户中使用。这听起来很简单,但为了实现这一目标,你仍然需要遵循很多步骤,所以让我们开始吧。首先,我们将登录到 AWS 账户并搜索 Sagemaker 服务。让我们深入了解:

  1. 对于这个练习,我们需要一个干净的环境,既能编写 Python 代码,又能在终端中配置权限。为此,在我的 AWS 账户中,我将创建另一个我们稍后会用到的东西:一个Sagemaker笔记本。Sagemaker 笔记本是一个在 AWS 服务器上运行的 Jupyter 笔记本服务:

图 4.1 – 在 Amazon Sagemaker 中创建笔记本实例的控制台

图 4.1 – 在 Amazon Sagemaker 中创建笔记本实例的控制台

如果你查看顶部的面包屑导航,你会看到路径是Amazon Sagemaker -> Notebook 实例 -> 创建 Notebook 实例

  1. 任何较小的笔记本都可以。我们将在这个练习中使用ml.t3.medium

图 4.2 – 已创建的笔记本

图 4.2 – 已创建的笔记本

一旦你的笔记本启动并运行,点击打开 Jupyter,进入你的Jupyter IDE。现在,实例本身将具有一些 AWS 权限,因为它是 AWS 创建的,但还不足以程序化地配置 EC2。然而,它将预装 boto3 和AWS CLI

重要提示

如果你没有安装它们,可以通过pip安装boto3,并通过 AWS 官网(https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)安装 AWS CLI,官网提供了适用于所有操作系统的安装程序。

  1. 现在,让我们尝试使用 Sagemaker 预先分配的现有角色来配置 EC2:

图 4.3 – 调用 boto3 API 的代码

图 4.3 – 调用 boto3 API 的代码

它成功了。但是,正如你在这里看到的,Sagemaker 实例已经预先配置了一个具有 EC2 访问权限的角色。如果情况不是这样,你将不得不授予角色一些权限或使用 AWS CLI 将角色配置文件附加到实例上。但它起作用了,这太棒了。你可以在 AWS 控制台上查看你的 EC2 实例。

但是,在这里需要注意几点:

  • 你总是需要定义一个 ImageID(AWS 有一个公共目录)。我使用的是 AWS 的专有 Linux 版本。

  • 您需要定义实例大小以及要创建的实例的最大和最小数量。

现在,这很简单易懂,不是吗?好的。现在我们可以继续讨论使资源配置如此必要的概念。缩放和自动缩放是 DevOps 中至关重要的概念,它们都只是资源配置的问题。

缩放和自动缩放

缩放 是根据需求增加或减少工作负载或资源的大小的行为。自动缩放 是根据某种触发器自动执行此操作。

在工作负载和应用程序的情况下经常会发生这种情况,你可能会成为自己成功的受害者。你的应用程序成功越多,由于用户或服务的需求而对其造成的压力就越大。要管理这种压力通常需要限制对你的应用程序的访问。如果你不想被请求压倒,你应该这样做,相信我,因为肯定会有人试图做到这一点。但你也应该在你的基础设施中有能力让它随着用户基数的增长而自然增长。

这就是缩放发挥作用的地方。缩放可以通过垂直方式(向设备添加更大的计算能力)或水平方式(添加更多计算机)来进行。在执行一项强大的操作时,垂直缩放是理想的选择,在处理更多请求时,你将需要水平缩放。大多数 DevOps 工作负载需要后者而不是前者。

现在,我们将探讨基于你需要处理的工作负载的参与程度而不同的缩放类型。我们将从手动缩放开始,逐步升级到更自动化的方法。

使用 Python 进行手动缩放

在我们深入讨论自动缩放之前,让我们看看一些常规的缩放(当然要使用 Python)。我们将使用 Python 的 SDK 手动垂直缩放一个实例。我只是使用我的常规本地 IDE。但你可以使用任何组合的 Python、AWS CLI 和 AWS 账户来执行此操作。所以,让我们进入使用 Python 脚本手动缩放 EC2 实例的步骤:

  1. 这是创建 EC2 实例的代码(这也将在书的存储库中发布):

图 4.4 – 创建 EC2 实例的功能

图 4.4 – 创建 EC2 实例的功能

当你运行它时,你会得到实例 ID(你在下一部分中需要用到它):

图 4.5 – 创建具有唯一 ID 的 EC2 实例

图 4.5 – 创建具有唯一 ID 的 EC2 实例

你会看到,在 AWS EC2 控制台上已创建具有相同实例大小和 ID 的实例:

图 4.6 – 运行中的 EC2 实例

图 4.6 – 运行中的 EC2 实例

  1. 现在,垂直扩展作用于同一个实例,但实例大小在运行时无法更改,所以我们需要先停止该实例:

图 4.7 – 停止 EC2 实例的功能

图 4.7 – 停止 EC2 实例的功能

这段代码在运行时会停止实例。确认实例已停止,并注意实例的大小仍然是t2.nano

图 4.8 – 停止的 EC2 实例

图 4.8 – 停止的 EC2 实例

  1. 现在,让我们编写代码,将实例修改为t2.micro实例:

图 4.9 – 更新 EC2 实例的代码

图 4.9 – 更新 EC2 实例的代码

运行这段代码后,你会发现控制台上显示的实例现在是t2.micro实例:

图 4.10 – 更新后的 EC2 实例大小

图 4.10 – 更新后的 EC2 实例大小

  1. 因此,一旦你重新启动实例,它将具备额外的计算能力。

你可能已经注意到,这是一个缓慢的过程。而垂直扩展 – 在很多情况下 – 都是一个停机的缓慢过程。尽管这种做法有其应用场景(尤其是在需要使用更强大单机的情况下),但这并不是常态。通常,水平自动扩展更适合你的使用场景,因为它关联的停机时间较少。接下来我们会深入探讨这个问题。

基于触发器的 Python 自动扩展

自动扩展需要根据某些度量标准或统计数据自动增加可用的计算资源。为了实现自动扩展,我们需要设计一个机制,当某个度量标准或阈值达到时,它会触发我们的 SDK 调用。

请理解,从单一云平台的角度来看这个例子,可能会觉得它有点不切实际,因为大多数云平台都内置了自动扩展。关键在于如何微调这个自动扩展。我将创建一个自动扩展组,并使用 Python 脚本定义扩展的阈值。然后,我将修改这些阈值,修改后我会告诉你为什么这么做的意义。

让我们编写一个基本脚本,创建一个自动扩展组,并使用 CPU 利用率策略设置阈值。我们将从启动配置、自动扩展组到实例扩展的规则一步步来:

  1. 首先,我们编写代码,创建一个启动配置,所有自动扩展组中的机器都会遵循这个配置:

图 4.11 – 创建启动配置的代码

图 4.11 – 创建启动配置的代码

  1. 接下来,我们创建自动伸缩组,该组使用我们之前创建的启动配置:

图 4.12 – 创建自动伸缩组的代码

图 4.12 – 创建自动伸缩组的代码

  1. 最后,我们将创建一个策略,如果 CPU 利用率超过 70%,则向上扩展该组:

图 4.13 – 创建伸缩策略的代码

图 4.13 – 创建伸缩策略的代码

运行这些操作将为你提供一个具有这些规格的基本虚拟机自动伸缩组。现在,你可能会问,Python 在这个自动伸缩中起什么作用。好吧,为此,你首先需要查看这些虚拟机生成的指标。

如果你查看虚拟机生成的指标,你将能够找到它们的 CPU 利用率指标,并且这些指标可以被导出。利用这些指标,你可以计算一段时间内 CPU 的平均利用率(据说是编程语言 Python 帮助的),然后使用这些数据来找到更好的自动伸缩目标。要修改目标,你可以简单地使用与之前相同的代码,只需更改指标值:

图 4.14 – 修改后的伸缩策略代码

图 4.14 – 修改后的伸缩策略代码

你的数据分析结果甚至可能揭示出,比 CPU 利用率更适合你工作负载的指标。你也可以在这里修改它。

这种类型的伸缩非常有用,很多情况下你肯定会用到它们。然而,这并不是实现伸缩和虚拟化的唯一方式。在下一节中,我们将探讨容器及其在伸缩和虚拟化领域中的角色和目的。

容器以及 Python 在容器中的角色

容器是小型的软件包,作为独特的运行时,包含运行应用程序某个小部分或有时整个应用程序所需的所有资源。

容器本身是用 Go 语言编写的,容器编排服务 Kubernetes 也是如此。除非应用程序代码本身是用 Python 编写的,否则这些部分不需要 Python。Python 的作用是作为各种容器化服务之间的胶水。可以说,它是在编排中的编排。

现在,我们将了解 Python 在整个容器体系中的角色。Python,一如既往,拥有许多支持容器和 Kubernetes 使用的库,我们将探索其中一些库,以及如何利用 Python 简化与这些重要基础设施元素相关的 DevOps 工作。

使用 Python 简化 Docker 管理

保持 Docker 镜像整齐有序是很有挑战的。这就是为什么他们发明了 Kubernetes。但 Kubernetes 本身也很复杂。这留下了两个空白:

  • 首先,当有多个 Docker 镜像,但不需要完全使用 Kubernetes 进行编排时

  • 第二,当 Kubernetes API 需要频繁调用或集群需要频繁更新时

对于这两种用途,Python 都是一个有用的工具。记住,这只是辅助支持,而不是重构。我们是辅助角色。

所以,在这一节中,我们将通过一个例子来展示如何使用 Python 管理多个容器。

我们将编写一个脚本来获取特定的 Docker 镜像并为其创建一个容器。这个脚本可以重复运行,完成相同的操作。如果容器出现故障,你也可以使用restart命令。现在让我们看一下拉取 Docker 镜像并启动容器的代码:

图 4.15 – 拉取 Docker 镜像并启动容器的代码

图 4.15 – 拉取 Docker 镜像并启动容器的代码

这很简单,但这正是关键所在。它的简洁性为进一步改进提供了基础。

即使我们保持简单,有时容器的使用也变得复杂,这就是 Kubernetes 发挥作用的地方。使用 Kubernetes 也会带来一些挑战,这些挑战可以通过 Python 进行简化和管理。

使用 Python 管理 Kubernetes

在你使用容器的过程中,总有一天 Kubernetes 会成为你需要的解决方案。这时,Python 也能提供帮助。Python 可以简化许多 Kubernetes 管理任务,而且由于大多数 Kubernetes 工作负载运行在云端,这些 SDK 也会非常有用。

我只会提供一个例子,即操作 Kubernetes 命名空间。为了安全起见,我们不希望因为计算资源问题而崩溃,尤其是当你对 Kubernetes 还不熟悉时。

命名空间是 Kubernetes 集群中的抽象,用于根据特定标准划分计算资源。标准可以是环境(开发、生产等)、网络限制,或基于可用的资源配额来定义命名空间。

你可以使用 Python 来创建和修改命名空间,并操作其中的资源。让我们来看一下初始化 Kubernetes 集群并使用 Python 管理它的步骤:

  1. 首先,你需要通过以下命令使用pip安装 Kubernetes:

    pip install kubernetes
    
  2. 接下来,让我们写一个脚本,在我们的集群中创建几个命名空间。我们可以稍后为这些命名空间添加资源:

图 4.16 – 从列表创建 Kubernetes 命名空间的代码

图 4.16 – 从列表创建 Kubernetes 命名空间的代码

  1. 现在,让我们为这些命名空间写一个策略,并将其实施到 Kubernetes 中:

图 4.17 – 为命名空间创建实施策略

图 4.17 – 为命名空间创建实施策略

这将为两个命名空间创建策略,阻止来自命名空间外部的外部流量。正如你所看到的,我们使用迭代器对两个命名空间实施了相同格式的策略。这只是使用 Python 在 Kubernetes 中进行自动化的众多方式之一。

最终,你可以将这些步骤自动化到一个程度,只需列出并可视化你的 Kubernetes 集群,按几个按钮就能根据需求调整。其他所有事情,集群将自动处理。

总结

本章让我们在使用 Python 的过程中进行了相当一番探索。我们搞清楚了 SDK 如何工作,它们的优势是什么,以及为什么 Python 在 SDK 使用的世界里如此有用。你几乎可以在应用之上再构建应用,我亲眼见过。

我们还学习了扩展性问题,以及如何因为需要在可用性和成本之间找到平衡而变得麻烦。这里,我们也发现了 Python 及其强大的 SDK 和数据处理能力的作用,帮助我们实现了这种平衡。

容器也得到了 Python 库的强力支持,这些库可以帮助填补 Docker 与 Kubernetes 之间的空白。我们了解了 Python 在管理这些服务中的辅助作用。

所以,总结来说,在这一章你学到了许多关于 Python SDK 的内容,包括如何使用它们进行自动扩展、调整大小以及管理容器。在下一章,我们将更详细地探讨如何使用 Python 操控和与已经配置好的资源进行交互。

第二部分:Python 在 DevOps 中的示例实现

本部分将涵盖一些常见 DevOps 用例的 Python 示例实现。

本部分包含以下章节:

  • 第五章资源操作

  • 第六章使用 Python 进行安全性与 DevSecOps

  • 第七章自动化任务

  • 第八章理解事件驱动架构

  • 第九章使用 Python 进行 CI/CD 流水线

第五章:操作资源

在这枚戒指中,他倾注了自己的残忍、恶意和支配一切生命的意志。一枚戒指,统治所有人。

– 加拉德瑞尔(《魔戒》)

在上一章中,我们讨论了如何创建资源。我们讨论了使用自动化每次都能正确配置资源的规格。那么,对于已有资源怎么办呢?

在一些实际场景中,你可能需要处理一些你没有从零开始创建的项目或工作负载,或者需要非常小心地调整它们。你也可能会与一些你控制权较小的资源打交道,比如在云中由云服务商管理的托管资源。

操作自己创建的资源很容易,但这些遗留资源和控制较少的资源是DevOps工程师的挑战。征服这些挑战需要学习如何操作资源,使其更符合 DevOps 理念。这可能涉及资源、代码、架构过程的改变,或者只是更好地理解工作负载,我们可以运用 DevOps 理念,使资源得到更好的配置和更好的性能。

说到性能,未来性能的最佳指标之一就是过去的性能。如果资源的使用情况能为我们提供一个模式,我们就可以利用这个模式来更好地预测资源的配置,并简化我们的工作负载。此外,任何关于使用模式的洞察,尤其是 DevOps 团队和应用团队如何处理资源并响应事件的方式,都是未来参考的有用信息。

采取预测性的方法非常有用,但这些预测必须建立在坚实的数据基础上。如果我们要进行预测和适应,必须根据某种逻辑来进行。这种逻辑必须被理解(这个世界上有些人做事只是因为别人告诉他们应该这么做,他们有用,但不能适应变化)才能采取有效的行动。

本章的主要目标是帮助你学习如何查看已有资源,并充分利用它们。这意味着了解资源如何扩展和缩减,以及这些资源的历史和历史组成部分。

本章将涵盖以下主题:

  • 使用 Python 作为事件触发器来调整资源以适应需求

  • 分析实时和历史数据,并将其应用于未来的工作负载

  • 逐步重构一个遗留应用

技术要求

完成本章,你将需要以下内容:

  • 一个 GitHub 账户

  • Python

  • 一个 AWS 账户

基于事件的资源调整

资源就是金钱。字面意思。在 DevOps 的世界里,常规做法是,你使用的资源越多,花费的金钱也就越多。这一点很简单,对吧?但是问题在于,当这些资源的回报不足以覆盖其成本时,问题就出现了。这种情况发生的主要原因之一是,资源的消耗是按照固定的速率进行的,即使它们并不需要。为了实现最优的资源消耗,出现了一种新方法:资源只有在实际使用时才会消耗,之后会根据需要适当缩减,直到下次使用。假设你正在进行购物狂欢。你并不清楚自己究竟会花多少钱,只是大致知道会在物品上花费多少。你很可能会超出自己预设的预算,除此之外,你可能还不一定对自己买的每一件商品感到满意。

DevOps 很像这种情况——它涉及到商品(资源)、你在这些商品上花费多少以及你能从中获得多少价值。保持购物/开销预算的一种常见方式是做一个漂亮的电子表格,记录下每一项支出。在这里,DevOps 也有相似之处,尽管在 DevOps 中,通常你会得到资源提供商提供的确切报价,并且你能比买菜更容易找到更好的交易——相信我。

现在,购物部分的最后一步是根据你所遇到的情况调整你购买的商品,对吧?如果你的朋友是素食主义者,你就做素食。如果他们得了糖尿病,菜单上就不再有糖分。如果你自己一个人,你可以买一桶冰淇淋,没有人会评判你。这正是我们希望在基于事件的调整中实现的目标。你可以根据接收到的事件类型来调整所提供的资源。你可以观察当前的事件,并相应地调整你的资源。

我们将通过几个例子来展示这个概念——一个涉及到本地化内容的全球分发,另一个涉及到对网站的新功能进行小范围用户测试。

基于边缘位置的资源共享

我们在全球应用中面临的最困难的挑战之一就是资源的加载时间。如果用户距离最近的数据中心太远,可能会导致他们使用应用时出现显著的延迟和卡顿。因此,大多数有大规模应用的公司都会在人口密集区域/网络流量高的区域设置数据中心和边缘缓存。边缘边缘位置只是一个离目标用户更近的数据中心,使得他们的加载时间更快。边缘缓存是存储在边缘位置的用户和网站数据(如 cookie),用于加速该用户的访问。这有助于用户快速访问能够提供最快响应的数据中心。

然而,现在的问题是,我们如何将这些用户引导到能够为他们提供最低延迟的适当数据中心或缓存?为了做到这一点,我们必须找到一种获取用户设备位置数据并将该数据用于重定向用户到最近数据中心的方法。

为了实现这一点,我们可以编写一个函数,从网页流量请求中提取特定的头部信息。让我们看看这段代码:

''' the request variable is a json that contains the request headers and body'''def process_request(request):'''get all headers'''headers  = request["headers"]''' we're using country in this example, but this is any arbitrary header you 	choose to pass '''request_country = headers["country"]''' throw to a function that maps each available server to a country '''server = server_map(request_country)''' return value from correct server as prescribed by server map '''return server_response(server, request)

这段代码的作用是挑选出国家头部,你需要自己定义(这是件好事,因为你可以自定义它),然后将网页请求引导到适当的服务器。请求和响应之间的这个小层能够对连接质量产生奇效。你可以在随后的图中看到这一点:

图 5.1 – 用于服务器位置映射的功能

图 5.1 – 用于服务器位置映射的功能

现在我们已经学会了如何将用户重定向到自定义位置,让我们看看如何将功能重定向到一组自定义用户。

在一小部分用户上测试功能

当一个应用程序需要实现新功能,应用程序团队希望在实时环境中测试该功能时,就需要一种机制将一小部分用户分类到将接收该新功能的组中。从这个组中获得的分析数据将用于根据特定标准评估该功能及其效果。反向操作——移除功能——也是这样进行的。如果你还记得 YouTube 删除他们不喜欢按钮上的数字时发生的情况,一些用户因为他们缓存的网站版本仍然保留了数周。即使如此,浏览器扩展程序也出现了,从 YouTube API 中提取正在观看的视频的不喜欢数字(直到 YouTube 完全从他们的 API 中删除这个功能)。

现在,你可能会想知道,鉴于这个功能可能已经在推出之前经过了一次又一次的测试,为什么还需要进行这样的测试呢?好吧,事实是这样的:

  • 也许并不是: 你会惊讶地发现,有多少公司愿意为一小部分用户推出新事物,而不经过用户验收测试。更令人惊讶的是,有时这并不是一种坏策略(但如果你要这样做,我建议你用我接下来要给你的示例)。

  • 用户会弄乱事情: 这是用户的本性,他们会以测试人员无法理解的方式搞砸事情。你无法完全理解人类的本性和其背后的混乱,而你所做的任何发明都必须经过其考验。采用更加受控的方法需要将你的工作扔给一小部分混乱,希望它能够经受住考验。

这种测试方法通常被称为A/B 测试方法

既然这些都解决了,我们就可以开始实施了。这个实现与边缘位置的实现非常相似,并且涉及到一个在这一方面非常相似的代理。

我们将以两种方式对用户进行子集划分:随机划分和基于某些标准划分。首先,让我们看看用于随机分配用户请求的代码(20% 分配到一台服务器,80% 分配到另一台服务器):

''' get request from user '''def process_request(request):''' get a random number between 1 and 10 '''num = random.randrange(1,10)''' conditionally return site containing special feature '''if num<=2:return special_response(request)else:return regular_response(request)

如果你愿意,可以选择一个不那么随意的范围。只需记得以不同的方式追踪这两个响应之间的统计数据,以获得正确的洞察。另一种选择是功能标志,但这需要根据特定标准将用户划分为子集,这正是我们在接下来的代码块中要讨论的内容。如果你不想区分用户或无法区分,只是想要活动数据,那么前一种方法是不错的选择。

''' get request from user '''def process_request(request):'''get request header with feature flag '''header = request["header"]feature = header["feature"]''' Check if feature flag is turned on, i.e. if it is True or False '''if feature:return featured_response(request)else:return normal_response(request)

在这里,我们可以看到启用了功能标志的用户获得了特色响应(如果他们的功能标志开启,他们会收到的独特响应)。这些特色响应与常规响应不同。功能标志的激活可以是对数据库的随机编辑,或者是提供给用户的选择加入优惠。再次强调,这两个资源的数据需要区分开来,以确保最大效果。

说到数据(我一直在说),现在我们拥有了来自使用功能标志的用户和未使用功能标志的用户的所有分析和操作数据,以及所有这些事件的发生,我们必须对这些数据做些事情——我们需要分析它并生成洞察。

数据分析

我发现——作为一个成年人——我越来越变得对自己更有责任感。然而,这种责任以及我现在的这个人,来自我过去的一系列事件。错误、成功和介于其中的一切,塑造了我和我对生活的态度。

我的许多生活方法也恰好是我对 DevOps 的方法——这就是事情的演变过程。通过我在生活(和 DevOps)中的经验,我发现了两件事:你必须为现在的自己而活,而这个人是由你的过去定义的,但又不完全是过去的那个人。

你的工作负载遵循类似的模式。它基于你的历史,但不能完全被认为是以前的样子。代码可能已经改变,基础设施也不同,甚至实施它的人员很可能已经发生变化。然而,尽管如此,过去的经验中仍有值得学习的教训。重要的教训。

在本书的早期部分(准确地说是第一章,《引入 DevOps 原则》),我强调了监控日志记录的必要性,并表示这些是处理事件和维护工作负载历史性能的绝佳工具。现在,我们将开始探索如何利用这些历史性能为我们提供可用的洞察。

我们将讨论几种分析技术:实时数据分析和历史数据分析。每一种都有其挑战。每一种都可以作为解决相当常见的 DevOps 问题的模板。

实时数据分析

实时或流式数据是系统当前不断处理的数据。它是系统接收或返回的数据。一个系统依赖于它所吸收和生成的数据(输入 -> 处理 -> 输出)。系统是由这些数据塑造的,有时,在关键系统中,它需要由这些数据来塑造。要根据最近的数据做出战术决策,收集实时数据并对其进行即时分析是必需的。

大多数云服务和监控系统都提供了存储和分析实时数据的默认方式。通常情况下,它们非常有效。它们可以存储数据,并在一定程度上生成数据的洞察。然而,有时需要采用自定义方法,这就是 Python 的优势所在。不是因为速度,而是因为方便性和预构建的分析库,Python(即使只有其默认库)也可以对系统输出的几乎任何类型的数据进行数据分析和转换。

所以,让我们来看一个示例,使用 Python 的内建marshal 库解码一个字节字符串

import marshal''' function to decode bytes '''def decode_bytes(data):''' Load binary data into readable data with marshal '''result = marshal.loads(data)''' Return raw data '''return result

字节字符串常用于网络通信密码学(这两者通常涉及实时数据),将其转换为其他语言可能需要添加库并可能创建自定义数据类型。而使用 Python 时没有这样的需求。

但这通常适用于较小的数据量和最近的数据,有时甚至是最近的毫秒级数据。对于真正的历史分析,你需要数百万行数据。你还需要能够分析这些数据的工具。在这方面,Python 更加出色。

历史数据分析

实时数据可以帮助我们进行调整;它们是战术性的。但要真正具备战略眼光——考虑全局并着眼长远——你需要历史数据和分析历史数据的方法。过去包含了大量数据;这些数据中蕴含了模式,而这些模式正是优化工作负载的关键所在。

历史数据分析需要将数据转换为程序可以大规模读取的格式。一旦数据被格式化,它就可以输入到一个算法中,处理成数据工程师可能需要的有用信息。

处理历史数据通常意味着处理一致且数据量大的数据。然而,其中一个潜在的变量是数据是否一致。通常,当记录数据的软件被更换或升级时,数据的某些方面也会发生变化。调和旧的和新的历史数据是 DevOps 工程师在处理数据时可能面临的挑战之一。

接下来,工程师可能面临的挑战是如何以人类或机器都能读取的格式展示数据。对于人类来说,这通常是某种文档或可视化。对于机器来说,则是某种可以读取的数据格式。

无论如何,所有这些数据都需要进行大规模的数据分析。你可以通过使用 Python 的多进程库来加速这一过程,通过利用多个 CPU 核心并行批处理大量数据。让我们通过代码来深入了解如何使用多个 CPU 核心:

import multiprocessing''' Get list and return sum '''def get_sum(numerical_array):''' return sum of array '''return sum(numerical_array)'''method that calls sum method to get total sum for 1 million records '''def get_total_sum(full_array):''' Initialize 4 processors '''pool = multiprocessing.Pool(processes=4)''' List of sums in 10,000 number blocks '''sum_list = []''' Get 10,000 values at a time for sum out of 1,000,000''''for i in range(0, count(full_array)/10000):sum_array = full_array[i*10000:(i+1)*10000]'''Make an array of 10,000 length arrays '''sum_list.append(sum_array)''' Get final array of sums of  each 10,000 value array '''final_array_list = pool.map(get_sum, sum_list)''' Get one final sum of all these elements '''return sum(final_array_list)

在这段代码中,一个包含 100 万个值的数组被分割成 100 个包含 10,000 个值的小数组,每个小数组的总和用于计算该数组的总值。这个过程通过多进程库在四个不同的处理器上同时进行。将这个庞大的任务拆分成较小的任务,可以优化资源用于数据处理。

好吧,这一切听起来都不错。但数据从哪里来呢?通常是你的应用程序。好吧,既然数据要好,应用程序就得好,对吧?是的,但这并不总是如此。让我们看看当一些应用程序停滞不前并变得过时时会发生什么。

重构旧应用程序

一个干净的白板是世上最方便的东西之一。我将给你看两张纸,你可以告诉我你想在其中哪一张上画画:

图 5.2 – 新应用程序与旧应用程序的对比图

图 5.2 – 新应用程序与旧应用程序的对比图

如果你选择了正确的道路,恭喜你——你现在是一名 DevOps 工程师!别太过深究这张图,它只是我随便拼凑的些东西。

你看,DevOps 工程师很少会直接工作在自己的代码上。即便是开发者,现在也很少能完全自己编写代码了。根据我的经验,我在任何非个人项目中,最多只有一两次真正从零开始做项目。对于那些刚刚起步的人来说,可能只处理过自己编写的项目或者一些找到的模板项目。然而,在实际工作中,这种情况是非常罕见的。

即使你在一个干净的白板上工作,除非你已经用自己定制的操作系统编写了汇编代码,否则你的工作负载仍然依赖于大量的依赖项(如果你真是这样做的,你得多无聊啊?)。

你所使用的 99%的工具都是由别人开发的——有时是数百或数千个不同技能水平、能力和观点的人的工作成果。你走的是由众多架构师(有时没有路线图/文档的情况下)所铺就的道路。

你看,最初,这很简单——你从代码的最新版本开始,将它托管在localhost上,瞧,瞬间你成为了你世界的主宰。但随之而来的是,你会意识到每个人都这么做,大家都急于保持那种控制感,即使是在专业环境中,也不顾其对工作质量的后果。这就导致了你会看到关键基础设施已经 5 年没有更新,关键服务仍然运行在已停产的产品上,而且团队既不愿意无法(不是任意一个,而是都)做出改进。这就是如何陷入技术债务的原因(技术术语,指糟糕的规划能力)。

要摆脱技术债务,你有三种选择:优化、重构或重新启动。这意味着你有三种选择(不仅仅是在 DevOps 中,实际上在人生中也是如此):接受你的错误、改善它们,或重新开始。你想做什么(以及你能做什么)取决于你和你的具体情况。我们将一起探讨这三种选择,帮助你找出最适合你工作负载的方案。

优化

你的第一个选择是优化。你可以查看当前的状态,确保它运行得尽可能高效,消耗最小的资源,同时保持所有功能完好并对用户可用。很多时候(尤其是在网站可靠性工程SRE)领域——一个你几乎只处理遗留应用的领域),优化你的遗留应用可能是唯一的选择,尤其是当你在处理一个关键应用时,无法改变其中一个方面而不需要同时改变许多其他方面——你可以称之为一个单体应用。这些限制就是为什么许多公司希望摆脱单体架构。

在优化遗留应用的情况下,我们的选择非常有限。但这并不意味着我们什么都不能做。DevOps 中最重要的概念之一是期望状态配置DSC),其重要性就在于维护这些系统。

在 DSC 中,虚拟机会被配置为必须维持某些设定。会不时检查这些设定的状态。如果状态发生了显著变化,那么它将被重置,恢复到原本的期望状态。

这一切都发生在幕后,并不会真正影响应用本身。这可能是处理无法重构或容器化到 Docker 容器中的应用的最佳方式。

重构

如果你在处理的代码质量尚可,或者你的项目团队相对可靠(这需要一些假设,但请耐心),那么你可能仅仅在处理一个已经过时并需要更新和调整的工作负载,或者是一个不再能满足所有需求的工作负载。在这些情况下,重构提供了一个健康的折衷方案,连接旧与新。

重构你的代码库可能包括一些简单的操作,比如升级依赖项、编写新组件,或移除不必要的组件。也可能涉及一个漫长且艰难的过程,比如将网站的前端和后端分离。

让我们来看一下最著名的重构方法:strangler fig。在自然界中,strangler fig 是一种植物,它以树木为基础生长,最终将树木勒死并取而代之。strangler fig 对于遗留应用的工作原理也类似。

以下是进行 strangler fig 重构的步骤:

  1. 分离数据库:如果数据库服务器还没有与应用服务器分离,现在就分离。这将有助于两者的可扩展性。

  2. 将第三方 API 调用转化为函数/微服务:如果调用了任何你没有直接维护的第三方 API,将它们拆分成可以在重构后的应用中调用的函数。将它们与其余的后端部分分开。

  3. 将后端和前端分离:将处理数据的后端部分和处理用户交互的前端部分分开。这些部分需要通过一个称为前端代理后端的缓冲区进行通信,后者是前端与 API 交互的中介。

  4. 将非关键的后端功能拆分为微服务:将那些仅处理数据且不与数据库交互的服务拆分为更小的微服务,这样它们就能独立且更具规模地处理数据。

  5. 创建数据库连接机制,并将其余的后端拆分为微服务:最后,应用的核心通过数据库连接字符串实现松耦合,这使得只要有正确的凭证,数据就能从任何地方进行操作。

听起来有点棘手,对吧?确实如此。但它也有回报……有时候。重构应用——无论其效果如何——是你实践学习构建和维护应用的最有效方式之一。但它确实是一个心力消耗的过程。你不希望能从前人的错误中解脱出来,重新开始一张白纸吗?如果是的话,那么接下来的部分将非常适合你的计划。

重启

有时候——希望这个时机能在开发过程的早期——你需要将已有的东西完全抛弃,重新开始。如我之前所说,白纸化是最容易的起点。如果你认为你的应用已经无法挽救,且无法从中挽回任何有价值的东西,那么你可以从头开始构建应用。

很多时候,因为栈的不兼容,或者旧组件没有任何价值,迁移组件在旧应用程序和新应用程序之间并不合适。在这些情况下,最有价值的是旧应用程序生成的数据,并将这些数据迁移到可以与新应用程序互动的数据库中。

如果你正在开发一个新应用程序,老实说,按你的方式做吧。但有一些建议:

  • 做出长期的决策,而不仅仅是基于你现在的想法。

  • 做出以质量为优先的决策,但也要明白,错误总是不可避免的。

  • 不要对每件事做出反应;过于反应性地处理问题 100%会导致应用程序变得更差。如果可以的话,不要对任何事情做出反应,要主动出击。

  • 将质量、可读性和可维护性放在首位。不要认为以后会回头处理某些事情。这种想法会在以后给你(或者更糟的是,给别人)带来许多额外的工作。

重新启动一个应用程序并从头开始构建是一个具有吸引力的概念。然而,别重启太多次,因为你最终什么都做不成。你最多只能重启一个项目这么多次,直到你意识到,或许是你的方法本身出了问题。

总结

本章有一些你可能没有预见到的绕道。这是一个关于简化、寻找更好方式完成工作,以及优化资源利用以完成工作的章节。

你学会了如何更快速、更定制化地向用户传递内容,同时收集他们的数据以便进行分析。你还学习了数据收集过程的方法和格式,以及 Python 在处理这些数据中所扮演的重要角色。

你还了解了应用程序、应用程序浪费是如何产生的,以及这种浪费如何在未来几年影响你的工作负载和继任者。你也学习了一些方法,可以缓解、绕过或消除这个问题。

总之,你现在已经明白了,资源的利用效率不仅仅是为了资源本身,更是为了节省你自己的时间。我希望你能从这一章的内容中反思,应用到你的 DevOps 工作负载和你的购物清单中。

在下一章中,我们将讨论一个我特别喜欢的话题:自动化。我们将找到一些方法,使我们的生活更轻松,减少那些我们不该被打扰的事情。

第六章:使用 Python 进行安全和 DevSecOps

外面的世界一片混乱,四处都是无序和困惑…

– Randy Newman

(如果你知道这首歌的其他部分,真棒。)

你最常用的密码是什么?你从不重复使用密码吗?在这种情况下,别对我撒谎,也别对自己撒谎,以免你被锁定在账户之外。我在这里提到密码,因为这是打开关于安全话题对话的最佳方式,密码也是最容易理解安全的方式。它们提供了一种安全的方式来区分账户和用户,并为每个用户设置独立的访问权限。本章将讨论的内容会稍微复杂一些(只是稍微复杂),但基本原理是一样的:我们讨论的是如何保护访问权限,确保只有正确的人才能访问。

DevSecOps(一个专注于安全的DevOps分支)中,目标是在任何安全漏洞发生之前、期间和之后都能采取安全措施。这要求使用最佳实践来保护访问密钥和其他凭证,确保基础设施(如容器)的安全,以及在发生安全事件后同样采取相应的最佳实践。

安全是应用团队中每个成员的责任,这个责任要求团队成员使用不会妥协安全的工具和技术,并能促进安全流程的自动化,从而减少人为错误。

恰巧我们知道一个非常好的工具来实现这一点,不是吗?没错,Python在促进和支持安全性与加密方面非常强大,而且它灵活易用,即使是没有太多编程经验的安全工程师也能轻松上手。Python 的瑞士军刀特性让它能够在本章中无缝整合到任何地方。

那么,让我们来看看本章将要探索的内容:

  • 我们将学习如何使用 Python 来保护和混淆敏感代码、API 密钥和密码。

  • 我们将了解容器验证是什么,以及 Python 如何在需要时帮助我们再次检查这个过程。

  • 我们将了解 Python 提供的工具,如何利用这些工具促进事件监控和响应。

技术要求

以下是一些要求,帮助你跟上本章活动的进度:

  • 一个 GitHub 帐户,用于克隆包含示例代码的代码库

  • 一个 AWS 帐户

  • 一个 Google Cloud 帐户

  • Google Colab

  • 任何安装了 Python 的环境

  • 宽容我的讽刺语气

你可以访问本书的代码库:github.com/PacktPublishing/Hands-On-Python-for-DevOps

保护 API 密钥和密码

API 密钥和密码之所以有价值,是因为大多数东西之所以有价值,是因为它们需要付出金钱,而且它们所保护的数据也非常珍贵。如果你看过任何一部盗窃电影(如果你没看过,我推荐Heat,这是一部非常好的电影),你就会知道,盗窃团队总是需要获得某种访问代码、签名或保险箱密码。这是因为,秘密代码、密码或加密在整个人类文明中作为访问敏感信息的障碍的概念是永恒的。密码在计算机出现之前已经存在了数万年。

在网络领域,一个盗窃团队也会尝试类似的策略。他们会试图从那些可能拥有密码和凭证的人那里提取信息,以便获取敏感数据。有时,这些信息本身并不一定是敏感的,有时人们只是因为无聊或者喜欢社会工程学(我听说有些人上瘾于操控别人,这让人感到相当可怕)。无论如何,你的信息安全始终受到攻击,无论是在服务器端还是用户端。因此,对于这类攻击的预防措施和机制是必要的。

现在,我们进入 Python 部分。我们可以尝试几种方法,通过 Python 来实现这个概念。我们可以创建环境变量和秘密,将敏感变量存储在单独的文件夹中或操作系统(OS)配置中。我们还可以使用 Python(独立使用或与其他工具或 API 一起使用)来提取和模糊化个人身份信息PII)。

存储环境变量

环境变量的存储不仅是为了将凭证与应用代码分离,以防止硬编码,还为了确保应用程序可以在不同的系统上使用不同的凭证运行,只需将这些凭证配置到操作系统或文件中即可。

这将我们引入了两种存储和检索环境变量的方法:我们可以将它们存储为文件或在操作系统中定义的变量。当然,你也可以在云端密钥管理器中或使用硬件设备来完成,但这些概念与我们正在讨论的类似。

对于第一种方式(文件),你可以通过以下方式创建并读取.env文件:

  1. 这些文件是存储环境变量的标准,并且在几乎所有的.gitignore文件中都会被忽略。要读取这些文件,我们首先需要安装 Python 的python-dotenv库:

    pip install python-dotenv
    
  2. 在此之后,我们可以创建一个.env文件,并将变量和秘密存储其中。我们根据它们所在的行来分隔这些秘密。以下是一个.env文件的几行示例:

    API_KEY = <insert_key_here>
    API_SECRET_KEY = <insert_secret_key_here>
    

    记住,大写字母是用于常量的。这种方法有助于将常量集中在一个地方,这样你就不必担心在代码中的多个位置更改它们,也不用担心某个地方漏掉了某个修改。

  3. 现在,让我们编写代码,让程序能够访问环境变量:

    from dotenv import load_dotenv
    import os
    #load .env file
    load_dotenv()
    #load API keys into variables
    api_key = os.getenv("API_KEY")
    api_secret_key = os.getenv("API_SECRET_KEY")
    

    运行此代码后,你将获得 API 密钥和密钥,在各自的变量中。

接下来,让我们尝试相同的操作,但直接从我们自己的操作系统中导出并使用环境变量:

  1. 在 Linux 中——这种服务器最为普遍——我们可以像在之前的例子中那样,使用以下命令设置环境变量:

    export API_KEY = <insert_key_here>
    API_KEY and API_SECRET_KEY in your OS.
    
  2. 现在的问题是如何访问这些值:

    import os
    # Get the API key and secret access key from the environment
    api_key = os.environ.get("API_KEY")
    api_secret_key = os.environ.get("API_SECRET_KEY")
    

    你会发现我们仍然在使用之前代码中的 os 库。对于较少数量的环境变量,这种方法相对更简单,也许比 .env 文件更安全。但是,当环境变量数量很大时,.env 文件在聚合和使用管理方面会更加方便。

这些方法教会了你如何处理你自己负责的敏感信息,并且这些信息是你自己添加到代码库中的。然而,终有一天,你会遇到需要处理他人敏感数据的情况。这些数据需要保护,你必须确保它不会落入不法之手。关键是什么?让这些数据对除你以外的任何用途都变得无用。接下来我们将讨论如何模糊处理个人身份信息(PII)。

提取并模糊处理个人身份信息(PII)

一个人的敏感个人信息是他们拥有的最有价值的东西之一:无论是在财务上、社交上,还是亲密关系上。泄露这些信息的安全性可能会对个人造成极大的伤害,涉及这三方面的各类损失。人们的隐私至关重要,我们必须采取一切可能的措施来保护它,尤其是当他们将部分信息托付给我们提供的服务时。

那么,开始这个工作的最佳方法是什么呢?有很多现成的服务,例如 Amazon Macie 或 Google Cloud 的 DLP(即 数据丢失预防)服务,这些服务非常方便,但你可能无法理解它们的内部运作,因为它们是基于某些机器学习算法进行训练的,这些算法几乎不需要用户输入(如果有的话),就能自动对各种个人信息进行删除和模糊处理。

但假设有一条信息不被这些服务覆盖,或者由于合规原因你无法使用它们。那么,你就需要从头开始创建自己的解决方案。在这种情况下,Python 就是你的好帮手。你可以使用 Python 读取文件,找到包含敏感信息的位置(基于某些标准),并以一种隐藏或模糊处理这些信息的方式修改它们。这种技术与通过数据挖掘找到重要模式来提取数据的方式相同,只不过在这种情况下,我们不是提取数据,而是确保如果一些恶意行为者尝试提取数据,他们无法恢复任何重要信息。

为了演示这个,我们将使用一个非常简单的正则表达式regex)模式来查找文本中的电话号码,并将其替换为某种形式的遮掩。我们可以尝试一个稍微复杂一点的正则表达式,但本质上还是相同的概念。如果你是正则表达式的新手,我建议你从简单的开始,慢慢发现正则表达式的魔力。真的,你会觉得自己像个巫师。

现在不需要再做过多的姿态展示了;让我们开始进入正题。首先,我们需要一个正则表达式来捕获电话号码的模式。除非你真的想深入了解正则表达式,否则不要自己尝试写一个。对于大多数使用场景,你可以在互联网上找到一个适合的现成正则表达式。在大多数情况下,你可以直接使用它,而在一些非常特定的情况下,你可能需要进行一些调整。所以,覆盖电话号码模式(包括带有国家代码和不带国家代码的)正则表达式可以这样写:\d{3}-\d{3}-\d{4}'

这对你来说可能看起来像一堆无意义的字符,但它有效,你应该相信这一点(可能有人为了让它完全正确而几乎疯掉)。这个正则表达式适用于带有和不带有国家代码的电话号码(尽管你也可以写一个适用于两者的正则表达式)。现在,让我们在一个包含电话号码的小段文本中实现这个正则表达式:

#initial textimport retext = "The first number is 901-895-7906\. The second number is: 081-548-3262"#pattern for searchsearch_pattern = r'\d{3}-\d{3}-\d{4}'#replacement for patternreplacement_text = "<phone_number>"#text replacementnew_text = re.sub(search_pattern, replacement_text, text)#output given: "The first number is <phone_number>. The second number is: <phone_number>"print(new_text)

好了,就这样;这段代码使用正则表达式模式找到电话号码,并通过替换电话号码来进行模糊处理。

这是迄今为止最简单且最容易获取正则表达式的方法,但你可以让它变得更加复杂。你可以用它来模糊处理社会保障号码、护照号码,以及几乎任何符合预定义模式的东西。

到目前为止,我们只在最简单的文本级别上进行了安全性处理。现在,我们需要关注基础设施的安全性。为此,我们可以看看容器镜像,因为它们非常普遍,而且验证它们非常重要。让我们看看如何验证这些镜像。

使用二进制授权验证和验证容器镜像

目前为止我已经花了很多时间谈论容器,这可能已经让你意识到容器可能有些重要了。当然,容器是应用程序中运行一个服务所需的所有资源需求和库的封装体。容器之间的隔离导致了每个容器中运行的服务所需的库之间的冲突消除,有效地为整体大系统或应用程序中的每个服务创建了一个隔离的系统。

然而,这也带来了双重漏洞:在某些情况下会增加复杂性和更大的威胁向量。管理所有这些容器和其中复杂的底层库(因此需要 Kubernetes)可能是一项艰巨的任务。不正确地管理这些复杂系统可能会导致系统中的结构性和信息性漏洞的产生。同时,如果多个集群暴露在面向公众的环境中,或者以某种方式可以被访问以赋予未经授权的用户对系统的特权访问,那么对一个容器的威胁可能会威胁到系统中存在的所有容器,以及所有您的信息。

Python 的 Docker 库确保您可以以自动化方式处理镜像的每一层。Python 还可以使用多种第三方镜像验证工具。我们将探讨其中一种工具与 Google 的二进制授权 API 和 Kubernetes 工作流。因此,让我们看看如何使用二进制授权验证合规性镜像!

合规性 是一个有趣的主题,其中很大一部分取决于看客的眼光。合规性的定义取决于您希望多么严格。在 Kubernetes - 特别是在Google Kubernetes Engine中 - 必须有某种保证或规定,以确保已使用正确的镜像。这个机制被称为二进制授权

二进制授权允许检查在 Kubernetes 集群中部署的 Docker 容器是否符合某些标准。授权可以通过使用合规性策略或认证者来完成。合规性策略创建规则,只允许满足某些标准的镜像。添加认证者意味着在您的项目中添加一个个体用户,可以证明将要使用的镜像是正确的。在二进制授权中,您可以使用这两者中的一个或两者都使用,以授权您的 Kubernetes 集群中的镜像。让我们看看如何做到这一点:

  1. 现在我们将编写一个脚本来创建一个认证者,并将该认证者分配给 Kubernetes 引擎。首先,我们必须安装容器和二进制授权的客户端库:

    pip install google-cloud-binary-authorization google-cloud-container
    
  2. 现在,让我们为集群的二进制授权编写脚本:

    from google.cloud import binaryauthorization_v1
    def sample_create_attestor():
        client = binaryauthorization_v1.BinauthzManagementServiceV1Client()
        attestor = binaryauthorization_v1.Attestor()
        attestor.name = <Enter_attestor_name>
        request = binaryauthorization_v1.CreateAttestorRequest(
            parent=<Enter_parent_value_of_attestor>,
            attestor_id=<Enter_attestor_id>,
            attestor=attestor,
        )
        client.create_attestor(request=request)
    

    这将创建一个能对您的集群请求进行认证的认证者。

  3. 现在,我们需要将二进制授权添加到已创建或即将创建的集群中:

    from google.cloud import container_v1
    def sample_update_cluster():
        client = container_v1.ClusterManagerClient()
        request = container_v1.UpdateClusterRequest(
    "desired_node_pool_id": <Node_pool_to_update>,
    "update": {
    "desired_binary_authorization": {
    "enabled": True,
    "evaluation_mode": 2
    }
    }
        )
        client.update_cluster(request=request)
    

    这个脚本将更新 Kubernetes 集群,使其开始使用我们的二进制授权方法。这可以解决你基础设施内部的事件。

现在,我们将探讨一些可以帮助你应对基础设施之外的事件的内容。

事件监控与响应

那么,如何定义一个事件呢?让我问你这个问题:如果一只熊袭击了你,你会怎么做?听起来很傻吗?这些问题是不是太多了?嗯,这个问题实际上是一种直接理解事件以及如何反应的方式。

熊正在袭击你,它比你大得多,嗯,你肯定不希望它抓到你。在这种情况下,熊的攻击就是事件,熊本身只是事件的一个载体。事件的结果取决于响应,而要有好的响应,你需要冷静(你需要监控局势,明白了吗?)。无论如何,事件响应的报告都会以某种方式出现。然而,如果你想成为那个写报告的人,你必须正确处理事件。

实际的安全事件并不像那样残酷(至少不在身体上)。别担心。但它们的工作方式类似。事件发生时,安全性在某种程度上受到威胁,存在用于利用漏洞的工具,而一旦漏洞被利用,就必须冷静处理并尽可能进行缓解。一旦完成并恢复了秩序,事件响应需要被记录并分发给相关方。

现在,Python 的作用就体现出来了。为了应对可能针对虚拟机群的安全威胁,Python 可以帮助运行所谓的运行手册,它是一系列命令,可以用来重置系统或让系统响应某种威胁。另一种使用 Python 的方式是查看事件发生时的监控数据,并将其与常规数据进行对比,从中寻找可以预测并提前应对未来事件的模式。

执行运行手册

当我们谈论事件响应时,最常见的响应方式之一就是重启受影响的资源。这种方法大约在 97%的情况下有效。说真的。但有时人们也不愿意这么做。对我来说,当我的笔记本蓝牙出现故障时,我不得不重启它。虽然这不合逻辑,也让我莫名其妙地烦恼,但它确实能解决问题(不像我在网上找到的 500 种重新启动蓝牙的办法,最终我还是选择重启)。

我们要聪明一点,集中精力解决那个 97%的部分,同时,我们也会提供一个蓝图,让你能够运行更复杂的代码和过程,按照你的需要进行编写。这个文档特别适配了 AWS,但类似的集群管理和操作也可以在所有主要云平台以及大多数本地操作中找到。

在这个练习中,我们使用AWS EventBridge触发一个步骤函数,随后在触发事件的目标 EC2 实例上运行命令,从内部重启该实例。再次强调,我们保持命令简单,但如果你需要,也可以做得更复杂。那么,让我们开始吧:

  1. 让我们从一个正在运行的实例开始。我将我的命名为test1。很有创意吧,我知道。

图 6.1 – 正在运行的实例

图 6.1 – 正在运行的实例

假设我们在一个场景中,这个实例有大量的网络流量通过。如果流量降到某个水平以下,就会表示实例出现故障。此时,可以通过一个简单的运行手册文档来解决这个故障,重启实例。

  1. 现在让我们在AWS Lambda中创建该命令的结构。但首先,我们需要发送的命令,它可以在Systems Manager中找到。打开Systems Manager,然后在侧边栏底部转到Documents标签页:

图 6.2 – AWS Systems Manager

图 6.2 – AWS Systems Manager

  1. 在文档中,搜索AWS-RestartEC2Instance文档。这是默认文档,你可以基于它创建许多其他虚拟机操作文档:

图 6.3 – 执行 EC2 重启的文档

图 6.3 – 执行 EC2 重启的文档

这是一个简单的基础文档,可以作为你执行任何脚本操作的基础。大多数其他云服务提供商也有类似的文档。

图 6.4 – 详细的 AWS-RestartEC2Instance 文档

图 6.4 – 详细的 AWS-RestartEC2Instance 文档

如果我们仔细观察一下,可以看到这个文档会根据实例 ID 停止并重新启动一个特定的实例,而实例 ID 就是我们需要提供的内容。

  1. 现在,让我们编写一个 EventBridge 事件,该事件将在五分钟内网络流量下降时触发一个 Lambda 函数。为此,首先转到 Amazon CloudWatch 并创建一个在我们的条件下触发的报警。转到CloudWatch | Alarms | Create alarm

图 6.5 – Cloudwatch Alarms 控制台

图 6.5 – Cloudwatch Alarms 控制台

我们现在可以为我们的test 1实例创建一个单独的报警,它将以图形形式显示,帮助你了解当前指标的变化情况:

图 6.6 – 创建报警指标

图 6.6 – 创建报警指标

  1. 接下来,让我们为警报的触发条件设置网络输入低于20,000字节,且持续五分钟:

图 6.7 – 设置警报的阈值

图 6.7 – 设置警报的阈值

  1. 在提示你输入警报名称的字段中命名警报为 test1-alarm,然后就可以开始了。

    现在,我们可以查看 详情 标签中的警报,找到 EventBridge 规则,我们需要用它来设置 EventBridge 触发器:

图 6.8 – 警报配置详情

图 6.8 – 警报配置详情

  1. 然后,进入 AWS 部分的 Amazon EventBridge,接着到 规则 标签页,在那里你将创建一个新事件。在第一页,选择 带有事件模式的规则,然后继续到下一页。在这里,你将粘贴从警报中获取的 EventBridge 规则:

图 6.9 – 事件桥的模式创建

图 6.9 – 事件桥的模式创建

  1. 现在你有了模式,点击 下一步,然后在另一个标签页中打开 AWS Lambda。在这里,我们将编写 Python 代码来执行 EC2 实例的重启操作。

    在 AWS Lambda 中,选择 Python 执行环境并保持所有默认设置不变。然后,添加以下代码:

    import json
    import boto3
    client = boto3.client('ssm')
    def lambda_handler(event, context):
        instance_id = event["instanceids"]
        client.send_command(InstanceIds = [instance_id], DocumentName = "AWS-RestartEC2Instance")
        return "Command to restart has been sent."
    

    这段代码将发送播放手册文档的命令来重启 EC2 实例。

    现在,让我们选择 Lambda 函数 作为 EventBridge 的目标:

图 6.10 – 目标 Lambda 函数

图 6.10 – 目标 Lambda 函数

  1. 不断点击 下一步,直到创建 EventBridge 规则。当警报被触发时,该规则将触发并运行 Lambda 函数。这将重启实例,并持续进行,直到网络输入恢复到可接受的水平。

现在,在这个例子中,我们看到一个基于即时反应和执行的应用程序。如果你的系统受到某种攻击,目的是压垮它或利用你无法修补的漏洞,这种方式是有效的。然而,攻击通常不是单独发生的。它们是有针对性的并且频繁发生,不断尝试寻找某种漏洞以攻击你的工作负载。为了帮助理解潜在的攻击模式,并了解你的工作负载如何应对变化,你可以使用监控日志的模式分析。

监控日志的模式分析

监控日志可以为你提供应用程序表现的洞察,并且如果在时间线中检测到一些异常模式,可以帮助你发现问题。例如,分布式拒绝服务DDoS)攻击通常会在应用程序中形成无法解释的高 CPU 使用模式。它基本上使应用程序遭受虚假的负载,这会影响到应用程序中的实际负载。

因此,为了检测这些攻击,我们必须有一个能够历史性地分析工作负载并潜在地发现这些攻击的算法。为了找到这些模式,Python 有一些很棒的数据科学库。

服务器日志的公共数据集很难找到,但它们可以相对容易地重新创建。对于这个示例,我将使用公共数据集模拟服务Mockaroo。它允许你免费创建一个包含 1,000 行的数据集。我将只创建包含 CPU 利用率和时间戳的数据集。

图 6.11 – 生成模拟数据集

图 6.11 – 生成模拟数据集

一旦数据生成,我们会看到它没有根据时间排序,因此这是我们将在脚本中添加的额外步骤。我们将使用 Google Colab 来编写我们的脚本。那么,让我们开始吧:

  1. 首先,安装 pandasmatplotlib 库。它们通常已经存在,但这一步对于其他笔记本应用很有帮助:

图 6.12 – 安装命令

图 6.12 – 安装命令

  1. 接下来,将 Mockaroo 创建的 CSV 文件上传到 Colab。为此,点击侧边面板中的文件夹图标,然后点击其中的第一个图标上传数据:

图 6.13 – 上传模拟数据

图 6.13 – 上传模拟数据

你会看到这里我已经上传了我的数据。

  1. 现在,下一步是使用 pandas 读取数据,创建数据框并按时间排序,因为 CSV 中的模拟数据并未预先按顺序排列。

图 6.14 – CPU 利用率表格

图 6.14 – CPU 利用率表格

  1. 现在,为了发现数据中的模式,我们可以开始通过可视化数据。让我们将数据作为线性图进行可视化,以时间戳作为 x 轴,CPU 利用率作为 y 轴:

图 6.15 – 绘制 CPU 利用率的代码

图 6.15 – 绘制 CPU 利用率的代码

现在,图表看起来有些随机,因为数据是随机生成的,因此该图表不一定代表实际图表的样子。

图 6.16 – 绘制 CPU 利用率随时间变化的图表

图 6.16 – 绘制 CPU 利用率随时间变化的图表

所以,这看起来很随机,但没关系,因为它只是为了代表一个随时间变化的条形图。让我们稍微调整一下数据集,制造一些异常。

  1. 所以,我们将修改数据集,使其中间有一个恒定的利用率:

图 6.17 – 修改数据集的图表

图 6.17 – 修改数据集的图表

这有点荒谬,但我相信它说明了持续正常运行的重要性。大多数系统在稳定时,并不会有这种利用率。如果你的服务正在产生这种数据,这要么表示成功的突然暴涨,要么是一次 DDoS 攻击。大多数情况下,是后者。所以,如果你在监控数据中发现这种激增,值得进行调查。

总结

在这一章中,你希望学到了一些关于安全的知识。我也希望你已经弄清楚了如何搭建基础设施,使得你所使用的所有访问密钥都能得到保障。

除此之外,你还学习了一些方法(成千上万种方法中的一部分),可以用来帮助保护你的基础设施。你可能还获得了一些洞见,了解到如何利用数据来驱动事件响应,并更好地理解某些事件及其特性。

所以,朋友们,我希望你们喜欢这一章,毕竟这是关于安全的内容。我尽力向你们解释了几个概念,希望你们至少觉得它有趣。但现在,是时候翻过这一页,进入下一章,我们将讨论我最喜欢的话题之一:自动化

第七章:自动化任务

未来的目标是实现完全失业。这样我们就可以去玩了。

– 亚瑟·C·克拉克

这可能会是我最喜欢的章节。说真的,一旦你品尝过自动化的甜美甘露,你就不会再回头了。嗯,我猜我有点超前了。我将从时间的概念开始。控制你命运、生活方式以及你做什么的唯一方法,就是控制你自己的时间,通过选择你要做的事。为了做到这一点,你需要选择如何使用你的时间。

所有的人类进步都与以一种方式做事情相关,这种方式需要越来越少的工人,并且持续减少努力,以便将更多的努力应用到其他地方。所有这些都定义了自动化的概念。它就是发明减少在乏味工作中时间投入的方法,以便你可以继续前进,做更多令人兴奋或有趣的事情。

人类发明农业是为了不必花太多时间去打猎和采集食物。他们可以自己种植;食物在很多方面自己就能生长(有人说是自动化的),而一点点的改良让我们今天吃到的食物得以诞生。农业的创造使得食物生产过程实现了自动化,进而推动了城镇的发展、地产的拥有以及大部分人类文明的诞生。

在工业革命期间,制造过程的自动化使得廉价的大规模生产商品几乎进入了每个家庭。创造出质量高、工作完美且完全相同的商品是一项革命性的成就。这意味着创造某样东西所需的劳动大大减少,从而可以将这些劳动力集中到其他地方。蒸汽机、电力和汽车的发明也在多个领域减少了劳动,同时在许多其他领域节省了时间。

环游世界八十天儒勒·凡尔纳所著的一本小说,象征着人类创新在缩短全球旅行时间方面的巨大进步。在那本小说发布后的不到一百年里,人类征服了所有形式的陆地旅行,使得这本书显得微不足道。接下来被提出的问题是,如何让人类大脑变得更快、更自动化。这就是计算机登场的地方。计算机将人类大脑转变为世界上最有效的工具。它们自动化了我们许多繁琐的思维工作,让人类大脑有更多的空闲时间去做更轻松的任务。

然而,这一过程是一个永无止境的循环。总有办法变得更好,做得更快,并且不会再占用更多的注意力。在 DevOps 领域,我们已将其作为核心原则之一,因为在技术领域中,创新、创造力和提出新想法被高度重视。你不能停留在昨天的地方。你必须自动化并继续前进。

这就是本章的内容。本章将涉及用 Python 自动化枯燥工作(这是Al Sweigart的另一本优秀的 Python 书籍),并让你最大限度地利用你的思维和创造力。你不想陷入反复上传、手动运行相同脚本或手动修复那些虽然只需要 5 秒钟但登录却需要 10 分钟的重复服务器问题的因果循环中。这些问题的答案就是自动化。

在本章中,你将学到以下内容:

  • 服务器内外的自动化维护

  • 通过托管服务及其他方式自动化容器创建

  • 使用 Google 表单自动启动播放本

自动化服务器维护和修补

我曾经有一个朋友,他的工作大多数日子里只是等待一个网站宕机,检查它为什么宕机,并执行他被指派的两项命令之一来将其恢复。我还有另一个朋友,他的工作是每次 NGINX 服务器宕机时手动重启它。我曾遇到一个人,他的工作基本上就是从一个地方下载 CSV 文件,把它们放到另一个地方,然后点击一个启动按钮。现在,对于一些人来说,这听起来可能是个不错的工作(对我来说也不算差),但问题在于,这对那个员工的雇主和员工自己来说都是浪费时间。对双方而言没有成长或改进,在我的生活经验中,这简直就是浪费人类的生命。

在接下来的示例中,我们将看到如何基于一系列常用命令来维护多个实例集群,然后在发现操作系统类型后,找到一种方法来修补操作系统。我们将借助 Python 来完成这一切。

示例 1:同时对多个实例集群进行集群维护

维护服务器涉及大量工作——很多重复的工作。这也是最初服务器维护实现自动化的原因。它最小化了人为错误,并确保每次都按相同方式进行。服务器集群的工作原理类似。只需对所有服务器使用自动化脚本,因为它们是原始服务器的副本。那么,如何处理具有不同需求的多个实例集群呢?在这里,Python 可以提供帮助。你需要做的就是将每个集群与正确的维护脚本关联起来。这可以让你在多个云环境中管理多个集群。所以,话不多说,让我们看看如何做到这一点:

  1. 让我们首先编写 AWS 实例的代码,找出正在运行的实例:

    import boto3
    ec2_client = boto3.client('ec2')
    response = ec2.describe_instances(Filters=[{'Name': 'instance-state-name', 'Values': ['running']}])
    aws_instances = response['Reservations']
    

    这将为我们提供一个来自 EC2 的实例列表。你可以使用多个标识符来定义你的集群。你甚至可以使用预定义的系统管理集群。

  2. 现在我们为 Google Cloud Compute Engine 实例做同样的事情:

    from google.cloud import compute_v1
    instance_client = compute_v1.InstancesClient()
    request = compute_v1.AggregatedListInstancesRequest()
    request.project = "ID of GCP project that you are using"
    gcp_instances= instance_client.aggregated_list(request=request, filter="status:RUNNING")
    

    Google Cloud PlatformGCP)代码中,由于需要指定 GCP 项目 ID,并且必须定义请求 API 及其本身,因此存在一些差异。

  3. 现在,让我们找一个命令来在这些实例中运行。它可以是任何占位符命令。你可以之后用你想要的命令来替代它:

    command = "sudo reboot"
    #for AWS
    ssm.send_command(InstanceIds=aws_instances, DocumentName="<Whatever you want to name it>",
        Comment='Run a command on an EC2 instance',
        Parameters={
            'commands': [command]
        }
    )
    #for Google Cloud
    import os
    import subprocess
    from google.oauth2 import service_account
    from googleapiclient import discovery
    # Load the service account credentials
    service_account_file = '<file_name_here>.json'
    credentials = service_account.Credentials.from_service_account_file(
        service_account_file, scopes=['https://www.googleapis.com/auth/cloud-platform']
    )
    # Create a Compute Engine API client
    compute = discovery.build('compute', 'v1', credentials=credentials)
    # Get the public IP address of the VM instance
    request = compute.instances().get(project="<your_project>",instance="your_instance_name")
    response = request.execute()
    public_ip = response['networkInterfaces'][0]['accessConfigs'][0]['natIP']
    # SSH into the VM instance and run the command
    ssh_command = f'gcloud compute ssh {instance_name} --zone {zone} --command "{command}"'
    try:
        subprocess.run(ssh_command, shell=True, check=True)
    except subprocess.CalledProcessError:
        print("Error executing SSH command.")
    

    由于 API 的开发方式不同,之前的 GCP 和 AWS 代码有所不同。然而,它们都会产生在服务器上执行 SSH 命令的结果。

    因此,如果我们遍历之前通过函数生成的列表,并用命令更新它们,我们就可以对整个实例集群进行批量更改或更新。

这种方法适用于通用的服务器集群,我们假设所有操作系统(OS)都是相同的,或者它们运行相同的命令。但是如果我们处于一个操作系统可能不同的环境中呢?那我们该如何使用命令呢?在接下来的部分,我们将探索这一可能性。

示例 2:为关键更新集中管理操作系统补丁

操作系统就像任何品牌一样。至少在大多数技术社区中,它就像可口可乐和百事可乐——只是如果可口可乐或百事可乐是你的好斗宠物,时常需要你的关注并且需要维护的话就另当别论了。我想表达的是,你喜欢的口味是个人偏好。但如果你要共享一个冰箱,可能会有一些你不熟悉的口味。所以,冰箱需要适应所有口味。当我们处理服务器时,这种“冰箱分类”变得更加困难(而且重要),因为服务器可能有类似的多样性。要正确修补操作系统,我们首先必须了解我们正在使用的操作系统。然后,我们需要应用正确的命令,以确保该操作系统的补丁正确安装。这正是 Python 的用武之地。它有能够同时完成这两项工作的库,结合起来可以成为强大的工具。

让我们从修补单个操作系统的过程开始。在这个案例中,我们将使用apt包管理器:

import subprocessupdate_command = "sudo apt update && sudo apt upgrade -y"subprocess.run(update_command, shell=True)

正如你在代码中看到的,这仅仅是通过 Python 的subprocess模块运行一个update命令,这再次强调了 Python 与其运行的操作系统之间的紧密联系。

但这只是针对 Debian Linux 实例的情况。如果该实例是 Red Hat 或 CentOS 呢?如果脚本需要同时支持这两者该怎么办?那我们只需要添加一个额外的库:platform。这个库将为我们提供区分平台所需的知识,并且使我们能够在一个脚本中编写所有补丁代码:

import subprocessimport platformdef update_os():    system = platform.system().lower()    if system == 'linux' or system == 'linux2':        if 'debian' in platform.linux_distribution()[0].lower() or 'ubuntu' in platform.linux_distribution()[0].lower():            update_command = "sudo apt update && sudo apt upgrade -y"        else:            update_command = "sudo dnf update -y" 	subprocess.run(update_command, shell=True)    elif system == 'windows':        update_command = 'powershell -Command "Start-Service -Name wuauserv; Get-WindowsUpdate; Install-WindowsUpdate;"'        subprocess.run(update_command, shell=True)if __name__ == "__main__":    update_os()

上面的代码适用于 Debian 发行版、最新的 RedHat 发行版(旧版使用 yum 命令),以及 Windows PowerShell。脚本会根据你当前使用的操作系统,运行相应的更新。由于命令是可以修改的,你可以更改它并使更新符合你的需求。你还可以添加像 Darwin(适用于 macOS)或其他不常见的 Linux 发行版等操作系统。

你现在可能在想“修补操作系统会破坏我的服务器。” 有道理,尤其是对于旧的依赖关系,这种情况发生得很频繁。对于许多 Linux 服务器来说,操作系统的最新版本可能需要几年时间才能成为正式的服务器版本。如果你觉得这很麻烦,那么或许你应该试试容器。对于自动化爱好者来说,这里有很多机会。

自动化容器创建

容器——在许多人眼中——是魔法。你可以将一个小型应用程序或大型应用程序的一部分所需的所有内容放入一个专门为其服务的环境中,使其能够独立运行。这就像创造一个独立的星球,让北极熊永远生活在其本土环境中,免受全球变暖的恐惧。通过这种方式,容器令人惊叹,因为它们可以帮助保持几乎已灭绝的技术,且这些技术能够在适合它们的环境中得以持续运行。这的确是魔法。但施法过程相当繁琐,这也是我们为什么要自动化操作的原因。

示例 1:基于需求列表创建容器

容器在初始化和停止之间会根据容器内文件和配置的状态变化而变化。从这个变化的容器中捕获镜像,将得到一个在初始层之上添加了几个层的镜像。这也是创建自定义容器的一种方式。当我们找到的容器大致符合我们的需求,但并不完全符合时,这种方法非常有用。我们可以添加几个步骤(以及几个层),使容器完全符合我们的要求。然后,我们可以将其转换为镜像,随后可以复制到其他容器中。所有这些操作都可以通过 Python 来完成(大惊喜,不是吗?):

  1. 让我们再次从一些简单的代码开始,基于镜像启动一个容器:

    import docker
    client = docker.from_env()
    container = client.containers.run('ubuntu:latest', detach=True, command='/bin/bash')
    container_id = container.id
    print("Container ID:" + container_id)
    

    这组命令将启动一个包含最新版本 Ubuntu 的容器。它还会给我们容器的 ID,这在下一步中非常重要。这将是我们的起点。

  2. 现在,让我们进一步完善它:

    #you can put in any command you want as long as it works
    new_command = "ls"
    new_image = client.containers.get(container_id).commit()
    new_image_tag = "<whatever_you_want>:latest"
    new_container = client.containers.run(new_image_tag, detach=True, command=new_command)
    

    现在,我们有了一个新容器,它在 Ubuntu 中所有其他内容的基础上添加了新命令。这个容器与原始容器不同,但它是基于原始容器构建的。

  3. 接下来,我们需要将这个镜像导出以便以后使用:

    image = client.images.get("<whatever_you_want>:latest")
    image.save("<insert_file_path_here>")
    

    这将把你的镜像保存在所需的文件路径中。将所有代码合并后,我们得到如下内容:

    import docker
    #Step 1: Intialize and run a container
    client = docker.from_env()
    container = client.containers.run('ubuntu:latest', detach=True, command='/bin/bash')
    container_id = container.id
    print("Container ID:" + container_id)
    #Step 2: Add a layer
    #you can put in any command you want as long as it works
    new_command = "ls"
    new_image = client.containers.get(container_id).commit()
    new_image_tag = "<whatever_you_want>:latest"
    new_container = client.containers.run(new_image_tag, detach=True, command=new_command)
    #Step 3: Export layered container as an image
    image = client.images.get("<whatever_you_want>:latest")
    image.save("<insert_file_path_here>")
    

    完整的代码给我们呈现了完整的图景,展示了所有这些可以在短短几步之内完成。添加层次意味着添加更多的命令。如果你愿意,你甚至可以从一个什么都没有的空模板开始。

如果你正在创建单独定制的图像,这一切都很好,但容器的另一个复杂方面是将多个容器编排在一起以执行某个任务。这需要大量的工作,也正是因此 Kubernetes 应运而生。Kubernetes 集群——尽管它们简化了容器编排很多——仍然可能非常复杂。这是容器自动化的另一个领域,Python 在其中可以发挥作用。

示例 2:启动 Kubernetes 集群

我先来讲一个个人经历:当我第一次接触 Kubernetes 时,它对我来说可能是世界上最难的事情。我来自开发背景,而像容器编排这样的东西当时对我来说完全是外来的。Kubernetes 是因为微服务的流行而应运而生的,它的目的是让大型项目中那些大小不一的小项目能够更轻松地管理。当我终于弄明白它的时候,我意识到 Kubernetes 是多么重要。然而,这并没有让我不再困惑。所以,我再次求助于编码,结果发现有许多资源适合像我这样的人。再次强调,Python 是一个大帮手。

创建 Kubernetes 集群通常需要在云服务中使用它。对于这个练习,我们将编写代码来设置 Google Cloud 和 Microsoft Azure 中的集群:

from google.cloud import container_v1# Specify your project ID and cluster detailsproject_id = "<YOUR_PROJECT_ID>"zone = "<PREFERRED_ZONE>"cluster_name = "<YOUR_CLUSTER>"node_pool_name = 'default-pool'node_count = 1     client = container_v1.ClusterManagerClient()    # Create a GKE cluster    cluster = container_v1.Cluster(        name=cluster_name,        initial_node_count=node_count,        node_config=container_v1.NodeConfig(            machine_type='n1-standard-2',        ),    )    operation = client.create_cluster(project_id, zone, cluster)

该操作将在你的 Google Cloud 项目中创建一个 Kubernetes 集群。现在,让我们看看如何在 Azure 中执行这个操作:

from azure.mgmt.containerservice.models import ManagedCluster, ManagedClusterAgentPoolProfileresource_group = '<RESOURCE_GROUP_HERE>'cluster_name = '<CLUSTER_NAME_HERE>'location = '<LOCATION_HERE>' agent_pool_profile = ManagedClusterAgentPoolProfile(    name='agentpool',    count=3,    vm_size='Standard_DS2_v2',) aks_cluster = ManagedCluster(location=location, kubernetes_version='1.21.0', agent_pool_profiles = [agent_pool_profile])aks_client.managed_clusters.begin_create_or_update(resource_group, cluster_name, aks_cluster).result()

这个创建代码也相当标准;它只是术语的不同。这可能不是为这个功能编写代码的最有效方式(这将在基础设施即代码(Infrastructure as Code)中进一步展开),但它完成了任务。

到目前为止,我们所看到的许多内容对外行来说都是一堆胡言乱语,而外行人有时是最频繁操作资源的人。所以让我们来看一个过程,它可以作为自动化更复杂流程的蓝图,使外行人更多地参与到资源创建的过程中。

基于参数自动启动 playbook

大多数时候,即使是最基本的自动化任务也可能变得难以理解。如果你需要自动化或触发多个任务,复杂性就会开始增加。并不是每个人都能理解它们,而且也不应该是每个人的工作去理解它们。这就是为什么即使是许多现代服务器也有用户界面,使得信息处理对许多人来说更加容易。

然而,在许多情况下,即使这种抽象层级也不足够。可能需要创建一个工具,让用户可以简单地输入他们的内容,而服务器自动处理复杂的工作流和资源的创建。简而言之,你可以制作带有参数的剧本,根据那些希望创建资源但不想处理其中复杂细节的人(在大多数地方,这些比较随意的人叫做客户)所提供的概述来创建资源。让我们看看如何做到这一点:

  1. 我们将从制作一个 Google 表单开始(是的,没错)。前往 forms.google.com 并点击大大的加号(+)按钮。

图 7.1 – 实例选择

图 7.1 – 实例选择

这是一个简单的 Google 表单,用于选择两种不同大小的 EC2 实例。

  1. 现在,我们将编写一个 Google Apps Script 脚本和一个 AWS Lambda 函数:

    import boto3
    ec2 = boto3.client('ec2')
    def lambda_handler(event, context):
        instance_size = event['instance_size']
        response = ec2.run_instances(
            ImageId='<INSERT_AMI_HERE>',
            InstanceType=instance_size,
            MinCount=1,
            MaxCount=1,
            SecurityGroupIds=['<INSERT_SECURITY_GROUP_HERE'],
            SubnetId='<INSERT_SUBNET_HERE>'
        )
        return response
    

    这个 Lambda 函数接收一个输入,包含要创建的 EC2 实例的大小,然后创建该实例。我们可以使用 Lambda URL 或 API 网关为其定义一个端点。

  2. 一旦这个函数和端点创建完成,你可以从 Apps Script 调用该端点,并根据表单中的触发器和输入进行处理。在表单编辑器中,点击右上角的三个点,并选择脚本编辑器

图 7.2 – 访问脚本编辑器

图 7.2 – 访问脚本编辑器

  1. 现在,你可以编写本质上是 JavaScript 的 API 脚本:

    function submitForm(e) {
    var responses = e.values;
    var size = responses[0];
    var apiUrl = '<YOUR_LAMBDA_URL>';
    var requestData = {
    'instance_size': size,
    };
    var requestBody = JSON.stringify(requestData);
    var options = {
    'method': 'get',
    'contentType': 'application/json',
    'payload': requestBody,
    };
    var response = UrlFetchApp.fetch(apiUrl, options);
    }
    

    这将运行 Lambda 函数,不过需要最后一步通过添加触发器来触发它。在 Apps Script 项目的左侧窗格中,点击触发器选项。

图 7.3 – 使用 Apps Script 调用 Lambda

图 7.3 – 使用 Apps Script 调用 Lambda

  1. 在右下角,点击添加触发器,这将打开一个表单,用于创建触发器,在那里你可以定义所有必要的参数:

图 7.4 – 添加一个触发器,用于在表单提交时触发…

图 7.4 – 添加一个触发器,用于在表单提交时触发…

在这里,我们可以添加事件的来源、功能,并选择事件类型。

这样,我们就创建了一个工作流,当表单数据提交时触发一个函数,并使用该函数提供的数据来触发一个 API URL。

就这样,这就是一种将所有幕后操作(发生在 Lambda 函数中的)与简单 Google 表单连接起来的方法。

总结

在这一章中,我们讨论了自动化的美妙及其实现手段。我们学习了如何自动化虚拟机维护和容器操作。我们甚至学习了如何在此基础上添加一层自动化,允许那些对相关领域了解较少的人参与到我们的过程当中。自动化是件好事。人们常常会有相反的看法,并且害怕很多任务的自动化,但自动化的目的是确保人们的生活变得更加轻松。生活不应当充满无聊的重复任务,它应该是为了探索。自动化是释放时间进行探索的关键。你通过控制时间来控制你的生活。自动化让你做到了这一点。

在下一章中,我们将讨论推动自动化乃至大多数 DevOps 基础设施的事件。我们将深入了解事件驱动架构及其有利的应用场景,当然,还会讨论 Python 如何提供帮助。

第八章:理解事件驱动架构

Al freir de los huevos lo vera.(“在煎蛋时会看到的。”)

– 米格尔·德·塞万提斯(《堂吉诃德》)

在任何应用中,一切都可以被划分为事件。事件是通过与外部行为者(另一个应用程序或用户)的某种交互,或通过其他事件触发的。一个应用程序本质上是触发多个事件序列以执行某种功能。例如,Google Drive 就是一个应用,它的功能就是存储。当然,这是一个过于简化的描述,存储、组织和提供文件的过程中有很多内容,但这就是其基本含义。它是基于一系列事件进行工作的,每个事件都来自于某个特定的来源。

现在,不同的事件需要系统中的不同技术、框架和库来完美地相互作用。当这种和谐自然地实现时,它是一种美丽的景象。然而,这几乎从来都不是这样。总会有某种瓶颈,或者需要制作某个自定义部分,而这几乎总是与来自事件的数据有关。你可以拥有适合你系统的完美工具,但如果它不能处理接收到的事件,那么它是没用的。那么,当所有聪明人意识到这一点时,他们得出了什么结论?他们意识到没有完美的系统,也没有完美的事件。所需要的是一个不与任何数据处理紧密绑定的系统,一个能够容忍一些人为错误的系统。不是一个不精确的系统,而是一个能够适应它所处现实情况的系统:一个松耦合的系统。

这类系统是为了能够从多个来源获取事件,并以尽可能简单的方式进行处理输出。它们将每一个事件和事件处理器分解为各自的组件。这些组件基于它们接收到的输入和它们产生的输出与其他组件进行交互。如果可以避免的话,任何组件都不应完全依赖于另一个组件。这样的系统可能看起来效率不高,但当目标是确保在不可靠的世界中实现可靠性时,它就变得非常有吸引力。

所以,现在我已经完成了惯常的独白,让我们看看如何打破那个不必要的巨型模块。在本章中,你将学习以下内容:

  • 发布者/订阅者Pub/Sub)架构的基本概念和使用

  • 松耦合架构的基本概念,以及为什么 Python 已经非常适合它

  • 将单体应用分解为更小松散组件的有效行业标准

这里还会涉及一些 Python 以及其他一些内容。事实上,这正是我现在要深入探讨的内容。

技术要求

以下内容可能有助于你充分受益于本章:

  • 已安装 confluent-kafka 库的 Python

  • 一个 AWS 账户

  • 打开思维(比喻地讲,不是字面上的;如果你非要字面理解,也没问题,但我不推荐这样做)

引入 Pub/Sub 并使用 confluent-kafka 库在 Python 中应用 Kafka

在我们深入探讨现代 Pub/Sub 模型之前,让我们先了解一些使这一领域成为可能的技术:Apache Kafka,继 Franz 和 Kafka on the Shore 之后的第三大著名 Kafka。最初为 LinkedIn(一个伟大的网站)设计,2011 年初开源。其背后的概念非常简单:有一个信息和事件的日志,任何数量的系统都可以消费该日志中的数据,并且数据可以发布到该日志中供这些系统消费。听起来很简单吧?现在是,但要想出这个想法也不是一件容易的事。但正是这个系统支撑着我们今天所看到的大多数现代数据基础设施。你曾收到过手机上的通知吗?那是因为这个库。你曾用手机或信用卡进行过无接触支付吗?很可能背后有 Kafka。你曾收到过 YouTube 视频的通知吗?肯定是 Kafka。

在大多数使用原生 Kafka 的情况下,信息的分发者称为 生产者,而接收该信息的则称为 消费者。在大多数现代术语中,尤其是在大多数云服务中,它们分别被称为 发布者订阅者

图 8.1 – 使用 Pub/Sub 模型的事件处理

图 8.1 – 使用 Pub/Sub 模型的事件处理

在我们深入探讨 Kafka 和 Python 在 DevOps 中的应用之前,我们首先需要看一下使用 confluent-kafka 库在 Python 中应用 Kafka 的示例:

  1. 让我们首先使用 pip 安装这个库:

    pip install confluent-kafka
    
  2. 如前所述,Kafka 分为生产者和消费者。那么,首先让我们写一段代码来创建一个生产者:

    from confluent_kafka import Producer
    import socket
    conf = {'bootstrap.servers': '<host_name>:9092',
            'client.id': socket.gethostname()}
    producer = Producer(conf)
    
  3. 这段代码将配置一个生产者。将 host_name 替换为一个 Apache Kafka 集群的名称(可以是在线或本地的)。接下来,我们需要使用配置好的生产者来发送一些数据。让我们现在看一下这段代码:

    producer.produce(topic, key="key", value="value")
    

    这里,topic 是发布者或生产者将其内容分发到的地方,供消费。keyvalue 元素是生产者将分发的键和值。

  4. 现在,让我们为消费者添加一些代码,消费者将接收生产者发送的消息:

    from confluent_kafka import Consumer
    conf = {'bootstrap.servers': '<host_name>:9092',
            'group.id': '<group_id_here>',
            'auto.offset.reset': 'smallest'}
    consumer = Consumer(conf)
    

    消费者现在在与生产者发送消息相同的主机上监听。因此,当生产者生产一条消息时,消费者就能消费它。当消费者订阅了一个主题时,该消费者会在特定时间间隔内不断监听该消息。一旦消息到达,它将开始解析消息并将其发送到适当的位置。

  5. 为了让消费者持续监听生产者的消息,我们可以将其放入一个循环中:

    while True:
    msg = consumer.poll(timeout=1.0)
    if msg is None: continue
    break
    #If msg is not None, it will break the loop and the message will be processed
    

这只是理解这些发布/订阅机制如何工作的方式。在实际应用中,这会变得更容易,因为某种执行这个机制的工具已经为你提供了。然而,如果你想了解如何创建自定义的发布/订阅结构,或者只是想一般性地了解发布/订阅结构,这是一个很好的学习方法。

这里应该有的关键收获是:这就是世界的运作方式。就像这样。你手机上大部分的内容就是这样传递的。你手机上发出的内容也大多是通过这种方式传递的。在更基础的层面上,这同样成立,正如我们在下一节中将看到的那样。

理解事件和后果的重要性

好吧,现在你已经知道了这个秘密。一切都是推送和拉取。人们只是把数据抛出去,希望它能碰到某个东西。如果你也意识到,这就是增长和发展的最有效方法,那么恭喜你。如果没有,那我们接下来要进入一个小故事时间。

我目前住在瑞典的乌普萨拉,曾经很长一段时间,我以为我在这里是唯一一个来自尼泊尔的人。现在,乌普萨拉按瑞典的标准算是一个大城市,来自世界各地的很多学生也住在这里。但即使尼泊尔人住在这里,我又怎么知道他们呢?即使在这个时代,这么具体的事情也很难找到。但随后,一系列极为巧合的事件(有些人甚至会说是“命运的安排”吧?)发生了,它们把我带到了其他来自尼泊尔的人身边。只有当我回顾这些事件时,我才意识到它们是多么令人瞩目。

我最近刚收到了一份工作邀请(就是我现在的工作),要去斯德哥尔摩,并且正在搭乘去斯德哥尔摩的火车来细化细节。在火车上,我遇到了一个朋友,他是我在乌普萨拉学生会合作项目中的合作者。事实上,我们前几天才见面讨论这些项目。我看到他坐在那,便坐到了他旁边,发现他还和另外两个朋友在一起。其中一个是我第二天就要提交的项目的助教。那挺酷的,但这甚至不是我在火车上遇到的最重要的事。另一位朋友和我开始聊天,经过他,我得到了一个在乌普萨拉生活的尼泊尔人的电话号码。真是命运!通过那个人,我实际上找到了将近十个住在和我同一个小镇的人,他们和我一样也走上了同样的旅程。

所以,事情是这样的:我跳到了故事的结尾,因为,从某种意义上说,这就是我们生活的方式,试图完成某些事情,走到故事的结尾,然后开始一个新的故事。这是我们阅读的方式,是我们消费内容的方式,基本上,也是我们社交的方式。但是,随着时间的推移,我反思了这个故事,回想起一系列将我带到那个时刻的事件,所以让我们倒回去(我保证,这个故事有一个令人满意的结局,并不仅仅是我在炫耀我的运气):

  • 我遇到了那位朋友,他把我介绍给了他在学生会开发团队的朋友。

  • 我加入那个团队是为了在乌普萨拉结交一些新朋友,但得到这个信息是因为我加入了一个 WhatsApp 群组,那个群组是根据我另一个学长的推荐加入的。

  • 在我们还没有加入学院之前,我就和他成为了朋友,因为我们最终一起参加了一个学生组织的旅行(如果你想了解更多有趣的内容,可以查一下乌普萨拉的学生社团)。

  • 我参加那个旅行是因为在我第一天参加乌普萨拉的迎新活动时,得到了推荐。

但这是导致这一事件的事件树的理性一半,另一半更有趣:

  • 我坐在那列火车上,因为我要去签工作合同。

  • 我获得那份工作的邀请,是因为我在 Google 论坛上发布了自己拥有所有 11 个 Google Cloud 认证的动态,而我现在工作的公司恰好注意到了。

  • 我发布那条动态是因为我获得了所有 11 个认证。我是在离开瑞典的前一天获得最后一个认证的。如果我只获得了 10 个,我就不会发布那条动态了。

  • 我获得那些认证,主要是作为我希望通过这本书传递的知识的一种补充。

  • 我收到出版社的这本书邀请是在我申请时通过他们其他书籍中的一个链接找到的,这本书是一本关于 Google Cloud 考试的书。

所以,间接地,写这本书帮助我获得了现在的工作,并最终让我在乌普萨拉找到了更多的尼泊尔人。如果你懒得了解这些,让我用图示给你展示:

图 8.2 – 驱动我生活一部分的事件

图 8.2 – 驱动我生活一部分的事件

我们生活中的事件会导致最非凡的情况,这只是从整个故事中出现的一个事件。在这一过程中发生了许多其他值得注意的事情,这让我意识到我们行动的真正后果是无法预测的,但大多数时候,这些后果比我们可能规划的要好得多,或者至少更令人兴奋。

自我放纵?可能吧。但这就是 DevOps 的魔力。遵循严格的结构和固有的先入为主的观念会导致恐惧并阻碍成长。它们隐藏并掩盖了自发性所带来的机会。没有空间去发现、探索和失败。是的,失败,因为在像单体架构这样紧凑的系统中,失败可能是灾难性的。而在松耦合的架构中,失败仅仅是一次成长的机会。你可以不断调整组件并反复迭代,直到达到最佳的版本。你可能会看我的故事,并说这其中有很多是运气和巧合——例如,在火车上碰到正好需要的人,或者恰巧我的论坛帖子被看到——但运气不过是持续尝试的结果。人们有时会幸运一次或两次,但那些不怕失败并顺其自然的人通常会变得更加幸运。上帝不喜欢懦夫。

现在,我们来转到一个稍微少一点哲学意味的话题,松耦合架构是一种框架,你可以用它来实现这种在工作负载中基于事件的有意义的系统。最初,这一段应该是下一节的内容,但它却自然而然地发展成了这样(这就是自发性,不是吗?)。那么,接下来我们就来深入挖掘,看看我们能发现什么。

探索松耦合架构

好的,在一个封闭的环境中,松耦合架构看起来像是个糟糕的主意。你把组件分散得太远,以至于信息从一个地方到另一个地方的传递没有任何规律可言。你无法为所有的数据找到一致的时间,确保它们能汇聚到一个地方,进而让你想要的事情最终发生。

然而,松耦合架构在实际环境中之所以如此有效,有一些因素是不可忽视的。这些因素既有哲学上的,也有架构上的。首先,无论你如何设计一个系统,它总会在某个时间、某个地方出现故障。松耦合架构允许系统优雅地失败,并能够以不影响系统其他组件和用户的方式从失败中恢复。由于每个组件都是独立的,故障通常可以通过失败的单个组件来定位(很多时候,克隆组件会成功)。这个故障可以被记录、检测,并且可以通知相关方,而不会对系统造成任何中断。失败的组件不会干扰活动,也不被视为坏事。事实上,失败教会了我们系统的弱点和不足之处,这些不足可以迅速被解决,因为你只需处理那个孤立的组件。

下一个因素来自于可用性。松散耦合的架构提供了为每个单独的用途复制的小组件。现在,你可能会说,这本身就是一个限制,因为即使你可以将资源在用户之间进行划分,仍然无法提供足够的资源供每个人使用。在过去,这确实是事实,但随着现代应用程序在云端运行,支持松散架构的服务可以进行无限制的资源配置。你可以有效地处理资源的数量,因为能够运行这种架构的服务的规模几乎是无限的。这导致了一个环境,在这种环境中,基于使用情况进行配置的架构成为最优架构。更紧密耦合的架构可能适合资源有限的情况,但对于资源灵活且负载不确定的场景来说,情况则不同。

最后,将松散耦合架构置于领先地位的最后一个因素是懒惰。是的,懒惰。我发现,在我的生活中,导致我懒惰的主要原因不是因为我不想做某事,而是因为我的大脑被关于某件我可能想做的事情的无用信息淹没。真正让我有所进展的是,当我不再试图以无效和无用的方式去弄清楚这些琐事,而是开始去做事情并在过程中搞明白它们时。基本上,这就是松散耦合架构之所以有效的原因。需要担心的事情更少,工作也更容易。你不需要在开始实现系统之前担心每一个细节,而是可以直接开始实现,优化的事情可以稍后再考虑。这对像我这样几乎在所有事情上都采用这种方法的人来说是完美的,对于一些全球最大的公司来说也是一样。如果你听说过丰田方式,它基本上遵循相同的原则:犯错并从中学习,变得更好。你可以去查阅一下;我鼓励你这么做。但总的来说,这种架构适合懒惰的、务实的开发者,他们只是在努力前进。

在过去的几段中,你已经忍受了我哲学性的漫谈,现在我们进入更实际的部分,我将展示一些东西并尝试强化我之前讨论的内容。所以,这就是我们现在要探讨的内容。我们将创建一个基本的应用程序(其实只是一个 lambda 函数),当图片上传到 S3 存储桶时触发,获取该图片并将其调整为标准大小,删除原始图片,并用调整后的图片替换它:

import osimport tempfileimport boto3from PIL import Images3 = boto3.client('s3')def lambda_handler(event, context):    # Get the name of the bucket and the image name when upload is triggered.    bucket = event['Records'][0]['s3']['bucket']['name']    key = event['Records'][0]['s3']['object']['key']    new_width = 300 #width of image    new_height = 200 #height of image    with tempfile.TemporaryDirectory() as tmpdir:        # Download the original image from S3 into a pre-defined temporary directory        download_path = os.path.join(tmpdir, 'original.jpg')       #download the S3 file into the temporay path        s3.download_file(bucket, key, download_path)        with Image.open(download_path) as image:            image = image.resize((new_width, new_height))            # Save the resized image in its own path            resized_path = os.path.join(tmpdir, 'resized.jpg')            image.save(resized_path)        # Upload the resized image back to the S3 bucket and delete the original       s3.delete_object(Bucket=bucket, Key=key)       s3.upload_file(resized_path, bucket, key)    return {        'statusCode': 200,        'body': 'You don't really need this because its not for people!'    }

这是一个非常简单的代码,执行一个简单却重要的功能。图像转换的触发器可以放在 lambda 函数或 S3 存储桶本身。如果你曾使用过那种在线服务,将 PDF 转换成 Microsoft Word 文档或将 WAV 文件转换成 MP3 文件,它们基本上就是基于这个概念运行的。即使界面非常简单,它们也能非常有效且相当受欢迎。

或许在阅读本书之前,你可能曾误以为构建这些服务可能很困难。但在我们生活的这个世界里,它们并不难。一旦我们打开了这些视野,一切就变得更加清晰,而最清晰的一点就是能够从旧的低效方式转变为更新、更简单的方式。让我们来看看这一过渡的路径。

用“缠绕无花果”杀死你的单体应用

如果你反对“杀死”任何东西(我能理解),你只需在心里把这些词换成更令人愉快的词汇(例如“沉睡”或“小憩”)。但这里提到的缠绕无花果,正是因为它是数字化转型和/或应用现代化过程中最显著的方法之一。你可能听过“数字化转型”这个词,并且立刻把它当作一个时髦词抛之脑后,这也不无道理,因为大多数时候人们提到这个词时,确实就是一个空洞的流行语。但请睁开眼睛和耳朵,暂时抛开成见,理解这个词的真正含义:它是在将旧事物转变为新事物。基本上,它是在不改变或增加功能的情况下,从内部改变你的系统。它是将单体应用拆解为松耦合架构。

首先,让我们来看一下单体应用的潜在结构:

图 8.3 – 一个基本的单体应用

图 8.3 – 一个基本的单体应用

这非常粗糙,但基本上大多数单体应用的结构就是这样。它们有一个用户界面,用户界面与两种不同类型的操作进行交互:对数据库的操作和通用数据操作(图中的杂项)。即便如此,这个单体应用也比普通的单体应用要更为分层,因为我们给它分配了一个独立的数据库。数据库有时也可能直接存在于单体应用内部。

打破这个单体应用并不仅仅是使其解耦,而是确保每个组件能够作为独立的实体存在,以便它在其他项目中或在同一项目中的不同方式下可能派上用场。但为了继续拆解这个单体应用,我们可以通过首先从单体应用中移除那些不需要与用户或数据库交互的杂项功能来实现。

图 8.4 – 从单体应用中移除杂项 API

图 8.4 – 从单体应用中移除杂项 API

所以,这个图表表明,单体应用与杂项功能之间的分离将这些功能划分为独立的组件。这是无服务器架构的基础。其核心思想是将每个功能作为单独的端点,仅在特定用例下进行调用。这一阶段有助于将简单的事情处理掉,并且帮助执行转型的人实际开始理解其中的概念。如果这些随机功能只是某个可以随时调用并根据需求进行修改的端点,管理起来会容易得多。

现在,拆分单体应用的下一步是用户界面与执行数据操作的后端部分的划分。这涉及将一个前端的 API 或后端置于它们之间。

图 8.5 – 完全解耦的架构

图 8.5 – 完全解耦的架构

在将单体应用拆分之后,最终的结构看起来像图 8.5。它有点像我的后果图,但不那么愚蠢。这里有很多活动的部分,但车子里也有很多活动的部件;这正是它们能运作的原因。更重要的是,让我们从本章强调的两个方面来看待这个问题:可用性和故障:

  • 如果用户界面由于某种原因停止加载,它将会回退到不同的区域。这个区域将具有相同的功能;如果它离用户更远,可能会更慢,但仍能完成工作。

  • 前端的 API 或后端可以接收来自多个用户界面的调用,并可以访问多个可以访问数据库的服务器。

  • 后端可以仅仅是用来连接数据库进行操作。除查询之外,那里不需要其他任何东西。

  • 由于存在层次结构,数据库本身变得更加安全,并且更容易提高其可用性。

  • 这些杂项功能只是可以根据开发团队的需要随时添加或移除的端点。

好吧,这确实是一个相当复杂的过程,对吧?但是它是一个能够带来成果的过程,帮助让你的应用或工作负载变得更加可持续并具备未来适应性。它并不适用于每一个工作负载,但它非常适合帮助旧系统更好地使用现代技术。

总结

在本章中,你了解了事件驱动架构,它是现代应用开发的一个重要组成部分。你也希望了解了行动及其后果,以及积极行动对开发和你生活的重要性。最后,你学会了如何通过“入侵式重构”方法,利用 strangler fig 来将旧应用程序现代化,转向这一新的理念。

在下一章,你将更加深入地使用 Python,并了解 Python 在CI/CD(即持续集成/持续交付)中所能发挥的作用。这是一个有趣的话题,将帮助你将本章中学习的 Python 技能和概念付诸实践。

第九章:使用 Python 进行 CI/CD 流水线

我在职业生涯中错过了超过 9,000 次投篮。我输掉了将近 300 场比赛。有 26 次我被信任去投决胜球,但未中。我在生活中屡战屡败。这就是为什么我成功的原因。

– 迈克尔·乔丹

在过去,当我还是个年轻的大学毕业生(大约两年前),在被教如何制作网站之后,最经常提到的术语是持续集成/持续交付CI/CD)。CI/CD 是我大学课程中没有教授的内容。大多数大学课程都没有;这不足以成为一个学术练习。但是,如果你是一名 DevOps 工程师,这就是你的全部工作。这是你的职责。而 Python 是提升工作效率的绝佳工具。

我经常将 Python 描述为一个伟大的促进者,即一种使所有其他工具更好的工具。这是我们将在本章进一步探讨的概念。Python 是一种非常善待犯错者的语言;相信我,我犯过很多错误。它具有许多功能,可以减少您错误的影响。它还有错误消息,可以简化和有效地追踪错误。如果我们从开发者的角度来讨论实际应用程序开发,每个人都有自己的观点,这在一定程度上是有效的。但正如我在本书中所说的那样,Python 在能够为 DevOps 工程师提供的功能方面显著领先。

因此,当我们探索如何为客户提供旅程时,这就是我们将要学习的内容:

  • CI/CD 的哲学和概念及其在创造过程中的演变

  • 使用 Python 进行的基本 CI/CD 任务,可以帮助您理解 CI/CD,它为项目带来了什么,以及其实施的重要性

  • 如何使用 Python 开发和增强开发团队内部的协作能力,利用其灵活的特性

  • 如何使用 Python 增强和自动化最神圣的 DevOps 传统:回滚

要完成这些任务,您需要准备好一些工具。在我们开始之前,有一些事情是必须设置的。

技术要求

在这一章中,我们有一个简短的清单,列出了您需要的一切:

  • 用于书籍库的 GitHub 账户

  • 一个 Todoist 账户

  • 一个 Microsoft 账户

  • 一个 AWS 账户

  • 能够容忍大量的讽刺和糟糕的幽默感

我们当然也有一个相应的 GitHub 代码库,在这里您可以参考本书中的编码内容:github.com/PacktPublishing/Hands-On-Python-for-DevOps/.

CI/CD 的起源和哲学

人们常问我一个问题:“你是从开发者做起的,怎么会走到今天这一步?”我在本书的几个章节中已经在一定程度上回答过这个问题,但简而言之,我是因为必要性才走到了这里。如今,开发应用程序的人从一开始就被教会如何做,而不清楚背后的所有理论,只知道如何使用它。这样做没问题;你不需要深入了解,除非你愿意,但请知道,过去并非如此。曾经有人必须弄明白所有这些事情(事实上,是几个不同的人)。我认为我的个人经历更能反映出那些弄清楚这些事情的人,而不是你典型的现代 DevOps 学员。所以,我将以三个场景的简短对话回顾这段旅程,场景里我将对着虚空大喊(我们可以称之为 DevOps Doug,听起来很有趣)。我们从持续集成(CI)开始。

场景 1 – 持续集成

那是一个深夜,我正在家里做我常规的 200 个俯卧撑和 300 个仰卧起坐时,突然意识到我真的很擅长写代码。这本不应该是一个启示,回想起来其实很明显,但在做出这个发现之后,我决定——作为一个慷慨的人——我想将我的编码天赋与世界分享,于是我打电话给我的老朋友 Doug,请他帮忙。

Me: 你好,我需要帮助。我有这么多代码,我想和我的朋友们分享。

Doug: 这个很简单。你只需创建、克隆、初始化、拉取、修改,然后推送 Git 仓库。听起来很简单,对吧?

Me: 什么是仓库?为什么我不能直接把代码发给他们,然后他们修改后再发回给我?

Doug: 嗯……

于是开始了长时间的讨论和大量的阐述,关于什么是版本控制、仓库如何工作、为什么要使用分支以及什么是拉取请求。如果我开始详细讲解这些内容,我得再写一本书。

Doug: 这样回答了你所有的问题吗?

Me: 是的,我理解它,我也明白它为什么有用,但为什么它是现在这种方式?

Doug: 做得更多,你就能学得更多。

旅程继续,就像集成一样,但要长得多,有时也非常无聊。我们跳过那些无聊的部分。

所以,我开始学得更多,并意识到版本控制有多么重要。我还意识到,实际上很少有年轻开发者能真正花时间去完全理解它。有些人甚至因此放弃了。但持续集成(CI)的目的很明确。它清楚地记录了代码修改的历史,并提供了小幅度的改进,这些改进在其他改进的基础上逐步叠加,形成了一系列可以查看、版本化并理解的小改动历史。它增加了组织性和理解,这些都是在提高速度之前你需要首先具备的东西。

随着我不断发展,我的技能也在发展,我的好奇心也在进化。我开始想,“这真的对项目中的其他人有帮助吗?”于是我问了他们,他们确认了确实有帮助。我觉得继续问下去不太礼貌(而且他们可能也没有进一步研究),于是我开始在网上寻找更高级的资料。接下来的这段关于持续交付的对话,倒是挺有意思的。

场景 2 – 持续交付

在 Doug 把我推下 DevOps 的河流一段时间后,我发现自己遇到了一个障碍。如果这是个实际的障碍,我完全不在乎,每天做 400 个仰卧起坐和 649 个(没错,正好是这么多)引体向上,我完全能应对。但这个障碍存在于计算机的世界中,它有一个收费站,拒绝任何不符合要求的代码。厌倦了我的代码被自动拒绝,我决定去和收费站的工作人员谈谈,结果,我发现那竟然是我的老朋友 Doug。

: Doug,你是怎么到这儿的?

Doug: 我一直都在这里。

: (显得有些困惑) 好吧... 那么,每次我推送我的代码时,它就会更新应用程序的测试版本吗?

Doug: 没错,前提是你的代码通过了测试并成功部署。否则,它会恢复到项目的旧版本,并告诉你出了什么问题。

: 好吧,你不能只相信我说的嘛?

Doug: 我很乐意,但上次我们这么做的时候,一只斑马从服务器房间里飞出来,开始咬人。斑马可凶了。

: 怎么会有一只斑马……

Doug: 这不是重点。重点是,这个系统不仅仅是为了启动东西,好吗?它存在是因为它为每个人简化并确保了流程。一旦开发者推送了代码,对他们来说就结束了。如果这段代码出错,他们会继续处理。DevOps 的人只是为了让这个过程尽可能简单。

: 好的,这个设置简单吗?

Doug: 不行。

这是我学习所有测试和安全原则、如何推送代码、创建审批工作流等一系列内容的蒙太奇部分,这些内容你在本书中已经看到过了。这绝对不是一段容易的旅程,但它是值得的。

: 好吧,这没关系,对吧?

Doug: 定义 全部

: (叹气) 好的,还有什么剩下的吗?

Doug: 多做一点,你就会学得更多。

因此,这段旅程继续着,每次都揭示出更多的问题。说实话,挺无聊的。但我很快意识到,这样其实是有原因的……

好吧,当真相揭示给我时,我意识到这实际上就是正确的方式。通过反复试验找到什么有效,什么无效的方式。一个基于现实问题及其解决方案的方法。一个更加务实的方式,它让混乱中有了秩序。但我也意识到,过度的秩序也是问题(如果是政府做的,我们叫它官僚主义)。有很多必要的步骤,但每当有变动时,总会产生一些不必要的步骤。这个过程需要自我适应。一个有趣的概念。而且它需要在创造价值的同时进行自我调整。但要创新,你必须自动化。而这就是持续部署的故事。

场景 3 – 持续部署

有一天,我坐在办公室里,按照平常的流程喝着咖啡,回复着几封邮件,推送着一些代码,突然间我感到脊背一凉,仿佛有人要进来颠覆我的生活...

道格: 嘿!

: 道格!你怎么进来的?那扇门锁着呢。

道格: 我是你想象中的人物,你永远逃不掉。我在这,你在干什么?

: (把那个令人不安的想法推到一边)好吧,算了。我已经创建了这个可爱的流水线,把代码推送进去。我已经为此工作了三个月。现在我要把它发布到生产环境中。

道格: 你一整周都没发布版本?你到底在干什么?

: 我的工作...?

道格: 哦,我明白了。那就糟糕了。孩子,听清楚了。如果你做的工作是“做工作”,那就说明你做得不好。

: 那我该做什么工作呢?

道格: 不是你的。

: 😐

道格: 你的工作是自动化你的工作,确保尽可能少地依赖人工判断。你必须成为一个艺术家,努力用尽可能少的笔触画出你的杰作。你现在已经进入了持续部署的领域。

: 如果我不断部署,那什么时候才算是开发?

道格: 诀窍是:你不部署你的应用。你需要创建一个持续部署它的部署。

: 什么?那我要是弄错了怎么办?

道格: 这就是另一个诀窍:不要害怕犯错。这就是 DevOps 和 CI/CD 流程成功的原因;你可以快速识别错误,回滚并重构。你不会因为失败而被束缚。

: 我明白了,但这些是我们真正的用户啊...

道格:没有比经验更好的老师了。如果你想学到任何有意义的东西,就必须将你的产品交给用户。他们会遇到一些小问题吗?当然,但几乎每个应用程序都会有这种情况,对吧?当你持续部署时,当你拥有了那条流水线,你就有了改变它并帮助用户的能力。不是明天,不是下周,而是现在。这就是 CD 和 CD 之间的区别(是的,我知道这很混乱)。持续交付某物和交付给客户是两件不同的事,其中一件远比另一件更有价值。

:这没道理,但我会试试的,没问题。

道格:做得更多,你就能学到更多。

就这样,和道格的这次交流结束了。虽然很有启发性,但像往常一样,给我留下了比答案更多的问题。从那以后他再也没出现过,但我知道他在那儿,什么时候我最不需要他时,他就会出现,带我穿越 道格宇宙,踏上另一段疯狂的旅程...

那真是道格关于 DevOps 的一番长篇大论,不是吗?但这本质上就是两种 CD 的区别——它们之间的哲学差异,就像半自动步枪和自动步枪的区别。每一种都有其用途。它们可以被看作是彼此的延伸,而且它们都有自己的位置。

既然我们已经把所有这些理论搞清楚了(这很好,因为这个领域仍在发展和演变),我们可以进入有趣的部分了。让我们做一些任务,帮助我们更清晰地了解如何使用 Python 来实现这些雄心勃勃的目标。我们就做一个简单的,几乎不需要任何设置就能完成的任务。因为就像某人曾告诉我(我记不清是谁了):“做得更多,你就能 学到更多。”

Python CI/CD 基础 – 自动化一个基本任务

DevOps 的一个重要但常被忽视的部分是,整个过程强迫你生产的文档的彻底性。这些文档不必庞大;它们不必详细到让人困惑,使人们在阅读或编写文档时三思而后行。它必须清晰、简洁且直截了当。最重要的是,它必须存在。说真的,最后这一点比你想象的还要稀缺。

那么,我们如何使用 Python 来促进文档生成过程呢?好吧,我们可以使用一个叫做 pip 的小库:

pip install sphinx

完成此步骤后,进入项目的根目录并使用以下命令初始化 Sphinx:

sphinx-quickstart

系统会为你提供一些设置选项。你现在可以将这些设置保留为默认值,以便获取示例文档。稍后你可以在 conf.py 文件中更改这些设置。

图 9.1 – Sphinx 快速启动菜单

图 9.1 – Sphinx 快速启动菜单

这将创建一个初始的目录结构,可以以 HTML 或 LaTeX 等多种文档格式构建。

图 9.2 – 完成 Sphinx 设置

图 9.2 – 完成 Sphinx 设置

现在,让我们写一点 Python 代码,和 Sphinx 生成器一起使用。

图 9.3 – 待文档化的初始代码

图 9.3 – 待文档化的初始代码

你会注意到代码注释中的细节。这些细节是 Sphinx 在创建文档时参考的内容。这就是开源项目如此重视代码注释的原因。如果你在库中遇到这些注释,你现在知道为什么了。接下来,运行以下命令来构建 HTML 文件作为文档:

make html

这将生成一个文档 HTML 文件,你可以托管它,它看起来大致是这样的:

图 9.4 – 文档 HTML 页面

图 9.4 – 文档 HTML 页面

当然,这个过程是手动完成的。但通过正确的文档化实践,它可以通过运行上述命令的工作流来实现自动化。

无论你是经验丰富的 DevOps 工程师,还是新手,你都知道,DevOps 工程师必须做的一件事(在这一部分中已经介绍过)就是与开发人员合作,要求他们做一些能够让自动化流程和其他 DevOps 流程更加顺畅的事情。获得开发人员信任的一个方法是通过某种方式让他们的工作变得更轻松,以此证明你的价值,同时,做一个好同事。你可以通过礼貌待人,咖啡喝完后再做一杯,为他们开门……以及在下一部分中提到的,帮他们简化生活的方式来做到这一点。

与开发人员和基础设施团队合作交付你的产品

作为一个 DevOps 工程师意味着你需要成为团队中的终极合作者。这也意味着你必须与团队中的几乎每个人保持社交关系,并且让他们对你有好感。老实说,这并不难;只要笑一笑他们尴尬的笑话,聊上几句闲话,你就会突然成为大家的朋友。如果你愿意,带领大家一起合作并不难。然而,有时确实很难让他们的努力与自己的协调一致。这就是我们为什么需要协作工具的原因。除了普通的 GitHub,我们还有各种各样的工具来支持你使用的开发模式。Jira、Slack、Zoom、Google Chat、Teams……我可以一直说下去。经常发生的情况是,很多团队使用多个协作工具。所以,问题就变成了,如何让这些协作工具彼此协作呢?

工具本身提供了许多连接器,但有时它们的功能需要通过一些代码和 API 调用来实现。我们现在将使用 Python 来连接两个非常常见的生产力工具:Todoist 和 Microsoft To Do。你可能听说过或者使用过其中一个或两个工具。

Todoist 是一个简单的待办事项应用程序。它没有什么特别的,它与其他类似的应用程序(如 Jira 或 Trello)非常相似。Microsoft To Do 也是如此,只不过它集成到了 Microsoft 365 中。

首先,我们从 Todoist API 中提取任务列表。为此,我们需要创建一个 Todoist 账户,并通过 UI 添加一些任务:

图 9.5 – Todoist 仪表板

图 9.5 – Todoist 仪表板

这里只是一些带有截止日期的任务。现在,让我们获取 API 令牌来调用这个 API。在你的账户的 todoist 库下:

pip install todoist_api_python

接下来,编写一个脚本,将你的 API 令牌集成并列出所有任务。

图 9.6 – 获取 Todoist 任务的代码

图 9.6 – 获取 Todoist 任务的代码

很简单,稍微加点语法糖,你就能得到如下的任务列表:

图 9.7 – 从 Todoist API 提取的数据

图 9.7 – 从 Todoist API 提取的数据

这只是一个小示例,但你可以看到内容、描述和到期日期,这些正是我们关注的重点。现在,我们将尝试用 Microsoft To Do 做同样的事情。为此,我们只需要调用 API 端点。Python 中也有一个用于 Microsoft To Do 的库,但它仍处于实验阶段。

你需要从 Microsoft 获取一个授权令牌,可以通过调用 API 来获得,示例如下:learn.microsoft.com/en-us/graph/auth-v2-user?context=graph%2Fapi%2F1.0&view=graph-rest-1.0&tabs=curl。我没有包括这个过程,因为它已经很清晰地在这里描述了。如果你希望自动生成令牌,可以稍后将其集成到代码中。现在,让我们修改代码,使用我们从 Todoist 获取的内容、描述和到期日期:

from todoist_api_python.api import TodoistAPIimport jsonfrom datetime import datetime, timezoneimport requests#API tokensapi = TodoistAPI("<your_api_token_here>")access_token = "<Your_Microsoft_token_here>"#Endpoint for your default task listTODO_API_ENDPOINT = YOUR_TODO_ENDPOINThttps://graph.microsoft.com/v1.0/me/todo/lists/@default/tasks#Function to create task in Microsoft Tododef create_todo_task(title,date):    headers = {'Authorization': 'Bearer '+access_token,        'Content-Type': 'application/json'}    payload = {'title': title,        'dueDateTime': date}    response = requests.post(TODO_API_ENDPOINT, headers=headers, json=payload)    return response.json() if response.status_code == 201 else Nonetry:     #Get task in JSON form     tasks = json.dumps(api.get_tasks(),default=lambda o: o.__dict__,sort_keys=True, indent=4)    #Parse tasks    for task in tasks:        title = task["content"]        date = task["due"]["date"].replace(tzinfo=timezone.utc).astimezone(tz=None)        #Create tasks based on information        create_todo_task(title, date)except Exception as error:    print(error)

就这么简单。我们可以使用这段代码将任务从 Todoist 转移到 Microsoft To Do。我们甚至可以利用我们前几章中的基于微服务的架构(例如在第八章理解事件驱动架构中)来使用 Webhook 和事件,使这个过程更加高效。说到事件,在很多服务器中,最常见的事件之一就是故障发生。在故障发生时,需要一个回滚策略。我们来看看 Python 如何帮助实现这一点。

执行回滚

我在本书中多次提到:犯错误是可以的。因为大多数错误都是可以恢复的。有些错误不能恢复,但如果你保持清醒,它们其实很容易被识别。在 DevOps 中,这同样适用。你可以撤销你的错误。通常,问题变成了你能多快、多安静、多有效地做到这一点。这正是回滚所做的。它们有助于识别和修正问题。

回滚可以手动执行,也可以自动执行。有时手动回滚太慢,且需要人们实际上意识到某个事件或错误,这在团队比如休假时可能需要很长时间才能发现。在这种情况下——以及许多其他情况——基于指标的自动回滚是必要的。正如我们所知道的,Python 在这方面做得非常好。

部署回滚有很多种方式:蓝绿部署(50% 的流量在旧实例上,50% 在新实例上),金丝雀部署(极少数用户会获得新特性以便在生产环境中进行测试),以及其他各种方法。我的一个最喜欢的方式是红黑部署。在这种部署方式中,应用程序有两个实例:当前实例(红色)和未来实例(黑色)。你将终端从红色切换到黑色。如果这样不行,你可以将其切回到红色。听起来简单吧?无论如何,这里有一个示意图:

 图 9.8 – 红黑部署示意图

图 9.8 – 红黑部署示意图

所以,记住,红对黑,你没事,黑对红,你完蛋。哦,等一下,那是珊瑚蛇的说法。但这里的概念其实很简单。这是一种可以很容易处理的故障转移类型。大多数 DNS 服务都内建了这种功能。但这并不意味着它不能从编码的角度来处理。我们尝试以这种方式处理它的原因,是为了给自己更多的控制权,并为未来可能的变更(比如一次性故障转移多个站点)留出空间。那么,让我们从编码角度尝试一下,使用 AWS Route 53 上的 Lambda 微服务执行故障转移:

import boto3def  lambda_handler(event, context):     domain_name = '<your_domain_name_here>'    default_endpoint = '<your_endpoint_here>'    # Initialize the Route 53 client    client = boto3.client('route53')    # Get the hosted zone ID for the domain    response = client.list_hosted_zones_by_name(DNSName=domain_name)    hosted_zone_id = response['HostedZoneId']    # Update the Route 53 record set to point to the default endpoint    changes= {        'Changes': [            {                'Action': 'UPSERT',                'ResourceRecordSet': {                    'Name': domain_name,                    'Type': 'A',                    'TTL': 300,                    'ResourceRecords': [{'Value': default_endpoint}]                }            }        ]    }       client.change_resource_record_sets(        HostedZoneId=hosted_zone_id,        ChangeBatch=changes    )        return {        'statusCode': 200,        'body': "Nothing, really, no one's gonna see this}"    }

好吧,这就是代码。将它放到一个 Lambda 函数中,并设置适当的触发条件,当你需要时就可以随时重置为默认值。当然,你也可以使用常规的 Route 53 来做这件事,但这种方法提供了更多修改的选项。修改是 CI/CD 过程中的一个非常重要的部分,因为没有什么是永恒不变的。

总结

那么,让我们总结一下你在本章中学到的内容。你已经了解了 DevOps 的哲学和方法论,它就像一条源源不断的河流,始终在变化之中。你还学到了在进行 CI/CD 工作时,你必须采用类似的哲学。在下一节中,你学习了一个可以在 Python 中执行的基本 CI/CD 任务。接下来的部分,你学会了如何通过简化开发人员的日常任务并确保他们拥有所有必要的便利和生产力工具,来帮助开发人员。随后,我们还学习了如何执行回滚,并介绍了一种非常独特的简单回滚技术。

如果你能坚持看到现在,感谢你。你已经阅读了本书的九章,而我依然能够吸引你的注意力。我想我一定做得还不错,对吧?也许?好吧,我们来展望下一章——其实是下一节——因为我们将变得更加雄心勃勃。我们将探讨一些大公司如何在日常工作中使用 Python。听起来很刺激吧!

第三部分:让我们更进一步,构建更大

在本部分中,我们将提升我们的 DevOps 和 Python 技能与知识,探索一些关于该主题的高级概念。

本部分包含以下章节:

  • 第十章全球一些最大公司的常见 DevOps 使用案例

  • 第十一章MLOps 和 DataOps

  • 第十二章Python 如何与 IaC 概念集成

  • 第十三章提升 DevOps 技能的工具

第十章:一些全球最大公司的常见 DevOps 用例

看到个人和组织实现他们原本可能认为超出自己能力范围的目标,我感到非常满意。

– Don W. Wilson

在本书中,我要求你对我和我写的内容充满信任。我还要求你对DevOps过程以及它最终带来的成果有更大的信任。随着新章节的开始,我想现在是时候让我付诸实践,展示一下背后的工作原理。

本章内容全部基于公开信息,这些信息要么由实施该用例的公司提供,要么由为其提供咨询的公司提供。它们可以在各大云公司客户成功案例页面上公开查找。这些商业信息被自愿公开,目的是作为示范,帮助其他企业和行业中的人们改善自己的工作负载。

我将使用这些用例作为示范,展示你如何运用 DevOps,并如何使用 Python 来支持它们的应用。我不想在这一部分使用假设的情况,因为那样会显得不真诚。然而,我使用这些案例的方式是为了展示如何使用 Python 来复制这些用例,而不是它们是否实际使用了 Python。它们可能用过,但这并不重要。

本章的主要动机是向你展示,DevOps 的理念在实际场景中已经得到了应用,创造了真正的客户价值。这也为你在 DevOps 学习旅程和本书过程中所学的技能提供了依据。

所以,废话不多说,在这一章中,你将了解以下内容:

  • 一个亚马逊网络服务AWS)的用例,帮助弥合了业务分析师与数据工程师之间的鸿沟

  • 一个 Azure 用例,节省了大量编码时间,并帮助提高了部署效率

  • 两个谷歌云平台GCP)的用例,涉及运动联盟找到解决方案,使他们的联盟更具可访问性

AWS 用例 – 三星电子

三星在很多方面都使用了云和 DevOps 原则。鉴于三星的庞大规模,这一点是显而易见的。因此,我没有对它们进行正式介绍,因为,嗯,你知道三星是什么。我们将讨论的特定案例非常有趣:它涉及到让普通的业务分析师参与机器学习ML),同时也让那些懂得编写 ML 算法和应用的人员最大限度地发挥他们的能力。这是一种双管齐下的方法,充分发挥两者的能力,增强它们之间的反馈并促进合作。下图展示了三星原始的数据分析工作流程:

图 10.1 – 三星原始的数据分析工作流

图 10.1 – 三星原始的数据分析工作流

那么,让我们来分析三星所面临的情况,以及他们用来解决问题的方法。

场景

三星的分析团队由两个不同的部分组成:业务分析师和数据科学家。业务分析师从理解人类行为的角度,分析三星消费电子产品提供的问题和数据,进而改善产品和预测客户行为。

为此,他们需要数据,海量的数据,并且需要能够处理这些数据的能力。这时,数据科学家发挥了作用;他们将分析师的直觉转化为可以解决的案例,从而提供具体的数据。他们创建了数据分析和机器学习算法,提供业务分析师可以用来为产品提供推荐和建议的洞察力,同时还会向数据科学团队提供反馈。

然而,这种合作关系并不像大家想象的那样顺利;由于多种因素,两个团队之间存在差距,主要原因是尽管两个团队相互依赖,但在工作方式上存在差异。

除此之外,数据科学家们还需要在不同的数据集上执行重复性的任务,以确保向业务分析师提供正确的输出。这导致数据科学家没有时间去尝试其他机器学习算法和他们可能想要实现的各种技术,也使得业务分析师在他们负责的数据上失去了显著的控制权,这意味着他们无法全面了解数据,无法进行有效的分析。

需求变成了找到一种方法来协调数据和算法,使得业务分析师可以使用常见的算法,同时数据科学家也能对其进行调整。这将创造一个环境,使得数据科学家不必等待业务分析师,反之亦然,从而为双方释放出时间和资源。

头脑风暴

现在,你可能会问,如果你对这个过程不熟悉,在现代社会中,业务分析师如果不会写代码,他们是做什么的呢?嗯,这比你想象的要普遍得多。分析师的工作并不是编写代码或解析数据(尽管这通常是他们会做的事情);他们的工作是分析摆在他们面前的信息,并利用这些信息提供某种推荐、洞察或解决方案。

问题的核心在于,两个团队之间存在着沟通和理解的鸿沟,而要弥补这一差距没有重大人员变动几乎是不可能的。而这种人员变动通常不符合公司的最佳利益。

您需要确保一个团队不依赖于另一个团队,并能根据他们收到的信息而不是信息来源的人操作(松耦合,记住?)。因此,一个理想的解决方案是找到一种方法,让两个团队能够同时处理相同的数据,而不需要通过不必要的沟通来阻碍彼此,这正是他们找到的解决方案。

解决方案

图 10.2 - 三星的新数据分析工作流程

图 10.2 - 三星的新数据分析工作流程

解决方案基本上是这样的:会有一个数据的单一来源,数据分析师和数据科学家都可以从中工作;数据科学家会为分析师提供访问用户界面的权限,以执行他们想要的数据操作。科学家们随后会调整这些操作和算法,试图在不需要分析师联系他们获取当前运行算法版本结果的情况下获得更好的结果。这本质上就像是为数据分析师内部使用创建的应用程序,由数据科学家构建。

存在不同的技能和思维方式是有其原因的,这就是为什么团队中需要有不同角色和视角的原因。在这种情况下唯一需要做的事情就是确保所有这些角色都处于可以成功的位置。通过为这两种角色提供一个平台和工作流程,使他们感到舒适,找到了一种合理的方式让整个团队能够最优化地工作。三星通过使数据集中化,并使两个团队都能访问数据,而且两个团队都不需要依赖彼此来取得进展,同时找到了一种方式使彼此能够互补和支持。

接下来,我们将看一个使用案例,在这个案例中,公司不得不处理具有类似技能水平但经验不同的人员,并同时应对业务需求和生产力。

Azure 使用案例 - Intertech

在搜索使用案例和场景时,我完全爱上了 Intertech 通过 DevOps 转型的力量所做的事情。它使用 Azure 的事实并不像它如何使用 Azure 和 GitHub 服务来现代化和革新其管道那样重要。这个使用案例就是 DevOps 应该为公司带来的价值。它还解决了本书后面部分(我们现在所在的部分)将变得非常相关的内容:生成式 AI。让我们来看一看旧 Intertech 工作流程:

图 10.3 - 旧 Intertech 工作流程

图 10.3 - 旧 Intertech 工作流程

这个图表展示了一种沉没成本谬论,这种谬论会导致生产力的严重损失。它代表了 Intertech 在运作上效率低下,浪费了大量本不该处理的任务上的核心人员。所以,我们来看看 Intertech 是如何应对这个问题的。

我们先从 Intertech 说起。他们是一家 IT 运营公司,支持土耳其一些最大商业客户的基础设施和运营需求。现在,你可以想象,这种运营在很多层面上会相当难以管理。问题并不大;实际上,它们是微小的割伤和针刺,但这些往往是最令人烦恼的痛苦。它们既令人恼火又分散注意力,如果你让它们存在,它们会偷走你的注意力。在这一部分,我们将讨论心理和身体上的痛苦,以及 Intertech 的优秀员工如何努力减少这些痛苦。

场景

Intertech 致力于为土耳其一些最大公司创建和维护解决方案。请记住这两个词:创建和维护。这是开发任何软件解决方案的两个基本部分,但它们是完全不同的。创建某物意味着赋予它生命;你在给它“生命”,这是痛苦且漫长的,但它是美好的。维护某物就像是给你刚出生的孩子换尿布。这是令人讨厌的,但你得对它负责,而这个孩子是不会自己照顾自己的,无论它多么独立或成熟(大多数孩子并非如此)。记住这个类比,它会贯穿接下来的各个小节。多年以后,如果我在为这本书写第二版的时候依旧在给它“换尿布”,我也会记住这一点。

所以,Intertech 面临的困难是:在开发新项目的同时,如何维持旧项目。而维持旧项目的任务虽然琐碎,却需要大量的人力、工时和精力,这些本可以用来探索新的价值传递方式。解决问题的思维循环,从最初的问题到最终找到解决方案,通常需要宝贵的时间和智力努力,而这些努力往往远低于解决问题者的能力。基本上,它迫使聪明的人去做一些愚蠢的任务。

那么,在思考这个问题的解决方案时,我们首先应该想到什么呢?也许有一种方法可以在不需要过多思考和研究的情况下找到解决方案。当然,这种方法不适用于那些需要真正集中精力的关键任务,但适用于那些琐碎的任务,比如需要搜索一堆术语然后找到合适的 Stack Overflow 页面,再将其复制粘贴到解决方案中,最后运行几次确认其有效性。也许有一种工具可以整合所有这些信息,为简单的日常问题生成一个简洁的解决方案,从而为我们节省时间。也许这种工具的名字与人工智能有些相似?好吧,没有什么能和人工智能押韵。AI,就是我所说的。

头脑风暴

好吧,到现在你大概已经知道我想说什么了,那么让我分享一下我个人的经验,讲讲我通常如何在日常生活中使用生成性 AI。这是一个非常有用的工具,像你们这些读这本书的人都会证明这一点,除非你是坚决反对 AI 的人,或者你脑袋里储存了所有世界上的信息。如果你是后者,联系我告诉我黑道家族结局到底发生了什么。

我使用生成性 AI 来制定解决方案的步骤,并将其框架化,以便我可以根据我给出的上下文进行理解和修改,或者根据它尚未训练过的内容进行修改。这比每次需要解决问题时都去查找教程容易多了,而当教程不完整时,还得去找另一个教程。通过像 ChatGPT 这样的生成性 AI,局面发生了变化;现在我可以不耐烦,而生成性 AI 则能耐心地处理我的请求。这非常棒,因为说实话,思考是很累人的,特别是当你不得不一次又一次地思考同样的事情时。它会让你的大脑麻木,降低生产力。Intertech 的人们得出了相同的结论。

如果你想继续使用孩子的类比,这就像有一股神奇的力量,它会自动给孩子换尿布,并把尿布围绕在孩子身上,只等你把别针别上去。所以,从这个角度来看,如果有一个重复的任务出现,并且任务足够简单,它就会通过各种方式妨碍生产力。在这种情况下,生成性 AI 可以来拯救我们。任何使用过它的人都知道 ChatGPT 在这方面有多么有用。实际上,凭我的个人经验,ChatGPT 对我最有帮助的事情就是解释我粘贴进去的代码行。因为我无法像 AI 那样迅速收集信息,这通常会比 ChatGPT 所需的几秒钟更长。因此,既然我们已经证明了生成性 AI 解决方案的有效性,让我们来看看 Intertech——他们也得出了类似的结论——是如何将这些概念融入到他们的 DevOps 和 Azure 工作负载及任务中的。

解决方案

Intertech 创建的解决方案围绕着 GitHub Copilot 和 Azure OpenAI 集成展开。Copilot 在经过某个特定代码库的训练后,只要给出正确的提示,它就能简单地编写所需的脚本。Intertech 将 Copilot 集成到开发人员使用的集成开发环境IDE)中,这样开发人员写一两行代码后,Copilot 就会推测他们的意图并完成大部分剩余代码。开发人员只需验证几个测试,瞧,一套高效的代码交付系统就完成了,节省了时间和思考力。下图展示了该解决方案:

图 10.4 – 具有生成式 AI 的新 Intertech 工作流程

图 10.4 – 具有生成式 AI 的新 Intertech 工作流程

图中的解决方案可以分解为以下步骤:

  1. 当初级开发人员对遗留系统进行维护时,他们使用已经在该系统的代码库上进行训练的 Copilot 实例。

  2. 这使得他们能够以易于阅读的形式查询并找到他们在代码库中寻找的答案。

  3. Copilot 还帮助自动完成代码,并保持与代码库其余部分风格的一致性。

  4. 高级开发人员在大多数情况下不参与这一过程,这样他们可以专注于较新的项目和系统。

由于大多数维护工作都由高级开发人员完成,因为他们曾参与过旧项目,这些开发人员(由于经验丰富,他们的时间更为宝贵)需要亲自处理许多问题以解决它们。即便这些问题交给了初级开发人员,他们仍然会向高级开发人员请教大量建议,这虽然并非负面行为,但仍在一定程度上阻碍了高级开发人员。

为了减少初级开发人员对高级开发人员的依赖,公司将 Azure OpenAI 聊天机器人集成到了他们的 IDE 中。这些聊天机器人能够从代码和项目文档中抓取并推断出信息,并解答大多数初级开发人员的疑问。这样不仅减少了高级人员在代码维护上的时间消耗,还通过个性化的“保姆”引导初级开发人员完成代码库。一个惊人的结果是,公司内部的邮件数量减少了 50%,这是一个值得注意的数字。难道你不想要减少 50% 的邮件,同时保持甚至提高生产力吗?更不用说,不用参加会议、避免重复做老事所释放出来的时间。这种价值乘数效应能将公司推向更高的水平。

说到价值,到目前为止我们所看到的价值都是基于事实表和计算机代码,但在下一部分,我们将看到 DevOps 如何在更具物理感知的环境中创造价值,以体育为例。

Google Cloud 用例 – MLB 与 AFL

如果你有一件事要了解我,那就是我非常喜欢体育。我热爱体育,喜欢学习新运动,就像我热衷于跟随多年来一直关注的老运动一样。

我已经跟随美国职业棒球大联盟MLB)多年,在这期间,棒球一直是数据分析的运动。现代大多数球队在选拔球员时都受到数据分析的驱动,球员的表现通过对他们比赛中收集的多个指标进行统计分析来衡量。你可能看过电影《点球成金》(或者读过迈克尔·刘易斯的书),它讲述了统计方法在选择棒球球员中的应用,以及这些方法如何帮助奥克兰运动家队在 90 年代末和 2000 年代初取得成功。

在 MLB 中,引入分析数据的结果之一是对比赛本身以及完成一场比赛所需时间的分析。为了简化比赛流程,MLB 引入了投球计时器,给球员仅有 15 秒的时间来投球。如下图所示,计时器需要在整个场馆内同步,并且与电视直播中的计时器保持同步。这种同步性为 MLB 实施其最新规则带来了独特的挑战:

图 10.5 – 原始 MLB 比赛计时系统

图 10.5 – 原始 MLB 比赛计时系统

我还想看看其他可能以某种方式使用数据分析和 Google Cloud 并将其集成到他们联盟基础设施中的体育联赛(或许采用了不同于 MLB 的方法)。在我的搜索中,我找到了一个完美的例子,就是澳大利亚足球联赛AFL)。以下图展示了 AFL 的教练员广播情况:

图 10.6 – AFL 教练员的广播情况

图 10.6 – AFL 教练员的广播情况

当我开始研究时,我几乎对澳大利亚规则足球(也叫做澳式足球)或 AFL 一无所知,除了知道大决赛当天是澳大利亚的公共假期。然而,在我的研究中,我确实发现了一点,那就是这两家公司都在使用先进的数据分析来寻找改善比赛观赏方式的方法,并分析是否有可能使比赛玩法和球员表现更加出色。

鉴于我对体育和所有围绕体育的“极客”内容充满热情,这一小节对我来说似乎是不可避免的。那么,让我们深入探讨,看看在这些用例中我们可以发现哪些 DevOps 和数据洞察。

场景

让我们从棒球开始;我喜欢棒球。它是坚忍不拔之人的运动;它是时间静止的运动。如果你有 23 个小时的空闲时间,请务必看看 Ken Burns 的《棒球》纪录片系列;你不会后悔的。但整体而言,棒球和尤其是 MLB 在过去几年中遭遇了一些挫折。休斯顿太空人有涉及作弊的丑闻,由于比赛时间过长,现场观众和电视观众的参与度下降。为了解决这些问题,同时保持比赛的完整性,MLB 转向了数据。他们利用各队单独使用的分析工具,并决定将其转向整个联盟。他们从整个赛季(包括 2430 场常规赛和季后赛)的所有比赛中收集与比赛、观众参与度相关的统计数据,并决定创建基础设施解决方案,以加强他们将要实施的解决方案。他们在这方面使用了 Google Cloud:既用于数据分析,也用于解决方案的实施。

澳大利亚足球规则是澳大利亚的头号运动。在这片下方的土地上,它是观众最多、影响最大的运动。它也是澳大利亚最具身体对抗性的运动,作为一个经常观看体育比赛和从事数据科学的人来说,一个体育运动在人类之间的身体接触越频繁,分析起来就越困难。在这里,数据需要在更加物理的层面上进行分解,关注于人体本身。这种方法使得澳式足球联盟能够开发出一套利用机器学习与人工教练相结合的系统,为那些依然对这项运动充满激情的弱势群体和不同能力的人提供训练方案。

这两个体育联盟面临了两个截然不同的问题,都需要完全不同的解决方法,但它们都在 Google Cloud 上采用了 DevOps 原则来解决。让我们看看它们是如何做到的。

集思广益

MLB 的问题影响了比赛及观众的观赛体验。人们讨厌浪费时间,也讨厌慢节奏的比赛。任何看过 NBA 季后赛最后两分钟比赛的人都知道这一点。MLB 通过观察每局所花的时间以及这些局中最耗时的部分来解决这个问题。他们发现投手投球前的准备时间和封杀时间延缓了比赛进程。这促成了投球计时器的诞生,一个记录投手在两次投球之间可以等待的时间的计时器。他们认为,投球之间的时间是比赛变慢的主要原因,并相信投球计时器能解决这个问题。该方案结合了 GCP 和 DevOps 原则,结合 GCP 服务和本地计时硬件来实现。作为一个球迷,我对此也非常感激。我们将在下一节讨论解决方案的完整内容。

AFL 的问题在于青少年参与度以及某些人在尝试参与这项运动时的身体局限性。澳大利亚还是一个面积广大的国家,人口分布非常广泛,这意味着需要接受澳式橄榄球训练的人们,尤其是偏远地区的人,需要远程进行训练和指导。目标便是尽可能覆盖更多人,并尽可能多地收集数据,以确保其准确性,同时还要能接触到在某些沟通形式上有障碍的人群。这个问题需要设备之间的通信方式,并且要求使用机器学习算法,特别是用于跟踪运动员在运动中使用的球,以便进行训练。

所以,考虑到这些问题,以及我们可能采取的解决方法,我们现在可以开始提出一些解决方案,帮助这些组织实现他们的目标。让我们看看他们究竟做了什么来达成这些目标。

解决方案

MLB 问题的解决方案涉及使用 Google Cloud 的 Anthos 服务网格将所有投球计时器协调在一起。集中式但分布式的计时器增强了比赛的完整性和这些新规则的执行。

MLB 的问题在于同步性。目标是将遍布整个体育场的比赛计时器同步,以协调它们的时间。在这里,Google 使用了其 Anthos 服务网格(如以下图所示),作为云端设备与棒球场地上本地设备之间的连接器。这使得投手的计时器可以在任何地方都清晰且准确,无延迟地显示,确保新系统的公平性。

图 10.7 – Anthos 同步游戏时钟用于 MLB

图 10.7 – Anthos 同步游戏时钟用于 MLB

引入比赛计时器使得平均棒球比赛时间缩短了 24 分钟。这是非常重要的,我认为这是我在如此非技术性场景中看到的 DevOps 思想最具意义的应用。让我们来做一下计算:每场比赛减少了 24 分钟,这使得比赛的平均观众人数从 26,843 增加到 29,295,取得了显著的成效。那么,究竟节省了多少时间呢?好吧,这是 24 分钟 x 29,295 人 x 2,430 场常规赛比赛。总共节省了 1,708,484,400 分钟,约等于 28,474,740 小时,1,186,447.5 天,或者 3,250 年。这是相当可观的节省,而球迷们也感受到了这一点,这就是为什么观众人数上升的原因。所有这一切都得益于能够协调松散耦合的基础设施。

AFL 问题的解决方法要求交付能够在网络不稳定时进行运动追踪的 ML 模型。这意味着任何使用的 ML 模型都必须使用移动设备端的计算能力,并且足够轻量,以免影响设备的正常运行。谷歌的开发团队曾帮助印度 印度超级联赛 (IPL) 提供过类似的教学解决方案。因此,他们最终得出的解决方案是一款名为 Footy Skills 的应用。

图 10.8 – AFL 教练指导广播系统

图 10.8 – AFL 教练指导广播系统

该应用使用了两个 ML 模型:一个用于根据大小、形状和颜色检测澳式橄榄球,另一个用于确定其空间深度和位置。它们还增加了一些功能,帮助听力和视觉受损的运动员以及使用轮椅的运动员参与比赛。

总结

这一章确实是一段奇妙的旅程。即使在我写的这本书中一些奇怪的内容面前,它依然独具特色。但我可以诚实地说,写这一章是一次愉快的经历,因为在写作过程中,我有机会研究了许多独特的解决方案。

我们从 AWS 解决方案开始,这展示了当你将人们放置在一个能够发挥他们技能并与其他技能结合而不是相互冲突的位置时,会发生什么。它还展示了 DevOps 解决方案如何不仅仅促进技术发展,也促进了这些技术背后的人才。

在 Azure 的应用案例中,我们了解了 AI 和 ML 与 DevOps 相结合如何提高从事创造性工作的团队的生产力。我们看到生成式 AI 如何被用来帮助开发者,减少关键人员需要完成的琐碎和重复性工作,并腾出时间和沟通渠道,使团队更加高效。

在第一个 GCP 使用案例中,大联盟棒球(MLB)需要一种方法来证明改变已经存在超过一个世纪的棒球规则是合理的。他们使用了可靠的数据和确凿的事实来做到这一点。这些事实通过收集整个赛季的棒球数据来提供。随后,他们利用这些数据和其他松耦合的协同技术来实现他们改变规则的愿景。通过数据和技术协调,他们创造了一个对他们有利且深受球迷喜爱的规则:这是一个几乎闻所未闻的壮举。

在第二个 GCP 使用案例中,澳大利亚足球联盟(AFL)通过将机器学习模型与 DevOps 原则结合,直接部署到遍布澳大利亚的设备上,从而扩展了其可用性和包容性,使得澳大利亚最偏远地区的人们也能接触到优秀的教练和教学,帮助他们提升体育水平。这为 AFL 提供了一个宝贵的增长和拓展平台,尤其是在他们的粉丝和未来球员之间。

总结来说,这些 DevOps 技术在现实生活中非常有用。许多解决方案如果没有 Python 也无法实现,特别是那些涉及机器学习和人工智能的解决方案。而这只是 DevOps 能带给你高度的起点。在下一章中,我们将更深入地探讨 DevOps 的数据科学方面,Python 与数据的结合,以及 MLOps

第十一章:MLOps 和 DataOps

未来很快将成为过去

– 乔治·卡林

我本来打算使用一些生成性 AI 来编写本章节的内容,但那样的话,内容会更像是自传而非技术书籍,因为生成性 AI 就变成了共同作者。我会尽量做一个非常客观的观察者,并公正地处理这个热门话题。我希望这能让我在未来可能由 AI 主宰的世界中占据一个有利位置。在过去的一个世纪里,机器学习和 AI 领域取得如此巨大的进展有很多原因:诺姆·乔姆斯基、艾伦·图灵、计算机本身的发明、科幻小说,以及人类对宇宙中新生命的永恒渴望。但在过去的几年里,这些概念作为可行产品和服务的交付,离不开 DevOps 的支持。毕竟,你觉得ChatGPT是怎么在大约五秒钟内,瞬间回答你四段问题,并且每分钟为一百万多人做到这一点的呢?

现在,本书中的所有章节和概念都让我们回到一个事实,那就是 DevOps 的核心是提供价值并使事情能够正常运行。当 DevOps 的焦点转向数据时,这一点并没有改变。在这种情况下,Python 变得更加有用,因为它是数据操作员的语言。如今,大多数人和数据开发环境默认使用 Python,因为它提供了进行数据处理和分析所需的工具。大多数高效的DataOps工作负载都会在某种程度上使用 Python。很多人会在运行脚本和任何需要编写的支持性操作脚本中都使用 Python。我们还将讨论MLOps以及帮助交付和优化机器学习模型和算法的操作。所有这些内容将在你阅读完技术 要求部分后,在本章节中进行讲解。

总结一下,在本章节中,你将学到以下内容:

  • 当涉及到 DataOps/MLOps 时,与常规 DevOps 的做法相比,存在着哪些不同?

  • 处理各种不同数据挑战的做法

  • ChatGPT 交付到你电脑屏幕背后的操作

技术要求

以下是一些帮助你跟上本章活动的要求:

MLOps 和 DataOps 与常规 DevOps 的区别

我们在任何技术行业中经常会遇到的一个问题是:数据角色和非数据角色之间的区别是什么?软件工程师和数据工程师、数据分析师和会计师,或者 DJ 和音乐作曲家之间的区别是什么?这是雇主经常提问的问题;人们猜测是否有一种是另一种的子集,或者它们是否完全不同。即使在瑞典语中,dator表示“计算机”,科学被翻译为vetenskap,而计算机科学则被称为datavetenskap,因此在某个时候,设计并更新瑞典语言的实体认为这两者之间几乎没有什么区别。

我们现在通过几个常见的 DevOps 用例来解释这些内容,这些用例可以应用到数据操作(DataOps)和机器学习操作(MLOps)这两个更具体的领域。在 DataOps 中,我们将介绍一个简单的方法,这个方法帮助我在使用 JSON 文件时,节省了很多 Python 中的数据连接操作。在 MLOps 中,我们将重点关注 GPU 方面,这是 MLOps 工程师可能需要处理的主要硬件。

DataOps 用例——JSON 连接

这是一个非常简单的小技巧,但它并不像你想象的那样为人所知。我真心认为,如果我能通过这一部分帮助到一个正在处理数据的人,那么我就算成功了。JSON 的操作是数据操作中非常重要的一个方面,尤其在 NoSQL 用例中非常显著,但在其他一些场景中也很常见。自然地操作 JSON 给 Python 带来了相较于许多其他编程语言的重大优势。它最有用的应用之一就是管道(|)操作符。这个小操作符可以用于执行连接、联合,甚至是数字的按位操作。它是 Python 使这些小数据操作变得更加易用的许多方式之一。

所以,我们将从仅仅一个连接 JSON 的函数开始:

a = {"one":1, "two":2}b = {"one":"one", "two":2, "three":3}print(a|b)

就这样。这就是代码,下面是代码的输出:

图 11.1 – JSON 连接的输出

图 11.1 – JSON 连接的输出

你会看到第二个 JSON 的键值会覆盖第一个 JSON 中的值,如果它们有相同的公共值,那么它们会保持不变,任何额外的值会被合并到整体 JSON 中。所以,记住这一点,每当你遇到 JSON 合并的问题时(而且这个问题可能会很常见),你就可以使用这个小技巧。现在,我们来看看另一个技巧,它肯定能帮助所有游戏硬件爱好者。它也会帮助其他人,但我特别提到硬件爱好者,因为他们拍了最多的 YouTube 视频,我希望能得到一些曝光。

MLOps 用例——超频 GPU

在这个 AI 艺术的现代时代,最高水平的图像生成可能需要大量的处理能力。对于任何类型的图形渲染,只有在没有其他选择的情况下才会使用 CPU,并且通常不建议用于较大的渲染。对于机器学习的 TensorFlow 算法,Google 的专有 TPU 是常见的选择。但同样,对于任何涉及图像生成或处理的任务,拥有一块好的 GPU 是非常有用的。如果出现了需要 GPU 额外能量才能完成任务的罕见情况,超频可能是必需的。

很多时候,GPU 处理器都有自己的驱动程序,而这些驱动程序也带有各自的命令行工具。在使用超频或其他 GPU 功能之前和之后执行这些命令可能会很麻烦。相反,通过使用 Python 内建的subprocess模块,我们可以自动超频或执行任何其他 GPU 相关的任务。对于这个例子,我们将使用 NVIDIA 的命令行工具,它可能是目前最流行的 GPU 品牌。NVIDIA 有一个命令行工具叫做nvidia-smi,它也包含了一个超频功能,这正是我们要调用的工具。现在,让我们写出能够帮助我们超频 GPU 的代码块:

import subprocessdef overclock_gpu():    # Set the new clock frequency for memory and graphics    new_clock_memory= <your_clock_frequency_in_MHz>    new_clock_graphics= <your_clock_frequency_in_MHz>    # Run NVIDIA command to overclock GPU    command = "nvidia-smi –i 0 --applications-clocks {new_clock_memory},{new_clock_graphics}"    subprocess.run(command, shell=True)if __name__ == "__main__":    overclock_gpu()

运行上面的代码后,将会对设备上设置的任何 NVIDIA GPU 进行超频。这将使得图像处理和生成等过程变得更快。这在对这些资源有更高需求时很有用,并且无法将这些需求转移到其他资源上时。所以下面的代码可以根据某些条件临时对 GPU 进行超频。一旦超频完成,你可以通过运行以下命令(无论是在脚本中还是外部)将其恢复到默认设置:

nvidia-smi –- reset-applications-clocks

这就是如何使用 Python 操作 GPU 的方式。这个部分的内容大多是学习如何操作数据及其相关方面。然而,数据本身也因为多种原因可能变得难以处理。一个主要的原因可能是数据的数量,可能会非常庞大。接下来的部分将会讲述如何找到应对来自各种来源的数据的方法,避免被大量数据压倒。

处理速度、体积和种类

在任何关于如何处理数据的教程中,通常都会简单介绍三维(three Vs)速度(velocity)体积(volume)种类(variety))。这三种方式描述了数据复杂性的扩展方式。在处理数据时,每种方式都会提出独特的问题,而你需要处理的大量数据可能是这三者的组合。速度指的是数据在一段时间内的流入速度,体积是数据的数量,种类则是呈现的数据的多样性。

因此,这一部分将根据三个 V 进行划分,每个小节中都将为可能出现的共同问题提供解决方案。这样一来,您将了解 Python 如何帮助处理如此大量的数据。让我们从容量开始,因为这是最简单的,而且当谈到大数据时,可能是人们首先想到的。

容量

数据的量是一件相当简单的事情。它表示一定量的数据,其中大多数(如果不是全部)将是相同类型的数据。如果我们要处理大量数据,将需要理解数据的时间敏感性以及我们手头的资源。通常处理的数据量根据数据是基于宽度还是长度而不同(即一行数据是否有很多字段或者数据行数非常多)。这两种情况需要不同的解决方案,有时甚至需要专门的数据库。还有可能数据集根本不是数字和字母,而是音频或视频文件。在本节中,我们将使用一个示例,在拥有包含大量字段/列的数据库或数据文件时会非常有用。

要开始,我们需要一个大容量数据集,因此我们将使用一个名为Mockaroo的应用程序,该应用程序允许您使用生成式 AI 生成数据字段和样本数据(在本章非常合适)。让我们转到 Mockaroo 网站,为我们的样本数据生成几个字段:

图 11.2 – Mockaroo 模式

图 11.2 – Mockaroo 模式

我们用 Mockaroo 生成的数据集如下所示:

图 11.3 – Mockaroo 创建的样本 CSV

图 11.3 – Mockaroo 创建的样本 CSV

上述图示仅显示了其一小部分;它是 1,000 行中有 20 个非常大字段。让我们编写脚本来解析它:

import csvdef read_large_csv(file_path):    with open(file_path, 'r') as csv_file:        csv_reader = csv.reader(csv_file)        next(csv_reader, None)        for row in csv_reader:            yield rowcsv_file_path = 'MOCK_DATA.csv'for row in read_large_csv(csv_file_path):    print(row)

脚本在读取 CSV 文件方面可能看起来有点多余,但之所以这样是为了确保 CSV 中的所有行不会同时加载到操作系统的内存中。这种方法将减少数据内存的负荷,是在内存无法容纳大量数据的系统中读取大量数据的绝佳方式。其原理是读取一行数据,然后释放内存中的数据再读取其他行。这是在读取高容量数据时有效管理内存的方式,从而使读取速度更快、更顺畅,正如本图所示:

图 11.4 – 生成器背后的工作流程

图 11.4 – 生成器背后的工作流程

现在,这已经足够简单了,但是当每次只有一行数据时,但是持续不断,例如流数据时会发生什么?所有这些都需要在其传入时进行实时处理。我们该如何实现这一点?让我们找出答案。

速度

处理数据速度是一个值得数十亿美元投资的问题。即使在今天,最大的在线视频流平台也在努力持续地发送直播数据。当然,造成这一现象的原因有很多,但事实是,许多解决方案没有正确的预算和质量组合,因此无法始终保持一致。不过,我们可以做到非常接近。

在这个练习中,我们将使用很多人称之为数据流的未来,甚至可能是现在的技术:Apache Flink。这是一个由 Apache 软件基金会开发的流式和批处理框架,旨在实现平滑快速的数据流。与许多由 Apache 软件基金会管理的框架不同,它是专门为基金会维护而创建的,而不是由某公司创建并开源以便于维护的项目。

Flink 本身不提供任何数据存储解决方案,它的工作是将传入的数据处理后存储到指定位置。它提供 Java、Python 和 Scala 的 API,并且支持所有云平台。

要开始使用 Python,你需要通过以下命令安装 pyflink

pip install apache-flink

如果你还没有安装 pandas,请同时安装:

pip install pandas

好的,现在让我们编写一些代码,将来自一堆 JSON 行的数据流转存到 CSV 表格中。这只是一个展示 Flink 工作流的示例程序,但它确实有效地实现了这个目的:

from pyflink.common import Rowfrom pyflink.datastream import StreamExecutionEnvironmentfrom pyflink.table import StreamTableEnvironment, DataTypesfrom pyflink.table.descriptors import FileSystem, Json, Schemaimport pandas as pd#Function to usedef flink_input(input_data):    # Set up the Flink environment    env = StreamExecutionEnvironment.get_execution_environment()    t_env = StreamTableEnvironment.create(env)    # Define the CSV file to output to along with temporary table name    t_env.connect(FileSystem().path('output.csv')) \        .with_format(Json().fail_on_missing_field(True)) \        .with_schema(Schema().field('data', DataTypes.STRING())) \        .create_temporary_table('output_table')    # Convert multiple JSON values into PyFlink CSV rows    input_rows = [Row(json.dumps(json_obj)) for json_obj in input_data]    df = pd.DataFrame([r[0] for r in input_rows], columns=['data'])    # Insert the rows into the output table which in turn inserts them into the CSV file    t_env.from_pandas(df).insert_into('output_table')    # Execute the Flink job    env.execute('CSVJob')input_data = [{'key1': 'value1'}, {'key2': 'value2'}, {'key3': 'value3'}]flink_input(input_data)

在这段代码中,你会看到 JSON 行通过临时表插入到 CSV 中,临时表作为插入数据的过渡。当插入临时表时,它也会将数据插入到 CSV 文件中。

这对 Flink 功能的解释相当简单,它的工作是处理基本相同的上下文,但同时处理数百万条流数据。所以,代码的扩展版本看起来相似,本质上执行相同的功能,唯一不同的是,它将在更多数据上执行这些操作。Flink 可以执行许多其他操作,数量庞大(这也是它如此受欢迎的原因之一),它们都遵循类似的模式,并可以与大多数现有的数据源集成。

现在,我们将处理数据中的一个常见复杂问题,事实上,这是一个总是需要以某种形式处理的问题。下一节讲的是多样性。

多样性

多样性是有趣的,可能也是大多数数据工作者面临的最复杂的负担。数据可以以各种形状和大小出现,并且通常以最出人意料的方式出现。许多黑客尝试通过将有效的 SQL 查询作为表单字段添加,从而发起 SQL 注入攻击,如果数据输入正确匹配,这些查询就会被执行。一个优质的质量保证测试员总是尝试各种测试,试图通过使用他们不应能输入到某些字段中的数据类型,来让很多应用程序陷入困境。但通常——当普通人有机会使用键盘时——会发生的是,人们会以某种方式意外地打破系统中的许多安全措施,从而暴露出之前未知的系统漏洞或问题。

所以,现在我们将进入一个可能会发生这种情况的示例,这在许多较为宽松的 NoSQL 数据库中尤为明显,这些数据库可能没有内建所有标准的数据格式化功能。我们将尝试在 JSON 文件中插入表情符号。表情符号通常使用 UTF-8 格式表示,但这种格式虽然在网页上很常见,通常在使用更不常规格式的数据库时,仍需要手动设置。

我们将使用 Google Colab 来进行这个练习,因为它对于像这样的简洁概念验证更加高效。让我们从添加一个包含表情符号的 JSON 变量开始:

user_data = {'username': 'user_with_emoji😊',}

接下来,我们将首先不使用任何 UTF-8 格式,将其插入到一个文件中:

import jsonwith open('sample.json', 'w') as file:json.dump(user_data, file)

这将生成如下格式的示例 JSON:

图 11.5 – 使用非 UTF-8 格式存储表情符号

图 11.5 – 使用非 UTF-8 格式存储表情符号

这个 JSON 在转换回网页时,将需要额外的解析步骤,这可能会减慢网页的加载速度。为了避免这种情况,我们可以找到一种方法,以表情符号输入的方式存储它。代码的最终输出将看起来更正确,如下所示:

图 11.6 – 使用 UTF-8 格式存储表情符号

图 11.6 – 使用 UTF-8 格式存储表情符号

这样就好多了,从长远来看,也会更加可持续。在 Google Colab 中的整体代码看起来如下:

图 11.7 – 用于基于 UTF-8 存储表情符号的 Colab 笔记本

图 11.7 – 用于基于 UTF-8 存储表情符号的 Colab 笔记本

这些只是一些简单的示例,帮助你入门如何优化大数据工作。我们已经讨论了很多关于数据的内容,也提到了机器学习的一些内容。但现在让我们把这一切总结一下,讨论一下最热门的话题:ChatGPT。接下来,我们将讨论 ChatGPT 背后的 DevOps 是如何工作的,以及当前类似的开源系统是如何广泛可用的。

ChatGPT 背后的操作

ChatGPT – 在我写这本书的过程中 – 从一个热门话题变成了生活中的一部分,有时几乎成为了信息工具的第二天性。它处理数据的方式以及它的本质一直是引发大量争议的话题。但我经常被我的一些不在行业内的朋友问到的是,它是怎么工作的?他们看到它几乎无缝地提供信息,无论用户的任何需求是什么,并且在聊天中将这些信息作为历史记录保留下来,以便以后回答问题。它还做得非常迅速。所以,大家都想知道它是怎么做到的。

让我们从 ChatGPT 是什么开始:它是一个大型语言模型LLM),这是一个非常大的神经网络,几乎接近于通用语言理解(即理解问题或查询并返回适当的解决方案的能力)。虽然 ChatGPT 目前是最大的热点,但这项技术实际上已经存在了几年,最初主要用于更具领域特定的聊天机器人。然而,最新的 LLM 已经被开发得可以讨论几乎任何话题,并在某些领域有轻微的知识专长。即便如此,ChatGPT 背后的概念非常简单:你给它提供的更多数据,它能容纳得更多,它就会变得更好。

当前的免费商业模型 GPT-3.5 由大约 1750 亿个参数组成,分布在 96 个神经网络层中。它是通过将超过 5000 亿个单词作为标记(数字)输入,并使用神经网络以模拟人类语言的方式找出这些标记之间的关联来实现的。用于这些标记的参考集就是互联网。就是这么简单,它将互联网的所有文本和数据拿来,利用这些来重建人类的互动和创造力。现在,你们中的大多数人可能已经看过 GPT-3/3.5 能做什么,而 GPT-4 则进一步提升了这个概念,使用了总计 1.7 万亿的数据点。正如我们在下图中所看到的,这就是向神经网络添加一些参数,直到它生成一个连贯的输出:

图 11.8 – ChatGPT 的工作流程

图 11.8 – ChatGPT 的工作流程

如图所示,你输入提示并获得由训练好的神经网络生成的答案。就是这么简单。现在,你可能会想,发生了什么呢?这个问题的答案非常有趣,但可以归结为一句简洁的话:我们不知道。

事实上,神经网络是一个谜,因为它们是围绕我们自己的神经元构建和建模的,因此它们不是由人类训练的;它们自我训练,以实现最佳的成功,类似于人类在通过考试时会找到最适合自己的学习方法。因此,我们并不真正知道这些神经网络的核心是什么;我们只知道我们可以训练它们,让它们变得擅长于对话。

你也可以在家里训练一个类似的模型。一些公司已经开发出了更压缩版本的 LLM,可以部署在较小的服务器上,比如 Meta 的 LLaMA。但即便如此,你也可以在任何你偏好的云服务提供商和像 Hugging Face 这样的开源网站上找到源源不断的生成型 AI 模型,随时使用并尝试,以帮助你更好地理解。

总结

DataOps 或 MLOps 工程师的旅程其实就是一个 DevOps 工程师,他对数据和机器学习概念有所了解。就这么简单。但正如我们在这一章中看到的,这些概念的应用确实非常有用。

首先,我们讨论了 DevOps 和这些相关领域之间的异同,以及它们如何相互连接。通过这一点,我们成功地展示了一些实际的用例,这些用例在将 Python 与 DataOps 和 MLOps 配合使用时非常有用。

接下来,我们讨论了如何处理所谓的大数据。我们分析了哪些因素使得数据如此庞大,并且通过每个用例逐一解决这些方面。

最后,我们讨论了 ChatGPT 以及它如何在全球范围内为用户提供所有服务。我们讨论了它复杂性背后的简单性和神秘感,以及加速生成型 AI 发展的开源大型语言模型(LLMs)的新时代。

在下一章中,我们将深入探讨 DevOps 工具箱中或许最强大的工具——基础设施即代码IaC),以及 Python 在这一领域中的应用。

第十二章:Python 如何与 IaC 概念集成

你永远不要在没有登顶之前测量山的高度。到达山顶时,你会发现它其实并不高。

—— 达格·哈马舍尔德

因此,在我们接近这一倒数第二章时,我们将讨论基础设施即代码IaC)这一话题。它的确是一个热门话题,已经在 IT 界掀起了风暴。这是对如今应用程序和工作负载资源比以往任何时候都要多的现实的回应,剩下要做的就是以最优化的方式排列它们。当然,你可能会发现手动完成一次就能成功。但如果要一遍又一遍地做,而且还保证不会出错?那简直是无稽之谈,是对人力的浪费。

基于这些观察,IaC 的概念应运而生。它提出,如果资源的创建、配置和更新以代码的形式进行标准化,并且常量和变量按有序的方式排列,那么你就可以标准化资源的复制,使得备份、故障转移、重新部署以及许多其他操作变得更加容易。

当然,IaC(基础设施即代码)也有其反对者,因为所有好的技术都会有批评者。他们认为它过于快速、强大,可能会导致云账单飙升,并且部署的资源可能变得难以解开,且不易于自定义。

这是一个有趣的观点,但当我看到 IaC 时,我想到了亚当·斯密在《国富论》中的开篇,他谈到劳动分工如何比任何其他因素更能提高生产力。IaC 概念也做到了这一点,其中劳动(资源)与构建它们的方案分离,使得它们可以一遍又一遍地重复生产。

Python 作为一种语言对于 IaC 非常友好,可能是仅次于 Go 的第二种语言。Python 中有许多本地的 IaC 库和栈(我们将在后面讲解),此外,还有与非 Python 编写的 IaC 工具交互的模块和 API。Python 的灵活性以及 IaC 所需的松散但严格的复制需求,使得 Python 在快速发展的 IaC 趋势中占据了如此重要的位置。

本章将帮助你理解 IaC 中的一个概念,这个概念被认为是 DevOps 的独特之处,是为了解决 DevOps 工作负载中对自动化和标准化的持续需求而创造的。它将帮助你理解为什么 DevOps 和编码需要如此紧密地联系在一起。这就是为什么,在本章中,你将学习以下内容:

  • SaltStack的基础知识,以及它是如何用 Python 构建的,还包括如何在代码和命令行级别评估 SaltStack 模块

  • Ansible的基础知识,以及如何创建你自己的自动化 Ansible 模块

  • 如何使用 Python 与其他 IaC 工具(如 Terraform)进行交互,在已经构建的自动化之上添加更多自动化功能

技术要求

为了将本章的内容推进到逻辑上的结论,您需要满足一些技术要求:

使用 Python 的 Salt 库进行自动化和定制

我们谈论所有这些我们想要构建的炫酷架构和框架,谈论我们想要使用的各种工具,所有这些工作流程,它们很有趣,非常令人兴奋,但许多 DevOps 的核心还是服务器管理;即 DevOps 的运营部分。它在今天的世界中依然重要,且随着人们的使用,它将永远保持相关。

现代的服务器管理要求根据所托管应用的需求创建现代的、定制化的环境。它还要求具备大量的自动化特性,以便维护并根据当前的情况保持优化状态。

收音机从未被电视取代,电视也很可能不会完全被流媒体取代。一切都会变化、进化或减少,但本质和使用它所需的技能始终会以某种方式保持相关。这就是为什么服务器管理工具永远存在,并且它们会不断发展的原因。

这就是 Salt 库(与 SaltStack 互换使用)的用途。它本质上由一个中央服务器组成,可以用来向所有连接到它的服务器发送命令。要求是 minion(待管理的服务器)必须配置为接收来自主服务器(负责管理的服务器)的命令。这是一个相当简单的概念,通过使用最精良的 Python 代码来实现。

那么,让我们开始吧。我们将从安装 Salt 服务器和 minion 开始,然后,我们将看看如何根据需要自定义构成 Salt 的 Python 代码。

Salt 库的架构如我们所说,包括一个主服务器和多个 minion。所以,让我们从创建与这个配置相匹配的服务器开始:

  1. 我们将在 GCP 中创建一个主服务器和一个 minion,并在 AWS 中创建一个 minion,以展示跨云功能。在本练习中,我们使用 Ubuntu 作为主要的操作系统OS),但其他操作系统同样适用。我们将在 GCP 中创建一个 minion 和一个 Salt 主实例:

图 12.1 – GCP 中的 Salt 实例

图 12.1 – GCP 中的 Salt 实例

  1. 我们还将在 AWS 中添加一个 minion 实例:

图 12.2 – AWS 中的 Salt 实例

图 12.2 – AWS 中的 Salt 实例

  1. 现在,我们可以通过 SSH 连接到 salt-master 实例并安装 Salt 主库:

    salt-master --version to run a check on the installation and get the version, similar to the following:
    

图 12.3 – Salt master 版本

图 12.3 – Salt master 版本

  1. 接下来,在两个 minion 实例中使用以下命令安装 Salt minion 库:

    salt-minion --version in order to verify installation and get the version, similar to the following:
    

图 12.4 – Salt minion 版本

图 12.4 – Salt minion 版本

  1. 接下来,返回到 Salt master 并运行以下命令:

    sudo nano /etc/salt/master
    
  2. 完成后,我们将在底部插入以下行,以处理 master 与 minion 之间的接口:

    interface: 0.0.0.0
    publish_port: 4505
    ret_port: 4506
    auto_accept: True
    
  3. 接下来,我们将运行sudo systemctl start salt-master来初始化 Salt master 系统模块,然后运行sudo systemctl status salt-master命令来获取 Salt master 的状态。当我们运行该命令时,得到的结果如下:

图 12.5 – 运行 Salt master 服务器

图 12.5 – 运行 Salt master 服务器

这是正在运行的 Salt master;现在,我们需要配置我们的 minions。

配置 minions 的步骤与配置 master 类似,但有一些不同之处,我们将在这里探讨:

  1. 在每个 minion 上,运行sudo nano /etc/salt/minion,然后在nano文件中输入以下内容:

    master: <salt_master_ip>
    

    salt_master_ip替换为你的 Salt master 服务器的 IP 地址。

  2. 然后,运行sudo systemctl start salt-minion,这将初始化 minion。接着,运行sudo systemctl status salt-minion来检查 minion 是否在运行:

图 12.6 – 运行 Salt minion

图 12.6 – 运行 Salt minion

  1. 现在,在 Salt master 上,你可以运行一个小示例命令,如下所示:

    sudo salt '*' test.ping
    

这就是基本的内容。现在,让我们深入看一下 Python 代码的细节。

让我们拆解一个特定的 Salt 模块,以便学习其背后的细节:

  1. 每个 Salt 模块都是一个可以调用的 Python 函数。我们以文档中的一个模块为例,在这里是网络连接模块:

图 12.7 – 来自文档的 Salt 模块

图 12.7 – 来自文档的 Salt 模块

这是很棒的文档!它显示了该功能及其 CLI 版本。

  1. 让我们来看一下这个功能:

    connect function takes a hostname compulsorily and it takes a port number optionally. It also includes **kwargs, which is just a large number of arguments such as proto and timeout that the function may have. The following command connects to a DNS instance:
    
    

    connect 函数。代码中的等效内容如下所示:

    connect("google-public-dns-a.google.com", port = 53, proto = "udp", timeout = 3)
    

    这两个命令是等效的,但你可以看到从命令行运行它们的实用性,因为这些命令在命令行中执行比作为函数调用要更为方便。这对于许多命令行工具都适用,也是它们存在的原因之一。

    
    

接下来,我们将了解 Ansible,它的执行思想与 Salt 类似,但采用了略微不同的方法,同时仍然使用 Python。

Ansible 是如何工作的,以及其背后的 Python 代码

本节的大部分内容将会是你在上一节看到的相似内容,类似的工具、相似的实现等等。但像 SaltStack 一样,这也是基础设施即代码(IaC)领域中一个重要且常见的工具,这就是为什么它值得我们这么详细介绍的原因。Ansible 功能强大,学习曲线可能没有 SaltStack 那么陡峭,而且它对那些喜欢清晰打包好的代码且不需要太多修改的用户来说更为友好。哦,另外它是用 Python 编写的。

Ansible 由 IBM 在其 Red Hat 品牌下运行和维护(我喜欢现在技术公司为其更为“前卫”的产品命名一些DC Vertigo风格的品牌,这真的使我所说的它是艺术的观点更加成立)。它旨在通过 SSH 密钥对访问服务器,从而维护和管理服务器。这简化了一些操作,例如当你控制一台服务器时,所有相关的服务器都在同一个虚拟私有 VPC)中。

Ansible 在可以控制的操作系统方面更加灵活,但在本练习中,我们将使用旧而可靠的 Ubuntu。所有这些背景介绍结束后,让我们进入 Ansible 的细节,给你一个它如何工作的简单示例。

我们将重用在 Salt 实验中使用的相同实例,所以让我们从主实例开始:

  1. 让我们首先通过 pip 在系统上安装 Ansible:

    pip as well, though that is not a frequent occurrence. Let’s run this command and install Ansible:
    

图 12.8 - Ansible 安装成功

图 12.8 - Ansible 安装成功

  1. 完成后,创建一个目录作为通用的 Ansible 配置目录,并进入该目录:

    mkdir ansible_project
    cd ansible_project
    
  2. 接下来的步骤是创建一个 inventory.ini 文件,它将起到与 SaltStack 中主 IP 相反的作用,将被控制的服务器的 IP 地址放入控制服务器中。

  3. 运行 sudo nano inventory.ini,这将创建一个清单文件,并在其中列出你希望运行的 IP 地址:

    [myhosts]
    <IP_1>
    <IP_2>
    
  4. 现在,你可以运行 ansible-inventory -i inventory.ini --list 命令,这将给你以下主机列表:

图 12.9 - Ansible 清单

图 12.9 - Ansible 清单

  1. 接下来,你可以通过以下命令对这些主机进行 ping 测试,以检查连接状态:

    ansible myhosts -m ping -i inventory.ini
    
  2. 现在,你可以通过集中命令服务器运行 playbooks 和 runbooks,甚至可以通过更改清单列表来将主机分组到不同的舰队中。

这些就是 Ansible 的基础;现在,让我们深入了解一下并查看 Ansible CLI 背后的 Python,正如我们在 SaltStack 中所做的那样。我们将通过创建一个自定义模块来使用 Ansible。这次我们会保持本地操作,但这基本上就是你如何通过 Ansible 运行自定义操作的方法。

现在,让我们创建一个由我们自定义的 Ansible 模块:

  1. 在你已安装 Ansible 的环境中,创建一个 hello_ansible.py 文件,并将以下代码添加到文件中:

    from ansible.module_utils.basic import AnsibleModule
    def join_strings(string_1, string_2):
        return string_1+string_2
    def main():
        module_args = dict(
            string_1=dict(type='str', required=True),
            string_2=dict(type='str', required=True),
        )
        result = dict(changed=False, message="" )
        module = AnsibleModule(
            argument_spec=module_args,
            supports_check_mode=True
        )
        string_1 = module.params['string_1']
        string_2 = module.params['string_2']
        result["message"] = string_1 + " " + string_2
        module.exit_json(**result)
    if __name__ == '__main__':
        main()
    

    这给我们提供了将要执行的 Python 代码,现在我们需要将 Python 代码的权限更改为可执行的。我们可以使用以下命令来实现这一点:

    chmod +x hello_ansible.py
    
  2. 接下来,我们需要制作一个剧本来在本地运行该函数,为此,我们可以创建一个名为hello.yml的剧本,并向其中添加一些代码:

    ---
    - hosts: localhost
      gather_facts: false
      tasks:
        - name: Hello World
          add_numbers:
            string_1: Hello
            string_2: Ansible
          register: result
        - debug:
            var: result
    
  3. 现在,执行 YAML 文件:

    ansible-playbook -M . hello.yml
    

    这将为你提供一个字符串形式的结果,如下所示:

图 12.10 – 我们的 Ansible 模块的结果

图 12.10 – 我们的 Ansible 模块的结果

这就是如何创建一个自定义的 Ansible 模块。这个模块将返回你输入的两个字符串的和,可能是你使用模块时能够做的最基本的操作。但其他一切都是相同的,即使是更复杂的操作。只需将代码中的join_strings替换为你选择的函数,并添加执行该函数所需的变量,最后返回结果值。例如,它可以是一个重新启动服务器或运行特定 CLI 命令的函数;几乎可以是你在使用的操作系统中可以执行的任何命令行操作。

现在,即便如此,这在可用资源及其使用方式方面仍然略显不足。它适用于更传统的系统,但对于需要非常规架构的系统,像 Terraform 这样的工具更为合适。接下来,我们将讨论如何使用 Terraform 与 Python 结合,进一步实现 IaC 自动化。

使用 Python 自动化 IaC 的自动化

基础设施即代码(IaC)已经变得越来越受欢迎,且以人们从未想象过的方式发展。当前最流行的 IaC 框架无疑是 Terraform。Terraform 不仅适用于服务器及其他更为稳定的资源,它同样适用于 API 和更松散的基础设施。基本上,任何可以在主要云平台中使用的资源,都有一个 Terraform API 可以使用。Terraform 在许多方面都是终极自动化工具,并且可以通过 Python 进一步实现自动化。

Terraform 当然是用 Go 语言编写的,这非常好,因为 Go 是 Python 的一个非常好的补充。两者各有所长,一个弥补另一个的不足。Go 适合单一实现,而 Python 在扩大该实现的有效性方面非常出色。基本上,Go 是子弹,Python 是枪,这是一个有效的组合。我们将在本节中充分利用这一组合。

Terraform 的创建者 HashiCorp 已经创建了允许 Python 与 Terraform 交互的 API。这些 API 被打包在cdktf库中,并由 HashiCorp 在多种语言中发布。让我们看看安装和运行cdktf库所需的步骤:

  1. 你首先需要安装 NPM、NodeJS 和 Terraform CLI,才能安装cdktf-cli

    npm install -g cdktf-cli
    
  2. 接下来,你可以使用cdktf创建一个 Python 环境:

    cdktf init --template=python
    
  3. 这将创建一个新的 Python 模板,之后可以用来运行 Terraform 模板。在模板中,你可以在stacks文件夹中找到main.py文件。在该文件夹中,你可以添加以下脚本:

    from constructs import Construct
    from cdktf import App, TerraformStack
    from imports.aws import AwsProvider, S3Bucket
    class MyStack(TerraformStack):
        def __init__(self, scope: Construct, ns: str):
            super().__init__(scope, ns)
            AwsProvider(self, 'Aws', region='us-east-1')
            S3Bucket(
                self,
                '<Terraform_Function_Name>',
                bucket=<'Bucket_Name_Here>',
                acl='private'
            )
    app = App()
    MyStack(app, "python-cdktf")
    app.synth()
    
  4. 这段代码足够简单,易于理解;它的工作方式与 IaC 模块完全相同,在你执行程序时创建一个名称为指定名称的 S3 存储桶。要执行程序,运行以下命令:

    cdktf deploy
    
  5. 如果你想撤销程序并仅删除你创建的资源,你只需运行以下命令:

    cdktf destroy
    

这基本上就是你需要做的所有事情。Terraform 的 AWS、Google Cloud 和 Azure 模块通常会随着安装一起提供,使用cdktf安装时,相应的 Python 库也会包含在内。你可以在 HashiCorp 的 GitHub 和官方网站上找到这些库的文档以及它们的功能。HashiCorp 对其产品能做什么非常明确,尽管有时将所有这些信息整理到一个地方可能会有些困难。所以,通过这些参考资料,你几乎可以创建任何你想要的资源;一切都取决于你的想象力。

总结

所以,这就是本章的内容,虽然比你可能习惯的章节要严肃一些,但这确实是严肃的事情。IaC 是一个值得认真对待的概念;它非常强大,可以解决许多需要应用 DevOps 原则的问题。

我们最初查看了使用 Python 在 SaltStack 中应用 IaC 的非常基础的案例。它虽然相当基础,但对于简单项目来说非常有效。我们通过深入了解 SaltStack 的内部原理,理解了它 Python 模块背后的逻辑。

之后,我们查看了稍微灵活一些的 Ansible,发现了它所提供的所有便利功能以及自定义的可能性。

最后,我们看了 Terraform 和cdktf,它与 Python Terraform 及资源模块一起使用,用于执行 Terraform 的资源创建。

所有这些内容希望能够帮助你获得一个新的视角,了解 IaC,并理解它在 DevOps 中的重要性以及它与 Python 的集成。

总结来说,IaC 是强大的、广阔的,且值得学习。在下一章中,我们将把你在这里和前几章学到的所有内容提升到一个更高的层次。

第十三章:将你的 DevOps 提升到新水平的工具

我所做的就是劝说你们所有人,无论老幼,不要只顾自己的人身安全或财产,而是主要关心灵魂的最大改善。

– 苏格拉底

好的,我们到了,最后一章,我们共同旅程的最后一站。过程非常愉快;希望你学到了些东西——我在教授你并试图帮助你进行你的旅程时也经历了许多快乐。整本书对我来说也是一段旅程。现在我们将以一些工具结束,本章将帮助你进一步推进你自己的 DevOps 和 Python 之旅。

将本章视为一个尾声:快速浏览本书中发生的一切,并回顾所有你所学到的知识,并将其放入实际的背景中。在《指环王》中,当战争结束时,我们看到所有角色都有自己生活继续的结局。这也是我写本章的目标,给你留下些能够激发你好奇心的东西,鼓励你进一步走上 DevOps 的道路,并自己去了解你能够实现什么。

说到个人感受,我可以说,在写这本书的过程中,我也在许多方面发生了变化。我在书中提到的协作概念帮助我与所有使这本书成为可能的优秀人士进行了很好的合作。它帮助我在某种程度上成长,并推动了我的能力的边界,让我明白了作为一名作者的责任。

我们所进行的旅程会改变我们,通常是变得更好。它们迫使我们面对自己是谁,以及我们想做什么。我在写这本书的过程中所进行的旅程让我更加欣赏我之前拥有的知识以及在本书研究过程中获得的知识。它让我成为了一名职业作家,虽然这本书不会像它对我一样成为你生活中的重要部分,但我希望它也能帮助你在自己的旅程中有所推进。

所以,最后一次,在本章中,你将学习以下内容:

  • 允许你将更复杂的自动化变为现实的高级自动化工具

  • 用于结合监控和数据分析工作负载并运用两项技能的高级监控工具

  • 当事件和警报真的失控时的高级事件响应策略

技术要求

要跟随本章,你将需要以下内容:

  • grafana.com上的 Grafana 账户

  • 一个 AWS 账户,能够使用 AWS Step Functions 和 Compute Engine

  • 一个 GitHub 账户,用于使用本书代码库中的代码 (github.com/PacktPublishing/Hands-On-Python-for-DevOps)

  • 启用了 Google Kubernetes API 的 Google Cloud Platform 项目

  • 你在学习过程中达到这一阶段所获得的成就感

高级自动化工具

在本书中,我强调了自动化的价值,它对于 DevOps 领域的重要性以及提高整体生产力的作用。只要小心使用并理解你的使用场景,自动化是很难过度的。在本书中,我们已经扩展了我们的工具箱,让我们可以在多个场景中自动化任务。我们甚至弄清楚了如何自动化那些自动化任务。但现在,让我们看看一个可能会将这个过程推进到更高层次的工具。它也恰好是我个人的最爱之一。

AWS Step Functions 是我总是非常兴奋地讨论和使用的工具。每当我寻找自动化工作流工具的金标准时,我总是想到它。我看到的任何其他表现良好的自动化工具,都具有与 Step Functions 非常相似的功能和可用性。当然,其他主要云服务也有可以替代的等效服务,并且在那些环境中也能很好地工作,但我更喜欢 Step Functions 的用户体验,并且它是最容易在这个工作流中解释的工具。

让我们来看一下 Step Functions 中的一些步骤(以及步骤中的步骤):

  1. 登录到你的 AWS 账户,在控制台中搜索 Step Functions

  2. 进入 Step Functions 页面,在右上角点击 Create state machine

图 13.1 – 创建状态机

图 13.1 – 创建状态机

Step Functions 工作流由动作组成,动作是服务的调用,完全基于它们的 API 参数;还有流程,流程决定数据的流向。

我们将演示的示例函数完全由流程组成,因为使用动作仅需要调用 API,你可以在任何需要的地方应用它们。以下是该流程的示意图:

图 13.2 – 状态机图

图 13.2 – 状态机图

这是直接从 Step Functions 控制台导出的,你可以在这里看到:

图 13.3 – 状态机控制台

图 13.3 – 状态机控制台

它也已经以代码格式导出,并放置在本书的 GitHub 仓库中以供使用。

我们可以像这样分解图中的内容 图 13.2

  1. 并行状态运行两个不同的功能,PingPong

  2. 它们都产生介于用户输入的起始值和结束值之间的随机数。

  3. 完成后,会比较这两个数字,看看哪一个大于另一个。

  4. 如果 Ping 大于 Pong,状态机会停止执行;否则,它将执行并行状态。

  5. 一旦保存了状态机,你可以访问它并使用 Start execution 按钮运行它。

图 13.4 – 在状态机上启动执行

图 13.4 – 在状态机上启动执行

  1. 你会被问到在此状态机中希望输入什么。在这种情况下,我们将选择起始数字为 5,结束数字为 100

图 13.5 – 启动状态机所需的参数

图 13.5 – 启动状态机所需的参数

  1. 该机器将继续直到 PingPong 给出相等的值,对于像这样的较小数字,约需两秒钟。

  2. Step Functions 记录了它通过的所有状态以及结果:

图 13.6 – 状态机中发生的事件

图 13.6 – 状态机中发生的事件

这个特定的应用看起来相当普通,但它是一个很好的概念验证,展示了如何使用 Step Functions 来创建工作流。一个实际应用的例子可以是我们以前用过的简单例子:删除 S3 存储桶中的所有对象。AWS 的用户界面中没有清晰的方式一次性删除所有对象;你需要一个一个地删除。但通过 Step Functions(它与每个 AWS 服务都集成),你可以选择并行列出并删除 S3 存储桶中的所有对象。如果你愿意的话。现在,我们将转向监控这些应用,了解如何进一步优化它们。

高级监控工具

监控的目的一直是确保你所说的工作项仍然在正常运行。而实际情况往往是,它们不会正常工作。这可能是你的错,也可能不是;故障的概念会妨碍解决问题。对工作负载进行适当的监控为更容易地解决问题提供了途径。

最简单的工作负载可以进行监控,并且对于接收到的信息的反应可以实现自动化,这样大部分时间你甚至无需参与处理问题。对于更复杂的工作负载,保持监控仍然很重要,以便能够快速响应任何情况,并且实时获得更多关于情况的信息。

通常情况下,随着你的解决方案变得越来越复杂,它开始使用更多的工具。更多的工具和服务意味着需要更多的监控,而且监控的复杂度也会增加。如果你的监控来自多个地方,它会变得相当麻烦。因此,一个好的解决方案是将监控集中在一个可以存储和管理所有数据的中心位置。

这时,Grafana 就派上用场了,它是我想要谈论的工具,因为它不仅提供了极为广泛的监控选项,还提供了令人惊叹的部署选项,允许你对 Grafana 实例进行管理或定制,这意味着它是针对两类人群的解决方案:那些偏好自制解决方案的人和那些偏好现成解决方案的人。

当我第一次开始研究 Grafana 时,我正在写一篇关于如何通过一个云的监控服务来监控多个计算实例的博客。我发现,虽然这相对简单,但仍然存在一些尴尬的问题。大多数时候,选择一个云平台往往会在其他服务上带来一些复杂问题。很多这些问题都被 Grafana 解决了,并且得益于它所投入的所有精力与工作。

那么,让我们深入了解使用 Grafana 的免费账户等级:

  1. 首先,创建一个 Grafana 账户。它是完全免费的,并且提供了很多单点登录(SSO)选项,这是我非常喜欢的。

图 13.7 – Grafana 注册

图 13.7 – Grafana 注册

  1. 然后,它会要求你输入个人监控子域名的 URL 以及部署实例的区域。设置好这些后,你就可以开始了。

  2. 或者,你也可以从这里下载并安装 Grafana 到自定义服务器:grafana.com/grafana/download?edition=oss。它几乎提供了所有操作系统的安装说明。

  3. 让我们来看看我们的仪表盘:

图 13.8 – Grafana 仪表盘

图 13.8 – Grafana 仪表盘

如你所见,Grafana 为我们提供了许多监控选项、集成和数据源。在这个练习中,我们将使用一个非常普遍的数据源,几乎任何人都能访问:Google 表格。

  1. 在控制台的搜索标签页中,搜索Google Sheets。接着,你需要安装插件。它位于最右侧的蓝色按钮:

图 13.9 – Google 表格连接器

图 13.9 – Google 表格连接器

  1. 然后,等待插件安装完成,并从同一页面添加新的数据源。

图 13.10 – 激活 Google 表格连接器

图 13.10 – 激活 Google 表格连接器

  1. 然后,Grafana 会要求你为要访问的 Google 表格实例创建身份验证凭据,并会提供适当的操作说明。

图 13.11 – Grafana 在其仪表盘上提供的说明

图 13.11 – Grafana 在其仪表盘上提供的说明

在这种情况下,从安全角度来看,JWT 令牌选项可能是最好的。你不希望随便放置 API 密钥,因此请按以下步骤操作:

  1. 按照给出的步骤创建一个服务账户,并将该服务账户的电子邮件地址授权访问你的示例 Google 表格。

  2. 一旦你上传了 JWT 密钥,你就可以访问该服务账户可以访问的所有内容。我使用了一个包含一些示例网页流量数据的 Google 表格,但你可以使用任何你想要的表格进行此操作。

该仪表盘的缓存时间已设置为零秒,这意味着它会在 Google 表格中的信息发生变化时自动刷新。

图 13.12 – 可添加到仪表盘的样本探索

图 13.12 – 可添加到仪表盘的样本探索

然后你可以将此探索添加到新的监控仪表盘中。如图 13.12所示,你可以选择添加按钮,然后选择添加到 仪表盘选项。

图 13.13 – 创建带有面板的新仪表盘

图 13.13 – 创建带有面板的新仪表盘

  1. 这将创建一个包含你的数据表的新仪表盘。现在我们可以在其上添加一些数据可视化。在新的仪表盘中,点击添加,然后选择可视化。你会看到一个类似于 Google Sheets 表格的面板,在这里你可以添加像这样的图表:

图 13.14 – 通过可视化生成的条形图

图 13.14 – 通过可视化生成的条形图

你可以看到我们有一个高度可自定义的图表,包含所有以可视化形式呈现的信息。

一旦你完成了可视化,返回到你新创建的仪表盘。你的最终可视化将像这样:

图 13.15 – 最终的 Grafana 仪表盘

图 13.15 – 最终的 Grafana 仪表盘

同样,这会随着数据的变化而改变。

到目前为止,我们已经看到了处理事件和自动化的事件。那么,当发生一个无法通过重置来解决的事件时会发生什么呢?当你处于不利的局面,被迫进行损害控制时又会怎样呢?好吧,我们将在下一部分找到答案。

高级事件响应策略

事件响应应该简单且有条理,应该解决最初导致事件发生的问题。但是,再次强调,不管我们如何努力,它永远不会那么简单。有时候你正在处理一个你不太熟悉的系统。有时候你所在的团队经验不足,无法提供有意义的帮助。这些情况由于时间和人员的限制是不可避免的,并且在很大程度上超出了我们的控制范围。在这种情况下,我们只能利用现有的资源应对。

但有一种情况非常常见,会造成广泛的损害,以至于对事件的响应不再是关于恢复损害,而是关于从已经造成的损害中恢复。这种情况通常发生在任何形式的事件长时间没有被报告或监控时。当发生数据泄露,或者系统中存在未知/未发现的错误时,这种情况尤其常见。

那么,当你遇到这种情况时,你该怎么办?首先,不要慌张。但也不要想“不要慌张”——那会让你更慌!以逻辑的方式面对问题。我现在会给你一些关于如何处理这种情况的见解。这是基于我过去面对的情况,以及我从各个地方学到的解决问题的经验。我认为这是一种很好的解决很多问题的方法,无论是 IT 问题还是其他问题。那么,接下来我们就开始吧。

步骤 0:阅读

因为:你没有 做过够多。

说真的,很多错误仅仅通过阅读错误信息或者故障的输出就能解决。相信你所看到的,它会给你回报;问题的解决方案就在你眼前。好吧,至少是解决方案的第一步。但如果你想走到那里,你需要阅读并理解解决这个问题的过程。你不能在没有了解所有细节的情况下就跳到第一步。

步骤 1:如果是一个单一的问题, 谷歌它

因为:你不是 一个人。

这是非常严肃的建议。90%你遇到的错误都可以通过谷歌搜索解决。这是因为别人可能也遇到过你面临的问题,他们可能也已经得到了正确的解决方案。你很有可能在Stack Overflow论坛、GitHub 仓库的评论、一个不太知名的 YouTube 视频或者类似的地方找到解决方案。你可能会想:“这不可能这么简单——我怎么可能仅仅通过谷歌就能解决我最大的难题。”马上停下这种想法!我曾经看到有人花了整整两个小时尝试一个迁移桥,我们在看完一个 YouTube 教程后五分钟就搞定了。事情就是这样——别浪费时间,寻找快速、知情的解决方案。它们能处理那些小问题,信我,大多数问题都是小问题。

步骤 2:如果是多个问题,就多次谷歌(或者,现在可以 用 ChatGPT)

因为:一个大问题是几个小问题 合成的一个。

这是真的:任何问题都可以分解成最小的组成部分,并在那个层面上解决。这是基本的逻辑;如果你有一个大问题,把它转化成更小的问题,然后去谷歌一下。如果它是一个真的大问题,或者将问题分解的过程琐碎且会占用你大量时间,你现在可以使用 ChatGPT 来解决;它在这方面相当不错。将大量无用的文字转化成更小、更有用的版本,可能是 ChatGPT 最擅长的事情。好好利用它,充分利用这些你手头的强大工具。

步骤 3:遵循归纳和演绎的方法

因为:它能解决你所遇到的任何日常问题

解决方案和解决问题与产生想法截然不同。想法像是来自上帝的闪电,它们是我们系统中的幽灵,从最奇怪的地方冒出来。抓住一个想法并将其付诸实践是一种虚无的体验,就像是一次宗教启示。解决方案不是这样的。它们简单、基础,且—如果你懂得如何解决问题—更容易处理。其逻辑可以用两个词来概括:归纳和演绎。

我解决过的每一个重大问题,只要我能接触到样本环境,我都会用这种逻辑来解决。这是我在《禅与摩托车维修艺术》一书中学到的理念,书中的叙述者通过这两种方法解决最平凡的问题,并将这些概念与量子物理和禅宗相联系。这些概念并不复杂,但所有问题的解决都可以在它们的框架内定义。

归纳是重现导致问题的元素,以准确了解问题发生的位置。在事件管理中,这对于在样本应用程序中重现事件并通过分解步骤找出问题所在非常有用。

演绎是从结果出发,回溯到开始。这种方法适用于错误的逻辑无法重现,或者事件已经发生,但不知道如何发生。在这种情况下,你从最终结果开始,推理出可能导致这些结果的原因。

因此,将这些结果总结成图表,你可以这样来看待它们:

图 13.16 – 解决任何 IT 问题的步骤

图 13.16 – 解决任何 IT 问题的步骤

这就是你解决所有问题的方法。试着在几个问题上应用看看,看看你能走多远。如果你想了解创意的形成过程,嗯,它们是相当随机的,但它们可以通过类似这些方法的手段被明确并转化为行动点。现在,你已经学会了一切。明智地使用你的知识吧。

总结

好的,这不仅仅是本章的结束,也是整本书的结束。作为这本书的读者,你的旅程结束了,而我作为首次作者的旅程也结束了。这是一次不小的历程。总体来说,谈到这一章,它比许多其他章节更加抽象,代码内容也相对较少,但那是因为我已经意识到一件事:所有这些系统归根结底都是代码。你接触到的每一件事,都一定是以某种方式被编码过的;关键在于你是否能够理解并操控背后的逻辑。

在关于步骤函数的部分,你学习了一个非常有用的自动化工具,但你也学到,这是一种以视觉化方式使用编码逻辑,并将这种方式整合进许多强大工具和服务的手段。

在高级监控部分,我们学习了一个强大的监控和可视化工具——Grafana,以及集中监控的重要性,通过集中管理,避免了从多个位置查看数据并解析数据的繁琐过程,而是有一个统一的位置来管理所有工作负载。

最后,你了解了我解决问题的方法,确实是一种有效的方式,如果我自己说的话。希望你能有机会在自己的工作负载中应用这个方法。这是一种元代码,一种不依赖于任何平台或技术,但能够在所有平台上有效工作的算法。

所以,我必须向你告别了。我们已经走到了尽头,但请将其视为开始的结束,因为接下来发生的才是最重要的部分。这将定义你是谁以及你将做什么。现在是时候将所有这些知识应用到你自己日常事务中了。记住:力量掌握在你手中;是时候 使用它

posted @ 2025-06-26 15:33  绝不原创的飞龙  阅读(84)  评论(0)    收藏  举报