原文:
annas-archive.org/md5/0228db3442938136abc9262d5596d201
译者:飞龙
序言
欢迎阅读本书!让我们来谈谈本书的内容以及你将从中学到的东西。本书涉及两件事: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。
随时随地,在任何设备上阅读。直接从您最喜欢的技术书籍中搜索、复制并粘贴代码到您的应用程序中。
福利不仅如此,您还可以独家获取折扣、新闻通讯以及每天发送到您邮箱的精彩免费内容
按照这些简单的步骤获取福利:
- 扫描二维码或访问以下链接
packt.link/free-ebook/9781835081167
-
提交您的购买证明
-
就是这样!我们会直接将您的免费 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),以及如何对日志作出合适的响应。但目前,你需要知道的是,良好的账务管理/日志记录已经帮助建立了帝国,因为历史和我们从中学到的教训非常重要。它们给我们提供了视角和应对未来事件所需的适当教训。
监控
当你看到这一节的标题《理解日志记录和监控》时,你可能会想,二者有什么区别?嗯,这是合理的。我花了一段时间才弄明白。我认为,这归结为几个方面:
-
监控关注的是特定的指标(通常由日志生成),以及该指标是否超过某个阈值。然而,日志记录仅仅是收集数据,而没有从中产生任何洞察或信息。
-
监控是主动的,专注于当前正在监控的实例或对象的状态,而日志记录是被动的,更多关注于大量历史数据的收集。
在许多方面,这就像事务型数据库与数据仓库之间的区别。一个处理当前数据,而另一个则是存储历史数据以发现趋势。两者几乎是不可分割的,因此通常会一起讨论。现在你已经记录和监控了所有数据,可能会问自己,这些数据的意义何在?接下来的部分将帮助解答这个问题。
警报
你无法谈论日志记录和监控而不提及警报的概念。日志度量通过监控服务进行监控。该服务查看从日志中产生的数据,并将其与为该度量设定的阈值进行比较。如果阈值在持续、定义的时间段内被突破,就会触发警报或警钟。
大多数时候,这些警报或警钟要么连接到通知系统,能够通知相关人员警报状态的提升,要么连接到响应系统,可以自动触发对事件的响应。
现在你已经了解了通过日志记录和监控获得的观察力和洞察力的力量,是时候学习如何运用这种力量了。让我们来看看当我们通过日志记录和监控发现重要且令人担忧的洞察时,我们应该采取什么行动。
事件和响应
我再一次提一下墨菲定律,因为我认为这句话非常重要:
任何可能出错的事情都会在最糟糕的时刻出错。
处理事件和响应涉及大量工作或没有工作。这取决于你有多准备,以及事件或问题的独特性。事件和响应涵盖了从自动化、成本控制到网络安全的广泛内容。
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(站点可靠性工程)类似于此,但工程师已不再身处数据中心,而是坐在办公室或自己家中的远程工作桌前。他们仍然生活在离数据中心或管理的资源所在云区域相对较近的地方,但与他们的前辈相比有几个不同之处:
-
他们的团队可能分散在各个地区,而不是集中在一个地方。
-
他们现在的重点是我们所称的预测性维护,即他们不会等到问题发生才做出响应。
事件响应团队
这种新的 SRE 趋势也促成了事件响应团队的产生,这些团队可以迅速从 DevOps 团队内部组建,来监控和处理事件。在处理事件的同时,它们还会与利益相关者进行沟通,确保他们了解情况,并找出事件的根本原因。这些团队还会生成报告,帮助 DevOps 团队应对和缓解未来可能发生的类似情况。在一个几分钟的停机可能会导致数百万美元损失和损害的世界里,事件响应团队已经成为任何 DevOps 工程师世界中的重要组成部分。
通常,事件响应团队由以下成员组成:
-
事件指挥官(IC):事件指挥官负责领导事件响应,并负责事件后的响应计划
-
沟通领导(CL):沟通领导是团队中负责向利益相关者沟通事件及事件缓解进展的公共成员
-
运营领导(OL):有时与事件指挥官同义,OL 通过查看日志、错误和指标来领导事件的技术解决,并找到使网站或应用恢复上线的方法
-
团队成员:由各自领导协调的 CL 和 OL 下的团队成员,以执行他们所需要的任务
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_01_01.jpg
图 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 到在操作系统中配置它,可能是所有主要编程语言中最顺利的安装过程。入门的门槛几乎为零。
所以,如果你想声明一个变量和做其他基本操作,从下图开始并搞清楚:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_1.jpg
图 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)
你不需要为每一行都这样做;我只是比平常更明确一些,为了以后参考。但这是定义变量和注释的基本方式。不再使用那种 i、j 和 k 的做法。要友善,并且要定义清晰。
定义简化事物,这就是我们在下一节将要讨论的内容。
简单-复杂-复杂化
必须尽可能保持简洁。这是规则,因为……那样更容易。然而,保持简洁是困难的。有时几乎不可能。随着应用或解决方案的规模变大,复杂性也会增加。我们不希望的是让代码变得复杂化。
复杂和复杂化有什么区别?当代码是为了可持续地动态且可理解地处理所有可能的场景时,它是复杂的。当(在一个复杂的解决方案中)代码是以一种根据静态、非常具体的参数(硬编码)来处理所有可能的情况的方式编写时,它就是复杂化的,且即使是写代码的人,也会觉得它很难理解。
在我的职业生涯中,我看过很多这样的代码;我刚开始时也写过很多。这是一个学习过程,如果你没有培养好的习惯,你就会陷入坏习惯,或者会退回到为更复杂问题找到一个更简单的解决方案。
曾经,在回顾一个旧的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,lib1
和 lib2
,它们都包含一个名为 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 容器中强大的本地库支持 —— 如json、asyncio和subprocess等库
-
出色的本地代码模块,简化了对数据的某些迭代和操作性操作,如
collection
模块 -
能够正确地本地处理通常用于微服务的半结构化和多样化的 JSON 数据
为了使这些微服务能够有效且一致地相互交互,我们需要一些重复,一些一致的重复。我要找的词是……自动化……不,那是机器人……签名……不,那是等这本书成为畅销书后的事……自动化,对,正是这个词。自动化。
自动化可能是 Python 对于 DevOps 工程师的主要卖点,因为它拥有令人难以置信的自动化库和支持功能。大多数从系统管理员转型为 DevOps 的人员更喜欢他们宝贵的Bash 脚本,这种脚本在某些环境中确实有其用武之地,但 Python 更加强大、更加灵活,而且得到了社区和行业中大多数公司的更好支持。在这种情况下,Python 在自动化方面的一些应用包括:
-
各种针对云端部署的软件开发工具包(SDKs),包括AWS、Azure、Google 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 实例。它们每秒钟的运行都会让我花费一些钱。然而,在工作时间我需要它们。它们是这样的:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_2.jpg
图 2.2 – 运行中的实例
起了个有创意的名字,我知道。但是它们确实在运行,并且总有一天,我希望它们不再运行。为了实现这一点,我需要找到某种方法来停止它们。我可以一个一个地停止它们,但那样很麻烦。如果这两个实例变成了 1000 个实例,我还会这样做吗?不会。那么,我们需要找到另一种方法。
我们可以尝试命令行界面(CLI),但这是一本编码书,而不是 CLI 书,所以我们不这么做。不过,如果你有兴趣,也可以尝试。让我们看看我们的老朋友 Python,并且也可以使用一个服务,允许你随时调用的函数,称为 AWS Lambda。下面是创建 Lambda 函数并用它来启动和停止 EC2 实例的步骤:
- 让我们创建一个名为
stopper
的函数,使用最新的 Python 运行时(本书中使用的是 3.10):
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_3.jpg
图 2.3 – 创建 Lambda 函数
-
接下来,你需要为 Lambda 函数创建一个执行角色,或者为其分配一个现有的角色。这一点稍后会很重要。不过现在,你可以选择你偏好的方式。默认情况下,点击
boto3
库,它非常适合资源交互。 -
在我们能够启动或停止任何实例之前,我们需要列出它们。你需要先加载并转储返回函数一次,以处理
datetime
数据类型。现在,让我们先初始化 EC2 的boto3
客户端,尝试列出当前可用的所有实例:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_4.jpg
图 2.4 – 描述实例的初始代码
运行此代码进行测试时,你会遇到类似于以下的异常:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_5.jpg
图 2.5 – 授权异常
这是因为 Lambda 函数也有一个身份与访问管理(IAM)角色,而这个角色没有描述实例所需的权限。因此,我们需要设置可能需要的权限。
- 如下图所示,在配置|权限下,你将找到分配给 Lambda 函数的角色:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_6.jpg
图 2.6 – 查找权限角色
- 在角色页面,转到添加权限,然后选择附加策略:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_7.jpg
图 2.7 – 附加权限
让我们赋予 Lambda 函数对 EC2 服务的完全访问权限,因为我们还需要它来停止实例。如果你觉得这权限过大,也可以创建自定义角色:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_8.jpg
图 2.8 – 附加适当的权限
- 让我们重新运行一次,看看结果:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_9.jpg
图 2.9 – 成功运行的代码
你将看到实例的展示,以及关于它们是否正在运行的信息。
- 现在,让我们进入关闭正在运行的实例的部分。添加代码以在实例中进行筛选,找出正在运行的实例,并获取它们的 ID 列表,稍后我们将使用这些 ID 来引用我们希望停止的实例:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_10.jpg
图 2.10 – 添加停止实例的代码
足够简单易懂,尤其是如果我们遵循可读性和明确性原则的话。
实例现在处于关闭状态,很快就会停止:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_11.jpg
图 2.11 – 关闭实例
- 现在我们已经做了一次,让我们通过使用名为 EventBridge 的服务进一步自动化它,EventBridge 可以每天触发该函数。前往Amazon EventBridge并创建 EventBridge 定时事件:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_12.jpg
图 2.12 – 在 EventBridge 上设置定时任务
- 选择我们的 Lambda 函数作为事件目标:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_13.jpg
图 2.13 – 为 EventBridge 选择 Lambda 进行调用
现在,你可以创建 EventBridge 定时事件,该事件将在每天晚上 8 点触发,列出并关闭你的 EC2 实例。
所以,按照这些步骤,你现在拥有了必要的工具,可以根据你想要的定时计划安排实例的关闭。
自动拉取 Docker 镜像列表
拉取 Docker 镜像可能会很繁琐,尤其是当需要拉取多个镜像时。那么,现在我们将看看如何使用 Python 库同时拉取多个 Docker 镜像:
-
首先,使用以下命令在虚拟环境中安装 Docker Python 库:
pip install docker
-
然后,编写一个名为
docker_pull.py
的脚本,循环遍历镜像名称列表,并通过利用 Docker 库拉取它们:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_14.jpg
图 2.14 – 拉取 Docker 镜像的代码
-
完成此操作后,使用以下命令运行文件:
docker images command to check out the Docker containers that you may have locally:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_02_15.jpg
图 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:
- 要打开 Colab 笔记本,你可以访问 colab.research.google.com,然后创建一个新的笔记本。最终结果应该是这样:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_1.jpg
图 3.1 – 使用 Google Colab 创建的初始笔记本
-
我们需要做的第一件事是安装正确的库。这些库包含了我们调用 API 所需的函数和模块。如果你愿意,你可以直接在笔记本中安装它们。我们将安装
huggingface_hub
和transformers[agents]
库。以下是安装命令:!pip install huggingface_hub transformers[agents]
当你将此命令输入到单元格中并按下播放按钮时,它将在你的运行环境中安装所需的库:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_2.jpg
图 3.2 – 安装所需的库
-
下一步是使用 API 密钥登录
huggingface_hub
。这就是 API 密钥的概念来源。API 密钥就像是软件的登录凭证。大多数公司只有通过购买 API 密钥才允许完全访问其 API。像 Hugging Face 这样的许多开源项目也使用 API 密钥来推广和跟踪用户交互,有时还会为用户提供高级版本,如果他们需要的话。
-
要获取 Hugging Face API 密钥,首先必须访问 huggingface.co 网站并注册账户,或者如果你已经注册过,直接登录。完成后,进入个人资料页面,点击 设置 标签,然后进入 访问令牌 标签。在这里,你可以生成一个访问令牌进行使用:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_3.jpg
图 3.3 – 为 Hugging Face API 生成访问令牌
-
你可以复制这个令牌,并在下一部分代码中使用它。在这里,你导入 Hugging Face 登录模块以调用登录 API,并输入你的密钥来使用 API:
from huggingface_hub import login login("<your_key_here>")
如果你正确加载了它,你会看到此消息。如果是这样,恭喜你,成功调用了登录 API:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_4.jpg
图 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 运行时:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_5.jpg
图 3.5 – 选择 GPU 以加快图像处理速度
- 最终产品将让你感到震惊和满意。你将得到如下内容:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_6.jpg
图 3.6 – 你的最终结果(很漂亮,不是吗?)
因此,我们完成了这个练习,并成功调用了一个 API,给我们带来了一个视觉上令人满意的结论。还有什么比这更让人期待的呢?现在,如果其他人也能见证你的劳动成果就好了!
好了,这就是调用 API 的全部意义所在。API 旨在供目标受众使用,因此现在,我们将看看如何分发我们的 API。
练习 2 – 创建和发布一个供消费的 API
部署应用程序是 DevOps 工程师可能会遇到的最频繁的任务之一。拥有一个良好且快速的部署非常重要,但在此之前,首先要有一个部署。在许多方面,部署较小和较大的应用程序是相似的。它们之间主要的不同之处在于你必须为保持较大应用程序的可用性而采取的措施。我们在本节不会讨论这一点。我们将尝试为加法 API 准备一个 API,就像我说的那样,让我们保持简单并开始在一个新的Replit Repl中进行编码。
- 在replit.com注册一个账号。你可以为几乎每个应用程序框架和代码库创建小型虚拟环境。注册后,你可以通过点击创建 Repl按钮来创建一个Repl,这是一个小型虚拟服务器:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_7.jpg
图 3.7 – 创建 Repl 的按钮
- 一旦完成,搜索并创建一个带有Flask模板的 Repl。名称不重要:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_8.jpg
图 3.8 – 初始化 Flask Repl
这将为您提供一个包含预初始化和安装了基本 Flask 库的样板 Flask 代码的 IDE:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_9.jpg
图 3.9 – 初始 Flask 框架
- 关于前述的图,当你点击
"/"
时已经定义。因此,如果你在新标签页中打开该网址,你将会得到如下内容:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_10.jpg
图 3.10 – 初始 Flask 网页
这个函数只是返回一个网页上的字符串。通常,API 是以 JSON 格式编写的。所以,我们将它转换为 JSON 格式。在 Flask 中,这是非常简单的。你只需在返回类型中传递一个字典变量:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_11.jpg
图 3.11 – 在 Flask 中编写简单的 JSON API
完成后,你将得到一个 JSON 格式的返回值:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_12.jpg
图 3.12 – JSON API 结果
- 这个 API 目前只返回静态值。要让它接受用户输入,只需在 URL 中添加
request
参数。让我们修改应用程序,使其接受两个参数,num1
和num2
,它们将被加在一起,并在 JSON 返回值中显示它们的和:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_13.jpg
图 3.13 – Flask API 代码,用于将两个数字相加
最终结果需要一个 URL,格式为<your_url_here>/?num1=<number>&num2=<number>
。结果将类似于以下内容:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_14.jpg
图 3.14 – Flask API 调用以将两个数字相加
所以,现在你已经学会了如何在 Python 中创建一个 API 来将两个数字相加并部署该 API。这是一个非常大的进步。编程世界中唯一会呈指数级复杂化的事情就是业务逻辑。安全性和网络同样重要,但它们通常遵循一定的公式。只要你能将你的逻辑部署到最终用户,你就可以了。
现在,你已经掌握了 API 的技巧,接下来我们将深入探讨如何将 API 交付给最终用户。我们将讨论网络。网络是 DevOps 和应用程序开发中不可或缺的一部分,以至于有时候它根本没有被提及。那么,接下来让我们看看一些我们可以在网络方面使用 Python 的有用方法。
网络
不,这不是在说如何扩展你的 LinkedIn 人脉,尽管我也推荐你去做这个。计算机网络对于当前每个应用程序的正常运行至关重要,因为它们是将持续价值交付给用户并保持他们与环境连接的唯一方式。如今,几乎每个设备都连接到网络,这也是为什么理解设备的网络和网络的网络(有个名字叫互联网,你听说过吗?)非常重要的原因。
接下来,我将演示两个如何使用 Python 进行网络分析和数据收集的例子。
练习 1 – 使用 Scapy 捕获数据包并可视化数据包大小随时间的变化
Scapy是一个 Python 库,可用于复制、模拟和操作通过计算机网络发送的数据包。Scapy 是任何开发者或 DevOps 专业人士工具箱中的一款非常有用的工具。
在这个练习中,我们将使用 Scapy 收集数据包列表,并获取它们的时间戳和数据包大小。然后,我们将这些信息映射到我们使用matplotlib 库制作的图表上。您可以使用前面提到的 Google Colab 来进行这个练习。那么,让我们初始化笔记本并开始编写代码:
-
首先,我们需要安装
matplotlib
和scapy
库:!pip install scapy matplotlib
-
现在,让我们编写代码,使用 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
变量中:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_15.jpg
图 3.15 – 在您的计算设备中嗅探数据包
-
现在让我们编写代码,使用
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 轴为数据包大小:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_16.jpg
图 3.16 – 随时间变化的数据包大小图表
上面的图表显示了网络活动模式,看起来涉及多个相关的数据包。所以,您可以看到网络分析库已经开始派上用场了。
所以,现在我们已经追踪了我们的网络活动,并通过 Python 生成了数据洞察。让我们看看另一个网络实现,这次重点关注您的设备(或您运行工作负载的设备)所拥有的路由规则。
练习 2 – 为您的设备生成路由表
路由表定义了某些网络流量在您的设备中所经过的路径。这些表几乎存在于每个设备中,并且定义了设备访问计算机网络的路径。您可以使用netifaces Python 库来生成路由表,显示设备中所有可用的路径和目的地。在这种情况下,netifaces 库用于收集操作系统的网络接口(因此得名 netifaces)。然后,您将解析这些信息并以表格形式展示。您仍然可以使用 Google Colab 来完成此操作,尽管为了获得更有趣的结果,您可以尝试在本地运行代码。
-
让我们开始为您的设备生成路由表的步骤。如果您一直跟随到现在,您已经知道第一步是安装库:
!pip install netifaces
-
接下来,编写代码来生成路由表:
#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 上试过,您会得到类似这样的结果:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_17.jpg
图 3.17 – Colab 上的路由表
如果你在个人计算机上操作,可能会得到如下所示的结果:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_03_18.jpg
图 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 Linux(WSL)来本地使用 Docker
-
一个 GitHub 账户,以及 Git 和仓库的基本知识
-
本书代码库的访问地址:
github.com/PacktPublishing/Hands-On-Python-for-DevOps
-
对虚拟化和 Kubernetes 的基本理解
Python SDK(以及为什么大家都在使用它们)
从头开始讲。SDKs(即 软件开发工具包)是由平台发布的官方编程库和 CLI,它们允许开发者开发能够利用该平台的工具和应用程序。这些 SDK 通常使用非常流行的语言编写,以覆盖尽可能多的开发者。
三大云平台(大多数 DevOps 工作都在这些平台上完成)在其 SDK 中共享以下编程语言:Java、.NET、C++、Go、JavaScript/TypeScript/Node.js 和 Python。如果你从事这些工作——而你从事其中一项的可能性远大于不从事——你需要选择一门编程语言。
那么,问题就变成了,为什么是 Python?而且,为什么我们要在本书的四个章节后才提出这个问题?我来告诉你。Python 恰好是那种松散和结构化之间的平衡,它是实现许多 DevOps 原则所必需的。
严格类型化的语言,如 Java、.NET 和 C++,对于开发来说是不错的选择,但对于现代 DevOps 工作负载所需的灵活性,它们将产生糟糕的结果。话虽如此,大多数云平台都是建立在这些语言上的。但在这些语言上进行操作完全是另一回事。把这些语言看作是提供坚固性的骨架,而把 Python 看作是提供灵活性的关节——它们应该存在于任何需要灵活性的地方。
然后,在另一个极端,你会看到那组 JavaScript 三重奏。即使这些语言得到了主要云平台的大量支持,它们有时仍然不适合这个用途,原因在于它们固有的局限性和语法怪癖。这些语言本来并不是为了这样原生工作,而且它们是单线程的,难以并发操作。
在这一领域,Python 的主要竞争对手,有时也是合作伙伴的是 Go。让我告诉你,Go 很棒。大多数云端工具,比如 Docker 和 Kubernetes,都是用 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 服务。让我们深入了解:
- 对于这个练习,我们需要一个干净的环境,既能编写 Python 代码,又能在终端中配置权限。为此,在我的 AWS 账户中,我将创建另一个我们稍后会用到的东西:一个Sagemaker笔记本。Sagemaker 笔记本是一个在 AWS 服务器上运行的 Jupyter 笔记本服务:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_1.jpg
图 4.1 – 在 Amazon Sagemaker 中创建笔记本实例的控制台
如果你查看顶部的面包屑导航,你会看到路径是Amazon Sagemaker -> Notebook 实例 -> 创建 Notebook 实例。
- 任何较小的笔记本都可以。我们将在这个练习中使用ml.t3.medium:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_2.jpg
图 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,官网提供了适用于所有操作系统的安装程序。
- 现在,让我们尝试使用 Sagemaker 预先分配的现有角色来配置 EC2:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_3.jpg
图 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 实例的步骤:
- 这是创建 EC2 实例的代码(这也将在书的存储库中发布):
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_4.jpg
图 4.4 – 创建 EC2 实例的功能
当你运行它时,你会得到实例 ID(你在下一部分中需要用到它):
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_5.jpg
图 4.5 – 创建具有唯一 ID 的 EC2 实例
你会看到,在 AWS EC2 控制台上已创建具有相同实例大小和 ID 的实例:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_6.jpg
图 4.6 – 运行中的 EC2 实例
- 现在,垂直扩展作用于同一个实例,但实例大小在运行时无法更改,所以我们需要先停止该实例:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_7.jpg
图 4.7 – 停止 EC2 实例的功能
这段代码在运行时会停止实例。确认实例已停止,并注意实例的大小仍然是t2.nano:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_8.jpg
图 4.8 – 停止的 EC2 实例
- 现在,让我们编写代码,将实例修改为
t2.micro
实例:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_9.jpg
图 4.9 – 更新 EC2 实例的代码
运行这段代码后,你会发现控制台上显示的实例现在是t2.micro实例:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_10.jpg
图 4.10 – 更新后的 EC2 实例大小
- 因此,一旦你重新启动实例,它将具备额外的计算能力。
你可能已经注意到,这是一个缓慢的过程。而垂直扩展 – 在很多情况下 – 都是一个停机的缓慢过程。尽管这种做法有其应用场景(尤其是在需要使用更强大单机的情况下),但这并不是常态。通常,水平自动扩展更适合你的使用场景,因为它关联的停机时间较少。接下来我们会深入探讨这个问题。
基于触发器的 Python 自动扩展
自动扩展需要根据某些度量标准或统计数据自动增加可用的计算资源。为了实现自动扩展,我们需要设计一个机制,当某个度量标准或阈值达到时,它会触发我们的 SDK 调用。
请理解,从单一云平台的角度来看这个例子,可能会觉得它有点不切实际,因为大多数云平台都内置了自动扩展。关键在于如何微调这个自动扩展。我将创建一个自动扩展组,并使用 Python 脚本定义扩展的阈值。然后,我将修改这些阈值,修改后我会告诉你为什么这么做的意义。
让我们编写一个基本脚本,创建一个自动扩展组,并使用 CPU 利用率策略设置阈值。我们将从启动配置、自动扩展组到实例扩展的规则一步步来:
- 首先,我们编写代码,创建一个启动配置,所有自动扩展组中的机器都会遵循这个配置:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_11.jpg
图 4.11 – 创建启动配置的代码
- 接下来,我们创建自动伸缩组,该组使用我们之前创建的启动配置:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_12.jpg
图 4.12 – 创建自动伸缩组的代码
- 最后,我们将创建一个策略,如果 CPU 利用率超过 70%,则向上扩展该组:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_13.jpg
图 4.13 – 创建伸缩策略的代码
运行这些操作将为你提供一个具有这些规格的基本虚拟机自动伸缩组。现在,你可能会问,Python 在这个自动伸缩中起什么作用。好吧,为此,你首先需要查看这些虚拟机生成的指标。
如果你查看虚拟机生成的指标,你将能够找到它们的 CPU 利用率指标,并且这些指标可以被导出。利用这些指标,你可以计算一段时间内 CPU 的平均利用率(据说是编程语言 Python 帮助的),然后使用这些数据来找到更好的自动伸缩目标。要修改目标,你可以简单地使用与之前相同的代码,只需更改指标值:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_14.jpg
图 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 镜像并启动容器的代码:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_15.jpg
图 4.15 – 拉取 Docker 镜像并启动容器的代码
这很简单,但这正是关键所在。它的简洁性为进一步改进提供了基础。
即使我们保持简单,有时容器的使用也变得复杂,这就是 Kubernetes 发挥作用的地方。使用 Kubernetes 也会带来一些挑战,这些挑战可以通过 Python 进行简化和管理。
使用 Python 管理 Kubernetes
在你使用容器的过程中,总有一天 Kubernetes 会成为你需要的解决方案。这时,Python 也能提供帮助。Python 可以简化许多 Kubernetes 管理任务,而且由于大多数 Kubernetes 工作负载运行在云端,这些 SDK 也会非常有用。
我只会提供一个例子,即操作 Kubernetes 命名空间。为了安全起见,我们不希望因为计算资源问题而崩溃,尤其是当你对 Kubernetes 还不熟悉时。
命名空间是 Kubernetes 集群中的抽象,用于根据特定标准划分计算资源。标准可以是环境(开发、生产等)、网络限制,或基于可用的资源配额来定义命名空间。
你可以使用 Python 来创建和修改命名空间,并操作其中的资源。让我们来看一下初始化 Kubernetes 集群并使用 Python 管理它的步骤:
-
首先,你需要通过以下命令使用
pip
安装 Kubernetes:pip install kubernetes
-
接下来,让我们写一个脚本,在我们的集群中创建几个命名空间。我们可以稍后为这些命名空间添加资源:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_16.jpg
图 4.16 – 从列表创建 Kubernetes 命名空间的代码
- 现在,让我们为这些命名空间写一个策略,并将其实施到 Kubernetes 中:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_04_17.jpg
图 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)
这段代码的作用是挑选出国家头部,你需要自己定义(这是件好事,因为你可以自定义它),然后将网页请求引导到适当的服务器。请求和响应之间的这个小层能够对连接质量产生奇效。你可以在随后的图中看到这一点:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_05_1.jpg
图 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 个值的小数组,每个小数组的总和用于计算该数组的总值。这个过程通过多进程库在四个不同的处理器上同时进行。将这个庞大的任务拆分成较小的任务,可以优化资源用于数据处理。
好吧,这一切听起来都不错。但数据从哪里来呢?通常是你的应用程序。好吧,既然数据要好,应用程序就得好,对吧?是的,但这并不总是如此。让我们看看当一些应用程序停滞不前并变得过时时会发生什么。
重构旧应用程序
一个干净的白板是世上最方便的东西之一。我将给你看两张纸,你可以告诉我你想在其中哪一张上画画:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_05_2.jpg
图 5.2 – 新应用程序与旧应用程序的对比图
如果你选择了正确的道路,恭喜你——你现在是一名 DevOps 工程师!别太过深究这张图,它只是我随便拼凑的些东西。
你看,DevOps 工程师很少会直接工作在自己的代码上。即便是开发者,现在也很少能完全自己编写代码了。根据我的经验,我在任何非个人项目中,最多只有一两次真正从零开始做项目。对于那些刚刚起步的人来说,可能只处理过自己编写的项目或者一些找到的模板项目。然而,在实际工作中,这种情况是非常罕见的。
即使你在一个干净的白板上工作,除非你已经用自己定制的操作系统编写了汇编代码,否则你的工作负载仍然依赖于大量的依赖项(如果你真是这样做的,你得多无聊啊?)。
你所使用的 99%的工具都是由别人开发的——有时是数百或数千个不同技能水平、能力和观点的人的工作成果。你走的是由众多架构师(有时没有路线图/文档的情况下)所铺就的道路。
你看,最初,这很简单——你从代码的最新版本开始,将它托管在localhost
上,瞧,瞬间你成为了你世界的主宰。但随之而来的是,你会意识到每个人都这么做,大家都急于保持那种控制感,即使是在专业环境中,也不顾其对工作质量的后果。这就导致了你会看到关键基础设施已经 5 年没有更新,关键服务仍然运行在已停产的产品上,而且团队既不愿意也无法(不是任意一个,而是都)做出改进。这就是如何陷入技术债务的原因(技术术语,指糟糕的规划能力)。
要摆脱技术债务,你有三种选择:优化、重构或重新启动。这意味着你有三种选择(不仅仅是在 DevOps 中,实际上在人生中也是如此):接受你的错误、改善它们,或重新开始。你想做什么(以及你能做什么)取决于你和你的具体情况。我们将一起探讨这三种选择,帮助你找出最适合你工作负载的方案。
优化
你的第一个选择是优化。你可以查看当前的状态,确保它运行得尽可能高效,消耗最小的资源,同时保持所有功能完好并对用户可用。很多时候(尤其是在网站可靠性工程(SRE)领域——一个你几乎只处理遗留应用的领域),优化你的遗留应用可能是唯一的选择,尤其是当你在处理一个关键应用时,无法改变其中一个方面而不需要同时改变许多其他方面——你可以称之为一个单体应用。这些限制就是为什么许多公司希望摆脱单体架构。
在优化遗留应用的情况下,我们的选择非常有限。但这并不意味着我们什么都不能做。DevOps 中最重要的概念之一是期望状态配置(DSC),其重要性就在于维护这些系统。
在 DSC 中,虚拟机会被配置为必须维持某些设定。会不时检查这些设定的状态。如果状态发生了显著变化,那么它将被重置,恢复到原本的期望状态。
这一切都发生在幕后,并不会真正影响应用本身。这可能是处理无法重构或容器化到 Docker 容器中的应用的最佳方式。
重构
如果你在处理的代码质量尚可,或者你的项目团队相对可靠(这需要一些假设,但请耐心),那么你可能仅仅在处理一个已经过时并需要更新和调整的工作负载,或者是一个不再能满足所有需求的工作负载。在这些情况下,重构提供了一个健康的折衷方案,连接旧与新。
重构你的代码库可能包括一些简单的操作,比如升级依赖项、编写新组件,或移除不必要的组件。也可能涉及一个漫长且艰难的过程,比如将网站的前端和后端分离。
让我们来看一下最著名的重构方法:strangler fig。在自然界中,strangler fig 是一种植物,它以树木为基础生长,最终将树木勒死并取而代之。strangler fig 对于遗留应用的工作原理也类似。
以下是进行 strangler fig 重构的步骤:
-
分离数据库:如果数据库服务器还没有与应用服务器分离,现在就分离。这将有助于两者的可扩展性。
-
将第三方 API 调用转化为函数/微服务:如果调用了任何你没有直接维护的第三方 API,将它们拆分成可以在重构后的应用中调用的函数。将它们与其余的后端部分分开。
-
将后端和前端分离:将处理数据的后端部分和处理用户交互的前端部分分开。这些部分需要通过一个称为前端代理后端的缓冲区进行通信,后者是前端与 API 交互的中介。
-
将非关键的后端功能拆分为微服务:将那些仅处理数据且不与数据库交互的服务拆分为更小的微服务,这样它们就能独立且更具规模地处理数据。
-
创建数据库连接机制,并将其余的后端拆分为微服务:最后,应用的核心通过数据库连接字符串实现松耦合,这使得只要有正确的凭证,数据就能从任何地方进行操作。
听起来有点棘手,对吧?确实如此。但它也有回报……有时候。重构应用——无论其效果如何——是你实践学习构建和维护应用的最有效方式之一。但它确实是一个心力消耗的过程。你不希望能从前人的错误中解脱出来,重新开始一张白纸吗?如果是的话,那么接下来的部分将非常适合你的计划。
重启
有时候——希望这个时机能在开发过程的早期——你需要将已有的东西完全抛弃,重新开始。如我之前所说,白纸化是最容易的起点。如果你认为你的应用已经无法挽救,且无法从中挽回任何有价值的东西,那么你可以从头开始构建应用。
很多时候,因为栈的不兼容,或者旧组件没有任何价值,迁移组件在旧应用程序和新应用程序之间并不合适。在这些情况下,最有价值的是旧应用程序生成的数据,并将这些数据迁移到可以与新应用程序互动的数据库中。
如果你正在开发一个新应用程序,老实说,按你的方式做吧。但有一些建议:
-
做出长期的决策,而不仅仅是基于你现在的想法。
-
做出以质量为优先的决策,但也要明白,错误总是不可避免的。
-
不要对每件事做出反应;过于反应性地处理问题 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
文件:
-
这些文件是存储环境变量的标准,并且在几乎所有的
.gitignore
文件中都会被忽略。要读取这些文件,我们首先需要安装 Python 的python-dotenv
库:pip install python-dotenv
-
在此之后,我们可以创建一个
.env
文件,并将变量和秘密存储其中。我们根据它们所在的行来分隔这些秘密。以下是一个.env
文件的几行示例:API_KEY = <insert_key_here> API_SECRET_KEY = <insert_secret_key_here>
记住,大写字母是用于常量的。这种方法有助于将常量集中在一个地方,这样你就不必担心在代码中的多个位置更改它们,也不用担心某个地方漏掉了某个修改。
-
现在,让我们编写代码,让程序能够访问环境变量:
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 密钥和密钥,在各自的变量中。
接下来,让我们尝试相同的操作,但直接从我们自己的操作系统中导出并使用环境变量:
-
在 Linux 中——这种服务器最为普遍——我们可以像在之前的例子中那样,使用以下命令设置环境变量:
export API_KEY = <insert_key_here> API_KEY and API_SECRET_KEY in your OS.
-
现在的问题是如何访问这些值:
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 集群中的镜像。让我们看看如何做到这一点:
-
现在我们将编写一个脚本来创建一个认证者,并将该认证者分配给 Kubernetes 引擎。首先,我们必须安装容器和二进制授权的客户端库:
pip install google-cloud-binary-authorization google-cloud-container
-
现在,让我们为集群的二进制授权编写脚本:
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)
这将创建一个能对您的集群请求进行认证的认证者。
-
现在,我们需要将二进制授权添加到已创建或即将创建的集群中:
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 实例上运行命令,从内部重启该实例。再次强调,我们保持命令简单,但如果你需要,也可以做得更复杂。那么,让我们开始吧:
- 让我们从一个正在运行的实例开始。我将我的命名为
test1
。很有创意吧,我知道。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_1.png
图 6.1 – 正在运行的实例
假设我们在一个场景中,这个实例有大量的网络流量通过。如果流量降到某个水平以下,就会表示实例出现故障。此时,可以通过一个简单的运行手册文档来解决这个故障,重启实例。
- 现在让我们在AWS Lambda中创建该命令的结构。但首先,我们需要发送的命令,它可以在Systems Manager中找到。打开Systems Manager,然后在侧边栏底部转到Documents标签页:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_2.jpg
图 6.2 – AWS Systems Manager
- 在文档中,搜索
AWS-RestartEC2Instance
文档。这是默认文档,你可以基于它创建许多其他虚拟机操作文档:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_3.jpg
图 6.3 – 执行 EC2 重启的文档
这是一个简单的基础文档,可以作为你执行任何脚本操作的基础。大多数其他云服务提供商也有类似的文档。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_4.jpg
图 6.4 – 详细的 AWS-RestartEC2Instance 文档
如果我们仔细观察一下,可以看到这个文档会根据实例 ID 停止并重新启动一个特定的实例,而实例 ID 就是我们需要提供的内容。
- 现在,让我们编写一个 EventBridge 事件,该事件将在五分钟内网络流量下降时触发一个 Lambda 函数。为此,首先转到 Amazon CloudWatch 并创建一个在我们的条件下触发的报警。转到CloudWatch | Alarms | Create alarm。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_5.jpg
图 6.5 – Cloudwatch Alarms 控制台
我们现在可以为我们的test 1
实例创建一个单独的报警,它将以图形形式显示,帮助你了解当前指标的变化情况:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_6.jpg
图 6.6 – 创建报警指标
- 接下来,让我们为警报的触发条件设置网络输入低于20,000字节,且持续五分钟:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_7.jpg
图 6.7 – 设置警报的阈值
-
在提示你输入警报名称的字段中命名警报为
test1-alarm
,然后就可以开始了。现在,我们可以查看 详情 标签中的警报,找到 EventBridge 规则,我们需要用它来设置 EventBridge 触发器:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_8.jpg
图 6.8 – 警报配置详情
- 然后,进入 AWS 部分的 Amazon EventBridge,接着到 规则 标签页,在那里你将创建一个新事件。在第一页,选择 带有事件模式的规则,然后继续到下一页。在这里,你将粘贴从警报中获取的 EventBridge 规则:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_9.jpg
图 6.9 – 事件桥的模式创建
-
现在你有了模式,点击 下一步,然后在另一个标签页中打开 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 的目标:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_10.jpg
图 6.10 – 目标 Lambda 函数
- 不断点击 下一步,直到创建 EventBridge 规则。当警报被触发时,该规则将触发并运行 Lambda 函数。这将重启实例,并持续进行,直到网络输入恢复到可接受的水平。
现在,在这个例子中,我们看到一个基于即时反应和执行的应用程序。如果你的系统受到某种攻击,目的是压垮它或利用你无法修补的漏洞,这种方式是有效的。然而,攻击通常不是单独发生的。它们是有针对性的并且频繁发生,不断尝试寻找某种漏洞以攻击你的工作负载。为了帮助理解潜在的攻击模式,并了解你的工作负载如何应对变化,你可以使用监控日志的模式分析。
监控日志的模式分析
监控日志可以为你提供应用程序表现的洞察,并且如果在时间线中检测到一些异常模式,可以帮助你发现问题。例如,分布式拒绝服务(DDoS)攻击通常会在应用程序中形成无法解释的高 CPU 使用模式。它基本上使应用程序遭受虚假的负载,这会影响到应用程序中的实际负载。
因此,为了检测这些攻击,我们必须有一个能够历史性地分析工作负载并潜在地发现这些攻击的算法。为了找到这些模式,Python 有一些很棒的数据科学库。
服务器日志的公共数据集很难找到,但它们可以相对容易地重新创建。对于这个示例,我将使用公共数据集模拟服务Mockaroo。它允许你免费创建一个包含 1,000 行的数据集。我将只创建包含 CPU 利用率和时间戳的数据集。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_11.jpg
图 6.11 – 生成模拟数据集
一旦数据生成,我们会看到它没有根据时间排序,因此这是我们将在脚本中添加的额外步骤。我们将使用 Google Colab 来编写我们的脚本。那么,让我们开始吧:
- 首先,安装
pandas
和matplotlib
库。它们通常已经存在,但这一步对于其他笔记本应用很有帮助:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_12.jpg
图 6.12 – 安装命令
- 接下来,将 Mockaroo 创建的 CSV 文件上传到 Colab。为此,点击侧边面板中的文件夹图标,然后点击其中的第一个图标上传数据:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_13.jpg
图 6.13 – 上传模拟数据
你会看到这里我已经上传了我的数据。
- 现在,下一步是使用 pandas 读取数据,创建数据框并按时间排序,因为 CSV 中的模拟数据并未预先按顺序排列。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_14.jpg
图 6.14 – CPU 利用率表格
- 现在,为了发现数据中的模式,我们可以开始通过可视化数据。让我们将数据作为线性图进行可视化,以时间戳作为 x 轴,CPU 利用率作为 y 轴:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_15.jpg
图 6.15 – 绘制 CPU 利用率的代码
现在,图表看起来有些随机,因为数据是随机生成的,因此该图表不一定代表实际图表的样子。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_16.jpg
图 6.16 – 绘制 CPU 利用率随时间变化的图表
所以,这看起来很随机,但没关系,因为它只是为了代表一个随时间变化的条形图。让我们稍微调整一下数据集,制造一些异常。
- 所以,我们将修改数据集,使其中间有一个恒定的利用率:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_06_17.jpg
图 6.17 – 修改数据集的图表
这有点荒谬,但我相信它说明了持续正常运行的重要性。大多数系统在稳定时,并不会有这种利用率。如果你的服务正在产生这种数据,这要么表示成功的突然暴涨,要么是一次 DDoS 攻击。大多数情况下,是后者。所以,如果你在监控数据中发现这种激增,值得进行调查。
总结
在这一章中,你希望学到了一些关于安全的知识。我也希望你已经弄清楚了如何搭建基础设施,使得你所使用的所有访问密钥都能得到保障。
除此之外,你还学习了一些方法(成千上万种方法中的一部分),可以用来帮助保护你的基础设施。你可能还获得了一些洞见,了解到如何利用数据来驱动事件响应,并更好地理解某些事件及其特性。
所以,朋友们,我希望你们喜欢这一章,毕竟这是关于安全的内容。我尽力向你们解释了几个概念,希望你们至少觉得它有趣。但现在,是时候翻过这一页,进入下一章,我们将讨论我最喜欢的话题之一:自动化。
第七章:自动化任务
未来的目标是实现完全失业。这样我们就可以去玩了。
– 亚瑟·C·克拉克
这可能会是我最喜欢的章节。说真的,一旦你品尝过自动化的甜美甘露,你就不会再回头了。嗯,我猜我有点超前了。我将从时间的概念开始。控制你命运、生活方式以及你做什么的唯一方法,就是控制你自己的时间,通过选择你要做的事。为了做到这一点,你需要选择如何使用你的时间。
所有的人类进步都与以一种方式做事情相关,这种方式需要越来越少的工人,并且持续减少努力,以便将更多的努力应用到其他地方。所有这些都定义了自动化的概念。它就是发明减少在乏味工作中时间投入的方法,以便你可以继续前进,做更多令人兴奋或有趣的事情。
人类发明农业是为了不必花太多时间去打猎和采集食物。他们可以自己种植;食物在很多方面自己就能生长(有人说是自动化的),而一点点的改良让我们今天吃到的食物得以诞生。农业的创造使得食物生产过程实现了自动化,进而推动了城镇的发展、地产的拥有以及大部分人类文明的诞生。
在工业革命期间,制造过程的自动化使得廉价的大规模生产商品几乎进入了每个家庭。创造出质量高、工作完美且完全相同的商品是一项革命性的成就。这意味着创造某样东西所需的劳动大大减少,从而可以将这些劳动力集中到其他地方。蒸汽机、电力和汽车的发明也在多个领域减少了劳动,同时在许多其他领域节省了时间。
环游世界八十天是儒勒·凡尔纳所著的一本小说,象征着人类创新在缩短全球旅行时间方面的巨大进步。在那本小说发布后的不到一百年里,人类征服了所有形式的陆地旅行,使得这本书显得微不足道。接下来被提出的问题是,如何让人类大脑变得更快、更自动化。这就是计算机登场的地方。计算机将人类大脑转变为世界上最有效的工具。它们自动化了我们许多繁琐的思维工作,让人类大脑有更多的空闲时间去做更轻松的任务。
然而,这一过程是一个永无止境的循环。总有办法变得更好,做得更快,并且不会再占用更多的注意力。在 DevOps 领域,我们已将其作为核心原则之一,因为在技术领域中,创新、创造力和提出新想法被高度重视。你不能停留在昨天的地方。你必须自动化并继续前进。
这就是本章的内容。本章将涉及用 Python 自动化枯燥工作(这是Al Sweigart的另一本优秀的 Python 书籍),并让你最大限度地利用你的思维和创造力。你不想陷入反复上传、手动运行相同脚本或手动修复那些虽然只需要 5 秒钟但登录却需要 10 分钟的重复服务器问题的因果循环中。这些问题的答案就是自动化。
在本章中,你将学到以下内容:
-
服务器内外的自动化维护
-
通过托管服务及其他方式自动化容器创建
-
使用 Google 表单自动启动播放本
自动化服务器维护和修补
我曾经有一个朋友,他的工作大多数日子里只是等待一个网站宕机,检查它为什么宕机,并执行他被指派的两项命令之一来将其恢复。我还有另一个朋友,他的工作是每次 NGINX 服务器宕机时手动重启它。我曾遇到一个人,他的工作基本上就是从一个地方下载 CSV 文件,把它们放到另一个地方,然后点击一个启动按钮。现在,对于一些人来说,这听起来可能是个不错的工作(对我来说也不算差),但问题在于,这对那个员工的雇主和员工自己来说都是浪费时间。对双方而言没有成长或改进,在我的生活经验中,这简直就是浪费人类的生命。
在接下来的示例中,我们将看到如何基于一系列常用命令来维护多个实例集群,然后在发现操作系统类型后,找到一种方法来修补操作系统。我们将借助 Python 来完成这一切。
示例 1:同时对多个实例集群进行集群维护
维护服务器涉及大量工作——很多重复的工作。这也是最初服务器维护实现自动化的原因。它最小化了人为错误,并确保每次都按相同方式进行。服务器集群的工作原理类似。只需对所有服务器使用自动化脚本,因为它们是原始服务器的副本。那么,如何处理具有不同需求的多个实例集群呢?在这里,Python 可以提供帮助。你需要做的就是将每个集群与正确的维护脚本关联起来。这可以让你在多个云环境中管理多个集群。所以,话不多说,让我们看看如何做到这一点:
-
让我们首先编写 AWS 实例的代码,找出正在运行的实例:
import boto3 ec2_client = boto3.client('ec2') response = ec2.describe_instances(Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]) aws_instances = response['Reservations']
这将为我们提供一个来自 EC2 的实例列表。你可以使用多个标识符来定义你的集群。你甚至可以使用预定义的系统管理集群。
-
现在我们为 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 Platform(GCP)代码中,由于需要指定 GCP 项目 ID,并且必须定义请求 API 及其本身,因此存在一些差异。
-
现在,让我们找一个命令来在这些实例中运行。它可以是任何占位符命令。你可以之后用你想要的命令来替代它:
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 来完成(大惊喜,不是吗?):
-
让我们再次从一些简单的代码开始,基于镜像启动一个容器:
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,这在下一步中非常重要。这将是我们的起点。
-
现在,让我们进一步完善它:
#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 中所有其他内容的基础上添加了新命令。这个容器与原始容器不同,但它是基于原始容器构建的。
-
接下来,我们需要将这个镜像导出以便以后使用:
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
大多数时候,即使是最基本的自动化任务也可能变得难以理解。如果你需要自动化或触发多个任务,复杂性就会开始增加。并不是每个人都能理解它们,而且也不应该是每个人的工作去理解它们。这就是为什么即使是许多现代服务器也有用户界面,使得信息处理对许多人来说更加容易。
然而,在许多情况下,即使这种抽象层级也不足够。可能需要创建一个工具,让用户可以简单地输入他们的内容,而服务器自动处理复杂的工作流和资源的创建。简而言之,你可以制作带有参数的剧本,根据那些希望创建资源但不想处理其中复杂细节的人(在大多数地方,这些比较随意的人叫做客户)所提供的概述来创建资源。让我们看看如何做到这一点:
- 我们将从制作一个 Google 表单开始(是的,没错)。前往 forms.google.com 并点击大大的加号(+)按钮。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_07_1.jpg
图 7.1 – 实例选择
这是一个简单的 Google 表单,用于选择两种不同大小的 EC2 实例。
-
现在,我们将编写一个 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 网关为其定义一个端点。
-
一旦这个函数和端点创建完成,你可以从 Apps Script 调用该端点,并根据表单中的触发器和输入进行处理。在表单编辑器中,点击右上角的三个点,并选择脚本编辑器:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_07_2.jpg
图 7.2 – 访问脚本编辑器
-
现在,你可以编写本质上是 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 项目的左侧窗格中,点击触发器选项。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_07_3.jpg
图 7.3 – 使用 Apps Script 调用 Lambda
- 在右下角,点击添加触发器,这将打开一个表单,用于创建触发器,在那里你可以定义所有必要的参数:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_07_4.jpg
图 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 的情况下,信息的分发者称为 生产者,而接收该信息的则称为 消费者。在大多数现代术语中,尤其是在大多数云服务中,它们分别被称为 发布者 和 订阅者。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_08_1.jpg
图 8.1 – 使用 Pub/Sub 模型的事件处理
在我们深入探讨 Kafka 和 Python 在 DevOps 中的应用之前,我们首先需要看一下使用 confluent-kafka
库在 Python 中应用 Kafka 的示例:
-
让我们首先使用
pip
安装这个库:pip install confluent-kafka
-
如前所述,Kafka 分为生产者和消费者。那么,首先让我们写一段代码来创建一个生产者:
from confluent_kafka import Producer import socket conf = {'bootstrap.servers': '<host_name>:9092', 'client.id': socket.gethostname()} producer = Producer(conf)
-
这段代码将配置一个生产者。将
host_name
替换为一个 Apache Kafka 集群的名称(可以是在线或本地的)。接下来,我们需要使用配置好的生产者来发送一些数据。让我们现在看一下这段代码:producer.produce(topic, key="key", value="value")
这里,
topic
是发布者或生产者将其内容分发到的地方,供消费。key
和value
元素是生产者将分发的键和值。 -
现在,让我们为消费者添加一些代码,消费者将接收生产者发送的消息:
from confluent_kafka import Consumer conf = {'bootstrap.servers': '<host_name>:9092', 'group.id': '<group_id_here>', 'auto.offset.reset': 'smallest'} consumer = Consumer(conf)
消费者现在在与生产者发送消息相同的主机上监听。因此,当生产者生产一条消息时,消费者就能消费它。当消费者订阅了一个主题时,该消费者会在特定时间间隔内不断监听该消息。一旦消息到达,它将开始解析消息并将其发送到适当的位置。
-
为了让消费者持续监听生产者的消息,我们可以将其放入一个循环中:
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 考试的书。
所以,间接地,写这本书帮助我获得了现在的工作,并最终让我在乌普萨拉找到了更多的尼泊尔人。如果你懒得了解这些,让我用图示给你展示:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_08_2.jpg
图 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 文件,它们基本上就是基于这个概念运行的。即使界面非常简单,它们也能非常有效且相当受欢迎。
或许在阅读本书之前,你可能曾误以为构建这些服务可能很困难。但在我们生活的这个世界里,它们并不难。一旦我们打开了这些视野,一切就变得更加清晰,而最清晰的一点就是能够从旧的低效方式转变为更新、更简单的方式。让我们来看看这一过渡的路径。
用“缠绕无花果”杀死你的单体应用
如果你反对“杀死”任何东西(我能理解),你只需在心里把这些词换成更令人愉快的词汇(例如“沉睡”或“小憩”)。但这里提到的缠绕无花果,正是因为它是数字化转型和/或应用现代化过程中最显著的方法之一。你可能听过“数字化转型”这个词,并且立刻把它当作一个时髦词抛之脑后,这也不无道理,因为大多数时候人们提到这个词时,确实就是一个空洞的流行语。但请睁开眼睛和耳朵,暂时抛开成见,理解这个词的真正含义:它是在将旧事物转变为新事物。基本上,它是在不改变或增加功能的情况下,从内部改变你的系统。它是将单体应用拆解为松耦合架构。
首先,让我们来看一下单体应用的潜在结构:
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_08_3.jpg
图 8.3 – 一个基本的单体应用
这非常粗糙,但基本上大多数单体应用的结构就是这样。它们有一个用户界面,用户界面与两种不同类型的操作进行交互:对数据库的操作和通用数据操作(图中的杂项)。即便如此,这个单体应用也比普通的单体应用要更为分层,因为我们给它分配了一个独立的数据库。数据库有时也可能直接存在于单体应用内部。
打破这个单体应用并不仅仅是使其解耦,而是确保每个组件能够作为独立的实体存在,以便它在其他项目中或在同一项目中的不同方式下可能派上用场。但为了继续拆解这个单体应用,我们可以通过首先从单体应用中移除那些不需要与用户或数据库交互的杂项功能来实现。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_08_4.jpg
图 8.4 – 从单体应用中移除杂项 API
所以,这个图表表明,单体应用与杂项功能之间的分离将这些功能划分为独立的组件。这是无服务器架构的基础。其核心思想是将每个功能作为单独的端点,仅在特定用例下进行调用。这一阶段有助于将简单的事情处理掉,并且帮助执行转型的人实际开始理解其中的概念。如果这些随机功能只是某个可以随时调用并根据需求进行修改的端点,管理起来会容易得多。
现在,拆分单体应用的下一步是用户界面与执行数据操作的后端部分的划分。这涉及将一个前端的 API 或后端置于它们之间。
https://github.com/OpenDocCN/freelearn-devops-pt3-zh/raw/master/docs/hsn-py-dop/img/B21320_08_5.jpg
图 8.5 – 完全解耦的架构
在将单体应用拆分之后,最终的结构看起来像图 8.5。它有点像我的后果图,但不那么愚蠢。这里有很多活动的部分,但车子里也有很多活动的部件;这正是它们能运作的原因。更重要的是,让我们从本章强调的两个方面来看待这个问题:可用性和故障:
-
如果用户界面由于某种原因停止加载,它将会回退到不同的区域。这个区域将具有相同的功能;如果它离用户更远,可能会更慢,但仍能完成工作。
-
前端的 API 或后端可以接收来自多个用户界面的调用,并可以访问多个可以访问数据库的服务器。
-
后端可以仅仅是用来连接数据库进行操作。除查询之外,那里不需要其他任何东西。
-
由于存在层次结构,数据库本身变得更加安全,并且更容易提高其可用性。
-
这些杂项功能只是可以根据开发团队的需要随时添加或移除的端点。
好吧,这确实是一个相当复杂的过程,对吧?但是它是一个能够带来成果的过程,帮助让你的应用或工作负载变得更加可持续并具备未来适应性。它并不适用于每一个工作负载,但它非常适合帮助旧系统更好地使用现代技术。
总结
在本章中,你了解了事件驱动架构,它是现代应用开发的一个重要组成部分。你也希望了解了行动及其后果,以及积极行动对开发和你生活的重要性。最后,你学会了如何通过“入侵式重构”方法,利用 strangler fig 来将旧应用程序现代化,转向这一新的理念。
在下一章,你将更加深入地使用 Python,并了解 Python 在CI/CD(即持续集成/持续交付)中所能发挥的作用。这是一个有趣的话题,将帮助你将本章中学习的 Python 技能和概念付诸实践。