C--AWS-开发指南-全-

C# AWS 开发指南(全)

原文:zh.annas-archive.org/md5/2e7a029346e8eb1f5a214ebb90ecfb4c

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

如果你必须选择过去 20 年中出现的两种流行技术进行重点关注,那么很难挑选出比 C#和 AWS 更好的选择。Anders Hejlsberg 于 2000 年为微软设计了 C#,几年后,.NET 框架和 Visual Studio 推出。接下来,在 2004 年,跨平台的 C#编译器和运行时环境 Mono 广泛可用。这个生态系统已经实现了包括跨平台移动框架 Xamarin 在内的独特平台和技术。

在 2000 年代初的另一个宇宙中,亚马逊转向了面向服务的架构,并在 2002 年发布了公共 Web 服务。随着服务的流行,亚马逊在 2004 年发布了 SQS,2006 年发布了 Amazon S3 和 EC2。这些存储、计算和消息传递服务仍然是亚马逊 Web 服务(AWS)的核心构建模块。截至 2022 年,AWS 至少拥有 27 个地理区域、87 个可用区、数百万台服务器和数百种服务。这些服务包括从机器学习(SageMaker)到无服务器计算(Lambda)的各种产品。本书展示了利用这两个卓越技术堆栈的可能性。你将学习云计算的理论以及如何使用 AWS 实现它。你还将使用 AWS 技术构建许多解决方案,从无服务器到利用.NET 生态系统实现的 DevOps,最终实现许多优雅的解决方案。

适合读者

这本书适合希望在 AWS 上探索可能性的 C#开发者。这本书也可以被看作是 AWS 和云计算的简介。我们将从零开始介绍许多云计算话题,所以如果这是你第一次涉足基于云的软件开发,那么放心,我们将提供所有你需要的背景信息。

虽然 C# 技术上 只是一种编程语言而不是框架,我们将几乎完全关注于构建 Web 应用程序的.NET 应用程序开发人员。会稍微提到 Blazor 和 Xamarin,但对这些的了解绝对不是必需的。我们还有内容适用于熟悉旧版.NET 框架和新版.NET(以前是.NET Core)的开发人员。

书籍组织方式

AWS 提供超过 200 种服务来构建各种基于云的应用程序,我们将本书组织成为覆盖对 C#开发者最重要的服务。各章节结构如下。

第一章,“在 AWS 上开始使用.NET”,作为 AWS 的介绍以及如何与服务进行交互和配置。

第二章,“AWS 核心服务”,详细讨论了存储和计算,这两个最广泛使用的服务对于习惯于部署到 Web 服务器的.NET 应用程序开发人员来说也会感觉最为熟悉。

第三章,“将传统 .NET 框架应用迁移到 AWS”,作为你可以迅速将现有代码库从现有物理或虚拟机或其他云提供商迁移到 AWS 的简要概述。

第四章,“现代化 .NET 应用程序为无服务器”,比迁移现有应用程序更进一步,探讨了如何将你的代码架构现代化为无服务器模型,并提供 AWS 上的示例。

第五章,“.NET 容器化”,关于容器的一切。如果你已经在为你的 .NET Web 应用使用 Docker 容器,这一章将帮助你快速将它们部署到 AWS 的托管容器服务中。

第六章,“DevOps”,你知道 AWS 有一个完全托管的持续交付服务叫做 CodePipeline 吗?本章将向你展示如何使用 AWS 提供的工具简化应用程序通向生产的路径。

第七章,“.NET 的日志记录、监控和仪表化”,继续从 DevOps 中展示你可以集成到应用程序中的日志记录和监控功能。我们还将看看如何直接从你的 C# 代码手动推送性能指标。

第八章,“使用 AWS C# SDK 进行开发”,这本书的最后一章,深入介绍了允许你将 AWS 服务集成到你的代码中的 C# SDK 工具。

最后,在书的后面有两个附录可能会对你有帮助,它们没有清晰地适应任何章节。附录 A,“基准测试 AWS”,详细介绍了如何对 AWS 机器进行基准测试,以及附录 B,“开始使用 .NET”,展示了一个简短的 C# 和 F# GitHub Codespaces 教程。

此外,每一章还包括批判性思维问题和练习。批判性思维问题是团队讨论、用户组或准备工作面试或认证的良好起点。练习通过实践学习并将章节内容应用到工作代码示例中。这些示例可能成为出色的作品集件。

附加资源

对于那些可以访问 O’Reilly 平台的人来说,一个可能对你 AWS 旅程有帮助的可选资源是 52 周 AWS-完整系列;它包含了所有重要认证的数小时实操视频。这个系列也可以作为免费播客在所有 主要播客渠道 上获得。

除了这些资源外,Noah Gift 每天和每周都在以下位置更新 AWS:

Noah Gift O’Reilly

Noah Gift 的 O’Reilly 个人资料页面,有数百个视频和课程,涵盖多种技术,包括 AWS,并经常进行在线培训。另外两本涵盖 AWS 的 O’Reilly 书籍可能也会有所帮助:Python for DevOpsPractical MLOps

Noah Gift Linkedin

Noah Gift 的 LinkedIn 页面,他定期直播 AWS 培训和正在进行的 O’Reilly 书籍笔记。

Noah Gift 网站

Noahgift.com 是获取最新课程、文章和演讲的最佳途径。

Noah Gift GitHub

Noah Gift 的 GitHub 个人资料页面,您可以找到数百个仓库和几乎每日更新。

Noah Gift AWS Hero 个人资料

Noah Gift 的 AWS Hero 个人资料页面,您可以找到涉及 AWS 平台工作的当前资源链接。

Noah Gift Coursera 个人资料页面

Noah Gift 的 Coursera 个人资料页面,您可以找到涵盖广泛的云计算主题,重点是 AWS 的多个专项课程。这些课程包括:

Pragmatic AI Labs 网站

有关 AWS 的许多免费资源可在 Pragmatic AI Labs and Solutions 网站 上找到。其中包括几本免费书籍:

本书中使用的约定

本书中使用了以下印刷约定:

斜体

表示新术语、网址、电子邮件地址、文件名和文件扩展名。

恒定宽度

用于程序清单,以及段落内引用程序元素如变量或函数名称、数据库、数据类型、环境变量、语句和关键字。

恒定宽度粗体

显示应由用户直接键入的命令或其他文本。

恒定宽度斜体

显示应由用户提供的文本或根据上下文确定的值替换。

提示

这个元素表示提示或建议。

注意

这个元素表示一般性的注意事项。

警告

这个元素表示警告或注意事项。

使用代码示例

补充资料(代码示例、练习等)可在 https://oreil.ly/AWS-with-C-Sharp-examples 下载。

如果您有技术问题或在使用代码示例时遇到问题,请发送电子邮件至 bookquestions@oreilly.com

本书旨在帮助您完成工作任务。一般而言,如果本书提供示例代码,您可以在您的程序和文档中使用它。除非您复制了代码的大部分内容,否则不需要联系我们以获取许可。例如,编写使用本书多个代码片段的程序不需要许可。出售或分发奥莱利书籍中的示例代码需要许可。引用本书并引用示例代码回答问题不需要许可。将本书中大量示例代码整合到产品文档中需要许可。

我们感谢,但通常不需要署名。署名通常包括标题、作者、出版商和 ISBN。例如:“使用 C#开发 AWS by Noah Gift and James Charlesworth (O’Reilly). 版权所有 2023 O’Reilly Media, Inc., 978-1-492-09623-8.”

如果您认为您使用的代码示例超出了合理使用范围或上述许可,请随时联系我们:permissions@oreilly.com

奥莱利在线学习

注意

超过 40 年来,奥莱利传媒 提供技术和商业培训、知识和见解,帮助公司取得成功。

我们独特的专家和创新者网络通过书籍、文章和我们的在线学习平台分享他们的知识和专业知识。奥莱利的在线学习平台让您随时访问直播培训课程、深入学习路径、交互式编码环境,以及奥莱利和其他 200 多个出版商的大量文本和视频内容。更多信息,请访问 https://oreilly.com

如何联系我们

请将有关本书的评论和问题发送至出版商:

  • O’Reilly Media, Inc.

  • 1005 Gravenstein Highway North

  • Sebastopol, CA 95472

  • 800-998-9938(美国或加拿大)

  • 707-829-0515(国际或本地)

  • 707-829-0104(传真)

我们有一个网页专门用于本书,列出勘误、示例和任何额外信息。您可以访问此页面:https://oreil.ly/AWS-with-C-sharp

通过电子邮件 bookquestions@oreilly.com 发表评论或询问有关本书的技术问题。

欲了解关于我们书籍和课程的最新消息,请访问 https://oreilly.com

在 LinkedIn 上找到我们:https://linkedin.com/company/oreilly-media

在 Twitter 关注我们:https://twitter.com/oreillymedia

在 YouTube 关注我们:https://youtube.com/oreillymedia

致谢

Noah

有机会能够参与 O’Reilly 的图书创作总是一种荣幸。这本书是我为 O’Reilly 出版的第四本书,目前正在撰写第五本关于企业 MLOps 的书籍。感谢 雷切尔·鲁米奥提斯 让我有机会参与这本书的创作,以及 赞·麦克奎德 提供的出色战略指导。

我的合著者 詹姆斯·查尔斯沃思 和我们的开发编辑 梅丽莎·波特 都是非常棒的合作伙伴,我很幸运能和这两位才华横溢的人一起完成这个需求量巨大的项目。感谢来自 AWS 的 Lior Kirshner-Shalom,以及 Nivas Durairaj、David Pallmann 和 Bryan Hogan,他们在技术审阅期间提供了许多有见地的评论和修正。

我也要感谢我的许多现任和前任学生,以及 杜克大学数据科学硕士项目杜克大学人工智能工程硕士项目加州大学戴维斯分校商业分析硕士项目加州大学伯克利分校西北大学数据科学硕士项目,以及 田纳西大学北卡罗来纳大学夏洛特分校 的教职员工。

这些人包括(排名不分先后):迪克森·刘易斯安德鲁·B·哈加顿海曼特·巴尔加瓦艾米·拉塞尔阿什温·阿拉温达克尚肯·吉尔伯特米歇尔·巴林斯乔恩·赖夫施奈德罗伯特·卡尔德班克阿尔弗雷多·德扎

最后,感谢我的家人,Leah,Liam 和 Theodore,在周末和深夜为了赶项目截止日期而忍受我工作。

James

这是我与 O’Reilly 的第一本书,我们从团队中收到了巨大的支持,特别是 Melissa Potter,没有她,你们将不得不忍受更多我那复杂的英式拼写。我也要感谢技术审阅者,特别是 AWS 的 David Pallmann 和 Bryan Hogan。

技术行业发展迅速,没有站在巨人的肩膀上,你就不可能有所成就。因此,我要感谢我的两位前任老板 詹姆斯·伍德利戴夫·伦辛,感谢他们近年来给予我的建议、辅导、指导和支持。

最后,特别感谢我的不可思议的妻子,卡雅(Kaja),在我所有努力中支持我,我永远是你的理查德·卡斯尔。

第一章:在 AWS 上开始使用.NET

本章涵盖了在 AWS 上使用 C#进行首日工作时的基本支架。这些基本支架包括使用 AWS CloudShell 等基于云的开发环境,以及利用 AWS SDK 的传统 Visual Studio 开发环境。

本书的材料通过添加许多.NET 6 开发的代码示例来帮助你成功。你可以在本书的源代码库中查看这些代码示例。此外,本书涵盖了传统开发方法(如 Visual Studio)和新的云原生策略(如使用 AWS Cloud9 进行开发)。对于所有类型的.NET 开发者都有所涉及。

对于脚本爱好者,还有使用 AWS 命令行界面和 PowerShell 来使用 AWS SDK 的示例。在章末,还提供了讨论问题和练习,帮助你进一步学习所涵盖的课程内容。这些练习是创建全面作品集的极好工具,可以帮助你获得工作。让我们开始简要介绍云计算。

什么是云计算?

云计算是通过互联网以及类似公用事业的定价方式交付 IT 资源。与购买和维护物理数据中心和硬件不同,你可以根据需求从云供应商访问 IT 服务,例如计算、存储和数据库。

或许描述云计算的最佳方式是从加州大学伯克利分校可靠自适应分布式系统实验室或 RAD 实验室的角度来看。在他们的论文“云上之上:伯克利对云计算的看法”中,他们提到了云计算的三个关键方面:“虚拟化的无限计算资源”,“取消云用户的前期承诺”,以及“按需短期使用计算资源的能力”。让我们更详细地讨论这些内容。

注意

在教授成千上万的学生和工作专业人士云计算之后,我(Noah)对尽快学习的强烈看法。一个关键的绩效指标(KPI)是每周能犯多少错误。在实践中,这意味着尝试事物,感到沮丧,然后找到解决问题的最佳方式,然后尽快重复这样做。

我在课堂上鼓励这种方法的一种方式是通过促使学生每周演示他们正在进行的工作进展,并建立一个作品集。同样,在这本书中,我建议建立一系列 GitHub 项目来记录你的工作,然后制作解释你的项目运作方式的演示视频。这一步实现了两件事情:教授自己更好的元认知技能,并建立一个使你更具市场竞争力的作品集。

虚拟化的无限计算资源

机器学习是“几乎无限”计算资源的一个理想应用案例的绝佳例子。深度学习需要大量的存储、磁盘 I/O 和计算资源。通过弹性计算和存储,Amazon S3 通过数据湖能力打开了以前不存在的工作流程。这种新的工作流程允许用户和系统在其所在的地方操作数据,而不是来回将其移动到工作站或专用文件服务器。请注意,在 图 1-1 中,Amazon S3 存储系统可以无缝处理计算、存储和磁盘 I/O 的扩展,任意数量的访问资源的工作者。Amazon S3 之所以能够做到这一点,不仅因为它具有近乎无限的弹性,还因为它具有冗余属性。这个概念意味着它不仅可以扩展,而且由于设计围绕着“事物总是会失败”的思想,因此几乎总是可用。详见 “things always fail”

注意

机器学习的一个定义是从历史数据中学习以创建预测的程序。您可以使用 AWS SageMaker Studio 使用称为 Amazon SageMaker Autopilot 的功能来训练自己的机器学习模型,而无需了解任何关于数据科学的知识。您可以在这里了解更多关于该服务的信息 here

doac 0101

图 1-1. 几乎无限的计算能力

通过云用户的前期承诺的消除

对于许多初创公司来说,在购买设备并尝试想法之前花费数十万美元是不可行的。云计算在云计算之前通过消除高固定成本的机会性方式来开发软件。不仅如此,您不会像在本地数据中心一样被锁定在特定的技术选择中。

能够根据需要短期支付计算资源的使用

通过切换到可变成本,公司只需支付他们所需的。其结果是更高效的架构,可以响应事件并根据需求进行扩展。正如你所见,云的关键好处在于能够使用弹性功能,即计算和存储。其中一种资源是 AWS 上的弹性文件系统(EFS)。根据 AWS 的说法,EFS 是一个“简单的、无服务器的、设置即忘的、弹性文件系统”。它与 AWS 的核心弹性能力很好地配合,允许开发人员为任务提供或关闭瞬态资源(如用于任务的竞价实例),同时与所有使用它的机器共享持久性存储进行交互。竞价实例是您可以竞标并获得高达 90% 成本节约的计算实例,非常适合可以随时运行或是瞬时的工作流程。¹

注意

AWS 非常重视成本优化,并将其作为他们AWS Well-Architected Framework支柱之一包含在内。根据 AWS 的说法,“AWS Well-Architected Framework 帮助您理解在 AWS 上构建系统时所做决策的利弊”。Spot 实例是成本优化故事的重要组成部分,因为它们允许您竞标未使用的容量,从而达到高达常规按需定价的 90%的折扣。对于学习某些内容的开发人员来说,这些资源是无价的,因为您只需“按需付费”。

完全托管的网络文件系统如 EFS 非常有用,因为它们提供一致的网络体验,如持久的主目录或数据共享。根据官方AWS 文档,“Amazon Elastic File System(Amazon EFS)在您添加和删除文件时自动增长和缩小,无需管理或配置”。

在处理大数据时最令人困扰的瓶颈之一是需要更多的计算能力、磁盘 I/O(输入/输出)和存储来处理任务,比如为具有大量数据的公司网站访问统计。特别是,能够无缝地使用可被机群挂载的文件系统(即 EFS)并根据机群的集体工作需求增长,是一种直到云计算出现前都不存在的高效率。

另一个弹性资源是EC2虚拟计算环境,也称为实例。EC2 实例是由 AWS 提供的基本可扩展计算能力,具有多种特性,包括使用 Amazon Machine Images(AMI)来打包完整的服务器安装。它们非常适用于扩展 Web 服务、试验原型或竞标剩余容量,如 AWS Spot Instances。Spot Instances 开启了全新的工作方式,因为虚拟机可以是一次性的,即在不需要时丢弃,并在成本低廉时使用。

云计算的类型

需要注意的一点是云计算的类型:IaaS(基础设施即服务)、PaaS(平台即服务)、FaaS(函数即服务)和 SaaS(软件即服务)。让我们详细讨论每一种。

IaaS

这个选项提供了云计算的基本构建块,包括网络、存储和计算。它提供了最接近传统 IT 资源的抽象和最大的灵活性。

PaaS

这个选项是一个托管服务,负责所有的 IT 管理工作,使开发人员能够专注于构建业务逻辑。一个极好的 AWS PaaS 示例是AWS App Runner,它允许以最小的努力进行.NET 应用程序的持续交付。这种类型的服务消除了管理 IT 基础设施的需求,使组织能够专注于应用程序的部署和管理。

FaaS

FaaS 作为一种开发范式,令人着迷,因为它允许开发者以思维的速度工作。AWS Lambda 是这一模型的典型例子,因为它自动管理计算资源。² 对于 .NET 开发者来说,这可能是将逻辑部署到云端最有效的方式,因为代码只在响应事件时运行。

SaaS

该选项提供了一个完整的可购买产品,使组织无需托管或开发此类产品即可使用。这类产品的一个绝佳例子是日志和监控分析服务。

其他(“即服务”)

云计算提供了更多的“即服务”选择。稍后本书将更详细地讨论这些服务。

普遍的直觉是,IaaS 意味着你付出较少但需要做更多工作,SaaS 意味着你付出更多但做的事情较少,而 PaaS 则处于中间位置。³ 这个概念类似于走进一个大型仓储店,要么买面粉做披萨,要么买冷冻披萨,要么在出门时在柜台上买一片现成的披萨。无论是披萨还是软件,当你有专业知识和时间自己动手时,你付出较少但需要做更多工作。或者,利用专家提供的更高级别服务的便利性,如热披萨,是值得的。

虽然 IaaS 的原始成本可能较低,但它更接近在物理数据中传统做法,这并不总是意味着更便宜。由于需要支付高素质员工来开发和管理 IaaS 上的应用程序,因此 IaaS 的总投资回报率(ROI)可能更高。此外,一些传统范式,如在虚拟机中运行 Web 服务,可能比仅在调用时运行的事件驱动 AWS Lambda 函数更昂贵。

描述像 AWS Lambda 这样高效的事件驱动服务的另一种方式是将其称为云原生服务。云原生服务是云计算所可能带来效率的新服务,提供了传统 IT 基础设施不可用的范式,如响应 Web 事件与全天候运行 Web 服务器不同。具体而言,AWS Lambda 函数一天可能只需要几秒钟的计算时间。而传统的 Web 服务则会无论收到每秒数千次请求还是一天仅接收十几个请求,都会全天候运行。这个概念类似停车场里的灯光:你可以浪费电力点亮空荡荡的车库,也可以使用传感器灯,只有检测到运动时才会开启。在这种情况下,如果考虑到构建所需的资源和应用运行时的低效性的投入回报率,IaaS 可能是一种更昂贵的解决方案。

另一种称呼那些不需要或只需要很少管理的服务是 managed services 。Amazon CloudSearch 是一个管理服务的绝佳例子。在 AWS 云中,这种管理服务使得为你的网站或应用程序建立、管理和扩展搜索解决方案变得简单和经济高效。与之相反的是需要专家来设置、运行和维护的 IaaS 服务。

注意

AWS 使用术语 managed services 来指代 PaaS、SaaS 和 FaaS 服务。在 AWS 文档中,你可能只会看到 managed services 这个术语的使用。

接下来,让我们深入了解如何开始使用 AWS。

开始使用 AWS

开始使用 AWS 的理想方式是注册一个免费使用层账户。当你首次创建账户时,请注意需要设置一个账户名和根用户的电子邮件地址,如图 1-2 所示。

doac 0102

图 1-2. 注册 AWS

创建账户后,首先要做的事情是将 AWS 账户根用户访问密钥锁定起来。[⁴]关于如何锁定 AWS 账户根用户访问密钥的详细步骤,请参考[⁴]。

这是因为如果暴露账户凭据,可能会失去对 AWS 账户的控制。一个基本的安全原则是最小权限原则(PLP),它指出你应该“从最小的权限集开始,并根据需要授予其他权限”。此外,根用户账户应该在 AWS 根用户账户上启用 AWS 多因素认证(MFA)来查看如何使用 MFA 保护你的账户的逐步说明。[⁶]关于如何使用 MFA 保护你的账户的详细步骤,请参考[⁶]。

注意

值得注意的是,在快速上手云计算过程中的一个标准障碍是术语。幸运的是,AWS 有一个详尽和更新的术语表,值得收藏。

通过 AWS Web 控制台是使用 AWS 的简单方法。我们接下来来解决这个问题。

使用 AWS 管理控制台

如图 Figure 1-3 所示,AWS 管理控制台是从 Web 浏览器控制 AWS 的中心位置。服务选项卡显示了平台上每个服务的分层视图。在该选项卡旁边是一个搜索框,允许您搜索服务。我们经常使用搜索框,因为它通常是导航到服务的最快方式。在搜索框旁边是 AWS CloudShell 图标,显示为黑白终端图标。这项服务 是在 AWS 平台上快速尝试命令的好方法,例如列出您账户中的S3 存储桶 — 对象存储的容器 — 或运行针对管理的自然语言处理 API 如AWS Comprehend 的命令。

注意

在概念上,S3 存储桶类似于消费者云存储,如 Dropbox、Box 或 Google Drive。

带圈的 N. Virginia 选项卡显示了当前使用的 AWS 区域。经常切换到不同的区域来启动虚拟机或尝试另一个区域中的服务是很常见的。通常,但并非总是这样,新服务首先会出现在 N. Virginia 地区,所以根据您的组织使用的区域,可能需要切换到 N. Virginia 来尝试新服务,然后再切回去。同样,如果新服务出现在像 US West(Oregon)这样的不同区域,您可能会发现自己在那个区域尝试新服务,然后需要切换回您的主要区域。

doac 0103

图 1-3. 使用 AWS 控制台
注意

服务成本和数据迁出 AWS 的成本可能因区域而异。

最后,最近访问选项卡显示了最近使用的服务。在长时间使用 AWS 后,大多数日常服务通常会出现在这个部分,提供一个有用的快捷菜单来使用 AWS。另一个需要注意的是,您可以在控制台中给一个项目加星标(Figure 1-4)。这个过程可以是访问经常使用的服务的便捷方式。

doac 0104

图 1-4. AWS 管理控制台中的收藏夹

接下来,让我们看看如何利用 AWS 社区和文档资源。

利用 AWS 社区和文档

AWS 平台提供了大量不同形式和深度的文档,可供学习。接下来让我们讨论这些资源。

.NET on AWS 网站

.NET on AWS 网站是关于在 AWS 上使用.NET 的信息中心。从这里,读者可以找到服务和 SDK 文档、AWS 工具包和迁移工具、入门教程、开发者社区链接以及其他内容。

对于 .NET 开发者来说,将这个网站加入书签是明智的,因为它提供了有帮助的资源的策划视图。我最喜欢的部分之一是 .NET 社区选项卡。这一部分包含了许多流行的开发者社区的链接,如图 1-5 所示,包括 Twitter、re:Post、Stack Overflow、Slack、GitHub 和全球 AWS 用户组。⁸

doac 0105

图 1-5. .NET 的开发者社区

社区门户的一个更有价值的部分之一是到 YouTube 频道和开发者博客的链接。最后,还有 AWS 投资的开源 .NET 项目的链接,包括 .NET on AWSAWS SDK for .NET

接下来,让我们看看 AWS SDK for .NET 文档。

AWS SDK for .NET 文档

你可以通过官方网站了解如何使用 AWS SDK for .NET 开发和部署应用程序。该网站包含几个重要的指南,包括开发者指南API 参考指南,和SDK 代码示例指南

另一个重要资源是 AWS 上必要的开发 .NET 工具,如图 1-6 所示。这些工具包括 Rider 的 AWS Toolkit,Visual Studio 的 AWS Toolkit,Visual Studio Code 的 AWS Toolkit,以及 Azure DevOps 的 AWS 工具。

doac 0106

图 1-6. 探索 AWS 上的 .NET 工具

我们稍后会深入研究本章的 AWS SDK。接下来,让我们看看 AWS 服务文档。

AWS 服务文档

AWS 有如此多的服务,初次理解哪种服务适合手头的任务可能有些困难。幸运的是,有一个集中的网页,AWS 文档网站,其中包括指南和 API 参考、教程和项目、SDK 和工具包,以及常见问题解答和案例研究的链接。

一个需要注意的项目是 AWS 拥有许多 SDK 和工具包,如图 1-7 所示,包括 AWS CLI。对于 .NET 开发者来说,看多种语言的示例以获取构建解决方案的想法是非常有帮助的。AWS 命令行界面通常是服务的最有效文档,因为它以易于理解的方式抽象了概念。这个概念的一个很好的例子是以下递归复制文件夹到 AWS S3 对象存储的命令:

aws s3 cp myfolder s3://mybucket/myfolder --recursive

每个空格分隔命令的动作,例如,首先aws然后 s3用于服务,s3动作,和其余命令。在许多情况下,从命令行调用 AWS 上的服务是学习的最快方式。

doac 0107

图 1-7. 探索 AWS SDK 和工具包

接下来,让我们谈谈安装 AWS 命令行。

使用 AWS 命令行界面

根据官方 AWS 文档,“AWS 命令行界面(CLI)是一个统一的工具,用于管理您的 AWS 服务。只需下载并配置一个工具,您就可以从命令行控制多个 AWS 服务并通过脚本自动化它们。”

有四种基本方法与 AWS 命令行界面进行交互。⁹ 让我们简要讨论每一种方法。

AWS CloudShell

AWS CloudShell是一款基于浏览器的云原生 Shell,这意味着它利用云的独特属性为您预先安装了 AWS CLI。您可以选择 Bash Shell、PowerShell 或 Z Shell 之间的任一种。

AWS Cloud9

AWS Cloud9是一款云原生交互式开发环境(IDE),具有深入的 AWS 集成和适用于在云中进行开发的独特开发工具。

Linux shells

通过使用Amazon Linux 2 AMI,您自动获得用于 AWS 集成的命令行工具。或者,您可以在任何Linux 系统上安装 AWS CLI。

Windows 命令行

托管的 AWS Windows AMIs附带 AWS CLI。或者,您可以下载并运行64 位 Windows 安装程序

接下来,让我们讨论每种方法的安装说明。

注意

您可以观看关于如何使用 AWS CloudShell 的更详细演示,包括与 Bash、ZSH、PowerShell、S3、Lambda、Python、IPython、Pip、Boto3、DynamoDB 和 Cloud9 的交互,这些演示可在YouTubeO'Reilly上找到。

如何安装 AWS CloudShell 和 AWS Cloud9

对于 AWS CloudShell 和 AWS Cloud9,无需安装过程,因为它们已包含在 AWS 控制台体验中,如图 1-8 所示。注意,在搜索结果中同时找到这两个工具,允许您“标记”(即添加到收藏夹)或单击它们。最后,终端图标还可以启动 AWS CloudShell。这些云原生终端的一个关键优势是它们是完全托管和自动更新的,并自动管理您的凭据。

doac 0108

图 1-8. 选择 AWS Cloud9 和 AWS CloudShell

如何安装 AWS CLI

AWS CLI 安装包含 AWS Cloud9 或 AWS CloudShell。如果您需要在 Windows 本地安装 AWS CLI,可以在 AWS 网站上找到详细指南

如何安装 AWSPowerShell.NETCore for AWS

如果你需要在 Windows、Linux 或 macOS 上本地运行 AWS 的 PowerShell,你可以参考 PowerShell Gallery 查找 AWSPowerShell.NETCore 的最新版本及安装说明。AWSPowerShell.NETCore 是推荐用于与 AWS 工作的 PowerShell 版本,因为它具有全面的跨平台支持。通常情况下,你可以使用以下 PowerShell 命令进行安装:Install-Module -Name AWSPowerShell.NetCore

在本地操作系统上安装 PowerShell 的替代方法是使用 AWS CloudShell,因为它预装了 PowerShell。接下来,让我们看看这个过程是如何运作的。

使用 AWS CloudShell 中的 PowerShell

有关使用 AWS PowerShell 工具的优秀指南可以在 AWS 网站 上找到。让我们看一个简单的例子来补充官方文档。在以下示例中,如 图 1-9 所示,我们通过调用 New-S3Bucket cmdlet 来创建一个新的 Amazon S3 存储桶。

doac 0109

图 1-9. 在 AWS PowerShell 中创建 AWS 存储桶

接下来,我们可以转到 AWS 控制台,选择 S3,并验证其是否显示,如 图 1-10 所示。

doac 0110

图 1-10. 在 AWS S3 控制台中显示 AWS 存储桶

值得一提的是,AWS 的 PowerShell 提供了一个复杂和高级的界面,用于控制和与 AWS 的服务(包括 EC2、S3、Lambda 等)交互。你可以在 AWS 文档 中进一步探索这些功能。

AWS CloudShell 支持三种 shell:PowerShell、Bash 和 Z shell。要使用 PowerShell,你可以输入 pwsh。你可以通过官方的 文档 探索 AWS CloudShell 的所有功能。AWS CloudShell 提供了 1GB 的持久存储空间,位于 /home/cloudshell-user。如 图 1-11 所示,操作菜单允许你执行许多实用操作,包括下载和上传文件、重新启动终端、删除主目录中的数据以及配置终端的标签布局。

doac 0111

图 1-11. AWS CloudShell 操作菜单

幸运的是,AWS CloudShell 的 PowerShell 用法非常简单,类似于本地的 PowerShell。首先,要使用 AWSPowerShell.NetCore,请按以下顺序导入它,将内容回显到文件中,并将其写入先前创建的存储桶中。最后,运行 Get-S3Object 来验证文件在 S3 中的创建情况,你可以在 图 1-12 中看到。

Import-Module AWSPowerShell.NetCore
echo "This is data" > data.txt
Write-S3Object -BucketName silly-name-1234 -File data.txt
Get-S3Object

doac 0112

图 1-12. AWS CloudShell 创建文件 S3

云外壳(CloudShell)和 PowerShell 环境的另一个特性是调用 C# 代码。要开始使用 PowerShell 和 C#,你可以创建一个 C# 片段并嵌入到名为 ./hello-ps-c-sharp.ps1 的脚本中。C# 代码在 $code = @" 块之后,并在 "@ 终止。总体思路是,如果你在 AWS CloudShell 中使用 PowerShell,可以使用现有的有用 C# 片段来增强 PowerShell 脚本,而不需要完整的编辑器:

$code = @"
using System;
namespace HelloWorld
{
 public class Program
 {
 public static void Main(){
 Console.WriteLine("Hello AWS Cloudshell!");
                }
        }
}
"@

Add-Type -TypeDefinition $code -Language CSharp
iex "[HelloWorld.Program]::Main()"

Install-Module -Name AWS.Tools.Installer -Force

接下来,在你的 PowerShell 提示符中运行它:

PS /home/cloudshell-user> ./hello-ps-c-sharp.ps1
Hello AWS Cloudshell!
PS /home/cloudshell-user>

根据特定情境选择使用 Bash 或 PowerShell。如果涉及纯粹的 .NET 功能,那么 PowerShell 是一个简单的选择。另一方面,你可能会在 AWS 网站上找到一些关于使用 Bash 脚本解决方案的文档。与其重写示例,不如直接使用并在 Bash 中扩展,这样可以节省时间。

在下面的示例中,我们使用一个 Bash 哈希来存储 Cloud9 的 ID 列表,然后获取关于它们的信息:

#!/usr/bin/env bash
# Loop through a list of Cloud9 Environments to find out more information

declare -A cloud9Env=([env1]="18acd120518340df8a73ccaab641851e"\
    [env2]="2c9eb66bf53b4083b9ab6345bae70dad"\
    [env3]="f104b0141c284a41af0c75fea7890770" )

## now loop through the above hash to get more information
for env in "${!cloud9Env[@]}"; do
   echo "Information for $env: "
   aws cloud9 describe-environments --environment-id "${cloud9Env[$env]}"
done

在这种特定情况下,我们不必使用 Bash,因为有关如何在 AWS 中使用 PowerShell 解决问题的优秀文档。PowerShell 可以像 Bash 一样做同样的事情,并且具有深度的 C# 集成。请注意以下示例展示了通过 PowerShell 循环遍历存储在哈希中的多个 ID 的方法:

# PowerShell script that loops through Cloud9 to get environment information

$cloud9Env = @{ env1 = "18acd120518340df8a73ccaab641851e";
                env2 = "2c9eb66bf53b4083b9ab6345bae70dad";
                env3 = "f104b0141c284a41af0c75fea7890770" }

foreach ($env in $cloud9Env.GetEnumerator()) {
            Write-Host "Information for $($env.Key):";
            Get-C9EnvironmentData $($env.Value)
}

总结一下,AWS CloudShell 对于 C# 开发者来说是一个很好的伴侣,值得放入你的工具包中。现在让我们使用 Visual Studio 来开发 AWS,这是有经验的 .NET 开发者使用 AWS 的首选环境。

使用 Visual Studio 开发 AWS 和 AWS Toolkit for Visual Studio

使用 Visual Studio 开发 AWS 是一个简单直接的过程。所需组件包括 AWS 账号、运行受支持版本的 Windows 的机器、Visual Studio Community 版或更高版本,以及 AWS Toolkit for Visual Studio。如果这是你第一次配置,请参阅官方 AWS 文档以获取有关设置 Visual Studio 开发 AWS 的进一步细节。

注意

如果你想使用最新的 .NET 6 特性,需要使用 Visual Studio 2022。在实践中,使用最新版本的 Visual Studio 在 Windows 环境中可以获得最佳的 Visual Studio 与 AWS 深度集成体验。例如,有 Mac 版本的 Visual Studio,但 AWS Toolkit 在 Mac 上无法工作。

AWS SDK for .NET 与 Visual Studio 环境深度集成,如 Figure 1-13 所示。它包括通过作为 AWS Toolkit for Visual Studio 一部分安装的 AWS Explorer 集成与 AWS 核心服务(包括 Amazon S3 和 Amazon EC2)进行交互。

doac 0113

图 1-13. AWS SDK for .NET

根据官方 AWS 文档,AWS Toolkit for Visual Studio 是运行在 Microsoft Windows 上的 Microsoft Visual Studio 的扩展,使开发者能够“更轻松地使用亚马逊网络服务开发、调试和部署.NET 应用程序”。此集成包括多种部署应用程序和管理服务的方法。这种集成的一个组件体现在服务器资源管理器工具箱或 AWS Explorer 中,如图 1-14 所示。AWS Explorer 允许您在 Microsoft Windows Visual Studio 环境中管理和与 AWS 资源如 Amazon S3 或 EC2 实例交互。

注意,通过单击鼠标可以轻松进入许多热门的 AWS 服务。

Visual Studio 是.NET 的流行开发工具,有两个原因:它既是一款出色的编辑器,也是.NET 开发者丰富的生态系统。AWS Toolkit for Visual Studio 作为 Microsoft Visual Studio 在 Windows 上的扩展,融入了这一生态系统,使开发者能够更轻松地使用亚马逊网络服务开发、调试和部署.NET 应用程序。您可以通过查阅安装程序的官方文档来查看所有可用功能的完整列表。

注意

注意,Visual Studio CodeJetBrains Rider都有 AWS 工具包。

doac 0114

图 1-14. AWS Explorer

安装 AWS Explorer 确实需要三个关键步骤:¹⁰

  1. 安装 AWS Toolkit for Visual Studio

    下载AWS Toolkit for Visual Studio 2022并在安装了 Microsoft Visual Studio 的 Windows 机器上安装此软件包。

  2. 创建 IAM 用户并下载凭据

    在 AWS 控制台中创建 IAM 用户,并应用最小特权原则。¹¹ 确保您选择访问密钥选项,如图 1-15 所示,并在提示时以 CSV 格式下载凭据。

doac 0115

图 1-15. 为 Visual Studio 创建 IAM 用户
  1. 将凭据添加到 Visual Studio

    最后,在 AWS Explorer 中添加您以 CSV 格式下载的凭据,如图 1-16 所示。

注意

在将 AWS 密钥下载到任何本地开发机器上时,需要特别注意,因为如果它们被 compromise,那么拥有它们的人就可以利用这些密钥的权限。使用 PLP,开发者应该选择仅与在 AWS 上创建解决方案所需的功能严格对应的 IAM 权限。

doac 0116

图 1-16. 添加凭据

一旦 AWS Toolkit for Visual Studio 安装完成,同样要注意及时更新工具,如 图 1-17 所示,因为它正在积极开发中,具有来自 AWS 的新功能。在 Visual Studio 中,您将会看到 AWS Toolkit for Visual Studio 的新更新通知。

doac 0117

图 1-17. 升级 AWS Toolkit for Visual Studio

接下来,让我们看看如何开始使用 AWS SDK。

开始使用 AWS SDK

学习一项新技术的最佳方法是动手构建一些东西。让我们通过创建一个以 AWSSDK.S3 从 NuGet 获取的控制台应用程序来开始使用 AWS SDK。步骤如下:

  1. 创建一个新的目标为 .NET 6 的控制台应用程序。

    在 Visual Studio 中,创建一个新的控制台应用程序,作为开发的 AWS S3 工具的载体。

  2. 使用 NuGet 安装 AWSSDK.S3。

    加载新创建的控制台应用程序后,选择“工具”,然后是 NuGet 包管理器,接着是“管理解决方案的 NuGet 包”。搜索 AWSSDK.S3 并将其安装到您的控制台项目中。

  3. 创建代码。

    通过将 Program.cs 文件替换为以下示例中显示的内容来构建以下控制台应用程序。

提示

另一种安装 NuGet 包的方法是右键单击项目,然后选择“管理 NuGet 包”。

using System;
using System.Threading.Tasks;

// To interact with Amazon S3.
using Amazon.S3;
using Amazon.S3.Model;

// Create an S3 client object.
var s3Client = new AmazonS3Client();

// Display Prompt
Console.WriteLine("AWS Bucket Lister" + Environment.NewLine);

// Process API Calls Async List AWS Buckets
var listResponse = await s3Client.ListBucketsAsync();
Console.WriteLine($"Number of buckets: {listResponse.Buckets.Count}");

// Loop through the AWS buckets
foreach (S3Bucket b in listResponse.Buckets)
{
  Console.WriteLine(b.BucketName);
}
注意

对于命令行的爱好者,值得注意的是,您还可以使用 dotnet 命令行工具创建一个控制台应用程序,如以下片段所示:dotnet new console --framework net6.0¹²。

控制台应用程序的结果详细列出了运行 .NET 应用程序的 AWS 用户拥有的 S3 存储桶列表,如 图 1-18 所示。

doac 0118

图 1-18. 异步列出存储桶
注意

您还可以观看关于从头开始构建此 S3 控制台应用程序的视频教程,地址为 O’ReillyYouTube

此控制台应用程序的后续步骤是扩展功能,不仅列出存储桶,还可以创建存储桶,如 AWS SDK for .NET 的此 AWS 教程 所示。

接下来,让我们总结本章涵盖的所有内容。

结论

云计算是技术创新的关键驱动因素,因为它开启了更高效的工作方式¹³。通过利用几乎无限的资源和云原生托管服务,开发人员可以通过专注于问题本身而非与构建解决方案无关的问题(如系统管理),从而提高生产力。本章介绍了如何使用云外壳开始使用亚马逊云服务,首先使用 CloudShell,然后转移到完整的 Visual Studio 代码环境。

Visual Studio 环境受益于 AWS Toolkit,该工具包具有与 AWS Cloud 交互的高级功能。AWS SDK 可以直接通过 NuGet 软件包管理器在 Visual Studio 中使用。AWS SDK 允许直接集成到像控制台应用程序这样的 Visual Studio 解决方案中。我们使用 AWS SDK 构建了利用 AWS S3 等低级服务和 AWS Comprehend 等高级 AI API 的控制台应用程序。

对读者的推荐下一步是查看批判性思维讨论问题,反思如何利用 AWS 的全部功能。您可以使用这些问题与团队或正在学习 AWS 的同事讨论。此外,下面的几个挑战练习建议也是构建针对.NET 6 的 AWS 服务的良好实践。其中一些服务尚未详细介绍,因此您可以在稍后的章节中跳过它们。

接下来,在第二章,我们将介绍 AWS 核心服务。这些包括 AWS 存储、EC2 计算以及像 DynamoDB 之类的服务。这些服务是构建利用.NET 生态系统强大功能的云原生应用程序的基础。

批判性思维讨论问题

  • 在 AWS Cloudshell 中使用 Bash 与 PowerShell 有哪些关键区别?

  • 何时使用 PowerShell 而不是 Bash 以及反之,将会带来什么优势?

  • 如何通过掌握命令行工具dotnet来提高生产力?

  • AWS Toolkit for Visual Studio 对您的组织中的非开发人员,如运营团队,是否有好处?为什么?

  • 使用从 Visual Studio 直接部署的方法原型化 C# Web 服务有什么优势?

挑战练习

  • 构建一个 C#或 F#控制台应用程序并部署到 AWS CloudShell。考虑云端开发环境的独特属性如何帮助您在 AWS 云上自动化工作流程,考虑到它们不需要 API 密钥,而且可以在不需要部署到 AWS 计算服务的情况下使用。

  • 参考 AWS CDK 文档(C#)并通过 CDK 创建一个 S3 存储桶。

  • 使用 Visual Studio 和AWS Toolkit for Visual Studio Code构建和部署一个 Elastic Beanstalk 应用程序。

  • 在 Visual Studio 中使用 NuGet 安装 600 多个可用的 AWS 包之一。

  • 使用 AWS 控制台创建一个 hello world AWS Lambda 函数,然后通过 VS Code 和 AWS Toolkit for Visual Studio Code 调用它。

¹ 您可以在《Python for DevOps》(O’Reilly)的第六章中详细了解 EFS 和 spot 实例。

² AWS 白皮书“在 AWS 上开发和部署.NET 应用程序”解释了 AWS Lambda 的机制。

³ 您可以在他们的“Amazon Web Services 概述”白皮书中详细了解 AWS 对云计算的看法。

⁴ AWS 身份和访问管理指南列有详细的列表https://oreil.ly/x90WL

⁵ 要创建管理员 IAM 用户,请参阅官方 AWS 指南

⁶ 参见AWS 多因素认证指南

⁷ 您可以在官方文档中详细了解 AWS 管理控制台。

⁸ 此外,还有一些链接指向著名的.NET 开发者,如Norm JohansonFrançois BouterucheSteve Roberts

⁹ 有一份详细的用户指南,介绍如何与 AWS 命令行界面交互。

¹⁰ 有一份详细的AWS Toolkit for Visual Studio 设置指南可供参考。

¹¹ 在 AWS 文档中有一份关于最小权限原则(PLP)概念的详细指南。PLP 的核心思想是只在需要时授予权限。

¹² AWS Lambda 等其他服务也提供了一个便捷的方式来通过命令行创建应用程序

¹³ 亚马逊在云计算领域的领导地位部分归因于其文化,其领导原则包括节俭、客户至上、发明和简化等。您可以在亚马逊的领导原则中了解更多。

¹⁴ AWS CDK 是一个库,自动化部署 AWS 资源。

第二章:AWS 核心服务

构建房屋最流行的原材料包括钢铁、砖块、石头和木材。这些原材料各具独特的属性,使得建筑师能够建造房屋。类似地,AWS 核心服务是构建复杂系统所需的基础材料。从高层次来看,这些服务包括计算和存储。

当房屋建造者建造房屋时,他们遵循全面的建筑规范,通常从一种被称为I-Codes的国际标准中获取。根据国际代码理事会,这些现代安全规范旨在帮助确保“安全、可持续、经济实惠和弹性结构”的工程。类似地,AWS Well-Architected Framework根据 AWS 的说法提供“指导,帮助客户在设计、交付和维护 AWS 环境中应用最佳实践”。

这些通用设计原则在有效使用 AWS 核心服务方面至关重要。让我们简要讨论它们:

停止猜测您的容量需求

简而言之,猜测容量是一种不良策略。相反,系统应该具备动态添加或移除资源的能力。

在生产规模上测试系统

云允许资源的完全自动化配置;这使得开发人员能够在与生产环境完全相同的环境中测试应用程序。能够复制生产环境解决了软件工程行业普遍存在的“在我的机器上可行”的问题。

自动化以简化架构实验

自动化会在长期内减少工作量,使开发人员能够审计、跟踪变更并在需要时回滚。

允许演进式架构

核心思想是设计一个预期会变化的系统。在设计软件时,我们应该考虑动态的自然系统,例如树木和河流,而不是静态的系统,例如桥梁或道路。

利用数据驱动架构

数据科学是一门流行的学科,但它不仅仅局限于业务问题。软件系统需要利用数据科学来识别必要的变化,以保持系统按设计性能运行。

通过游戏日不断改进

一个充分架构的系统需要定期进行完整的模拟运行;没有这样做可能无法彻底测试所有情景。

在我们继续本章之前,请记住这些原则。接下来,让我们更详细地讨论 AWS 存储。

AWS 存储

存储是一个简单表面上看起来简单但可以迅速复杂化的示例。在 AWS 存储的核心是 Amazon 简单存储服务,也称为AWS S3。它于 2006 年推出,是 AWS 向公众提供的第一个服务之一。截至 2021 年,存储在 S3 中的对象数量为 100 万亿。¹如果你想要一个“大数据”的例子,这就是最好的。

虽然 S3 是 AWS 推出的第一个存储解决方案,但还有许多其他选择。以.NET 开发者为中心的观点,开始的一种方式是将存储分为两大类:核心存储和数据库。

让我们简要地分解这两者。

核心存储

核心存储指的是 AWS 所有服务使用的低级存储组件,包括对象存储 Amazon S3 和 Amazon Elastic Block Store (EBS)。AWS 核心存储选项包括对象存储 Amazon S3,Amazon Elastic Block Store (EBS),完全托管的网络文件存储,如 Amazon File System (EFS),以及 Amazon FSx for Windows File Server(FSx 选项的一个示例),最后还包括AWS 备份和存储网关等实用存储服务。

数据库

从高层次来看,数据库是从计算机系统访问的组织数据集合。数据库存储选项包括关系数据库,包括 Amazon RDS,键值数据库 Amazon DynamoDB,以及类似 Amazon Neptune(用于查询基于图形的数据)的特殊用途数据库。

接下来我们深入了解如何使用 S3 存储。掌握 S3 存储是至关重要的,因为它提供了多种成本效益的层次来处理对象数据。

使用 S3 存储开发

一种看待 S3 的方式是作为一个高级服务,以规模存储和检索对象。对于开发者来说,这个视角使你可以专注于开发应用程序的业务逻辑,而不是管理高性能对象存储系统。让我们首先确定 Amazon S3 的关键优势:

耐久性

耐久性 是指确保数据不丢失的概念。S3 标准存储层提供了 99.999999999%(即十一个 9)的耐久性

可用性

可用性 是指在需要时访问数据的概念。S3 标准存储层具有 99.99%(即四个 9)的可用性。

可扩展性

可扩展性 是增加容量以满足需求的能力。S3 作为托管服务,在磁盘 I/O 和存储方面提供几乎无限的容量。它可以存储大小不超过 5TB 的单个对象。

安全性

S3 具有细粒度的访问控制以及在传输和静态状态下的加密。

性能

S3 文件系统的性能支持许多不同的访问模式,包括流式传输、大文件、机器学习和大数据。

作为开发者与 S3 交互有几种不同的方式。第一种选项是通过 .NET SDK。在 第一章 中列出 S3 存储桶的示例是这一过程的一个很好的例子。您还可以通过使用使用 S3 的托管服务间接与 S3 交互。使用 S3 的这些托管服务的示例包括 AWS Athena² 和 AWS SageMaker。通过 Athena,您可以通过无服务器 SQL 查询查询所有 S3,但无需担心处理服务器的物流。同样,Amazon SageMaker³,一个完全托管的机器学习服务,大量使用 S3 来训练机器学习模型和存储模型工件。其中一个关键原因是 S3 是无服务器的,并且无需用户管理即可扩展。

另一种使用 S3 的方式是利用 S3 生命周期配置。生命周期配置允许复杂的自动化工作流将数据迁移到不同的存储层级和归档数据。让我们看看这些存储类别。

S3 标准

针对频繁访问的数据非常理想,并且对包括云原生应用、内容、游戏和大数据在内的各种用例都非常有帮助。

S3 标准 IA(不经常访问)

这种存储类别与 S3 标准具有相同的好处,但提供了不同的成本模型,使其非常适合诸如旧日志文件之类的项目,因为检索成本较高。

S3 单区域-IA

单区域 (存储数据在单个可用区) 选项对于希望最低成本的情况非常有帮助。一个示例用例是次要备份,因为它作为数据的次要副本在主要备份系统故障时保护数据免受永久丢失的影响而具有成本效益。

亚马逊 S3 冰川深度归档

Glacier 是一个安全、耐用且成本低廉的选项,非常适合数据归档。它与 S3 生命周期配合良好,作为最终交付点。

在 图 2-1 中,你可以看到亚马逊 S3 如何在数据活动的中心起到独特作用,不仅仅是存储媒体资产或 HTML 文件。在构建全球规模平台或机器学习系统时的一个挑战性约束是存储容量和磁盘 I/O。AWS 提供了核心服务 S3,消除了这些约束。因此,S3 的弹性特性与近乎无限的存储和磁盘 I/O 创造了一种新型工作流程,AWS 管理的服务建立在这个核心服务之上,例如 AWS SageMakerAWS Glue

doac 0201

图 2-1. S3 对象存储作为中心枢纽

诺亚最喜欢的 Amazon S3 使用案例之一是托管静态网站。在 O’Reilly 的书籍 Python for DevOps 中,他详细介绍了如何构建一个使用 Hugo 网站,该网站使用 AWS CodePipeline、Amazon S3 和 Amazon CloudFront CDN。您还可以在 YouTubeO’Reilly 上查看部署 Hugo S3 网站到 AWS 所需步骤的详细演示。

AWS 利用 S3 的另一个突出例子是通过 Athena 查询日志文件。

现在您了解了 S3 对象存储的不同存储类别,让我们讨论 Elastic Block Store(EBS)存储。

使用 EBS 存储进行开发

EBS 是高性能的网络附加存储。EBS 存储 通过将块存储挂载到单个 EC2 实例来工作。EBS 卷类似于原始的 未格式化的块设备(物理硬盘),但被虚拟化并作为服务提供。最后,EBS 是与 EC2 实例附加的块级存储,与 S3 不同,后者独立存在,将数据存储为对象,并可从多个实例访问。

让我们讨论 EBS 的关键优势和使用场景。

优势

EBS 存储用于数据需要快速访问但具有长期持久性。这种存储类型提供了专用的高性能网络连接和分配磁盘 I/O 的能力。其他关键功能包括为备份创建快照到 S3 或创建自定义 Amazon Machine Images(AMIs)。

使用案例

EBS 存储非常适合 Web 应用程序和数据库存储。高性能的专用磁盘 I/O 也非常适合构建通过 Server Message Block(SMB)或 Network File System(NFS)协议共享数据的高性能文件服务器。

EBS 的一个显著用途是通过 io2 Block Express 提供高性能的每秒 IOPS(输入/输出操作)创建“云中的 SAN”,如 Figure 2-2 所示。注意,以这种方式配置的 EBS 存储能够创建一个高性能的 Microsoft SQL Server 实例,作为商业智能(BI)系统的核心。

此 BI Web 服务可以通过 Elastic Beanstalk 提供 ASP .NET Web 服务,从而在数据密集型查询在高峰期间达到峰值时实现快速自动扩展。“云中的 SAN” 概念的其他使用案例包括部署高性能 NoSQL 服务器或专业分析平台,如 SAP HANA 或 SAS Analytics。

doac 0202

图 2-2. 云中带有 SQL Server 的 EBS SAN
注意

另一种存储类型是实例存储。这种存储类型是实例的临时块存储,非常适合缓冲区、缓存或临时分区。与实例存储的一个关键区别是,您不能将实例存储卷从一个实例分离并附加到另一个实例上。与 EBS 存储不同,实例存储在实例停止、休眠、终止或磁盘驱动器失败时终止。

有一个 EBS 无法解决的使用案例:如果您有多个需要使用同一存储的实例,您需要一个新的解决方案。在这种情况下,您可以同时使用 Amazon EFS 和 Amazon FSx for Windows File Server。我们接下来讨论这个话题。

使用网络存储:EFS 和 FSx

在 20 世纪 90 年代末和 21 世纪初的早期互联网时代,网络存储是大规模 Unix 计算机网络在大学和商业组织中运行的重要组成部分。NFS 或网络文件系统是 Sun Microsystems 在 1984 年开发的协议,在那个早期互联网时代变得无处不在。

NFS 存储早期解决的一个关键问题是能够创建可移动的个人主目录。这种能力意味着用户可以从任何工作站创建基于终端的连接,并且他们的 shell 配置文件和数据是可用的。基于 NFS 存储的系统的缺点是 NFS 文件服务器是中心枢纽;因此,它经常在工作流程中造成瓶颈,因为系统被请求压倒。

早在 21 世纪初的云计算时代到来时,许多组织转向了云端,并建立了不再使用集中式网络存储的系统。相反,他们转向了挂载在一台机器上的块存储,或者通过像 Hadoop 这样的分布式文件系统挂载的对象存储。随着EFS(托管 NFS)和FSx(托管 Windows 网络存储)的可用性,集中式网络挂载点的优势回归,而没有了集中式文件服务器性能不佳的缺点。

NFSOps 的一个很好的示例显示在 Figure 2-3 中。NFSOps 描述了使用网络文件系统作为部署软件和配置的方法。在 GitHub 中对源代码的更改触发了通过Jenkins部署服务器的构建过程。这个部署服务器有一个与之关联的 EFS 挂载点。因此,构建服务器可以使用 Rsync 将脚本更改部署到网络挂载点,仅需几毫秒的时间。

部署过程解决后,此图显示了在计算机视觉系统中使用的真实解决方案。由于这个特定的工作负载使用对集中式存储的访问,所以这个工作流程将 EFS 挂载为“真相的来源”,允许成千上万的 spot 实例同时使用、读取和写入数据。此外,执行读写操作的源代码存储在 EFS 卷上,极大地简化了配置和部署。

doac 0203

图 2-3. NFSOPs:使用 NFS 存储增强操作

对于.NET 开发人员来说,FSx for Windows 也开启了许多令人兴奋的新工作流程。在图 2-4 中的一个出色示例展示了一个 FSx 挂载点,该挂载点与托管在 Elastic Beanstalk 上的多个.NET Web 服务实例通信。

另一个 FSx 的标准工作流程是通过挂载存储端点为每个编辑工作站开发特色电影。在这篇AWS 博客文章中,你可以了解到一个影视公司如何利用 AWS 上的 DaVinci Resolve 编辑软件包在家庭编辑套件上创建一部特色电影。在主要电影制作流水线中使用 FSx 作为中央文件系统,已成为媒体公司的一种流行选项。因为 Web 服务可以挂载文件系统,所以它可以作为资产管理工作流程的一部分跟踪资产的位置。当动画师需要渲染整个文件序列时,他们将工作发送到 ECS 进行批处理处理。由于 ECS 还可以访问同一文件系统,这使得动画师的工作流程集成快速而无缝。

doac 0204

图 2-4. 媒体工作流程中的 FSx for Windows
注意

媒体行业长期以来一直使用高性能文件服务器和高性能计算(HPC)集群。现代基于云的版本的一个优秀例子是Epic Games 使用 FSx 的虚拟制作的博客文章

使用 EFS 或 FSx for Windows 都可以开启新的云原生架构,值得进一步研究,可以使用AWS 文档网站上的示例教程进行深入探讨。接下来,让我们讨论 AWS 上可用的计算选项。

使用 AWS 计算核心服务

Amazon EC2 提供可调整的计算能力,包括在几分钟内配置服务器、自动扩展实例,以及只支付所需容量的能力。这项服务的一个必要方面是弹性内置于服务的基础及其与其他 AWS 服务的集成中。

EC2 实例的使用案例包括从 Web 到数据库的服务器,其中开发人员希望更精细地控制部署过程。虽然像 AWS App Runner 这样的托管服务比自行管理服务器方便得多,但在开发人员需要对 EC2 实例进行较低级别访问的情况下,也存在一些场景。此服务还允许完全控制计算资源,这意味着您可以启动 Linux 和 Microsoft Windows 实例。此外,您还可以通过使用不同的定价计划来优化成本。

接下来,让我们更详细地探讨这些不同的计算选项。

AWS 计算核心服务比较

另一种理解 AWS EC2 的方式是查看官方文档 比较 AWS 计算资源 的更细粒度方式,这比本章早些时候讨论的要详细。在他们的官方 “Amazon Web Services 概述” 白皮书 中,AWS 将计算划分为几个类别:

实例(虚拟机)

在云中提供安全可调整的计算能力。EC2 实例和 AWS Batch(任意规模的完全托管批处理处理)是其中的两个例子。

容器

容器提供了一种将应用程序打包成单一镜像的标准方式。根据 AWS 文档,将容器在 AWS 上运行“为开发人员和管理员提供了一种高度可靠且低成本的方式来构建、发布和运行分布式应用程序”。例子包括 AWS App Runner⁴ 和 Amazon 弹性容器服务(ECS)。

无服务器

这种计算服务允许在不需要预配或管理基础设施的情况下运行代码,并响应任意规模的事件。AWS Lambda 是无服务器技术的一个例子。

边缘和混合

边缘服务在数据所在地附近处理数据,而不是在数据中心处理。这种服务通过提供在云端、本地或边缘使用的能力,为您提供了一致的 AWS 使用体验。AWS Snow Family 就是一个例子。⁵

成本和容量管理

AWS 通过提供服务和工具来帮助您确定成本和容量管理。客户可以根据推荐的实例类型测试工作负载,以优化价格性能。弹性 Beanstalk 就是这一类别中的一个服务的例子。⁶

另一种推理这些选择的方式在图 2-5 中表示。请注意,高级基础设施控制和快速应用部署之间存在反向关系。完全托管的服务还包括 AWS Lambda 和 Fargate 等额外的生产特性,允许最快的应用程序开发和部署。在某些情况下,这种关系并不是绝对规则,存在例外,也许在某个特定领域,开发 AWS Lambda 微服务比开发 AWS App Runner 微服务更难建立和部署。

doac 0205

图 2-5. AWS 计算选择

通过我们对 EC2 核心服务的了解,让我们谈谈如何开始使用 EC2。

使用 EC2

表面上看,EC2 通过允许您启动计算实例并用于任务来“只是工作”。在更深层次上,EC2 具有广泛的功能集合。从开发者的角度来看,让我们深入探讨 EC2 的关键部分:

Amazon 机器映像(AMIs)

AMI 是预配置的模板,方便地安装服务器实例所需的软件。有适用于 Linux 的 AWS 推荐的 AMI,Windows,甚至是 macOS 的 AMI。请注意,在图 2-6 中,AMI 目录可通过在 AWS 控制台内导航到 EC2 仪表板并选择镜像来访问。快速入门 AMI 是常用的 AMI,包括 Amazon Linux 2 和 Windows。 “我的 AMI”部分显示自定义 AMI。最后,市场和社区均提供了 AMI。

doac 0206

图 2-6. AMI 目录

实例类型

总共有数百种实例类型,按类别排列,包括计算优化、内存优化、加速计算(硬件加速器)和存储优化。一个最佳实践是评估您的应用程序的需求,并进行负载测试,以确保它满足您期望的成本和性能要求。

防火墙

EC2 具有名为安全组的防火墙,可用于对实例的入站和出站访问进行协议、端口和 IP 范围的精细配置。

元数据

您可以创建和使用标签来启用 EC2 实例的资源跟踪和自动化。

弹性 IP 地址

静态 IPv4 地址可以动态分配给 EC2 实例,从而增强了弹性和自动化。

虚拟私有云(VPC)

虽然 VPC 是一个独立的服务,但它与 EC2 有深度集成。虚拟网络创建了一个与其他 AWS 资源精确控制连接的隔离环境。

基础知识介绍完毕,让我们来看看在图 2-7 中如何配置 EC2 实例。请注意,实例启动还可以使用“用户数据”在启动时分配自定义命令或在 EBS 和实例存储之间进行选择。其他关键决策包括设置开放必要服务之间网络通信所需端口的安全组。其他可配置选项包括选择适当的 IAM 角色,并为 EC2 实例赋予与 Amazon S3 通信的能力。

doac 0207

图 2-7. 配置 EC2 实例
注意

您可以在以下YouTube 视频或在O’Reilly上查看如何从 AWS CloudShell 或 AWS 控制台配置 EC2 实例的完整演练。

需要注意的是,普通的 Bash 终端非常适合自动化 EC2。请注意以下命令,启动一个实例,描述它,然后终止它:

aws ec2 run-instances --image-id ami-033594f8862b03bb2
aws ec2 describe-instances --filters "Name=instance-type,Values=t2.micro"
aws ec2 terminate-instances --instance-ids i-00cbf30e33063f1a4
注意

建议深入研究 EC2 实例类型的资源是 AWS Knowledge Center 文档“如何为我的工作负载选择合适的 EC2 实例类型?”

现在您已经更详细地了解如何使用 EC2,让我们讨论与 EC2 的网络连接。

网络

AWS 网络由全球基础设施组成,使 EC2 实例能够在全球范围内工作,并且是可靠且分布式的。请注意在图 2-8 中,每个地区在地理上都是独特的,并且有多个可用区。

doac 0208

图 2-8. AWS 地区和区域

全球有超过 25 个地区和超过 85 个可用区。这些数据意味着每个地区平均至少有 3 个可用区。幸运的是,可以使用 AWS CloudShell 和 PowerShell 轻松获取您地区确切的可用区数。作为具有查询 EC2 权限的用户,在 AWS CloudShell 中启动一个实例。接下来,导入AWSPowerShell.NetCore,然后查询 AWS CloudShell 所在地区的可用区:

Import-Module AWSPowerShell.NetCore
Get-EC2AvailabilityZone
注意

在地区级别下,AZ 通过高带宽、低延迟的加密网络互联。网络性能足以在 AZ 之间实现同步复制。这些 AZ 在物理上分开,以实现真正的灾难恢复解决方案。

您可以通过将输出分配给 PowerShell 变量$zones,然后使用$zones.count来计算状态“available”中您地区的可用区确切数量:

$zones = Get-EC2AvailabilityZone -Filter @{ Name="state";Values="available" }
$zones.count

输出显示,只需几行代码,就可以使用便捷的 AWS CloudShell 作为开发环境在 PowerShell 中轻松脚本化 EC2:

PS /home/cloudshell-user> Import-Module AWSPowerShell.NetCore
PS /home/cloudshell-user> $zones = Get-EC2AvailabilityZone -Filter `
  @{ Name="state";Values="available" }
PS /home/cloudshell-user> $zones.count
6
注意

您可以查看PowerShell 文档来查看所有可用的标志。还可以在YouTubeO’Reilly上看到这个过程的演示。

现在您对 EC2 的网络更详细了解,让我们谈谈 EC2 的定价选项。

使用 EC2 定价选项

虽然购买选项可能不是许多开发者的首要考虑,但这是一个重要的考虑因素。AWS Well-Architected Framework 的五大支柱之一是成本优化。具体而言,该框架建议您“采用消费模型”,即仅支付您所需的计算资源。让我们详细了解一下 EC2 的不同定价选项:

按需实例

这些实例非常适合尖峰工作负载或原型设计。您可以按秒计算的计算,没有长期承诺。

预留实例

这些实例非常适合承诺的稳态工作负载。例如,一旦公司对其云架构的配置和负载有了清晰的了解,就会知道需要预留多少实例。有一年或三年的承诺选项,可以从按需实例中获得显著的折扣。

储蓄计划

此计划适用于 Amazon EC2 和 AWS Fargate,以及 AWS Lambda 工作负载,因为它允许您预留每小时的支出承诺。与预留实例相同的折扣,但为灵活性增加了每小时的专用支出承诺。

Spot 实例

这些适合容错工作负载,可以灵活和无状态。一个很好的例子是可以在白天运行的批处理作业,比如视频转码。这些实例可以享受高达按需定价 90%的折扣。

专用主机

对于需要单租户或软件许可证的工作负载,另一个选择是使用专用物理服务器。

了解 AWS 的定价选项对于云迁移的有效性非常重要。有了这些信息,让我们继续讨论 AWS 的安全最佳实践。

AWS 的安全最佳实践

对于软件工程来说,安全不是可选的,而是任何项目的核心要求。对于云计算的新手来说,可能令人惊讶的是,迁移到云端增加了项目的安全性。解释这一概念的一种方式是通过 AWS 的独特概念“共享责任模型”,如“共享责任模型”所示,详见图 2-9。

doac 0209

图 2-9. AWS 共享责任模型

请注意,AWS 负责整个全球基础设施,包括物理安全要求。然后客户在这个安全基础之上构建,负责像客户数据或防火墙配置这样的项目。⁷

设计一个安全系统在 AWS 上的好方法是遵循AWS Well-Architected Framework website提供的指南。有七个设计原则。我们来看看:

实施坚实的身份验证基础

使用最小权限原则(PLP)为用户分配足够的资源访问权限以完成其分配的任务。使用身份和访问管理(IAM)系统来控制用户、组和访问策略,同时消除静态凭证。

启用可追溯性

启用实时监控、警报和审核生产环境变更至关重要。这种能力通过全面的日志和度量收集系统来实现,从而实现可操作性。

在所有层面应用安全措施

最佳防御是采用分层方法,云计算的每个组件,从计算到存储再到网络,都增强了安全性。

自动化安全最佳实践

将安全最佳实践自动化为代码,可以高效且幂等地减少组织风险。

保护数据在传输和静止时

数据需要在其位置和移动时进行保护。这一最佳实践通过综合加密、令牌化和访问控制来实现。

远离数据的人员

手动数据处理是要避免的反模式,因为它将人为因素引入环节,可能导致有意或无意的安全漏洞。

准备安全事件

在亚马逊公司,一个一贯的主题是“为失败设计”。同样,对于安全事件的处理和与组织需求对齐的流程,拥有计划至关重要。

AWS 安全的一个重要要点是自动化在实施安全性方面的关键性。DevOps 是云原生自动化的核心,重点在第六章。随着基础安全最佳实践的推进,让我们讨论 AWS 最佳实践加密。

静止和传输中的加密

加密是一种转换数据的方法,使其在没有访问秘密加密密钥的情况下无法阅读。⁸ 在传输和静止的加密策略的中心是数据在任何时候都不以未加密形式暴露。

注意

“传输”指的是将数据从一个位置发送到另一个位置,比如从用户的手机到银行网站。静止 指的是数据的存储;例如,存储在数据库中的数据处于静止状态。

一个很好的类比是考虑通过真空包装密封易腐食品的概念。真空包装去除氧气,从而延长产品的保质期并保持其新鲜。一旦密封破裂,食品会立即变质。类似地,以未加密形式暴露的数据立即面临风险并违反了存储和管理数据的最佳实践。AWS 通过提供的服务解决了这个问题,允许您在平台上数据操作的整个生命周期中全面加密您的数据。

注意

要考虑的一个重要问题是,为了使加密工作,需要有一个加密密钥。只有特权用户拥有该密钥才能访问数据。

现在我们已经涵盖了加密,让我们继续深入探讨 AWS 上的安全概念,接下来将详细讲解最小特权原则。

PLP(最小特权原则)

邮递员没有您家的钥匙,这是最小特权原则(PLP)的安全最佳实践。无论是非 IT 人员还是 IT 人员,都应该遵循这一原则,即永远不要给资源更多的访问权限。您可以在图 2-10 中看到这一原则在实际中的应用。邮递员可以访问邮箱但不能进入房屋。同样地,家人可以进入房屋,但只有父母可以进入保险柜。这个概念意味着在 AWS 中,您只给用户完成任务所需的最少访问权限和责任。

doac 0210

图 2-10 最小特权原则示例

最小特权原则保护资源和特权的接收者。考虑一下家中的保险柜场景。保险柜中可能有对孩子有危险的物品,它保护了保险柜和孩子,使其无法访问。在 AWS 中,这一设计原则是有效的:仅分配最少特权的 IAM 策略来完成任务。

这种方法在像图 2-11 中展示的真实世界微服务架构中同样适用。请注意,AWS Lambda 微服务正在监听 AWS S3。当用户上传新的个人资料图片时,AWS Lambda 函数使用“S3 只读”策略,因为此服务只需接受来自 S3 的事件有效负载,其中包括上传的图像名称和 S3 URI,其中包含图像的完整路径。然后,AWS Lambda 微服务使用具有访问特定表并更新该表的权限的角色将元数据写入 DynamoDB 表。

doac 0211

图 2-11 无服务器个人资料图片更新微服务

这种 PLP 工作流程既防止了安全漏洞,也避免了开发人员的错误。严格限制微服务仅能执行其任务所需的操作范围,可以降低组织风险。这种风险在没有 PLP 的系统中可能并不明显,但这并不意味着风险不存在。

在深入了解如何实施安全性之后,让我们讨论 AWS 身份和访问管理在任何安全设计系统中的中心组件。

使用 AWS 身份和访问管理(IAM)

安全地控制访问 AWS 服务的核心是IAM。通过 AWS IAM,您可以指定谁或什么可以访问 AWS 中的服务和资源,集中管理细粒度权限,并分析访问以优化 AWS 中的权限,正如您在图 2-12 中所见。

doac 0212

图 2-12. IAM 高级别分解

要更详细地了解 IAM 的工作原理,图 2-13 展示了主体是指可以通过认证请求 AWS 资源的人或应用程序。接下来,用户(账户 ID 0123*)必须获得授权才能创建请求,其中策略确定是否允许或拒绝请求。这些规则包括基于身份的策略、其他策略和基于资源的策略来控制授权。

在请求获得批准后,可用的操作来自服务本身,例如,CreateDatabaseCreateUser或服务支持的任何操作。最终,在服务请求的操作获得批准后,它们将在资源上执行。资源的示例包括 EC2 实例或 Amazon S3 存储桶。

您可以通过阅读理解 IAM 工作方式用户指南来详细了解此过程。IAM 服务具有五个关键组件:IAM 用户、IAM 组、IAM 角色以及 IAM 权限和策略(为简洁起见,这些组件被捆绑在一起)。让我们讨论每一个:

IAM 用户

AWS 中的一个用户是您在 AWS 中创建的实体,用于代表人或应用程序。一个用户在 AWS 中可以通过访问密钥进行程序化访问,通过密码访问 AWS 管理控制台,或两者兼有。访问密钥使得可以访问 SDK、CLI 命令和 API。

IAM 组

IAM 用户组是 IAM 用户的集合。使用组允许您为多个用户指定权限。

IAM 角色

IAM 角色是具有特定权限的 IAM 身份。一个典型的场景是,像 EC2 这样的服务被分配了一个具有特殊权限的角色,以调用 AWS API,比如从 S3 下载数据,而不需要在 EC2 实例上保留 API 密钥。

IAM 权限和策略

通过创建或使用默认策略并将其附加到 IAM 身份(如用户、组和角色),您可以管理访问到 AWS。这些策略中的权限确定了允许或拒绝的操作。

doac 0213

图 2-13. IAM 的工作原理

通过深入理解 AWS 上的身份和访问管理,让我们转向在 AWS 上使用 DynamoDB 构建 NoSQL 解决方案。

使用 DynamoDB 开发 NoSQL 解决方案

亚马逊的 CTO Werner Vogel 指出,“一种数据库不适合所有人”。这个问题在图 2-14 中有所体现,显示出每种类型的数据库都有特定的用途。选择正确的存储解决方案,包括使用哪些数据库,对于.NET 架构师来说是至关重要的,以确保系统能够以最佳性能运行。在AWS 产品页面上可以找到一个比较 AWS 云数据库的优秀资源。

doac 0214

图 2-14. 每种类型的数据库都有其特定用途

在设计一个完全自动化和高效的系统时,需要考虑维护问题。例如,如果某种技术选择被滥用,比如将关系型数据库用于高可用性消息队列,维护成本可能会激增,从而增加更多的自动化工作。因此,需要考虑的另一个组成部分是维护解决方案所需的自动化工作量。

DynamoDB的核心概念是一个分布式数据库,具有最终一致性。⁹ 在实践中,该数据库可以自动从零扩展到数百万次请求每秒,同时保持低延迟。

让我们通过特征、用例和主要特点来详细解析关键的 DynamoDB 概念:

特征

DynamoDB 是一个完全托管的非关系型键值和文档数据库,可以在任何规模下运行。它也是无服务器的,非常适合事件驱动的编程。DynamoDB 的企业特性包括加密和备份。

用例

该服务非常适合需要快速扩展且不需要复杂连接的简单高容量数据,也非常适合需要高吞吐量和低延迟的解决方案。

主要特点

一些关键特点包括 NoSQL 表和具有不同属性的项目。DynamoDB 还支持缓存和高达 20M+请求每秒的峰值。

我们已经介绍了 DynamoDB 的基础知识;接下来,让我们使用 DynamoDB 构建一个简单的应用程序。

构建一个示例 C# DynamoDB 控制台应用程序

让我们在 C#中构建一个简单的 DynamoDB 应用程序,从表中读取数据。有许多手动创建表的方法,包括图 2-15,首先导航到 DynamoDB 界面,然后选择“创建表”。

doac 0215

图 2-15. 在 Amazon 控制台中创建 DynamoDB 表

另一种创建表格并填充值的方法是使用 Visual Studio AWS Explorer,如 图 2-16 所示。

doac 0216

图 2-16. 在 Visual Studio Explorer 中创建 DynamoDB 表格

再次,我们用水果填充表格。如果要查询,我们可以通过 .NET SDK 或控制台进行,如 图 2-17 所示。

doac 0217

图 2-17. 查询表格

要创建一个查询 DynamoDB 的应用程序,请打开 Visual Studio 并创建一个新的控制台应用程序,然后按照 DynamoDB NuGet 包 的安装步骤安装,如 图 2-18 所示。

doac 0218

图 2-18. 安装 DynamoDB

安装完成后,查询表格、打印整个表格并随机选择一个水果的过程在以下 C# 代码示例中已解决:

using System;
using System.Collections.Generic;

// To interact with AWS DynamoDB using Amazon.DynamoDBv2;
using Amazon.DynamoDBv2.DocumentModel;

// Create client ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)
var client = new AmazonDynamoDBClient();
var fruitTable = Table.LoadTable(client, "fruit");

// Display Prompt Console.WriteLine("Table Scan " + Environment.NewLine);

// Scan ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)
ScanFilter scanFilter = new ScanFilter();
Search search = fruitTable.Scan(scanFilter);

//All Fruit ![3](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/3.png)
var fruitList = new List<string> ();

//print ![4](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/4.png)
do
{
    var documentList = await search.GetNextSetAsync();

    foreach (Document document in documentList)
    {
        var fruit = document.First().Value;
        Console.WriteLine($"Fruit: {fruit}");
        fruitList.Add(fruit);   //Add scanned fruit to list
    }
} while (!search.IsDone);

//Now pick a random fruit var random = new Random();
int index = random.Next(fruitList.Count);
Console.WriteLine($"Random Fruit: {fruitList[index]}");

1

首先,创建一个客户端。

2

接下来,扫描表格。

3

然后,创建一个列表来保存结果。

4

最后,循环遍历表格结果,将它们放入列表中,并随机选择一个水果进行打印。

控制台应用程序的结果在 图 2-19 中显示了成功的表扫描和随机水果选择。

doac 0219

图 2-19. 表扫描控制台应用程序

查看 AWS 文档 GitHub 仓库 获取更多构建应用程序的想法。这是一个关于使用 DynamoDB 构建解决方案想法的优秀资源。

注意

CRUD 应用程序从数据库中创建、读取、更新和删除项目。这个 CRUD 示例 用于 DynamoDB 是构建这种类型应用程序时的良好参考资源。

现在您已经了解如何使用 DynamoDB 构建解决方案,让我们讨论一个互补服务:亚马逊关系数据库服务(RDS)。

亚马逊关系数据库服务

RDS 是一个允许您在云中点按设置企业数据库的服务。RDS 之前的时代对于那些曾经运行自己的 SQL 数据库的开发者来说是黑暗的日子。正确管理数据库所需的任务列表相当可怕。因此,许多职位称为 DBA 或数据库管理员,帮助保持 SQL 系统的运行,如 Microsoft SQL Server、MySQL 和 Postgres。现在,许多这些任务都成为了 RDS 的特性。

这些特性综合成一个统一的解决方案,为 .NET 项目带来了灵活性。RDS 的关键优势在于它允许开发人员专注于构建业务逻辑,而不是处理数据库。这一优势的出现是因为 RDS 解除了管理数据库的痛苦,使开发人员能够专注于应用程序的构建。让我们看一下几个核心特性的选择列表,以突出 RDS 的强大功能:

核心特性

RDS 易于使用。它还具有自动软件修补功能,可以降低安全风险。产品中内置了最佳实践建议,包括使用 SSD 存储,显著提高了服务的可扩展性。

可靠性

该服务包括创建自动备份和构建数据库快照的能力。最终,通过多可用区部署的最佳实践,为可用区故障提供了强大的恢复选项。

安全性和运营

RDS 包括重要的加密能力,包括在静止和传输中使用。进一步的网络隔离提高了操作安全性和精细化的资源级权限。此外,通过 CloudWatch 进行广泛的监控,提高了服务的成本效益。

了解了 RDS 的核心特性之后,接下来让我们讨论一个完全托管的无服务器解决方案。

与 Amazon Aurora 无服务器 v2 的完全托管数据库

由于无服务器云原生世界能够直接将思想转化为业务逻辑的实现,因此开发解决方案具有成瘾性。随着 Amazon Aurora 无服务器 v2 的增加,执行业务逻辑作为代码的能力进一步增强。

这里是 Amazon Aurora 无服务器 v2 的一部分好处的子集:

高度可扩展

实例可以在几秒钟内扩展到数十万个交易。

简单的

Aurora 消除了预留和管理数据库容量的复杂性。

可靠的

Aurora 存储通过六向复制实现自我修复。

这些关键用例包括以下内容:

可变工作负载

运行使用频率不高的应用程序,每天几次高峰达到 30 分钟,是这项服务的一个甜蜜点。

不可预测的工作负载

一个内容密集的站点,经常访问的情况下,可以依赖数据库自动扩展容量,然后缩减容量。

企业数据库集群管理

拥有数千个数据库的企业可以根据每个应用程序的需求自动扩展数据库容量,无需单独管理每个数据库。

软件即服务(SaaS)应用程序

运营数千个 Aurora 数据库的 SaaS 供应商可以为每个客户提供 Aurora 数据库集群,而无需预留容量。它在不使用时会自动关闭数据库以降低成本。

分布在多台服务器上的分布式数据库

高写入或读取需求常常需要分布到多个数据库中。Aurora Serverless v2 的容量可以立即满足,并且自动管理,简化了部署过程。

将所有内容整合起来,一个 SaaS 公司可以构建如图 2-20 所示的架构,其中每个客户都有一个专用的 AWS Step Functions 无服务器流水线,通过 AWS Lambda 负载进行编排,然后进入 Aurora Serverless v2。

doac 0220

图 2-20. 使用 Aurora Serverless 的 SaaS 公司架构

这种架构的好处在于很容易调试每个付费客户的流水线。此外,由于整个流水线使用自动缩放的无服务器组件,管理每个客户端的服务器负载复杂性也得到了缓解。

使用 Aurora Serverless 开发.NET Web API 的一个显著例子可以在这篇AWS 博客文章中找到。

本章涵盖了掌握 AWS 所必需的各种重要主题。让我们总结一下重点。

结论

AWS 核心服务是允许开发人员构建复杂解决方案的基础。在本章中,我们涵盖了计算和存储的核心服务。这些涵盖内容包括建议如何使用 EC2 和 S3 等服务的服务选项以及托管服务。

需要注意的是,无论是 EC2 还是 S3,都有大量的定价选项。在构建良好架构的 AWS 系统中,选择适当的定价选项至关重要。幸运的是,AWS 在AWS 成本管理控制台中提供了详细的定价监控和工具。

本章涵盖了传统和 NoSQL 数据库,包括使用 DynamoDB 构建简单控制台应用程序的示例。查看批判性思维问题和练习,挑战自己将这些材料应用到您的场景中。下一章将涵盖将传统.NET 应用程序迁移到 AWS。

批判性思维讨论问题

  • 网络文件系统如 Amazon EFS 和 Amazon FSx 提供的新软件架构工作流是什么?

  • 使用 AWS EC2 Spot 实例原型化解决方案的优势是什么?

  • 一到三名开发人员的小型初创公司在构建 SaaS API 时应倾向于使用哪些计算和数据库服务?

  • 一个大型数据中心转移到云上的公司应考虑哪些计算服务?

  • 您的组织如何将博客文章"一种数据库不适合所有人"的精神转化为如何为所面对的问题使用正确类型数据库的计划?

练习

  • 为 AWS S3 构建一个 CRUD(创建、读取、更新、删除)C#控制台应用程序。

  • 为 AWS DynamoDB 构建一个 CRUD(创建、读取、更新、删除)C#控制台应用程序。

  • 为 AWS RDS for Aurora Serverless v2 构建一个 CRUD(创建、读取、更新、删除)C#控制台应用程序。

  • 更改 DynamoDB 示例以选择表中的水果而无需随机扫描表格。

  • 启动 Windows EC2 实例,在其上安装自定义工具或库,将其转换为 AMI,并使用您的自定义 AMI启动 EC2 实例。

¹ AWS S3 还定期达到“每秒数千万次请求的高峰”

² AWS Athena 是一种“交互式查询服务”,允许通过 SQL 轻松分析 S3 中的数据。你可以在入门指南中详细了解它。

³ 您可以通过官方文档详细了解托管服务 SageMaker。

⁴ AWS App Runner 允许您将容器打包成一个持续部署的微服务。您将在容器章节中看到此服务的示例。

Snow Family是一组“高度安全的便携设备,用于在边缘收集和处理数据,并在 AWS 中迁移数据。”

⁶ 根据 AWS,AWS Elastic Beanstalk“是一个易于使用的服务,用于部署和扩展 Web 应用程序和服务。”

⁷ 进一步的安全信息可以访问 AWS Well-Architected Framework 网站的安全支柱部分获取。

⁸ 您可以通过AWS 白皮书“AWS 上的逻辑分离”进一步了解此主题。

⁹ 在理论计算机科学中的一个基础定理是CAP 定理,指出在一致性、可用性和分区容错之间存在折衷。实际上,这意味着许多 NoSQL 数据库(包括 DynamoDB)都是“最终一致性”,意味着它们最终会将新写入的数据汇总到不同节点以创建数据的一致视图。

第三章:将传统.NET Framework 应用程序迁移到 AWS

在前几章中,我们看到了 AWS 为开发人员提供的一些令人兴奋的工具和服务。接下来,我们将探讨如何处理我们的一些传统.NET 应用程序,以及通过将它们移到云端所能实现的可能性。

软件开发并不仅仅是关于全新项目、干净的代码库、整洁的待办事项和最新的工具集。各种规模的组织可能都有遗留代码,其中一些可能仍在本地运行,包括内部工具、API、工作流和应用程序,虽然仍在使用,但没有得到积极的维护。将这些内容迁移到云端可以为您的组织带来成本节约、性能提升,以及极大的扩展能力改进。

在本章中,您将学习如何选择、计划和执行迁移,这是一款在 IIS 上运行的 Web 应用程序,构建于.NET Framework 或.NET Core/6+之上。

注意

随着 2020 年 11 月发布的.NET 5,微软将.NET Core 简称为“.NET”。在接下来的章节中,我们将把.NET Core 和所有未来版本称为.NET,而将旧版框架称为.NET Framework。

选择迁移路径

每个.NET 应用程序在迁移到云端的道路上都会有所不同,虽然我们无法为迁移传统.NET 应用程序创建一种适合所有情况的框架,但我们可以从先前迁移的经验中汲取教训。2011 年,技术研究公司 Gartner 确定了五种迁移策略,用于将本地软件迁移到云端。这些策略被称为“五个 R”,多年来经过不断改进、调整和扩展,已经涵盖了迁移和现代化传统 Web 应用程序可能面临的所有挑战。

现在,我们有了 6 个 R,可应用于在 IIS 上运行的传统.NET Web 应用程序,无论是建立在.NET Framework 还是更新版本的.NET 上:

  • 重新托管

  • 重新平台化

  • 重新购买

  • 重新架构

  • 重建

  • 保留

这五种策略的复杂度和投入逐步增加;然而,通过增加的价值和未来迭代的能力来获得回报。我们将在本章的后面更深入地探讨其中的一些方法。

重新托管

重新托管是将应用程序从一个主机迁移到另一个主机的过程。这可能是将应用程序从公司机房中的服务器迁移到云中的虚拟机,或者是从一个云提供商迁移到另一个云提供商。作为迁移策略,重新托管不会改变(甚至需要访问)源代码。这是将资产以其最终构建或编译状态移动的过程。在 .NET 的世界中,这意味着 .dll 文件、.config 文件、.cshtml 视图、静态资产以及为提供应用程序所需的任何其他内容。因此,重新托管有时被称为迁移的“举起和移动”方法。整个应用程序被一次性“举起”并“移动”到新主机上。

将应用程序重新托管的优势包括能够利用云托管虚拟机上可能实现的成本节约和性能提升。如果您能够在 AWS 上重新托管您的较少维护或遗留应用程序,这也可以使得管理基础设施变得更加容易,同时与更活跃地开发的代码并行。

如果您选择遵循这条迁移路径,可以查看一些可用的工具和资源概述,参见 “在 AWS 上重新托管”。

重新平台化

重新平台化方法比简单的重新托管更进一步,不仅改变了应用程序托管的位置,还改变了其托管方式。与重新托管不同,重新平台化 可能 包括对代码的更改,尽管应将这些更改保持在最小限度以保持策略的可行性。

对于“平台”的定义有很多种,但我们作为 .NET 开发人员都知道的一个平台是运行在 Windows Server 上的 Internet Information Services (IIS)。重新平台化将是将应用程序从 IIS 迁移 到更云原生的托管环境,例如 Kubernetes。在本章后面,我们将更详细地探讨一种重新平台化类型:“通过容器化进行重新平台化”。

重新购买

当你的应用程序依赖于无法在云基础设施上运行的许可的第三方服务或应用程序时,这种策略就显得非常相关。也许你在应用程序中使用了自托管的产品来进行客户关系管理(CRM)或内容管理系统(CMS)功能,这些功能无法迁移到云上。重新购买是依赖这些产品的应用程序的一种迁移策略,涉及终止现有的自托管许可证,并购买云替代品的新许可证。这可以是类似产品的云版本(例如,Umbraco CMS 到 Umbraco Cloud),或者在 AWS Marketplace 上的替代产品。

重构

正如其名称所示,重新架构涉及到您应用程序的整体架构,并要求您考虑如何进行改变以便于其迁移到云端。¹ 对于传统的.NET Framework 应用程序来说,这几乎肯定意味着迁移到.NET。微软在 2019 年宣布,4.8 版本将是.NET Framework 的最后一个重大发布版本,虽然将继续在未来的 Windows 发布版本中得到支持和分发,但微软将不再积极开发它。²

历史为重新架构单体 Web 应用程序描绘出了一个相当线性的旅程,如图 3-1 所示。

doac 0301

图 3-1. 单体 Web 应用程序的演变

我们将深入探讨将.NET Framework 迁移到.NET 的章节“重新架构:迁移到.NET(Core)”。

重建

有时,您的旧代码库未能通过迁移的成本与价值基准,您别无选择,只能从头开始重建。当然,并非完全从零开始。您将能够迁移一些业务逻辑;您的旧代码库花费多年解决的所有问题可以在新的代码库上重新创建。然而,代码本身——架构、库、数据库、API 模式和文档³——将无法与您一同迁移。

保留

此列表中的最终迁移策略实际上只是上述策略之外。也许您的旧应用程序具有一些特殊要求,无法连接到互联网,必须经过冗长的再认证过程。许多独特且通常是意想不到的原因,导致一些旧代码库无法迁移到云端,或者在目前时刻无法迁移。您应仅迁移能够做出可行业务案例的应用程序;如果不可能,那么有时选择上述策略之外 是您最佳的选择。

选择策略

您选择的迁移策略将取决于您应用程序当前的架构,您希望达到的目标以及为了达到这些目标您愿意进行多少改变。图 3-2 中的图表总结了您可以做出的决策,以选择最适合您个人用例的迁移路径。

doac 0302

图 3-2. 选择策略

AWS 迁移中心策略建议

欲了解更多关于选择迁移策略的信息,AWS 提供了一个名为AWS 迁移中心策略建议 的工具。该服务从您现有的服务器收集数据,结合对源代码和 SQL 模式的分析,然后根据我们之前覆盖的内容推荐迁移策略。

提示

策略建议是 AWS 迁移中心的一部分:一套用于分析基础架构规划并随后跟踪迁移到 AWS 的工具。迁移中心不另外收费;您只需支付您使用的任何工具的成本和在此过程中消耗的任何 AWS 资源的费用。

要开始使用策略建议,我们首先需要尽可能提供关于现有基础设施的数据。我们可以通过AWS 应用发现服务,AWS 迁移中心提供的另一个服务来实现这一点。要开始应用程序发现,请导航至AWS 管理控制台中的发现工具选择您的主区域⁴,然后从图 3-3 中显示的三种收集数据的方法中选择一种。

doac 0303

图 3-3. 应用发现服务收集方法

让我们通过使用发现代理进行快速设置。在开始之前,请确保已安装 AWS CLI 并且您拥有 IAM 用户访问密钥(和密钥)。您可以使用以下命令将这些值保存在 AWS 配置文件中。这些设置将被本章中的所有工具使用。

$ aws configure set aws_access_key_id <your-key-id>
$ aws configure set aws_secret_access_key <your-key-secret>
$ aws configure set default.region <home-region>

接下来,在您要开始收集数据的 Windows 服务器上打开 PowerShell 终端,然后下载代理安装程序:

PS C:\> mkdir ADSAgent
PS C:\> cd .\ADSAgent\
PS C:\ADSAgent> Invoke-WebRequest
https://s3.us-west-2.amazonaws.com/aws-discovery-agent.us-west-2/windows/latest/
ADSAgentInstaller.exe -OutFile ADSAgentInstaller.exe

接下来设置您的 AWS 迁移中心主区域、访问密钥 ID 和密钥。

然后在此服务器上运行发现代理安装程序:

PS C:\ADSAgent> .\ADSAgentInstaller.exe REGION=$AWS_REGION KEY_ID=$KEY_ID
KEY_SECRET=$KEY_SECRET INSTALLLOCATION="C:\ADSAgent" /quiet

这将在您创建的新文件夹 C:\ADSAgent 中安装代理。在 AWS 管理控制台的迁移中心部分,您可以导航至发现 → 数据收集器 → 代理,如果一切顺利,您安装的代理应该出现在列表中。选择代理,然后单击“启动数据收集”以允许 ADS 开始收集有关您服务器的数据。

警告

如果您的代理未显示,请确保您的服务器允许代理进程通过 TCP 端口 443 发送数据至 https://arsenal-discovery..amazonaws.com:443

发现代理将每隔约 15 分钟轮询其主机服务器,并报告包括 CPU 使用率、空闲 RAM、操作系统属性和已发现的正在运行进程的进程 ID 在内的数据。您可以通过导航至仪表板上的发现 → 服务器来查看在迁移中心中看到的服务器。一旦您将所有服务器添加到 ADS 中,您就可以开始整理为策略建议所需的数据。

战略推荐服务具有一个无代理、自动化的数据收集器,您可以使用该收集器分析当前在 ADS 中运行的 .NET 应用程序的服务器。要开始,请导航到迁移中心控制台中的策略 → 开始,并按照向导的步骤下载数据收集器作为一个 Open Virtual Appliance(OVA)。然后可以部署到您的 VMware vCenter Server。有关设置数据收集器的完整说明,请参阅AWS 文档

设置好您的数据收集器后,您可以转到向导的下一页,并选择迁移的优先级。 图 3-4 显示了战略推荐中的优先级选择屏幕。这将使 AWS 能够推荐最符合您业务需求和未来计划的迁移策略。选择与您迁移原因最符合的选项。

doac 0304

图 3-4. 战略推荐服务目标

在分析了您的服务器数据后,该服务将为每个应用程序提供推荐,包括链接到任何相关的 AWS 工具,以帮助您进行该类型的迁移。在 图 3-5 中,您可以看到该工具建议我们使用重新托管作为迁移到 EC2 的迁移策略,使用应用迁移服务,接下来我们将详细介绍这一点。

doac 0305

图 3-5. AWS 建议重新托管策略

在 AWS 上重新托管

将您的传统 .NET 应用程序重新托管到 AWS 的方法将根据当前应用程序的托管方式而异。对于部署在运行 IIS 的单个服务器上的 Web 应用程序,您可以轻松地在 AWS 的 Amazon EC2 实例上复制此环境。如果您的 .NET 应用程序当前部署在托管环境中(其他云提供商可能称之为“应用服务”),那么 AWS 上的等价物是 Elastic Beanstalk,并且您应该会发现与 Elastic Beanstalk 一起工作的经验非常熟悉。我们将在后面的“Elastic Beanstalk”部分介绍如何将托管托管服务迁移到 Elastic Beanstalk,但首先让我们看看将运行 IIS 的虚拟机重新托管到 EC2 的情况。

应用迁移服务(MGN)

AWS 用于执行 EC2 提升和迁移的最新产品称为应用迁移服务或 MGN。⁵ 这项服务起源于 AWS 在 2018 年收购的 CloudEndure 产品。CloudEndure 是一种灾难恢复解决方案,通过在 AWS EC2 和弹性块存储(EBS)上创建和维护您的生产服务器的副本来工作。这种复制概念可以重新用于执行提升和迁移的重放。您只需设置到 AWS 的复制,然后在准备好时,切换到仅从 AWS 副本运行您的应用程序,从而可以停用原始服务器。应用迁移服务如何复制您的服务器的概述如图 3-6 所示。

doac 0306

图 3-6. 应用迁移服务概述

可通过 AWS 管理控制台访问应用迁移服务;在迁移中心的搜索中键入“MGN”或在菜单中找到,如图 3-7 所示。

doac 0307

图 3-7. 通过 AWS 管理控制台访问 MGN

应用迁移服务的设置从在服务器上安装复制代理开始,类似于我们为应用程序发现服务安装发现代理的方式。首先登录到您的 Windows 服务器,并以管理员身份打开 PowerShell 窗口以下载 Windows 的代理安装程序。将<region>替换为您希望将服务器迁移到的 AWS 区域:

PS C:\> mkdir MGNAgent
PS C:\> cd .\MGNAgent\
PS C:\MGNAgent> Invoke-WebRequest
https://aws-application-migration-service-<region>.s3.<region>.amazonaws.com
/latest/windows/AwsReplicationWindowsInstaller.exe
 -OutFile C:\MGNAgent\AwsReplicationWindowsInstaller.exe

如果尚未将区域、访问密钥 ID 和秘密放入变量中,请执行安装程序:

PS C:\ADSAgent> $AWS_REGION="<region>"
PS C:\MGNAgent> $KEY_ID="<your-key-id>"
PS C:\MGNAgent> $KEY_SECRET="<your-key-secret>"

PS C:\MGNAgent> .\AwsReplicationWindowsInstaller.exe --region $AWS_REGION
 --aws-access-key-id $KEY_ID --aws-secret-access-key $KEY_SECRET

安装程序将询问您希望在此服务器上复制哪些磁盘,然后开始将您的磁盘与 AWS 进行同步。您可以在控制台窗口中查看代理安装程序操作的状态,如图 3-8 所示。

doac 0308

图 3-8. MGN 复制代理控制台窗口

如果您返回 MGN 管理控制台,您将能够在菜单中的源服务器下看到您的服务器。单击服务器名称,您可以查看此服务器复制的状态,如图 3-9 所示。完成所有阶段可能需要一些时间,但完成后,控制台中的状态将变为“准备测试”。应用迁移服务的一个良好功能是能够从副本中启动服务器实例并测试一切是否符合预期,而不会中断或干扰复制本身。

doac 0309

图 3-9. 管理控制台中的 MGN 复制状态

要测试服务器,请从源服务器的“测试和切换”操作菜单中选择“启动测试实例”。这将启动一个新的 EC2 实例,应该与您复制的原始 Windows 服务器相似,许可证的转移将由应用程序迁移服务自动处理。一旦 EC2 实例准备就绪,您可以通过管理控制台中的 EC2 实例列表选择它,使用远程桌面(RDP)连接到该 EC2 实例。当您对测试实例的工作方式感到满意时,您可以执行迁移的最后阶段:切换。

如果您参考图示 Figure 3-6 中通过复制应用程序迁移的阶段,您可以看到最终阶段是执行切换。这是我们为所有源服务器创建资源的地方(即:为每台服务器启动一个新的 EC2 实例),停止我们旧服务器的复制,并允许我们卸载安装了复制代理的原始服务器。

现在我们的所有服务器都像在 EC2 上一样运行,并且我们已经执行了抬升和迁移的重托,接下来怎么办?在重新托管的领域内,我们可以进一步采用 AWS 的托管环境来运行 Web 应用程序:Elastic Beanstalk。

Elastic Beanstalk

Elastic Beanstalk 是一个托管的 Web 应用程序托管环境。它支持多种后端堆栈,如 Java、Ruby 或 PHP,但我们在这里最感兴趣的是 .NET Framework 的支持。在这里,我们将我们的应用程序托管在 EC2 上的 Windows 服务器与使用 Elastic Beanstalk 的区别可以归结为一个关键差异。

使用非托管服务器,您将已编译和打包的网站文件上传到服务器,然后调整该服务器的设置以处理网络流量。使用托管服务,您将包上传到云中,服务会为您处理剩余的事务,设置负载均衡器并动态扩展虚拟机的水平。托管服务是将应用程序部署到云的真正吸引力,您越多依赖 AWS 管理基础设施,您就能更多地专注于编写代码和解决业务问题。本书后面我们将介绍无服务器编程,以及如何使您的 .NET 应用程序尽可能地设计成无服务器,这个概念深植于托管服务之中。您可以将 Elastic Beanstalk 等同于“应用服务”,这可能会让您想起稍微更蓝色的云提供商。

让我们开始使用 Elastic Beanstalk 上的第一个托管服务。通过在 Visual Studio 中安装 AWS Toolkit,部署到 Elastic Beanstalk 真的就像右键单击解决方案,然后选择“发布到 Elastic Beanstalk”一样简单。图 3-10 在 Visual Studio 的解决方案资源管理器中展示了工具包的使用情况。

doac 0310

图 3-10. 直接从 Visual Studio 发布到 Elastic Beanstalk

当然,您也可以在 CI 流水线中使用 CLI 工具。要开始使用 Elastic Beanstalk CLI,请参阅 GitHub 上的 EB CLI Installer。该 CLI 工具允许您直接从 AWS CodeBuild 或 GitHub 将代码发布到 Elastic Beanstalk。

最后,如果您考虑尝试 Elastic Beanstalk,还有一个值得一提的工具,那就是 Windows Web App Migration Assistant (WWMA)。这是一个 PowerShell 脚本,您可以在任何运行 IIS 托管 Web 应用程序的 Windows Server 上运行,自动将应用程序迁移到 Elastic Beanstalk。如果您有一个不再维护的传统 .NET 应用程序,并且希望利用 Elastic Beanstalk 的优势,而不再为该应用发布新版本,这将非常有用。它是一个真正的 迁移 工具,简单地将编译的网站资产从服务器上的 C:\inetpub 文件夹移动到由 Elastic Beanstalk 管理和扩展的 EC2 实例中。

通过容器化进行重新平台化

作为迁移策略,重新平台化关注的是我们的 .NET 应用程序运行的 平台,并探索将其迁移到其他地方。对于 .NET Framework Web 应用程序来说,该 平台 将是运行 IIS 的某个版本的 Windows Server。虽然之前我们将 Elastic Beanstalk 视为提升基础设施效能的一种方式,但 Elastic Beanstalk 仍然在 Windows Server 上的 IIS 上托管您的应用程序,尽管方式更加可扩展和高效。如果我们希望真正推动可扩展性和性能的极限,同时又不接触原始源代码(毕竟这些是“传统”应用程序),那么我们就需要摆脱 IIS,转向其他解决方案。这就是容器化的用武之地。

我们将跳过容器化的确切定义和其重要性,因为本书稍后的 第五章 将专门讨论 .NET 的容器化。简而言之,将您的传统 .NET 应用程序从 Windows Server Web 托管迁移到容器中,为您的组织带来性能和成本效益,而无需修改任何传统代码。

App2Container 是 AWS 提供的一个命令行工具,用于分析运行在 Windows Server 上的 IIS 上的 .NET 应用程序及其依赖关系,然后创建可以部署到云中编排服务(如 Elastic Container Service (ECS) 或 Amazon Elastic Kubernetes Service (EKS))的 Docker 容器镜像。由于 App2Container 在已部署到服务器上的应用程序上运行,不需要访问源代码,并且位于部署流水线的最后阶段。因此,App2Container 非常适合快速重建一个旧的不再活跃开发的应用程序;只需跳过在 Figure 3-11 中显示的流水线的最后两个步骤,并对生产文件进行容器化。

doac 0311

Figure 3-11. 使用 App2Container 部署 .NET Framework 应用程序的部署流水线

要将 .NET 应用程序容器化,首先需要在运行应用程序的服务器上下载并安装 App2Container。你可以在 AWS 的网站 找到安装包。下载、解压并在管理员 PowerShell 终端上运行 .\install.ps1。这将安装 app2container 命令行实用程序。如果尚未安装,请确保你的应用程序服务器已安装 AWS Tools for Windows PowerShell,并且已配置了允许你从应用程序服务器访问和管理 AWS 资源的默认配置文件。如果你的服务器正在运行 EC2 实例(例如,如果你使用应用程序迁移服务将其重新托管,请参阅 “在 AWS 上重新托管”),那么这些工具已经安装了,因为它们包含在 EC2 使用的基于 Windows 的机器映像中。确认你已经拥有一个具有 IAM 权限来管理 AWS 资源的 AWS 配置文件后,可以通过运行以下命令初始化 App2Container:

PS C:\> app2container init

该工具将询问是否收集使用情况指标,并要求提供一个 S3 存储桶来上传构件,但这完全是可选的,你可以跳过这些选项。初始化完成后,你可以开始分析运行 .NET 应用程序的应用服务器,这些应用程序可以进行容器化。运行 app2container inventory 命令以获取以 JSON 格式列出的运行中应用程序的列表,然后将 JSON 键传递给你想要进行容器化的应用的 --application-id,如 Figure 3-12 所示:

PS C:\> app2container inventory
PS C:\> app2container analyze --application-id iis-example-d87652a0

doac 0312

Figure 3-12. 列出可容器化的 IIS 站点

我们鼓励大家查看为我们生成的 analysis.json 文件,我们对此表示赞同。可以在App2Container 用户指南中找到 analysis.json 中出现的所有字段的完整列表,但花时间探索分析输出是值得的,因为这些设置将用于配置我们的容器。在容器化之前,如果需要,您可以编辑 analysis.json 中的 containerParameters 部分。同样值得注意的是,打开与此处相同文件夹中的 report.txt,这是分析命令添加任何连接字符串的地方。准备好之后,运行 containerizegenerate 命令来构建一个 Docker 镜像,然后生成部署所需的所有工件到 ECS 或者 EKS:⁶

# Create a Dockerfile
PS> app2container containerize --application-id iis-example-d87652a0

# Generate a deployment
PS> app2container generate app-deployment --application-id iis-example-d87652a0

这里的第二个命令 (generate app-deployment) 将上传您的容器到 Elastic Container Registry (ECR),并创建一个 CloudFormation 模板,您可以用来部署您的应用程序到(在本例中)ECS。工具将向您显示此 CloudFormation 模板的输出目的地(参见图 3-13),并为您提供部署到 AWS 所需的命令。

doac 0313

图 3-13. App2Container 部署生成结果

这是来自 AWS 的 App2Container 的简要概述,但是这个工具可以实现的远不止我们在这里介绍的内容。与在应用程序服务器本身上执行容器化不同,App2Container 还允许您将工作机器部署到 EC2 或您的本地虚拟化环境中。如果您希望保护正在生产中提供 Web 应用程序的应用程序服务器免于执行容器化过程所消耗的资源,这将非常有用。由于 App2Container 是一个 CLI 工具,将其整合到您仍在积极开发和发布更改的代码的完整部署流水线中将非常简单。如果您回顾 图 3-11,您可以看到如何使用 App2Container 将现有的 .NET 部署流水线扩展到容器化,而不必触及任何更上游的内容,包括您的代码。

App2Container 的另一个注意事项是框架版本支持,随着工具的新版本发布而不断扩展。您可以在运行 .NET Framework 和 .NET 6+ 应用程序的 Windows 和最近的 Linux 上使用 App2Container。对于 .NET Framework,最低支持版本是在 IIS 7.5 上运行的 .NET 3.5。Java 应用程序也可以通过 App2Container 进行容器化,类似于我们在这里探讨的方式,因此不仅我们 C# 开发人员可以从中受益。

重构:迁移到 .NET(Core)

到目前为止,我们已经研究了不需要对代码进行更改的.NET 应用程序迁移方法,但是如果允许更改代码,我们可以做些什么呢?在下一章中,我们将研究现代化.NET 应用程序;然而,如果您的应用程序仍然建立在.NET Framework 上,那么几乎每条现代化路径的第一步都将是迁移到.NET 6+。对于.NET Framework 应用程序而言,前方的道路并不漫长,在本书中我们已经涵盖了重新托管和重新平台化的方法,最终您将不可避免地接触到迁移框架版本的话题。毕竟,.NET 是微软框架的完全重写,功能平等并不是目标。API 已经发生了变化,像System.Web.Services这样的命名空间不再存在,而一些您依赖的第三方库可能还未迁移,迫使您替换为替代方案。因此,非常有必要尽可能多地进行调查,以评估将您的传统.NET Framework 应用程序迁移到现代.NET 所需的工作量。

虽然没有自动重构整个解决方案并将.NET Framework 单体应用程序转换为.NET 6+的工具,但确实存在一些极其有用的工具,可以分析您的项目,执行小的重构任务,并让您了解在哪里可能会遇到兼容性问题。我将简要介绍其中两个工具:来自微软的.NET 升级助手和来自 AWS 的移植助手。

但在开始之前,值得了解哪些.NET Framework 技术在.NET 6+上不可用。这些技术几乎涵盖了所有使用组件对象模型(COM、COM+、DCOM)的内容,例如.NET Remoting 和 Windows Workflow Foundation。对于严重依赖这些仅限于 Windows 的框架的应用程序,本章早期讨论的迁移策略可能更为适合。使用 Windows Communication Foundation(WCF)的应用程序可以利用CoreWCF 项目继续在现代.NET 上使用 WCF 功能。

Microsoft .NET 升级助手

随着.NET 5 和 6 的发布,微软巩固了其未来统一单一框架的愿景,其中.NET 6 是该平台的长期支持(LTS)版本。为了帮助将.NET Framework 应用程序迁移到这个新的统一框架版本,微软一直在开发一个名为.NET Upgrade Assistant的命令行工具。升级助手旨在成为指导您完成迁移旅程的单一入口点,并在其中包含了更为久远的.NET Framework 转换工具 try-convert。建议您在升级助手的上下文中使用 try-convert,因为这样您将获得更多分析和指导,以便采用最适合您项目的策略。

在撰写本文时,此工具可用于以下类型的.NET Framework 应用程序:

  • .NET 类库

  • 控制台应用程序

  • Windows 窗体

  • Windows Presentation Foundation (WPF)

  • ASP.NET MVC Web 应用程序

.NET Upgrade Assistant 具有可扩展的架构,鼓励社区贡献扩展和分析器/代码修复程序。您甚至可以编写自己的分析器,根据您定义的规则执行自动代码重构。升级助手附带一组默认分析器,用于查找代码中的常见不兼容性并提供解决方案。例如,HttpContextCurrentAnalyzer查找对静态System.Web.HttpContext.Current的调用,这是.NET Framework 应用程序控制器动作中经常使用的模式,需要进行重构,因为.NET Core 中已删除HttpContext.Current。在图 3-14 中,您可以看到当在您的代码中发现HttpContext.Current时,此分析器发出的消息示例。

那么让我们开始升级吧。在本示例中,我们使用.NET Framework 版本 4.7.2 在 Visual Studio 2019 中创建了一个非常简单的 ASP.NET MVC Web 应用程序。有一个控制器动作从 Web 请求中获取查询字符串,将其添加到ViewBag中,然后在Index.cshtml Razor 视图中显示它。这个网站的输出可以在图 3-14 中看到。

doac 0314

图 3-14. 在浏览器中运行的示例 ASP.NET MVC 网站

我们故意向这个示例中添加了一些我们知道与.NET Core 和框架的后续版本不兼容的内容。首先,正如前面介绍的,我们在HomeController.cs的第 9 行中调用了HttpContext.Current,这将需要替换为.NET 6 中等效的 HTTP 上下文属性。我们还在Index.cshtml中有一个 Razor 助手,后续版本的.NET 中不再支持@helper语法。我们将此代码检入了一个具有干净工作树的 Git 存储库;这将有助于通过使用 Git diff 工具查看.NET Upgrade Assistant 将进行的代码更改。

示例 3-1. HomeController.cs
using System.Web.Mvc;

namespace NetFrameworkMvcWebsite.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            ViewBag.QueryString =
                System.Web.HttpContext.Current.Request.QueryString;

            return View();
        }

    }
}
示例 3-2. Index.cshtml
@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
    <p class="lead">This is a simple MVC application using .NET Framework</p>

    <h2>The query string is <strong>@ViewBag.QueryString</strong></h2>
</div>

<hr />

@WriteIndexFooter("Footer written using a Razor helper")

@helper WriteIndexFooter(string content)
{
    <footer>
        <p>@content</p>
    </footer>
}

要开始使用.NET Upgrade 助手,请首先将其安装为.NET CLI 工具:

dotnet tool install -g upgrade-assistant

接下来,在包含解决方案文件的目录中,运行升级助手并按照指示选择要用作入口点的项目:

upgrade-assistant upgrade NetFrameworkMvcWebsite.sln

根据你的.NET Framework 项目类型(如 WebForms、WPF 等),升级助手会提供不同的步骤集。我们有一个 ASP.NET MVC Web 应用程序,因此我们提供了一个包含十个步骤的升级过程,如下所示:

Upgrade Steps

Entrypoint: D:\Code\CSharpBookExamples\NetFrameworkMvcWebsite\
NetFrameworkMvcWebsite.csproj
Current Project: D:\Code\CSharpBookExamples\NetFrameworkMvcWebsite\
NetFrameworkMvcWebsite.csproj

1\. [Complete] Back up project
2\. [Complete] Convert project file to SDK style
3\. [Complete] Clean up NuGet package references
4\. [Complete] Update TFM
5\. [Complete] Update NuGet Packages
6\. [Complete] Add template files
7\. [Complete] Upgrade app config files
    a. [Complete] Convert Application Settings
    b. [Complete] Convert Connection Strings
    c. [Complete] Disable unsupported configuration sections
    d. [Complete] Convert system.web.webPages.razor/pages/namespaces
8\. [Complete] Update Razor files
    a. [Complete] Apply code fixes to Razor documents
    b. [Complete] Replace @helper syntax in Razor files
9\. [Complete] Update source code
    a. [Complete] Apply fix for UA0001: ASP.NET Core projects should not
    reference ASP.NET namespaces
    b. [Complete] Apply fix for UA0002: Types should be upgraded
    c. [Complete] Apply fix for UA0005: Do not use HttpContext.Current
    d. [Complete] Apply fix for UA0006: HttpContext.DebuggerEnabled should be
    replaced with System.Diagnostics.Debugger.IsAttached
    e. [Complete] Apply fix for UA0007: HtmlHelper should be replaced with
    IHtmlHelper
    f. [Complete] Apply fix for UA0008: UrlHelper should be replaced with
    IUrlHelper
    g. [Complete] Apply fix for UA0010: Attributes should be upgraded
    h. [Complete] Apply fix for UA0012: 'UnsafeDeserialize()' does not exist
10\. [Next step] Move to next project

Choose a command:
   1\. Apply next step (Move to next project)
   2\. Skip next step (Move to next project)
   3\. See more step details
   4\. Configure logging
   5\. Exit
>

大多数步骤都很容易理解;例如,我的第 2 步是“将项目转换为 SDK 样式”。这意味着重新格式化.csproj文件以符合以<Project Sdk="Microsoft.NET.Sdk">开头的较新.NET 格式。在进行这些步骤时,使用源代码控制的差异工具(例如 KDiff、VS Code 或 Visual Studio 中的 Git Changes 窗口)查看对代码和项目文件的更改。

升级助手的日志以紧凑日志事件格式(CLEF)存储在你运行工具的目录内。它还将备份你的项目;但是,如果你将所有内容都提交到源代码控制(你确实将所有内容都提交到源代码控制了吧?),这并不特别有用。

你可以从上述列表中看到,第 9 步 c 是“不要使用 HttpContext.Current”。这来自我们之前介绍的 HttpContextCurrentAnalyzer,这个分析器的修复将会将你代码中所有的 HttpContext.Current 用 HttpContextHelper.Current 替换。然而,我们仍然会收到关于 HttpContextHelper.Current 已过时且建议使用依赖注入的警告;然而,这并不会阻止我升级后的代码编译。升级助手还重新设计了我的 Razor 视图中的@helper 语法(第 8 步 b),并用.NET 6 兼容的帮助方法替换了它。运行升级助手后的代码如下(示例 3-3 和 3-4):

示例 3-3. HomeController.cs
namespace NetFrameworkMvcWebsite.Controllers
{
    public class HomeController : Microsoft.AspNetCore.Mvc.Controller
    {
        public Microsoft.AspNetCore.Mvc.ActionResult Index()
        {
            ViewBag.QueryString = HttpContextHelper.Current.Request.QueryString;

            return View();
        }

    }
}
示例 3-4. Index.cshtml
@using Microsoft.AspNetCore.Mvc.Razor
@{
    ViewBag.Title = "Home Page";
}

<div class="jumbotron">
    <p class="lead">This is a simple MVC application using .NET Framework</p>

    <h2>The query string is <strong>@ViewBag.QueryString</strong></h2>
</div>

<hr />

@WriteIndexFooter("Footer written using a Razor helper")

@{ HelperResult WriteIndexFooter(string content)
	{
	    <footer>
	        <p>@content</p>
	    </footer>
		return new HelperResult(w => Task.CompletedTask);
    }
}

AWS 迁移助手

另一个帮助你从.NET Framework 迁移的工具是由 AWS 自己提供的迁移助手。这是一个 Windows 应用程序,你需要将其下载到你的.NET Framework 解决方案所在的计算机上。虽然迁移助手在本地运行你的代码,但它需要连接 AWS 以从 S3 存储桶中检索 NuGet 包升级信息;因此,你需要像图 3-15 中所示设置本地 AWS 配置文件。不会在你的 AWS 配置文件上创建任何资源。你可以从AWS 迁移助手页面下载迁移助手。

doac 0315

图 3-15. 从 AWS 运行.NET 迁移助手

如果我们逐步操作向导,并使用与微软升级助手相同的 ASP.NET MVC Web 应用程序,我们可以看到它已正确识别解决方案正在目标为.NET Framework 4.7.5,并且我们有 7 个不兼容的 NuGet 包和 15 个不兼容的 API。这是图 3-16 中突出显示的 17 个值中的15。您可以在迁移助手的相关选项卡中查看有关这些内容的更多信息,包括已识别为不兼容的代码的链接。

doac 0316

图 3-16. AWS 迁移助手分析结果

当你准备好后,点击“迁移解决方案”开始对项目文件进行更改。应用程序会询问您希望将迁移的解决方案保存到哪里;如果您的代码在源代码控制中,您可以选择“直接修改源代码”。与微软的工具不同,AWS 迁移工具确实允许您在界面中对要升级的 NuGet 包的版本进行微调。在本例中,我们只是保留了所有包的默认设置,并通过助手逐步操作。现在您可以从图 3-17 看到,项目文件已经升级,项目现在在迁移助手中显示为.NET 6。

doac 0317

图 3-17. AWS 迁移助手完成

如果您点击链接以在 Visual Studio 中打开,您将看到解决方案加载,并且您的 NuGet 包已升级为与.NET Core/6+兼容的版本。然而,当迁移助手的帮助到此为止时,仍会通过重构所有不受支持的代码而获得错误。当我们尝试构建时,我们仍会收到System.Web.HttpContext不存在和“不支持辅助指令”等错误,因此值得尝试所有可用工具并比较结果。总体而言,AWS 的.NET 迁移助手提供了一个非常快速和易于访问的界面,用于可视化和评估重构您的.NET Framework 代码以与现代.NET 兼容所需的工作量。

结论

本章涵盖的策略和工具将帮助您将运行在 IIS 上的现有应用程序迁移到 AWS。从在不触及原始源代码的基础上进行的“在 AWS 上进行再托管”,到“重新架构:转移到.NET(Core)”并开始现代化您的代码库。无论您选择哪种策略,您都将从在 AWS 云中运行.NET 中至少一些优势中受益;然而,考虑到您的应用程序的下一步是值得的。我们在这里介绍的一些方法将使您的代码与迁移前基本相同。如果在迁移之前未积极开发代码库,则选择这些路径之一后,您的处境将不会好转。对于您打算继续开发、迭代并添加功能的代码库,您应当现代化您的遗留应用程序。现代化不仅涉及移至.NET 6+(尽管这将是先决条件),还涉及替换第三方依赖项、替换外部服务和重构应用程序的架构,以使用更好地利用您当前运行的云环境的模式。所有这些内容将在下一章中进行介绍,我们将讨论术语无服务器以及它如何适用于您作为.NET 开发人员。

批判性思维讨论问题

  • 对于小型初创企业来说,六个“Rs”中哪一个是最简单的?

  • 在迁移策略中,对于大型企业来说,六个“Rs”中哪一个是最简单的?

  • 以编程方式将成千上万的 Windows 服务转换为容器的 App2Container 的策略是什么?

  • 对于.NET 的 Porting Assistant 来说,什么是强大的企业用例?

  • 弹性 Beanstalk 应用程序在从本地到 AWS 云的企业设置中的迁移中有什么优势?

练习

  • 在 Windows Server 上构建一个 hello world Blazor 应用程序,然后使用 App2Container 将其转换为 Docker 容器映像。

  • 在 Amazon EC2 实例上部署策略建议收集器。

  • 运行AWS 迁移中心编排器

  • 在 Windows Server 上构建一个 hello world Blazor 应用程序,然后使用 App2Container 将其转换为 Docker 容器映像,并将其部署到 AWS App Runner。

  • 在 Windows Server 上构建一个 hello world Blazor 应用程序,然后使用 App2Container 将其转换为 Docker 容器映像,并使用AWS Copilot将其部署到 Fargate。

¹ 这种迁移策略有时被称为“重构”;然而,这个术语可能是个变色龙,所以我会在本书中坚持使用“重新架构”。

² 您可以在这里找到.NET Framework 支持政策。

³ 在将“文档”添加到这个列表之前,我承认从屏幕上抬起头来,喝了一大口咖啡。我的猫给了我一样知道的目光。

⁴ AWS 迁移中心中的“主区域”是存储用于发现、规划和迁移跟踪的迁移数据的区域。您可以从迁移中心设置页面设置主区域。

⁵ 有趣的是,应用迁移服务的三字缩写 MGN 是一个缩略语而不是首字母缩写。也许 AMSAWS 太相似了。

⁶ 我们将在本书后面重新讨论这两种容器编排服务,但简而言之,ECS 是一个更简单和更“托管”的服务,用于运行容器,而无需处理 Kubernetes 的附加复杂性。

第四章:现代化 .NET 应用程序到无服务器

无服务器 这个术语在软件工程界可能会引起混淆。如何在没有服务器的情况下运行 web 应用程序?这真的只是一种营销用语吗?从某种意义上说,是的。无服务器计算是指将后端系统架构为使用托管服务而不是手动配置服务器的广义术语。无服务器应用程序在后端系统上确实“无服务器”,就像您的智能手机的无线充电器是“无线”的一样。无线充电器仍然有线;它从充电器出来,插入到主电源插座,将能量从您的墙上传输到您的桌子上。 无线 部分只是充电器与您的手机之间的接口。无服务器计算中的 “无服务器” 一词的使用方式相同。是的,有服务器,但它们都由 AWS 在幕后进行管理。服务器就像是将您的无线充电器插入到墙上的主电源电缆。另一侧,即我们作为开发者可见的部分,在部署和运行代码时,不涉及任何服务器的配置或管理,因此是 无服务器 的。

您很可能已经在某些任务中使用了无服务器计算。考虑三种流行的服务器类型:

  • Web 服务器

  • 电子邮件服务器

  • 文件服务器

其中后两者,我们一直在用托管的无服务器解决方案替换。如果你曾通过 API 调用向 Mailchimp、Mailgun、SendGrid、SparkPost 或亚马逊的简单电子邮件服务 (SES) 发送电子邮件,那么你已经使用过无服务器的电子邮件解决方案。对于许多组织来说,运行 SMTP 服务器的时代,无论是内部还是云端,已经彻底过去了。文件服务器也正在逐渐过时,被无服务器文件存储解决方案取代。许多现代 web 应用程序完全依赖云原生服务,例如亚马逊简单存储服务 (S3) 作为其主要文件存储机制。这个列表中的最后一块石头就是 web 服务器。在本章中,我们将向您展示如何将您的 .NET web 服务器替换为无服务器、云原生实现,并说明这对您编写代码的方式意味着什么。

一个无服务器 Web 服务器

Web 服务器是一台计算机,可以是物理的也可以是虚拟的,永久连接到互联网,随时准备响应 HTTP(S) 请求。在 web 服务器内部,一个 HTTP 请求的生命周期涉及许多步骤,并执行由许多不同方编写的代码来读取、转换和执行每个请求。在这段旅程的中途,有你这位应用开发者编写的代码。

为了复制 Web 服务器的功能,我们需要一种在托管和按需基础上运行该自定义逻辑的方法。AWS 为我们提供了几种方法来实现这一点;我们将看看“App Runner”,这是一种允许我们部署完全托管的容器化应用程序而无需担心服务器的服务。AWS Fargate 是另一种基于按使用付费定价模型运行 Web 服务器的无服务器解决方案。这些服务将您的 Web 服务以无服务器方式部署到云中。但是,我们可以更进一步,将服务本身拆分为独立部署到云中的个体函数。为此,我们将需要函数即服务(FaaS)。

诸如 AWS Lambda 之类的 FaaS 解决方案是由事件触发的无状态计算容器。您上传要在事件发生时运行的代码,云提供商将为您处理资源的配置、执行,然后解除配置。这带来了两个主要优势:

  • 函数执行期间,FaaS 允许您的代码缩减至零。您只支付您使用的资源,而不是介于期间的未使用时间。这意味着您可以编写遵循真正的按使用付费定价模型的应用程序,仅支付每次函数执行的费用,而不是期间未使用的时间。

  • FaaS 迫使您考虑关注点分离,并对您编写应用程序的方式施加限制,这通常会导致更好的代码。

缺点源于实现 FaaS 解决方案所需的额外工作量。考虑一下 Rich Hickley 的这句话:

简单是一项艰苦的工作。但是,它会有巨大的回报。那些拥有真正简单系统的人——由真正简单部件组成的系统,将能够以最少的工作量产生最大的变化。他将会击败你。他将花更多时间在前期简化事务上,从长远来看,他将会把你的碗擦干净,因为他将有能力在你挣扎着推动大象时改变事物。

Rich Hickey,Clojure 编程语言的创作者

简单,就代码编写 FaaS 而言,意味着以纯函数为中心思想,并遵循单一职责原则。函数应接收一个值对象,执行一个动作,然后返回一个值对象。一个遵循这种模式的良好示例是字符串的Reverse()函数:

public static string Reverse(string str)
{
    char[] charArray = str.ToCharArray();
    Array.Reverse( charArray );
    return new string( charArray );
}

这个函数满足称为“纯函数”的两个要求:

  • 它对于给定的输入始终返回相同的输出。

  • 它没有副作用;它不会改变任何静态变量或可变引用参数,也不需要函数范围之外的状态持久性。

这些都是编写 FaaS 函数所需的特性。由于 FaaS 函数是响应事件运行的,你希望保持第一点的真实性,以便句子“当发生 X 事件时,执行 Y”对于每次事件 X 的发生都保持真实。你还需要保持你的函数无状态(第二点)。这既是在托管的 FaaS 上运行函数的限制,也是其特征。因为 AWS 可以并且会在执行函数期间取消分配资源,所以无法共享静态变量。如果你希望在 FaaS 函数中持久化数据,你必须有意地将其持久化到共享存储区域,例如保存到数据库或 Redis 缓存中。

将您的代码架构为没有副作用的无状态函数可能是一个需要纪律的挑战。它迫使您思考如何编写您的逻辑以及每个代码区域可以真正控制的范围。它还迫使您考虑关注点分离。

当多个 FaaS 函数使用事件连接在一起,并增加其他无服务器服务(如消息队列和 API 网关),就有可能构建一个能够执行传统 Web API 功能的后端应用程序。这使您可以编写完全托管在 AWS 上的无服务器后端应用程序。更妙的是,您可以在 C#中完成所有这些!AWS 提供了许多服务,可以用作这种无服务器应用程序的构建块。让我们看看其中一些组件。

选择 AWS 上的.NET 无服务器组件

我们将向您介绍 AWS 中一些最有用的无服务器服务,以及如何在您的代码中使用AWS SDK for .NET的各种包与这些服务进行交互。让我们逐步构建一个无服务器 Web 应用程序,随着每个引入的新概念而增加功能。

想象一下,我们是一家软件开发咨询公司,正在招聘周围最优秀的 C#开发人员!为了让他们申请,我们希望他们访问我们的网站,在那里他们将找到一个 HTML 表单,允许他们上传他们的简历作为 PDF 文件。提交此表单将把他们的 PDF 简历发送到我们的 API,将其保存到文件服务器,并发送电子邮件给我们的招聘团队,请他们审核。我们将其称为无服务器 C#简历上传器。图 4-1 显示了架构的高级概述。

doac 0401

图 4-1. 无服务器 C#简历上传器架构

我们后端的当前实现使用 Web API 控制器动作来接受 PDF 文件上传,将其保存到云存储,并向我们的招聘团队发送电子邮件。代码如下:

[ApiController]
[Route("[controller]")] public class ApplicationController : ControllerBase
{
    private readonly IStorageService _storageService;
    private readonly IEmailService _emailService;

    public ApplicationController(IStorageService storageService,
                                 IEmailService emailService)
    {
        _storageService = storageService;
        _emailService = emailService;
    }
 [HttpPost]
    public async Task<IActionResult> SaveUploadedResume()
    {
        Request.EnableBuffering();
        using var fileStream = new MemoryStream();
        using var reader = new StreamReader(fileStream);
        await Request.Body.CopyToAsync(fileStream); ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)

        var storedFileUrl = await _storageService.Upload(fileStream); ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)

        await _emailService.Send("recruitment@example.com",
            $"Somebody has uploaded a resume! Read it here: {storedFileUrl}"); ![3](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/3.png)

        return Ok();
    }
}

1

从请求中读取上传的文件。

2

将其保存到云存储中。

3

向我们的招聘团队发送一封电子邮件,其中包含指向该文件的链接。

IStorageService的代码如下:

public class AwsS3StorageService : IStorageService
{
    const string BucketName = "csharp-examples-bucket";

    public async Task<string> Upload(Stream stream)
    {
        var fileName = Guid.NewGuid().ToString() + ".pdf"; ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)

        using var s3Client = new AmazonS3Client(RegionEndpoint.EUWest2);

        await s3Client.PutObjectAsync(new PutObjectRequest()
        {
            InputStream = stream,
            BucketName = BucketName,
            Key = fileName,
        }); ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)

        var url = s3Client.GetPreSignedURL(new GetPreSignedUrlRequest()
        {
            BucketName = BucketName,
            Key = fileName,
            Expires = DateTime.UtcNow.AddMinutes(10)
        }); ![3](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/3.png)

        return url;
    }
}

1

创建一个唯一的 S3 键名。

2

将文件上传到 S3。

3

生成一个指向我们新文件的预签名 URL。¹

使用AWSSDK.S3 NuGet 包将文件保存到 AWS S3 存储桶中。在此示例中,存储桶名为csharp-examples-bucket²,文件将通过Guid.NewGuid()获得一个唯一键。

我们示例中的第二个服务是IEmailService,它使用 AWS SES 向我们的招聘团队发送电子邮件。此实现使用 AWS SDK 中的另一个 NuGet 包称为Amazon.SimpleEmail

public class AwsSesEmailService : IEmailService
{
    public async Task Send(string emailAddress, string body)
    {
        using var emailClient = new AmazonSimpleEmailServiceClient(
                                                RegionEndpoint.EUWest1);

        await emailClient.SendEmailAsync(new SendEmailRequest
        {
            Source = "from@example.com",
            Destination = new Destination
            {
                ToAddresses = new List<string> { emailAddress }
            },
            Message = new Message
            {
                Subject = new Content("Email Subject"),
                Body = new Body { Text = new Content(body) }
            }
        });
    }
}

我们将在本章剩余的示例中使用这两个实现。如前所述,通过将 PDF 文件保存在 S3 中并通过 SES 发送电子邮件,我们已经在文件存储和电子邮件服务中使用了无服务器解决方案。因此,让我们也摆脱这个 Web 服务器,并将我们的控制器动作移至托管云函数。

使用 AWS Lambda 和 C#进行开发

AWS 的 FaaS 产品称为 Lambda。AWS Lambda 由亚马逊的 CTO Werner Vogels 于 2014 年推出,以下总结我们认为非常恰当地概括了 AWS Lambda 背后的动机和价值:

这里的重点是事件。事件可能由触发这些事件的 Web 服务驱动。您会编写一些代码,比如 JavaScript,这些代码会在不需要为其提供硬件的情况下运行。

Werner Vogels

自 2014 年以来,AWS Lambda 在采纳率和功能上都有了巨大的增长。他们添加了越来越多的事件选项来触发您的函数,如 Lambda@Edge,在最接近用户的 CDN 服务器上运行您的函数(即“边缘计算”),自定义运行时,用于库的共享内存层称为“Lambda 层”,以及对我们至关重要的是,在 x86_64 和 ARM64 CPU 架构上内置对.NET 的支持。

AWS 提供了很多在 C#中编写 Lambda 函数和重构现有代码以便部署到 AWS Lambda 的示例。下面是一个快速示例,利用Amazon.Lambda.Templates NuGet 包中的模板创建并部署一个简单的 C# Lambda 函数。该包包含了可以在.NET Core CLI 的dotnet new命令中使用的项目模板:

dotnet new -i Amazon.Lambda.Templates

dotnet new lambda.EmptyFunction --name SingleCSharpLambda

这将创建一个名为SingleCSharpLambda的文件夹,其中包含用于你的函数的源代码和测试项目。示例函数位于名为Function.cs的文件中。

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.
 DefaultLambdaJsonSerializer))]

namespace SingleCSharpLambda
{
    public class Function
    {
        public string FunctionHandler(string input, ILambdaContext context)
        {
            LambdaLogger.Log("Hello from" + context.FunctionName);

            return input?.ToUpper();
        }
    }
}

模板创建了一个带有公共函数FunctionHandler(input, context)的类。这是每个 C# Lambda 函数在 AWS Lambda 中的入口点方法签名。两个参数分别是输入参数,其形状将根据我们将 Lambda 函数挂钩到的任何事件确定,以及ILambdaContext context,由 AWS 在执行时生成,包含有关当前执行 Lambda 函数的信息。

我们还在上一个模板函数中添加了一行打印日志消息的代码,以便我们在 AWS 上运行时可以检查其执行情况。这里的静态类LambdaLogger属于Amazon.Lambda.Core包,这是我们模板中添加的一部分。你也可以在这里使用Console.WriteLine();AWS 会将任何写入stdoutstderr的调用发送到与函数关联的 CloudWatch 日志流。³

现在我们可以开始部署我们的函数到 AWS 了。如果你还没有安装它们,现在是获取AWS Lambda .NET 全局工具的好时机。全局工具是一种特殊类型的 NuGet 包,你可以从dotnet命令行执行:

dotnet tool install -g Amazon.Lambda.Tools

然后部署你的函数:

dotnet lambda deploy-function SingleCSharpLambda

如果在aws-lambda-tools-defaults.json配置文件中还没有设置,将会要求你提供 AWS 区域和 IAM 角色的 ARN,用于函数执行时使用的角色。IAM 角色可以由Amazon.Lambda.Tools工具动态创建,包括所需的所有权限。你可以访问IAM 控制台查看和编辑这个(或其他任何)IAM 角色的权限。

图 4-2 展示了我们在部署的 AWS 管理控制台中的示例函数。你可以直接从下一个屏幕的测试选项卡中,在控制台中测试该函数。

doac 0402

图 4-2. AWS Lambda 控制台中的函数列表

这个测试窗口(显示在图 4-3 中)允许你输入一些 JSON 数据作为我们之前定义的input参数传递给你的函数。由于我们在FunctionHandler(string input, ILambdaContext context)中声明了输入值为string类型,我们应该将其更改为任意字符串,并且可以在管理控制台中直接测试函数。

doac 0403

图 4-3. 测试你的部署 Lambda 函数

您的执行结果将被插入到页面中,包括内联的“日志输出”窗口,也显示在 图 4-3 中。这使您能够快速查找和调试 Lambda 函数中的错误,并进行手动测试。

C# 简历示例:AWS Lambda

现在我们对 AWS Lambda 比较熟悉了,让我们将其应用到无服务器示例中。我们将从 C# 简历上传示例中获取我们的 Web API 控制器,并将其转换为 AWS Lambda 函数。任何 Web API 控制器都可以借助 AWS 的一个称为 Amazon.Lambda.AspNetCoreServer.Hosting 的包部署到 AWS Lambda 中。该包使得将 .NET 控制器包装成 API Gateway 调用的 Lambda 函数变得简单。

注意

API Gateway 是 AWS 的一项服务,使得构建由 Lambda 函数组成的 API 成为可能。它提供了 HTTP 请求和 Lambda 函数之间的管道,允许您配置一组单独的 Lambda 函数,以在 API Gateway 中配置的 API 路由上运行 GETPUTPOSTPATCHDELETE 请求。在本例中,我们使用 API Gateway 将路由 POST: https://our-api/Application 映射到一个 Lambda 函数上。

要使用这种方法将 ASP .NET 应用程序部署到 AWS Lambda,请安装 Amazon.Lambda.AspNetCoreServer 包,然后在应用程序的服务集合中添加对 AddAWSLambdaHosting() 的调用。⁴ 这允许 API Gateway 和 AWS Lambda 在 Lambda 上运行时充当 Web 服务器:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

// Add AWS Lambda support. builder.Services.AddAWSLambdaHosting(LambdaEventSource.HttpApi); ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)

app.MapRazorPages();

var app = builder.Build();

1

这是我们需要添加的唯一一行。

就像我们在 “使用 AWS Lambda 和 C# 进行开发” 中所做的那样,使用我们的新类作为入口点将此 Lambda 函数部署到 AWS。我们将不得不为设置创建一个 JSON 文件。如果您安装了 Visual Studio 和 AWS Toolkit for Visual Studio,则可以尝试一些模板项目,这些项目演示了如何配置此 JSON 文件,因此我们建议您试一试。但是,在我们这里的示例中,我们只是直接将其部署到 AWS Lambda。

dotnet lambda deploy-function UploadNewResume

图 4-4 显示了如果在您的 JSON 文件中没有配置所有这些设置,dotnet lambda deploy-function 将为您提供的步骤。对于 IAM 角色,我们需要创建一个带有适当策略的角色,以执行简历上传功能所需的所有操作(保存到 S3、通过 SES 发送电子邮件),同时调用 Lambda 函数并创建 CloudWatch 日志组。

doac 0404

图 4-4. 在控制台中执行 dotnet lambda deploy-function

接下来我们需要创建一个 API 网关服务,将我们的 Lambda 函数连接到外部世界。在本章后面,我们将探索 AWS 无服务器应用模型(SAM)——这是一种创建和管理无服务器资源(如 API 网关)的绝佳方式,使用可提交到源代码控制的模板文件。但现在,我们建议使用 AWS 管理控制台创建 API 网关服务,如 图 4-5 所示。

doac 0405

图 4-5. 在 API 网关控制台中创建 API

AWS 管理控制台是探索和熟悉这些托管 AWS 服务的各种设置和选项的好方法,因此当您需要将这些设置保留在模板文件中时,您可以在脑海中找到一个锚点,了解所有组件如何配合。

图 4-6 显示了我们如何设置一个带有 Lambda 函数代理的 API 网关服务,将所有路由转发到我们刚刚部署的 AWS Lambda 函数 UploadNewResume。您可以通过控制台中的 TEST 工具(在 图 4-6 左侧显示)测试此功能,或者更好的方法是,通过向 API 网关服务的公共 URL 发送实际的 HTTP POST 请求,并将 PDF 文件放在请求体中。您可以在管理控制台的阶段设置中找到 API 网关的 URL。例如,我的 API 网关实例具有 ID xxxlrx74l3,位于 eu-west-2 区域。要将简历上传到我们的 API,我们可以使用 curl 执行 POST 请求:

curl https://xxxlrx74l3.execute-api.eu-west-2.amazonaws.com/Prod/Application
 --data-binary @MyResumeFile.pdf

这将上传我们的 PDF 文件到 API 网关,API 网关将其传递给我们的 Lambda 函数,在其中,我们的代码将其保存到 S3 并发送电子邮件。

doac 0406

图 4-6. API 网关 Lambda 代理设置

所有这些都是通过将我们的 API 控制器封装在 APIGatewayProxyFunction 类中,并部署为 AWS Lambda 云函数来实现的。接下来,我们将探讨在我们的应用程序中添加多个 Lambda 函数以执行更复杂和细粒度任务时可能出现的可能性。

使用 AWS Step Functions 进行开发

到此时为止,我们已经将 Web API 控制器部署到 Lambda 函数,并将其连接到 API Gateway,以便通过 HTTP 请求调用它。接下来,让我们考虑如何真正利用我们的新函数作为服务执行模型。还记得我们谈过的 FaaS 强制您更注重关注点分离吗?我们示例中的函数正在做很多事情。这通常是任何语言中 API 控制器动作的情况,不仅仅是 C# —— 它们倾向于同时承担多个责任。我们的 UploadNewResume() 做的不仅仅是简单地上传简历;它还在给招聘团队发邮件,并创建一个新文件名。如果我们想要进一步解锁无服务器和 FaaS 的某些灵活性,我们需要将这些操作拆分为它们自己的函数。AWS 有一个非常好的工具,用于管理涉及多个 Lambda 函数(以及更多内容)的工作流程,它被称为 AWS Step Functions。

AWS Step Functions 在工作流程中的步骤之间提供了抽象。我们可以看一下前面示例中的控制器动作,并看看其中多少代码只是管道工作:

[HttpPost]
public async Task<IActionResult> SaveUploadedResume()
{
    Request.EnableBuffering();
    using var fileStream = new MemoryStream();
    using var reader = new StreamReader(fileStream);
    await Request.Body.CopyToAsync(fileStream); ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)

    var storedFileUrl = await _storageService.Upload(fileStream); ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)

    await _emailService.Send("recruitment@example.com",
        $"Somebody has uploaded a resume! Read it here: {storedFileUrl}"); ![3](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/3.png)

    return Ok(); ![4](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/4.png)
}

1

在内存流之间移动比特。

2

执行存储服务上的函数。

3

在电子邮件服务上执行函数。

4

创建 HTTP 响应,状态码为 200。

这个函数做了四件事情,虽然没有特别复杂的一项,但所有都构成了我们后端 API 的业务逻辑(或“工作流”)。它也并没有真正尊重单一责任原则。该函数被称为 SaveUploadedResume(),所以你可以认为它的单一责任应该是将简历文件插入到 S3 中。那么,为什么它还要发送电子邮件和构建 HTTP 响应呢?

如果你要为这个函数编写单元测试,最好能简单地覆盖所有你期望在“保存上传的简历”时遇到的情况。然而,你必须考虑模拟电子邮件传播以及在单元测试中执行 API 控制器动作所需的样板代码。这增加了函数的范围,并增加了需要测试的内容数量,最终增加了修改此代码片段时涉及的摩擦力。

如果我们能将这个函数的范围减小到真正只有一个职责(保存简历),并将电子邮件和 HTTP 相关问题分离出去,那不是很好吗?

使用 AWS Step Functions,您可以将工作流程的部分或全部移出您的代码并放入配置文件中。Step Functions 使用一种名为 Amazon States Language 的专有 JSON 对象格式来构建它称为状态机的内容。AWS Step Functions 中的这些状态机由事件触发,并通过定义文件中配置的步骤流动,执行任务并沿着工作流传递数据,直到达到结束状态。

在下一节中,我们将重构我们的 Serverless C# 简历上传应用程序以使用 Step Functions,但可以简要地说,使用 Step Functions 的主要优势之一是能够将应用程序的管道开发与单个函数分开。Figure 4-7 是来自 AWS 管理控制台中 AWS Step Functions 部分的屏幕截图,显示了执行该状态机的操作,“执行事件历史”部分展示了这次执行状态机操作达到结束状态的过程。

doac 0407

图 4-7. Step Functions 在管理控制台中的执行日志

C# 简历上传示例:Step Functions

为了演示我们可以如何使用 AWS Step Functions,让我们将在我们简历上传示例的前一部分创建的 AWS Lambda 函数拆分为多个 Lambda 函数。UploadNewResume Lambda 函数可以负责将文件上传到 S3,然后我们有第二个 Lambda 函数来发送电子邮件(EmailRecruitment)。HTTP 部分也可以完全抽象并由 API Gateway 处理。这使得我们在这两个函数开发、测试和部署后仍能更灵活地修改或优化后端的工作流程:

[assembly: LambdaSerializer(
    typeof(Amazon.Lambda.Serialization.SystemTextJson.
            DefaultLambdaJsonSerializer))]

namespace ServerlessResumeUploader
{
    public class LambdaFunctions
    {
        private readonly IStorageService _storageService
                                            = new AwsS3StorageService();
        private readonly IEmailService _emailService
                                            = new AwsSesEmailService();

        ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)
        public async Task<StepFunctionsState> UploadNewResume(
            StepFunctionsState state, ILambdaContext context)
        {
            byte[] bytes = Convert.FromBase64String(state.FileBase64);
            using var memoryStream = new MemoryStream(bytes);

            state.StoredFileUrl = await _storageService.Upload(memoryStream);

            state.FileBase64 = null;

            return state;
        }

        ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)
        public async Task<StepFunctionsState> EmailRecruitment(
            StepFunctionsState state, ILambdaContext context)
        {
            await _emailService.Send("recruitment@example.com",
            $"Somebody uploaded a resume! Read here: {state.StoredFileUrl}\n\n" +
            $"...and check out their GitHub profile: {state.GithubProfileUrl}");

            return state;
        }
    }
}

1

第一个 Lambda 函数接收文件并保存到 S3。

2

第二个 Lambda 函数向我们的招聘团队发送包含文件链接的电子邮件。

你可以看到这段代码与本章早些时候的ApplicationController代码非常相似,只是已经重构为LambdaFunctions类上的两个方法。这将部署为两个独立的 AWS Lambda 函数,它们使用相同的二进制文件,这是部署多个共享代码的 Lambda 函数的一种常见方式。实际的上传和发送电子邮件再次由AwsS3StorageServiceAwsSesEmailService执行;然而,我们不再使用依赖注入了。⁵

对于这两个函数,需要注意的是,现在的第一个参数是state,它们都属于StepFunctionsState类型。AWS Step Functions 通过在每个任务之间传递一个状态对象(在本例中,任务是一个函数)来执行您的工作流。这些函数会向状态添加或删除属性。我们先前函数的状态如下:

public class StepFunctionsState
{
    public string FileBase64 { get; set; }

    public string StoredFileUrl { get; set; }
}

当这个state输入被传递给第一个函数UploadNewResume()时,它将具有来自我们前端 HTTP POST请求的 API Gateway 设置的state.FileBase64。该函数将把这个文件保存到 S3,然后设置state.StoredFileUrl,然后将状态对象传递给下一个函数。它还会从状态对象中清除state.FileBase64。在 AWS Step Functions 中,状态对象在每个步骤之间传递,由于这个 base64 字符串可能会很大,我们可以在读取后将其设置为 null,以减少传递的状态对象的大小。

我们可以像以前一样将这两个函数部署到 AWS Lambda 上;但是,这一次我们需要为每个函数指定--function-handler参数。函数处理程序是我们代码中作为 Lambda 入口点的 C#函数,正如我们之前在“使用 AWS Lambda 和 C#开发”中看到的:

dotnet lambda deploy-function UploadNewResume
    --function-handler ServerlessResumeUploader::ServerlessResumeUploader.
    LambdaFunctions::UploadNewResume

dotnet lambda deploy-function EmailRecruitment
    --function-handler ServerlessResumeUploader::ServerlessResumeUploader.
    LambdaFunctions::EmailRecruitment

如果我们进入 AWS Step Functions,我们可以创建一个新的状态机,并将这两个 Lambda 函数以及 API Gateway 连接起来。AWS 管理控制台确实为我们提供了一个图形用户界面来构建这些工作流程;然而,作为工程师,我们将在 Amazon States 语言的 JSON 文件中编写它。这也有一个关键的好处,即利用一个可以检入源代码控制的纯文本 JSON 文件,为我们提供了一个出色的基础设施即代码(IaC)部署模型。这里我们简单工作流的 JSON 配置如下:

{
  "Comment": "Resume Uploader State Machine",
  "StartAt": "SaveUploadedResume",
  "States": {
    "SaveUploadedResume": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-2:00000000:function:UploadNewResume",
      "Next": "EmailRecruitment"
    },
    "EmailRecruitment": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-2:00000000:function:EmailRecruitment",
      "End": true
    }
  }
}

Lambda 函数的 Amazon 资源名称(ARN)可以在管理控制台的 AWS Lambda 部分找到,它允许我们从 AWS 的任何地方引用这些 Lambda 函数。如果我们将前面的 JSON 复制到 AWS Step Functions 状态机的定义中,您可以在控制台中看到它为我们创建了工作流程的图形表示。本章后面的图 4-11 展示了这个图形的样子。

此示例的最后一步是将我们状态机的启动连接到 API Gateway。之前,API Gateway 被配置为将所有请求代理到我们的 AWS Lambda 函数,现在我们希望在 API Gateway 中指定确切的路由,并将其转发到我们的 Step Functions 工作流程。如图 4-8 所示,设置了一个名为 /applicationsPOST 端点,AWS 服务设定为“Step Functions”。您还可以在图 4-8 中看到,我们已将内容处理设置为“必要时转换为文本”。这是我们简历上传示例中特定的选项。因为我们将以原始二进制形式 POST PDF 文件到我们的 API,此选项告诉 API Gateway 将其转换为 Base64 文本。然后可以轻松地将其添加到我们的状态对象中(如前面展示的 StepFunctionsState.cs),并在 AWS Step Functions 工作流程的 Lambda 函数之间传递。

doac 0408

图 4-8. 触发 Step Functions 的 API Gateway 配置

最后,我们可以在 API 网关中设置一些请求映射,将请求转换为告知 AWS Step Functions 要执行哪个状态机的 JSON 对象。如图 4-9 所示,这是一个链接到 application/pdf 内容类型头部的映射模板。

现在我们已经准备好向我们的 API 发送文件,并观察 Step Functions 工作流的执行:

curl https://xxxlrx74l3.execute-api.eu-west-2.amazonaws.com/Prod/Application
 --data-binary @MyResumeFile.pdf

doac 0409

图 4-9. PDF 文件的内容类型映射

如果我们在管理控制台的 AWS Step Functions 状态机的执行选项卡中导航,我们将能够看到此上传触发我们状态机的执行。如图 4-10 所示,展示了执行过程的可视化表示,您可以通过单击最新执行来查看此图表。

doac 0410

图 4-10. 我们状态机的成功执行

我们承认,这似乎是为了保存文件到 S3 并发送电子邮件而做的大量工作。到这一步,您可能会想,这究竟有什么意义?难道只是为了将我们的工作流程抽象成一个 JSON 文件?重构所有这些并部署到 AWS Step Functions 的目的在于,现在我们有了更灵活和可扩展的起点。现在我们可以做一些非常酷的事情,而无需接触我们部署的两个 Lambda 函数。例如,为什么不利用 AWS Textract 读取 PDF 文件并提取候选人的 GitHub 个人资料?这就是我们接下来将要做的事情。

C# 简历上传示例:AWS Textract

这是一种功能,不久之前编码起来可能会非常复杂,但是通过 Step Functions 和 AWS Textract,我们可以很容易地添加此功能,甚至无需将额外的第三方库引入到我们的现有代码中,甚至不需要重新编译它:

public async Task<StepFunctionsState> LookForGithubProfile(
    StepFunctionsState state, ILambdaContext context)
{
    using var textractClient =
            new AmazonTextractClient(RegionEndpoint.EUWest2);![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)

    var s3ObjectKey = Regex.Match(state.StoredFileUrl,
                "amazonaws\\.com\\/(.+?(?=\\.pdf))").Groups[1].Value + ".pdf";

    var detectResponse = await textractClient.DetectDocumentTextAsync(
        new DetectDocumentTextRequest
    {
        Document = new Document
        {
            S3Object = new S3Object
            {
                Bucket = AwsS3StorageService.BucketName,
                Name = s3ObjectKey,
            }
        }
    });

    state.GithubProfileUrl = detectResponse.Blocks
            .FirstOrDefault(x => x.BlockType == BlockType.WORD &&
                                 x.Text.Contains("github.com"))
            ?.Text;

    return state;
}

1

AmazonTextractClient 可在 AWSSDK.Textract NuGet 包中找到,并允许我们轻松调用 AWS Textract。

提示

Textract 是 AWS 提供的众多机器学习服务之一。它们还提供 AWS Comprehend 用于对文本进行自然语言处理,以及 Amazon Rekognition 用于图像标记。所有这些服务都采用按使用量付费的模式,并可以从 Lambda 函数中调用,就像我们在这里使用 AWS Textract 一样。

这里有另一个 C# Lambda 函数的代码,它将接收与我们所有功能中一直在使用的相同状态对象,并将 PDF 文件发送到 Textract。从这个函数的响应中,我们将得到从 PDF 文件中提取的文本块数组。如果任何这些文本块包含字符串“github.com”,我们将其添加到我们的状态对象中,以供稍后的 Lambda 函数使用。例如,这允许我们在发送的电子邮件中包含它:

public async Task<StepFunctionsState> EmailRecruitment(StepFunctionsState state,
                                                       ILambdaContext context)
{
    await _emailService.Send("recruitment@example.com",
    $"Somebody uploaded a resume! Read it here: {state.StoredFileUrl}\n\n" +![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)
    $"...check out their GitHub profile: {state.GithubProfileUrl}");![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)

    return state;
}

1

存储的文件 URL 仍然留在从第一个函数保存到 S3 的状态对象中。

2

此新领域是在 PDF 中的 GitHub 网址被 Textract 发现时,通过 LookForGithubProfile() 添加的。

我们可以将新的 LookForGithubProfile() 函数部署为第三个 AWS Lambda,并将其添加到我们的状态机 JSON 中。我们还在这里添加了一个错误处理程序,调用一个函数通知我们上传简历时出现错误。使用 Amazon States Language JSON 定义,可以创建复杂路径和工作流中的许多不同步骤和方式。Figure 4-11 在 AWS 管理控制台中显示了这一切,左侧是我们的定义 JSON,右侧是工作流的视觉表示。

doac 0411

图 4-11. AWS Step Functions 中的工作流定义和图形表示

我们还可以更新我们的架构图表,删除 Figure 4-1 中显示的 Web 服务器,并将应用程序完全无服务器化,如 Figure 4-12 所示。

doac 0412

图 4-12. 无服务器 C# 简历上传器架构

现在我们有一个真正的无服务器应用程序,其中各个组件可以独立开发、测试和部署。但是,如果我们不想依赖 AWS Step Functions 来将所有这些逻辑绑定在一起怎么办?

使用 SQS 和 SNS 进行开发

AWS Step Functions 不是在 AWS Lambda 调用和其他无服务器服务之间进行通信的唯一方式。在 AWS Step Functions 存在很久之前,我们已经有了消息队列和发布/订阅模式。这是 AWS 提供的两种服务之一:

Amazon Simple Notification Service (SNS)

SNS 是发布/订阅模式的分布式实现。订阅者可以附加到 SNS 通道(称为“主题”),当消息发布时,所有订阅者将立即收到通知。在 SNS 的任何给定主题上可以有多个订阅者监听发布的消息,并且支持多种端点,如电子邮件和短信,以及触发 Lambda 函数或发起 HTTP 请求。

Amazon 简单队列服务(SQS)

SQS 是 AWS 的消息队列。消息不会直接推送给订阅者,而是添加到队列中并在那里存储一段时间(最多 14 天)。消息接收者以适合他们的速率轮询队列,根据需要读取并删除队列中的消息。SQS 队列使得延迟操作或批处理消息直到订阅者准备好处理它们成为可能。

这两个服务使我们可以从“消息”和“事件”的角度思考,这些是构建无服务器系统的重要概念。它们将允许我们在我们的示例系统中实现发布者/订阅者模式。

C# 简历上传示例:SQS

想象一下我们在 Serverless C# 简历上传应用程序中可以如何利用 SQS 或 SNS。我们知道用户可以通过 API Gateway 将他们的简历上传到我们的 API,并将其存储在 S3 中,然后使用 Textract 读取候选人的 GitHub 档案。如果不是立即发送电子邮件给招聘团队,而是将消息添加到队列中怎么样?这样,我们可以每天早上运行一次作业,并发送一封包含队列中过去 24 小时内所有消息的电子邮件。这可能会让我们的招聘团队更容易一次性阅读所有当天的简历,而不是分散在一整天内进行。我们可以在不必触及其余代码的情况下完成所有这些吗?而不必重建、重新测试或重新部署整个应用程序?是的,以下是如何实现的。

由于我们正在使用 AWS Step Functions,我们可以简单地在定义 JSON 文件中创建一个任务,将消息发布到 SQS 队列中。我们不需要另一个 Lambda 函数来执行此操作;相反,我们可以直接在 JSON 中指定消息正文,然后更新我们的状态引擎定义。这里有一个名为 "QueueEmail" 的任务,它将消息发布到我们设置的名为 UploadedResumeFiles 的 SQS 队列,通过其 ARN 引用如下:

{
  "Comment": "Resume Uploader State Machine",
  "StartAt": "SaveUploadedResume",
  "States": {
    "SaveUploadedResume": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:eu-west-2:00000000:function:UploadNewResume",
      "Next": "QueueEmail"
    },
    "QueueEmail": {
      "Type": "Task",
      "Resource": "arn:aws:states:::sqs:sendMessage",
      "Parameters": {
        "QueueUrl": "https://sqs.eu-west-2.amazonaws.com/UploadedResumeFiles",
        "MessageBody": {
          "StoredFileUrl.$": "$.StoredFileUrl"
        }
      },
      "End": true
    }
  }
}

现在,当文件上传触发状态机时,EmailRecruitment Lambda 将不会被执行;我们只会收到一个发布到队列的消息。现在我们只需要编写一个新的 AWS Lambda 函数,每天运行一次,读取队列中的所有消息,并发送包含所有文件 URL 的电子邮件。这样的 Lambda 的代码可能如下所示:

public async Task<string> BatchEmailRecruitment(object input, ILambdaContext c)
{
    using var sqsClient = new AmazonSQSClient(RegionEndpoint.EUWest2);
    var messageResponse = await sqsClient.ReceiveMessageAsync(
        new ReceiveMessageRequest()![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)
        {
            QueueUrl = queueUrl,
            MaxNumberOfMessages = 10
        });

    var stateObjects =
        messageResponse.Messages.Select(msg => Deserialize(msg.Body));

    var listOfFiles =
        string.Join("\n\n", stateObjects.Select(x => x.StoredFileUrl));![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)

    await _emailService.Send("recruitment@example.com", ![3](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/3.png)
        $"You have {messageResponse.Messages.Count} new resumes to review!\n\n"
         + listOfFiles);

    await sqsClient.DeleteMessageBatchAsync(new DeleteMessageBatchRequest() ![4](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/4.png)
    {
        QueueUrl = queueUrl,
        Entries = messageResponse.Messages.Select(x =>
                    new DeleteMessageBatchRequestEntry()
        {
            Id = x.MessageId,
            ReceiptHandle = x.ReceiptHandle
        }).ToList()
    });

    return "ok";
}

1

连接到 SQS 队列并读取一批消息。

2

将状态对象中发布的文件 URL 连接起来。

3

发送包含所有链接的一封电子邮件。

4

删除队列中的消息——这不会自动发生。

每天触发此 Lambda 可以使用 AWS EventBridge,如图 4-13 所示。这个“添加触发器”表单可以从 AWS 管理控制台中我们新 Lambda 函数的“函数概述”窗口访问。

doac 0413

图 4-13. 将计划的 EventBridge 触发器添加到 Lambda 函数

现在我们的系统每天会发送一封电子邮件,其中包含队列中所有简历的链接,就像图 4-14 中一样。

doac 0414

图 4-14. 从我们的定期 Lambda 函数发送的电子邮件

通过添加 SQS 队列,我们在运行时修改了系统的行为,并通过将所有链接汇总到每日电子邮件中来改善其提供的服务。然而,所有这些仍然是由对我们的 API 的 HTTP 请求启动的。接下来,我们将探讨一些其他类型的事件,这些事件可以触发逻辑和函数在我们的状态引擎中运行。

使用 AWS 触发器开发事件驱动系统

想象一下,你与一个有着一个出色的应用想法并希望你为其构建的人交谈。⁶ 你让他们描述功能,他们说了这样一些内容:

“当用户上传照片时,我希望通知他们的关注者;当他们发送电子邮件到我的支持邮箱时,我希望打开一个支持工单。”

这些“当 X 发生时,执行 Y”语句描述了一个事件驱动系统。所有系统在底层都是事件驱动的;问题是,对于许多后端应用程序来说,这些事件只是 HTTP 请求。与其说“当用户上传照片时,我希望通知他们的关注者”,我们通常实现的是“当我们的服务器接收到一个 HTTP 消息,指示用户上传了照片时,通知他们的关注者”。

这是可以理解的;在无服务器计算出现之前,你真正能实现这一点的唯一方法是监听 API 上的 HTTP 事件。但如果我们能够去掉这一额外步骤,并实现我们的架构响应实时事件而不是某些中介实现细节,比如 HTTP 请求,那不是更好吗?

这是事件驱动无服务器系统的核心理念。您执行函数并触发工作流,以解决问题时真正有意义的变化。不是编写一个应用程序来监听人类的操作,然后发送 HTTP 请求到 API 来触发效果,而是让 AWS 完成所有这些工作,并直接将事件附加到操作上。在前面的示例中,您可以监听 S3 中的s3:ObjectCreated:Post事件,并在事件发生时运行您的代码,完全绕过 API 步骤。

C# 简历上传示例:事件驱动

让我们最后再次访问我们的无服务器简历上传器。如果您回顾图 4-12,您会看到 PDF 文件通过 API Gateway 从我们的网站发送到步函数任务。没有必要让 API Gateway 存在;这只是一个实现细节,允许我们的前端在上传文件时轻松进行 HTTP 调用。如果我们完全摆脱这个 API 并允许网站直接将文件上传到 S3 存储桶,那该有多好?这也将允许我们放弃我们的UploadNewResumeLambda 函数,并且在保留系统所有功能的同时删除我们不再需要的代码,这是软件开发中最令人愉悦的事情之一。

通过消除 API 步骤并让前端直接将文件上传到 S3,我们还为我们的系统开辟了新的可能性。例如,除了在我们的网站上传简历外,如果我们想接受发送到application@ouremail.com的简历呢?使用 SES,相对简单地连接一个电子邮件转发器,该转发器将提取附加的 PDF 文件并保存到 S3。然后,这将触发后端相同的工作流程,因为我们已将逻辑连接到了 S3 事件,而不是某个中介的 HTTP API 调用。语句“当我们在 S3 中看到一个新的 PDF 文件时,启动我们的工作流程”对我们正在解决的业务问题变得更加自然。实际上,PDF 文件如何进入其中已经不再重要,因为我们的系统以相同的方式做出反应。

关于直接将 PDF 文件上传到 S3 的问题,我们有几种选择。如果我们已经在前端使用AWS Amplify⁷,那么我们可以使用存储模块和"protected"上传方法,在我们的 S3 存储桶中基于已认证用户的 Cognito 身份限制访问路径:

amplify add storage

前端 JavaScript 用于将文件上传到 S3 的代码如下:

import Amplify, { Auth, Storage } from 'aws-amplify';

Amplify.configure({ Storage: { AWSS3: {
            bucket: '<bucket-name>'
        } } });

async function uploadFile(fileName, file) {
    await Storage.put(fileName, file, { level: 'protected',
            contentType: file.type });
}

即使没有 AWS Amplify 和前端上的经过身份验证的用户,我们仍然可以通过 预签名 URL 直接上传到 S3 存储桶。这种情况下的过程是在 API 网关后面创建一个 Lambda 函数,当执行时将调用 AmazonS3Client.GetPreSignedURL() 并将其返回给前端以用于上传文件。在这种场景中仍然存在 API,但此 API 的 功能 更符合执行一项通用任务。毕竟,您可以使用预签名 URL 在前端上传其他类型的文件,并将多个 Step Function 工作流连接到每个文件上传中。

一旦您的前端直接将文件上传到 S3 而不是通过 Web API 发送它们,可以直接在 S3 存储桶配置的“属性”选项卡中的管理控制台中添加触发器,如 图 4-15 所示。

doac 0415

图 4-15. 向 S3 存储桶添加事件通知

将 Step Functions 状态引擎作为此事件的目标,并且我们的事件驱动、无服务器、C# 简历上传服务的最终形式完成了!最终的架构显示在 图 4-16 中。

doac 0416

图 4-16. 我们简历上传示例的最终事件驱动架构

正如您所见,我们现在拥有一个简洁、描述性和 事件驱动 的系统,足以允许我们在不引入错误或其他问题的风险的情况下扩展或修改工作流的各个部分。最后需要考虑的是如何在一个地方管理所有这些运作中的部分并轻松配置它们。

无服务器应用程序模型(SAM)

到目前为止,我们一直通过登录 AWS 管理控制台并在 UI 中点击来进行所有配置更改。这对于实验是可以的,但并不是特别适合运行生产系统的方法。一次错误的点击可能导致应用程序的部分中断。这就是基础设施即代码(IaC)可以提供帮助的地方。

IaC 是通过可机器读取的定义文件配置您的无服务器基础设施的过程。CloudFormation 是 AWS 上使用的 IaC 工具,允许您将整个云配置保留在 YAML 或 JSON 文件中。几乎可以在 CloudFormation 模板中对 AWS 中的所有内容进行建模,从 DNS 设置到 S3 存储桶属性再到 IAM 角色和权限。当需要更改设置时,您只需在 CloudFormation 模板中更改值,并告知 AWS 应用这些更改到您的资源上。其最明显的优势是您可以将模板 JSON/YAML 文件检入版本控制,并像对待任何其他代码更改一样进行代码审查、审核和在沙盒/测试环境中测试。

但是,CloudFormation 的一个缺点是它可能会非常复杂和冗长。由于在 AWS 资源中有大量可供修改的设置,当尝试配置由多个 Lambda 函数、消息队列和 IAM 角色组成的无服务器系统时,CloudFormation 模板可能会变得难以管理。有各种工具可以在 CloudFormation 周围添加抽象层,并帮助配置无服务器系统。像 Serverless Framework 和 Serverless Application Model(SAM)这样的工具就是为解决这个问题而创建的。

你可以将 SAM 看作是在 CloudFormation 之上的一层,将无服务器应用程序的最相关设置置于前沿。你可以在AWS SAM 文档找到 SAM 的完整规范,但为了给你一个概览,这里是我们 C#简历上传系统的 SAM YAML 文件的一部分:

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Resume Uploader Serverless C# Application.
Resources:
  SaveUploadedResumeLambda:
    Type: AWS::Lambda::Function
    Properties:
      Handler: ServerlessResumeUploader::ServerlessResumeUploader.
      LambdaFunctions::SaveUploadedResume
      Role: arn:aws:iam::0000000000:role/ResumeUploaderLambdaRole
      Runtime: dotnetcore3.1
      MemorySize: 256
      Timeout: 30
  LookForGithubProfileLambda:
    Type: AWS::Lambda::Function
    Properties:
      Handler: ServerlessResumeUploader::ServerlessResumeUploader.
      LambdaFunctions::LookForGithubProfile
      Role: arn:aws:iam::0000000000:role/ResumeUploaderLambdaRole
      Runtime: dotnetcore3.1
      MemorySize: 256
      Timeout: 30

你可以看到我们如何定义了SaveUploadedResumeLambdaLookForGithubProfileLambda Lambda 函数,在我们的 C#代码中指出了入口点,并为它们配置了执行的内存、超时和权限设置。

像这样在 SAM 文件中配置了基础设施后,你可以轻松地为测试或分段环境部署新环境。你从代码审查中受益,并且你可以像处理应用程序代码一样为你的资源创建自动化部署流水线。

结论

无服务器计算允许你构建复杂但灵活和可扩展的解决方案,采用按需付费的定价模型,并可以缩减至零。就个人而言,每当我们需要快速构建以验证想法、解决业务问题或预算有限时,我们都会使用无服务器执行模型。因为你只为所使用的付费,基于 AWS Lambda 的无服务器架构可以是构建最小可行产品(MVP)或部署移动应用程序后端的极低成本方式。AWS 每个月提供了高达 100 万次的 Lambda 执行免费额度,这对于使创业初期脱离理念阶段或测试产品进行 Beta 测试可能足够了。本章介绍的其他服务也都有非常慷慨的免费套餐,允许你尝试各种想法和架构而不会损害财务。

虽然如此,使用无服务器架构并不会自动降低系统运行成本。您需要考虑设计应用程序,避免不必要地大量调用 AWS Lambda。仅仅因为两个函数可以分别作为两个独立的 Lambda,并不总是意味着从成本和性能角度来看它们应该这样。进行实验和测量。如果您的应用程序架构不是为了有效利用每次调用而设计的,那么在高负载时,函数即服务也可能显得很昂贵。因为您每次执行都要付费,所以在非常高的调用量下,您可能会发现成本远远超过简单在 EC2 或 Elastic Beanstalk 上运行dotnet进程的成本。无服务器的成本应始终与其他优势(如可扩展性)进行权衡。

灵活性是无服务器架构的另一个巨大优势,正如本章的示例所展示的那样。在我们的无服务器 C# 简历上传器的每个步骤中,我们通过进行非常小的更改——大多数情况下甚至无需重新部署其余的应用程序——彻底改变了其某一部分的架构。您系统中各部分之间的这种分离使得系统的扩展变得更加容易,并且解锁了开发团队内人才的多样化。无服务器架构中没有规定个别组件的版本、技术甚至编程语言。您是否曾想过尝试 F#?使用构建在 AWS Lambda 上的系统,没有任何阻碍您在 F# Lambda 中编写最新功能并将其植入到您的无服务器架构中。需要将一些 HTTP 调用路由到第三方 API 吗?如果需要,您可以直接通过 API Gateway 进行代理,而无需在代码中创建接口和“管道”。通过采用 IaC 工具(如 SAM 或第三方框架如 Serverless Framework 和 Terraform),您可以自动化对基础架构的更改,并在基础架构配置本身上运行代码审查、拉取请求和自动化测试管道。对于无服务器系统的拉取请求通常由两个更改组成:一个简单、易于审查的 AWS Lambda 和一个 SAM/Terraform/CloudFormation 模板条目,显示该 Lambda 如何与系统的其余部分集成。

批判性思维讨论问题

  • 在构建 AWS 上的 FaaS 服务时,使用函数式编程风格的优势是什么?

  • 朋友告诉您,AWS Lambda 的速度比另一个开源的 FaaS 框架稍慢,因此他们不打算使用它。通过不使用 AWS Lambda,他们可能会错过 AWS 上的更大优势是什么?

  • 为什么 AWS Step Functions 中的执行事件历史是该服务中最关键的特性之一?

  • 描述一个架构,其中 Amazon SQS 是全球规模平台中至关重要的组件之一。

  • SQS 和 SNS 之间的关键区别是什么?

练习

  • 构建一个 AWS Lambda 函数,从 SQS 队列触发器中拉取消息。

  • 构建一个 AWS Lambda 函数,接受来自 SNS 主题的消息。

  • 构建一个 AWS Lambda 函数,通过 S3 触发器打印放置在存储桶中的文件名称。

  • 构建一个 AWS Step Function,在第一个 AWS Lambda 中接受有效负载“hello”,并在第二个 AWS Lambda 函数中返回有效负载“goodbye”。

  • 使用 AWS CloudShell 并使用 AWS CLI 调用 AWS Lambda 函数或 AWS Step Function。

¹ 预签名 URL 是指向 S3 资源的链接,可从任何地方匿名下载内容(即输入到浏览器窗口中)。此 URL 是使用发出此请求的 IAM 角色的身份验证权限预签名的。这有效地允许您与分享此预签名 URL 的任何人共享对受保护 S3 资源的访问权限。

² S3 存储桶名称在区域内所有 AWS 帐户中是唯一的。因此,您可能会发现您想要的存储桶名称不可用,因为它已被其他 AWS 帐户使用。您可以通过在存储桶名称前加上您的产品或组织名称来避免这种情况。

³ CloudWatch 是 AWS 提供的一项服务,为您的整个云基础设施提供日志记录和监控。AWS Lambda 连接到 CloudWatch 并发布日志消息,您可以从 CloudWatch 控制台查看这些消息,或将其集成到另一个 AWS 服务中。

⁴ 这种方法使用了在.NET 6 中引入的配置最小 API 样式和 ASP .NET 应用程序。Amazon.Lambda.AspNetCoreServer 也支持基于较早.NET Core 版本构建的.NET 应用程序;但是,配置略有不同,并涉及实现 Amazon.Lambda.AspNetCoreServer.APIGatewayProxyFunction。更多信息请参阅Amazon.Lambda.AspNetCoreServer

⁵ 在 C#中,可以通过手动编码设置或使用第三方库实现 AWS Lambda 函数的依赖注入。然而,您可能经常发现,这些函数非常简单,实际上不需要使用它进行装饰。

⁶ 成为软件开发人员的一个缺点是这种情况经常发生,不管您是否请求。

⁷ AWS Amplify 是一个用于移动和 Web 应用程序的前端框架,允许我们快速围绕无服务器 AWS 服务(如 S3 简单存储服务)构建用户界面。

第五章:.NET 的容器化

认识容器的一种方式是将其视为技术革命。想象一下内燃机,它在改变社会对交通方式使用的方式上取得了长足进步。但现在,随着电动汽车的普及,正在发生一场新的变革!与虚拟机相比,容器也是如此。您一会儿就会明白我们的意思。

随着技术的进步,用于交通的机器经历了多次变革。¹ 目前,引擎创新的下一波浪潮围绕电动汽车展开。电动汽车更快、扭矩更大、续航更长,并且允许新的加油方式,无需访问燃料库,因为它们可以通过太阳或电网充电。电动汽车创造了一种新的汽车加油方式,例如在家中、工作中或在旅途中停车时充电。电动汽车的发展紧密结合着自动驾驶或半自动驾驶汽车的建设。新技术促使了新的工作方式。

几十年来,计算机的发展经历了类似的进展,如图 5-1 所示。² 计算机已经演变成更小、更便携的计算单元,目前体现为容器。这些新的计算单元为新的工作方式铺平了道路。容器提供了将应用程序的代码、配置和依赖项打包成单一实体的标准方式。容器在主机操作系统中运行,但作为轻量级、资源隔离的进程运行,确保快速、可靠、可重复和一致的部署。

doac 0501

图 5-1. 计算技术的技术进步

在我们深入讨论如何在 AWS 上使用容器服务之前,我们首先需要更详细地讨论容器,从容器和 Docker 的概述开始,后者是一个用于设计、交付和执行应用程序的开放平台。首先,让我们更深入地了解容器。

容器简介

容器的关键创新在于能够将软件解决方案所需的运行时与代码一起打包。由于现代容器技术,用户可以运行docker run命令来执行解决方案,而不必担心安装任何软件。同样,开发人员可以查看Dockerfile,查看需要运行代码和运行时的详细信息,如图 5-2 所示。在此示例中,GitHub 作为中央的“真相源”,存储了部署应用程序所需的每个组件。Dockerfile定义了运行时。基础设施即代码(IaC)描述了云配置,如网络和负载均衡。构建系统配置文件指定了软件项目构建和部署的过程。

注意

容器优势的一个很好的例子是使用 Docker 单行命令运行Docker Hub SQL Server。以下示例展示了如何启动运行 SQL Express 版本的mssql-server实例:

docker run -e "ACCEPT_EULA=Y" \
    -e "SA_PASSWORD=ABC!1234!pass" \
    -e "MSSQL_PID=Express" \
    -p 1433:1433 -it \
    -d mcr.microsoft.com/mssql/server:2019-latest

doac 0502

图 5-2. 可复现的基于容器的部署

虚拟机继承了物理数据中心的遗产。从某种意义上说,虚拟机是物理数据中心计算技术的复制。但是如果你看看容器,它是一种全新的思考和工作方式。基础设施定义、运行时定义、源代码和构建服务器配置可以全部在同一个项目中。由于这种新的工作方式,软件开发项目的生命周期有了新的透明度。

注意

并非所有项目都将 IaC、构建配置、Dockerfile 和源代码保存在同一个存储库中。这些资产可以存在于多个仓库,也可以存在于单个仓库中。

如果你看看虚拟机,就不清楚它内部安装了什么软件和配置。虚拟机的另一个显著缺点是启动时间长,因为启动虚拟机可能需要几分钟时间。³如果你要部署微服务或使用负载均衡器和虚拟机的 Web 应用程序,你必须设计解决这些限制。通过基于容器的服务,您可以依靠使用 ECS 在几秒钟内部署成千上万个容器实例,⁴因此通过容器部署事物有相当大的优势。

让我们来看看另一种启动容器的方式——桌面与云的对比。Docker 环境是桌面上进行本地实验的理想环境。它允许你上传或下载容器,从而使你能够开始使用独立容器或使用 Kubernetes 工作流程,如图 5-3 所示。Dockerfile 使用存储在容器注册表中的基础只读镜像。本地开发工作流程涉及构建一个可写新容器,开发人员将在其中构建、测试、运行,并最终通过将其推送到容器注册表来部署容器。

doac 0503

图 5-3. 容器工作流程

在转向云之前,这是一个很好的地方,可以在此处玩弄你的想法。同样,开发人员可以下载由 AWS 的领域专家构建的容器,并在本地环境中执行它们。⁵

一旦您决定要做什么并在本地进行了一些实验后,自然而然地,您可以进入 AWS 环境,并开始以云原生方式与这些容器进行交互。从 AWS Cloud9 开始是一个很好的实验容器的方法。您可以在云开发环境中构建容器,保存环境,然后将该容器部署到 ECR。您还可以通过启动虚拟机来实验容器,然后在该虚拟机上进行构建过程。

另一个选项是使用 Docker 工具和 Visual Studio 在本地进行开发。让我们接下来讨论 Docker。

Docker 简介

Docker 是一个用于管理容器生命周期的开源平台。它允许开发人员将应用程序打包并开发为 Docker 容器镜像,由 Dockerfile 定义,并运行在外部或本地开发的容器化应用程序。Docker 的一个更受欢迎的方面是其容器注册表 Docker Hub,它允许与容器进行协作。

注意

除了 Docker 容器镜像格式 外,还有其他容器镜像格式,比如 Open Container Initiative (OCI) 规范

Docker 容器解决了什么问题?操作系统、运行时和代码包装在构建的容器镜像中。这一行动解决了一个历史悠久的非常复杂的问题。有一个著名的段子说,“在我的机器上可以运行!”虽然这经常被当作一个玩笑来说明部署软件的复杂性,但没有容器将运行时与代码一起打包,很难验证本地开发解决方案在分发到生产环境时是否会表现相同。容器解决了这个确切的问题。如果代码在容器中运行良好,那么容器配置文件将像任何其他类型的代码一样检查源代码库。

注意

现代应用程序最佳实践通常包括 IaC,它会同时配置环境和容器。在这篇关于亚马逊内部最佳实践的博文中,作者指出对于容器化应用程序,通过相同的 CI/CD 发布流水线部署代码更被认为是最佳实践。

容器已经存在了相当长的时间,但是以不同的形式存在。现代容器的一种形式是 Solaris Containers,于 2004 年发布。它允许您 telnet 到一个关机的机器,通过 Lights Out Management (LOM) 卡片响应命令,告诉它启动。然后它会从网络启动一个没有操作系统的机器,并通过 ssh 和 vim 文本编辑器创建新的容器,这些容器也从网络引导启动。

从那时起,容器继续改进并支持额外的工作流程,如持续交付和将代码与运行时打包在一起。Docker 是最流行的容器格式。在 图 5-4 中,注意生态系统在实践中的运作方式。Docker 的两个主要组成部分是 Docker DesktopDocker Hub。对于 Docker Desktop,本地开发工作流程包括访问 Linux 容器运行时、开发工具、Docker 应用程序本身以及可选的 Kubernetes 安装。对于 Docker Hub,有私有和公共容器仓库、容器镜像的自动构建、团队和组织等协作功能,以及认证镜像。

doac 0504

图 5-4. Docker 生态系统
注意

现代容器的另一个创新是从基础镜像继承的概念。基础镜像允许您利用开发人员在各种领域(如 Python、.NET 或 Linux)的专业知识来构建您的容器。此外,它们节省了开发人员从头开始组装整个镜像的时间和精力。

接下来,让我们深入了解 Docker 生态系统。

Docker 生态系统

Docker 的运作方式是通过提供一个确定的方法来运行你的代码。多个 AWS 服务与 Docker 容器镜像配合工作。这些服务包括 Amazon ECS(Amazon 弹性容器服务),以及 Amazon ECR(弹性容器注册表),一个安全的容器镜像仓库。值得注意的还有 Amazon EKS(弹性 Kubernetes 服务),这是一个托管的容器服务,支持 Kubernetes 应用,以及 AWS App Runner,一个用于容器化应用程序的 PaaS,在本章节后面会详细讨论,最后还有 AWS Lambda。

桌面应用程序包含容器运行时,允许容器执行。它还编排本地开发工作流程,包括使用 Kubernetes,这是一个来自 Google 的用于管理容器化应用程序的开源系统。

接下来,让我们讨论 Docker Hub 如何与 Docker Desktop 和其他容器开发环境交互。就像 git 源代码生态系统有本地开发工具如 VimeMacsVisual Studio Code,或者 Xcode 与之配合一样,Docker Desktop 与 Docker 容器一起工作,支持本地使用和开发。

当开发人员在本地环境之外使用git进行协作时,通常会使用类似GitHubGitLab等平台与其他方沟通并共享代码。Docker Hub的工作方式类似。Docker Hub 允许开发人员共享 Docker 容器,这些容器可以作为构建新解决方案和下载完整解决方案(如 SQL 服务器镜像)的基础映像。

这些由专家构建的基础映像已经获得高质量认证,例如来自 Microsoft 的官方 ASP.NET Core Runtime。这个过程允许开发人员利用特定软件组件的专家知识,并改进其容器的整体质量。这个概念类似于使用另一位开发者开发的库而不是自己编写它。

注意

就像一个软件库,Dockerfile 允许您将实现绑定到现有版本,并在封装的环境中运行。

接下来,让我们深入探讨 Docker 容器与虚拟机的比较。

容器与虚拟机?

表 5-1 提供了容器和虚拟机之间差异的高级分解。

表 5-1. 容器与虚拟机

类别 容器 虚拟机
大小 MB GB
速度 几毫秒启动 几分钟启动
可组合性 源代码作为文件 基于映像的构建过程
注意

注意除了 Docker 容器外,还有其他容器,包括 Windows 和 Linux 的替代品。Docker 是最流行的格式,为了本章的目的,假设所有对容器的引用都将是 Docker 容器。

容器的核心优势在于它们更小、可组合且启动更快。虚拟机在需要复制物理数据中心范例的场景中表现出色。例如,将运行在物理数据中心中的 Web 应用程序移动到基于云的虚拟机中而不改变代码。让我们看一些真实世界的例子,容器如何帮助项目顺利运行:

开发人员分享本地项目

开发人员可以使用.NET Web 应用程序进行开发,使用Blazor(稍后在本章中讨论的一个示例)。Docker 容器镜像处理了底层操作系统的安装和配置。另一位团队成员可以检出代码并使用docker run来运行项目。这个过程消除了配置笔记本电脑以正确运行软件项目可能需要数天的问题。

数据科学家与另一所大学的研究人员分享 Jupyter 笔记本

一位与Jupyter 风格笔记本一起工作的数据科学家希望与多个依赖于 C、Julia、Fortran、R 和 Python 代码的复杂数据科学项目分享。他们将运行时打包为 Docker 容器镜像,消除了在分享此类项目时几周来回的问题。

一位机器学习工程师在生产机器学习模型中进行负载测试

一位机器学习工程师构建了一个新的机器学习模型,并将其部署到生产环境中。此前,他们担心在承诺之前准确测试新模型的准确性。该模型推荐产品给付费客户以购买可能喜欢的额外产品。如果模型不准确,可能会给公司带来大量收入损失。在此示例中使用容器部署 ML 模型,可以将模型部署到一小部分客户。一开始可以从只有 10%开始,并且如果出现问题,可以快速回滚模型。如果模型表现良好,可以迅速替换现有模型。

最后,容器的其他场景包括构建微服务、进行持续集成、数据处理以及作为服务的容器(CaaS)。让我们在下一节中深入探讨其中一些主题。

使用 AWS 容器兼容服务进行开发

.NET 开发人员可以通过多种方式在 AWS 上部署容器,包括 AWS Lambda、Amazon ECS、Amazon EKS 和 AWS App Runner。深入了解最新容器服务的一个好的起点是AWS 容器文档。除其他内容外,它还涵盖了 AWS 提供的当前容器服务的高级概述和日常使用案例。

选择最佳的抽象取决于开发人员希望在共享责任模型的哪个级别[⁶]。接下来,让我们使用完全云原生的工作流程与 Cloud9 和 AWS 容器服务深入探讨这些容器场景。

使用 Cloud9 和 AWS 容器服务进行.NET 开发

空间站是一艘停留在地球低轨道的太空飞船,允许宇航员在太空中度过时间、在实验室进行研究或为前往新目的地的未来旅行做准备。同样,如果你是云开发人员,为云开发的最佳地方就是云!

Cloud9 是一种基于云的开发工具,与 AWS 深度集成,并在浏览器中运行。这项技术从传统的软件工程开发实践中彻底出发,因为它开启了许多新的工作方式。

以下是 Cloud9 在云开发中如此出色的几个原因:

靠近 AWS 资源

如果您在咖啡店里,复制文件到云中或从云中复制文件可能会很有挑战性,但是如果您使用 Web 浏览器 IDE,响应时间并不重要,因为 IDE 就在与 AWS 内的服务器通信的旁边。这个优势在构建容器时非常有用,因为您可以快速地推送容器映像到 Amazon ECR。

与生产环境几乎相同的开发环境

另一件方便的事情是能够在与运行环境相同的操作系统中开发代码。Cloud9 运行最新版本的 Amazon Linux,因此没有部署上的意外。

专门的云 IDE

Cloud9 具有专门的 IDE 功能,这些功能只存在于 AWS 云 IDE 中。例如,能够浏览 S3 存储桶,调用 AWS Lambda 函数,并与其他具有对 AWS 账户访问权限的开发者进行配对编程。

要开始,请在 AWS 控制台中搜索并创建一个新的 Cloud9 环境,选择该服务,并为实例的名称提供一个有用的描述,如图 5-5 所示。值得注意的是,在幕后,EC2 实例运行 Cloud9,您可以通过 AWS EC2 控制台访问它,以进行像增加存储大小或更改网络设置的修改。

doac 0505

图 5-5. 启动 Cloud9

接下来,配置一台性能足够的机器,因为您将使用这个环境构建容器,如图 5-6 所示。

注意

值得一提的是,因为 Cloud9没有额外费用,成本驱动因素是 EC2。选择一个适当的实例大小以节省成本。

一旦 Cloud9 环境加载完成,接下来需要安装.NET 6

sudo rpm -Uvh \
https://packages.microsoft.com/config/centos/7/packages-microsoft-prod.rpm
sudo yum -y update
sudo yum install dotnet-sdk-6.0

doac 0506

图 5-6. 选择 Cloud9 实例

接下来,通过创建一个简单的控制台应用程序来测试环境是很有必要的:

dotnet new console -o hello \
    && cd hello \
    && dotnet run

//Output of command below
The template "Console App" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /home/ec2-user/environment/hello/hello.csproj...
  Determining projects to restore...
  Restored /home/ec2-user/environment/hello/hello.csproj (in 122 ms).
Restore succeeded.

Hello, World!

这个测试命令是使用dotnet命令行界面工作的,允许创建一个新的“控制台应用程序”,无需使用 Visual Studio。

在 Lambda 上容器化.NET 6

容器支持的另一个服务是 AWS Lambda。一个很好的参考点是AWS Lambda Dockerfile。此文档包含了如何构建目标为.NET 6 运行时的 AWS Lambda 的指令。另一个很好的资源是官方.NET 6 在 AWS 上的支持。查看无服务器章节,获取更多关于构建 AWS Lambda 的见解。

要构建容器,首先需要调整 Cloud9 环境的大小。让我们接下来解决这个问题。

调整大小

AWS Cloud9 在被配置时有一个最小的磁盘空间,当与容器一起工作时,磁盘空间可能会迅速填满。调整环境大小并清理不需要的旧容器映像是很有必要的。您可以参考 AWS 提供的 Bash 脚本,轻松地调整Cloud9 的大小

您可以在这里找到脚本的副本。要运行它,请执行以下命令,它将实例的大小调整为 50 GB:

chmod +x resize.sh
./resize.sh 50

在您的系统上运行后,您将看到以下输出。请注意,挂载点/dev/nvme0n1p1现在有41G可用空间:

ec2-user:~/environment/dot-net-6-aws (main) $ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs         32G     0   32G   0% /dev
tmpfs            32G     0   32G   0% /dev/shm
tmpfs            32G  536K   32G   1% /run
tmpfs            32G     0   32G   0% /sys/fs/cgroup
/dev/nvme0n1p1   50G  9.6G   41G  20% /
tmpfs           6.3G     0  6.3G   0% /run/user/1000
tmpfs           6.3G     0  6.3G   0% /run/user/0

接下来,让我们构建一个容器化的.NET 6 API。

容器化的.NET 6 API

另一种开发.NET 6 的方法是构建一个使用像 AWS ECS 或 AWS App Runner 这样的容器服务部署的微服务。这两种方法都提供了一种高效的方式来部署 API,几乎不费吹灰之力。要开始,请首先在 Cloud9 中创建一个新的 Web API 项目:

dotnet new web -n WebServiceAWS

在 Cloud9 环境中运行此代码将生成以下输出:

ec2-user:~/environment/dot-net-6-aws (main) $ dotnet new web -n WebServiceAWS
The template "ASP.NET Core Empty" was created successfully.
Processing post-creation actions...
Running 'dotnet restore' on ...WebServiceAWS/
Determining projects to restore...
Restored /home/ec2-user/environment/dot-net-6-aws/WebServiceAWS/
Restore succeeded.

让我们通过添加稍微复杂的路由来改变从dotnet工具生成的默认代码,以进一步理解构建容器化 API 的过程。你可以在 ASP.NET Core这里找到更多关于路由的信息。请注意,这段代码与其他高级语言如 Node、Ruby、Python 或 Swift 非常相似:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/", () => "Home Page");
app.MapGet("/hello/{name:alpha}", (string name) => $"Hello {name}!");
app.Run();

现在您可以通过进入目录并使用dotnet run来运行此代码:

cd WebServiceAWS && dotnet run

在 AWS Cloud9 中输出看起来像这样。请注意,看到 Cloud9 环境的完整内容根路径是多么有帮助,这样可以轻松地托管多个项目并在它们之间切换:

ec2-user:~/environment/dot-net-6-aws (main) $ cd WebServiceAWS && dotnet run
Building...
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7117
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5262
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
      Content root path: /home/ec2-user/environment/dot-net-6-aws/WebServiceAWS/

您可以在图 5-7 中看到输出;请注意,您可以在代码旁边并排切换终端。

通过dotnet命令行界面进行此测试。有两个独立的curl命令:第一个curl命令调用主页,第二个curl命令调用路由/hello/aws

注意

“HTTP”URL 在两个curl命令中都可以工作,但“HTTPS”将返回无效的证书问题。

doac 0507

图 5-7. Cloud9 与 ASP.NET

项目在本地工作后,让我们继续将代码容器化。

为项目容器化

现在让我们将项目转换为使用注册到 Amazon ECR 的容器。一旦注册到服务支持容器的地方,我们的代码将部署到服务上。我们的示例是 AWS App Runner,但也可以是 Amazon ECS、Amazon EKS 或 Amazon Batch 等 AWS 上的许多容器服务。为此,请在项目文件夹中创建一个 Dockerfile:

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)
WORKDIR /src
COPY ["WebServiceAWS.csproj", "./"]
RUN dotnet restore "WebServiceAWS.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "WebServiceAWS.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "WebServiceAWS.csproj" -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 8080
ENV ASPNETCORE_URLS=http://+:8080
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "WebServiceAWS.dll"] ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)

1

请注意,此容器拉取了.NET 6 运行时,配置了正确的端口并构建了项目。

2

最后,为.dll创建一个入口点。

现在使用以下命令构建此容器:

docker build . -t web-service-dotnet:latest

您可以使用docker image ls查看容器。输出应该类似于以下内容:

web-service-dotnet  latest  3c191e7643d5   38 seconds ago   208MB

要运行它,请执行以下操作:

docker run -p 8080:8080 web-service-dotnet:latest

输出应该类似于以下结果:

 listening on: {address}"}}
{"EventId":0,"LogLevel":"Information","Category"..}
{"EventId":0,"LogLevel":"Information","Category"...
...{contentRoot}"}}

现在通过 curl 调用它:curl http://localhost:8080/hello/aws 如 图 5-8 所示。请注意,AWS Cloud9 提供了一个简单但功能强大的云开发环境,具有专门为在 AWS 平台上开发而设计的特色功能。

doac 0508

图 5-8. 容器化的 .NET 6 Web API

接下来,让我们讨论 ECR 及其如何在 AWS 上启用许多新的工作流程。

Amazon Elastic Container Registry

在容器新世界的一个关键组成部分是一个针对您使用的云进行优化的容器注册表。它安全地允许快速部署深度集成的云服务。Amazon Elastic Container Registry(ECR)具有强大的容器策略所需的核心服务,如 图 5-9 所示。

doac 0509

图 5-9. Amazon ECR

ECR 使得像在 Cloud9(或 CloudShell)中开发,然后通过 AWS CodeBuild 自动推送容器到 ECR(Elastic Container Registry)这样的工作流程成为可能。这个构建过程触发了一个连续交付管道到 AWS App Runner,如 图 5-10 所示。

doac 0510

图 5-10. Amazon ECR 到 App Runner 架构

通过转到 AWS 控制台并搜索 ECR 创建一个新的 ECR 仓库。您可以如 图 5-11 所示创建一个新的仓库来使用此 ECR 服务。

doac 0511

图 5-11. 创建 ECR 仓库

接下来,点击仓库(位于 web-service-aws 仓库页面右上角),查找推送此容器到 ECR 所需的命令,如 图 5-12 所示。

注意

这些命令可以轻松集成到 AWS CodeBuild 管道中,以供稍后进行持续交付,方法是将它们添加到 buildspec.yml 文件,并创建一个新的 AWS CodeBuild 管道,该管道与 GitHub 或 AWS CodeCommit 等源仓库通信。

接下来,在您的本地 AWS Cloud9 环境中运行 ECR 推送命令。它们会与 图 5-12 类似。我们将此仓库命名为 web-service-aws,在推送到 ECR 的构建命令中反映出来。

doac 0512

图 5-12. 推送到 ECR 仓库

现在,如 图 5-13 所示检查镜像。

doac 0513

图 5-13. 检查创建的镜像
注意

注意,AWS App Runner 服务名称无需链接到容器名称或 ECR 中的存储库。

有了 ECR 托管我们的容器,让我们讨论使用能够自动部署的服务。

App Runner

AWS App Runner 是一种引人注目的 PaaS 服务,它解决了一个复杂的问题,创建了一个安全的微服务,并将其简单化,如图 5-14 所示。它通过允许开发人员直接从 ECR 部署容器,为开发人员提供了便利。此外,它将监听 ECR 存储库,当在那里部署了新映像时,会触发 AWS App Runner 的新版本部署。

doac 0514

图 5-14. AWS App Runner

将一个容器化的.NET 6 Web API 从 Amazon ECR 部署为 AWS App Runner 微服务只需要很少的工作。首先,打开 AWS App Runner,并选择您之前构建的容器映像,如图 5-15 所示。

doac 0515

图 5-15. 选择 ECR 映像

接下来,选择部署过程,可以是手动或自动,如图 5-16 所示。通常,开发生产应用程序的开发人员会选择自动部署,因为它将使用 ECR 作为真实数据源设置持续交付。当初次尝试服务时,手动部署可能是最佳选择。

doac 0516

图 5-16. 选择 App Runner 部署过程
注意

请注意,在部署过程中使用了现有的 App Runner,它赋予了 App Runner 从 ECR 拉取映像的能力。如果您尚未设置 IAM 角色,请选择该复选框以创建新的服务角色。您可以参考官方 App Runner 文档详细了解您的设置选项。

现在选择容器公开的端口;这将与.NET 6 应用程序的 Dockerfile 配置的端口匹配。在我们的案例中,这是8080,如图 5-17 所示。

doac 0517

图 5-17. 选择 App Runner 端口
注意

注意,此配置使用了默认设置。您可能希望配置许多选项,包括设置环境变量、健康检查配置和自动缩放配置。您可以参考最新文档了解如何执行这些操作的详细信息。

最后,在创建服务后,请如图 5-18 所示观察服务。这一步显示了服务正在部署,我们可以通过观察事件日志逐步查看其激活过程。

doac 0518

图 5-18. 观察 AWS App Runner 服务
注意

服务首次部署后,您可以通过选择部署按钮手动“重新部署”应用程序。在 ECR 的情况下,这将手动部署存储库中的最新映像。同样,任何对 ECR 的新推送将由于自动部署配置的启用而触发该映像的重新部署。

服务运行后,切换到 AWS CloudShell 并在 CloudShell 或 Cloud9 终端中运行以下 curl 命令来调用 API,如 图 5-19 所示。您还可以从支持 curl 命令和 Web 浏览器的任何终端调用 API。

doac 0519

图 5-19. curl 运行服务
注意

您还可以观看从零开始容器化 .NET 6 应用程序的演示,链接在 YouTubeO’Reilly。该项目的源代码位于 此存储库

使用 Amazon ECS 管理容器服务

处理容器时的一个重要考虑因素是它们运行的位置。例如,在您的桌面或像 Cloud9 这样的云开发环境中,启动容器并使用 Docker Desktop 等工具进行实验非常简单。然而,在现实世界中,部署变得更加复杂,这正是 AWS 托管容器服务在创建强大的部署目标中发挥重要作用的地方。

在 AWS 平台上提供的两种全面的端到端解决方案,用于管理规模化容器的选项是 Amazon 弹性 Kubernetes 服务(Amazon EKS)和 Amazon 弹性容器服务(Amazon ECS)。接下来让我们详细讨论自家研发的 Amazon 解决方案 ECS。

Amazon ECS 是一个完全托管的容器编排服务,是计算选项的中心枢纽,如 图 5-20 所示。从存储构建的容器镜像的 ECR 开始,ECS 服务允许使用容器镜像定义应用程序,并结合计算选项。最后,ECS 根据 AWS 最佳实践无缝扩展您的应用程序,例如弹性和可用性。

doac 0520

图 5-20. ECS

对于 .NET 开发人员,有两种常见的 ECS 部署方式。第一种是 AWS Copilot,第二种是 AWS .NET 部署工具。较新的 .NET 部署工具的优势在于它还可以部署到 App Runner 和 Beanstalk。

此外,ECS 支持三个关键用例。让我们详细解释一下:

混合场景

使用 Amazon ECS Anywhere 构建容器,并在任何地方运行。

批处理场景

在 AWS 服务中协调批处理,包括 EC2、Fargate 和 Spot 实例。

扩展 Web 场景

使用 Amazon 最佳实践构建和部署可扩展的 Web 应用程序。

注意

Amazon ECS 支持 Linux 以及 Windows 容器。请注意以下关于 Windows 容器的要点:首先,它们支持使用 EC2 和 Fargate 启动类型的任务。此外,并非所有适用于 Linux 容器的任务定义参数都适用于 Windows 容器。最后,Windows 容器实例所需的存储空间比 Linux 容器多。

开始使用 ECS 的最佳方式是通过 .NET 部署工具 AWS .NET deployment tool for the .NET CLI。让我们列举此工具的关键功能:

无服务器部署

使用此工具通过 AWS Fargate 部署到 AWS Elastic Beanstalk 或 Amazon ECS。

云原生 Linux 部署

此实现部署基于 .NET Core 2.1 及更高版本并面向 Linux 的云原生 .NET 应用程序。

部署实用 .NET 应用程序

许多 .NET 实用程序具有部署功能,包括 ASP.NET Core Web 应用程序、Blazor WebAssembly 应用程序、长时间运行的服务应用程序和定时任务。

注意

AWS Fargate 是一种技术,您可以与 Amazon ECS 一起使用,无需管理服务器或 Amazon EC2 实例的集群即可运行容器。有了这项技术,您不再需要为运行容器而提供、配置或扩展虚拟机集群。

让我们使用 dotnet aws deploy 部署到 ECS Fargate。我们可以利用 AWS Cloud9 和 Blazor 进行这项工作。首先,让我们更新工具,确保启用最新版本的部署工具。由于此工具正在积极开发中,经常使用以下命令进行更新是最佳实践:

dotnet tool update -g aws.deploy.tools

现在观察完整的软件开发生命周期,如 图 5-21 所示。

doac 0521

图 5-21. ECS 和 Cloud9 软件开发生命周期

要创建新的 Blazor 应用程序,请使用以下命令:

dotnet new blazorserver -o BlazorApp -f net6.0

接下来,进入 Blazor 目录:

cd BlazorApp

您可以通过以下 dotnet 命令在端口 8080 上运行应用程序:

dotnet run --urls=http://localhost:8080

选择在应用程序运行时如 图 5-22 所示的预览功能,以在 IDE 中作为网页查看它。

doac 0522

图 5-22. Cloud9 中的 Blazor
注意

注意 AWS Cloud9 使用端口 8080、8081 或 8082 进行 预览

现在我们知道应用程序在本地运行正常,让我们在部署到 AWS 之前通过 Cloud9 IDE 编辑 Index.razor 页面为以下内容:

@page "/"
<PageTitle>Index</PageTitle>
<h1>Hello, AWS dotnet aws deploy!</h1>
Welcome to your new app.

另外,在项目目录中创建一个包含以下内容的 Dockerfile。此步骤允许自定义 ECS 运行时:

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["BlazorApp.csproj", "./"]
RUN dotnet restore "BlazorApp.csproj"
COPY . .
RUN dotnet publish "BlazorApp.csproj" -c Release -o /app/publish
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
WORKDIR /app
COPY --from=build /app/publish
ENTRYPOINT ["dotnet", "BlazorApp.dll"]

最后,在完成这些步骤之后,现在是时候在新的 Cloud9 终端中使用以下命令部署到 ECS Fargate:

dotnet aws deploy

在提示时,您会看到几个选项,应选择与 ASP.NET Core App to Amazon ECS using Fargate 相关的数字,如下面(截断)的代码输出所示。根据您环境的条件,数字可能会有所不同,因此请选取与 Fargate 相关的数字。

Recommended Deployment Option
1: ASP.NET Core App to Amazon ECS using Fargate...

对于接下来的提示,您应选择“Enter”以使用除 Desired Task Count: 3 外的默认选项,应将其更改为单个任务或 1. 此过程将启动容器推送到 ECR 并随后部署到 ECS。

注意

注意,当在云端开发环境中使用容器时,常见问题之一是容器空间不足。解决这个问题的一个简单粗暴的方法是定期使用命令docker rmi -f $(docker images -aq)删除所有本地容器镜像。

部署完成后,我们可以使用从deploy命令生成的 URL 测试应用程序,如图 5-23 所示。

doac 0523

图 5-23. Blazor 部署到 ECS Fargate
注意

您可以在YouTubeO'Reilly 平台上观看完整的部署过程演示。示例的源代码可以在GitHub上找到。

部署成功测试后,最好先列出部署信息,使用以下命令:dotnet aws list-deployments。接下来,您可以删除堆栈dotnet aws delete-deployment *<stack-name>*来清理您的堆栈。

注意

在将 Blazor 部署到 Fargate 时需要注意的一点是,您需要进行以下一项更改以避免出现错误:

  • 创建一个单一任务,而不是默认的三个任务。

  • 在 EC2 目标组中打开粘性

现在我们的 ECS 示例已经完成,让我们结束本章并讨论接下来的步骤。

结论

新技术为解决问题开辟了新的途径。云计算通过虚拟化提供了近乎无限的计算和存储能力,允许更复杂的技术在其上构建。其中之一就是容器技术,并且在 AWS 上有许多先进的服务集成。

新技术常常以非直观的方式打开新的工作方式。我们介绍了 AWS Cloud9 如何通过与 AWS 生态系统的深度集成,为使用容器提供了一种新而激动人心的工作方式。这种深度集成包括访问高性能的计算、存储和网络,超出了典型家庭或工作桌面提供的能力。您可能会发现 Cloud9 是传统 Visual Studio 工作流程的可靠补充,使您能够更高效地完成一些开发任务。

对于 .NET 开发者来说,掌握容器技术是最好的投资之一。本章介绍了容器的基础知识,并作为后续建立更复杂解决方案的基础。在下一章中,我们将进一步扩展这些主题,探讨在 AWS 上的 DevOps。涉及的 DevOps 主题包括 AWS Code Build、AWS Code Pipeline,以及如何与 GitHub Actions、TeamCity 和 Jenkins 等第三方服务器集成。在阅读该章之前,您可能需要通过批判性思维讨论和练习讨论。

批判性思维讨论问题

  • 如何管理容器镜像的大小?

  • 对于小型初创公司,哪种 AWS 容器服务是最好的选择?

  • 对于大量使用容器进行批量计算的大型公司来说,最佳的 AWS 容器服务是什么?

  • 使用 Amazon Linux 2 部署.NET 6 的优势是什么?

  • 使用 Amazon Linux 2 部署.NET 6 的劣势是什么?

练习

  • 将本章中构建的容器化项目通过 AWS CodeBuild 进行持续交付部署。⁷

  • 构建一个目标为.NET 6 的 AWS Lambda 容器,并将其部署到 AWS。

  • 使用 Cloud9 调用您部署的 AWS Lambda 函数。

  • 构建另一个使用.NET 6 和 Amazon Linux 2 的容器,并将其推送到 ECR。

  • 构建一个 Console App 命令行工具,目标为.NET 6,并使用 AWS SDK 调用 AWS Comprehend,并将其推送到公共 ECR 仓库。

¹ 在他的书 How Innovation Works: And Why It Flourishes in Freedom(HarperCollins)中,Matt Ridley 指出:“内燃机的故事展示了创新的典型特征:长期且深刻的前史,以失败为特征;短期则以经济性改进为特征,同时伴随专利和竞争。”

² Isaacson 指出,个人计算机创造的一个巨大推动力是对主机上更多时间的渴望。(Walter Isaacson. The Innovators: How a Group of Hackers, Geniuses, and Geeks Created the Digital Revolution. New York: Simon & Schuster, 2014.)

³ 根据AWS 的说法,一个实例重新启动通常需要“几分钟完成”。

⁴ 您可以在这篇AWS 博客文章中了解有关容器启动时间高级功能的更多信息。

⁵ 一个很好的例子是AWS Lambda Runtime Interface Emulator工作流程。根据 AWS 的说法,“Lambda Runtime Interface Emulator 是 Lambda 运行时和扩展 API 的代理,允许客户以容器映像打包的方式在本地测试他们的 Lambda 函数。”

⁶ AWS 根据服务提供多个级别的共享责任

⁷ 您可以参考buildspec.yml文件获取想法。

第六章:DevOps

行业专业人士共识认为,云计算使新的工作流程成为可能。例如,像无服务器计算这样的云原生解决方案以事件驱动的方式开辟了新的架构解决方案。同样,云计算的弹性能力支持虚拟化存储、网络和计算。DevOps 是一种将软件开发和运营最佳实践结合起来的理想方法,是利用这些新工作流程的理想方法之一。

本章的中心焦点是识别 DevOps 对充分利用云计算的重要性。它涵盖了在 AWS 上开始使用 DevOps 以及根植于日本文化中的支持 DevOps 的原则。

在 AWS 上开始使用 DevOps

在 AWS 上开始使用 DevOps 的理想方式是了解AWS 如何看待 DevOps:“结合了文化哲学、实践和工具的组合,提高组织交付应用程序和服务的能力。”在实践中,这意味着 AWS 提供了支持 DevOps 高速工作流程的托管服务。

在 DevOps 的表面下,有一个明确的历史趋势,即支持 DevOps 崛起的最佳组织实践。让我们接下来讨论这些概念。

DevOps 背后的原则是什么?

DevOps 的核心是日语词汇改善(Kaizen),意为“改进”或“改变为更好”。在《丰田方式》第二版(O’Reilly)中,杰弗里·K·利克提到,二战后,丰田开发了一个融合了这种改善哲学的精益生产系统。最终,这一哲学使丰田成为汽车制造质量的领导者之一。

丰田生产方式的核心原则之一是组装线工人将停止移动的生产线来修正异常。另一种描述这一过程的方式是计划-执行-检查-执行(PDCA)循环,如图 6-1 所示。首先需要确定问题;接下来尝试解决方案,分析结果,如果解决了问题则实施修正,或者重复整个 PDCA 过程。

doac 0601

图 6-1. 计划-执行-检查-执行生命周期

本质上,PDCA 是作为制造业务实践实施的科学方法。

注意

根据大英百科全书科学方法是“在科学中使用的数学和实验技术。更具体地说,它是用于构建和测试科学假设的技术。”

与 Kaizen 和科学方法相关的是在调试问题的根本原因时使用的“五个为什么”技术。这种技术的工作方式如下。首先,您确定一个问题。接下来,您问“为什么”,当您得到答案时,再次问为什么,直到最终通过第五次,您找到问题的根本原因并找到解决方案。 “五个为什么”的起源具有历史血统,可以追溯到丰田生产系统,并且它与持续改进系统的概念非常契合。在 图 6-2 中,AWS 的一个真实场景展示了五个调试阶段。

doac 0602

图 6-2. 使用“五个为什么”调试生产系统
注意

儿童在使用“五个为什么”技术时非常擅长,这就是为什么他们会问诸如“为什么天空是蓝色?”这样简单而实际的问题,接着是下一个问题。采用与儿童问问题方式相同的方式来使用“五个为什么”技术,是一个非常有效的方法来进入正确的思维框架。

请注意,一系列问题最终导致了一个相当简单的修复,即不同的机器配置,即使用 EBS 存储,启用交换分区,并配置 Java 进程的内存约束以匹配服务器上的资源。

正如您所见,DevOps 不是一夜之间发明的。DevOps 源自几个世纪以来对批判性思维的改进,从几个世纪前的科学方法到最近的改善和日本汽车工业的 Kaizen。在 DevOps 的核心是古老的科学方法的概念,即提出“为什么”。日本汽车工业将这一理念精炼为提出“为什么”的方法论,结合制造业中的持续改进。DevOps 是这一持续改进制造方法论在软件工程领域的进一步完善,现在非常适合云原生开发。现在我们知道 DevOps 的起源,让我们讨论 AWS 平台上的最佳实践。

AWS DevOps 最佳实践

开始使用 AWS 最佳实践的理想位置是 “在 AWS 上介绍 DevOps” 的 AWS 白皮书。内部列出了六个最佳实践:

持续集成(CI)

DevOps 的核心是持续集成系统。开发人员定期将更改合并到中央源代码存储库,在那里自动化测试运行在代码上。您可以在 图 6-3 中看到 CI 周围的工作流程。在一个环境中的开发人员,也许是笔记本电脑或 Cloud9 工作区,推送更改到源代码仓库,触发构建,测试代码,并允许其合并。稍后,第二个开发人员将这些改进拉入他们的新本地环境的检出。请注意这里与 Kaizen 的概念的联系,或者说持续改进,因为每次构建服务器测试更改时,系统都可以提高源代码的质量。

doac 0603

图 6-3. 持续集成工作流

持续交付 (CD)

持续交付通过自动测试推送到仓库的软件并为发布到任意数量的环境做准备来构建在持续集成概念之上。在 图 6-4 中,您将看到由 CI 建立的基础。现在,随着 IaC 的添加,它可以自动部署基础设施和现有软件,整个系统可以无缝地部署到新环境,因为整个过程是自动化的。在 CD 工作流中,容器是部署的一个补充方面,因为它们与代码和基础设施的部署并行工作。改进的自动部署再次发挥了改进的作用。每次对源代码仓库的提交都会为系统添加改进,由于更改很容易进行,这鼓励频繁的小幅度增强。

doac 0604

图 6-4. 持续交付工作流

基础设施即代码 (IaC)

IaC 是软件开发的最佳实践,描述了将基础设施的规划和管理作为代码提交到仓库中。观察 图 6-5,IaC 工作流可以执行许多有价值的操作,超出了最初创建基础设施的范围。IaC 的一些用例包括进行幂等更改和通过删除整个堆栈高效地清理实验。

doac 0605

图 6-5. 基础设施即代码工作流
注意

Idempotent 是 DevOps 中经常使用的词汇,因为能够重复执行相同的操作并始终保持相同状态非常重要。一个杰出的 idempotent 部署流程的例子是 IaC 创建具有只读权限的 S3 存储桶。无论这段代码运行多少次,结果都将是相同的:一个具有只读权限的 S3 存储桶。

idempotent actions 的概念在 DevOps 中非常重要,因为自动化的敏捷工作流依赖于无论运行多少次都具有相同效果的自动化操作。一个出色的数学基础的例子就是将一系列数字乘以零。结果始终为零,无论乘以零的是什么数字。

监控和日志

用于软件系统的数据科学是思考监控和日志的独特方式。使用基础设施和部署的应用程序的数据来确定需要采取的操作是非常重要的。在 图 6-6 中,服务器发送系统级和应用程序级日志、指标和来自监控代理的数据到 AWS CloudWatch,数据在那里被集中并分发到仪表板、警报、搜索和自动化见解。

doac 0606

图 6-6. 监控和日志是软件系统的数据科学

通信与协作

DevOps 是一种行为,而不是一个要在清单上勾选的特定任务。因此,当团队通过沟通和协作来实施 DevOps 实践时,会产生最佳结果。在图 6-7 中,我们看到沟通嵌入到 DevOps 生命周期的每一个步骤中,从代码本身及其周围的对话,到从生产系统发出的警报进入聊天频道。还要注意可能的人类互动,例如拉取请求、推送到生产环境以及在生产中监控应用程序。

doac 0607

图 6-7. DevOps 的沟通和协作

安全性

安全需要在构建软件系统的每个级别进行集成。此外,持续集成和交付系统需要严格的访问控制治理,因为它们将软件交付到生产环境。在图 6-8 中,请注意在 AWS 云上充分架构的系统中的多层安全性。这个系统包括了防火墙规则分层到 VPC 中,以防止未经授权的网络访问,并且利用最小特权原则来控制策略,以保护系统安全。数据传输和静态数据的加密加固了环境,以防止数据泄露。最后,通过 AWS CloudTrail 审计所有安全事件,并为访问 AWS 控制台添加了双因素认证,进一步增强了保护措施。

doac 0608

图 6-8. DevOps 安全集成

在 AWS 云上设计现代解决方案时,这些核心的 DevOps 原则至关重要。让我们更深入地探讨 AWS 平台上特定的 CI/CD(持续集成和持续交付)服务。

在 AWS 上使用 CI/CD 进行开发

AWS 管理的多个服务处理 CI/CD,但其中两个关键服务是AWS CodeBuildCodePipeline。让我们深入了解它们的工作方式。

AWS 代码部署服务

AWS CodePipeline、AWS CodeBuild、AWS CodeCommit 和 AWS CodeDeploy 是 AWS 上深度集成的服务,并包括互补的工作流程。AWS CodePipeline 是一个完全自动化软件发布的持续集成和持续交付(CI/CD)托管服务。AWS CodeBuild 是一个完全托管的构建服务,处理构建过程的各个组成部分,包括测试、构建和发布软件包。AWS CodeDeploy 是一个托管服务,自动化代码部署到任何实例,包括 EC2 实例或本地服务器。最后,AWS CodeCommit 是一个完全托管的代码托管服务,类似于 GitHub 或 GitLab。

让我们看一下 AWS CodePipeline 在图 6-9 中的流程,并注意它是如何从左到右流动的:源码、构建、测试、暂存、然后是生产。这个工作流程完整地包括了运行在 AWS 上的项目的生命周期。

doac 0609

Figure 6-9. CodePipeline 工作流程

接下来,如果你在 AWS 控制台中输入 CodePipeline,弹出的界面如 Figure 6-10 所示,将这些真实世界的步骤映射到 AWS 平台上软件部署过程中的不同阶段。

doac 0610

Figure 6-10. CodePipeline 界面

既然我们已经简要介绍了使用 AWS CodeBuild 在 Chapter 5 中持续交付 .NET 6 应用程序,让我们看看如何使用 AWS CodeBuild 持续部署 Hugo 网站。AWS 是通过 Amazon S3、Amazon Route 53 和 Amazon CloudFront 托管静态网站的常见部署目标,如 Figure 6-11 所示。AWS CodeBuild 非常适合这些站点的部署机制。你可以登录 AWS CodeBuild,设置一个新的构建项目,并告诉它使用 buildspec.yml

注意

Hugo 是一种独特的静态网站托管技术,用 Go 编程语言 编写,每页构建速度 <1ms。你不需要使用 Go 来使用 Hugo;你可以用 Markdown 语言 写网站页面。Hugo 的构建速度和在 Markdown 中编写页面的便利性使其成为 S3 静态网站的优秀技术选择。

一旦 GitHub 接收到变更事件,CodeBuild 就会在容器中运行安装:

  1. 它会获取在 buildspec.yml 中指定的 Hugo 版本。

  2. 它会构建 Hugo 页面。由于 Go 语言的速度快,数千个 Hugo 页面可以在亚秒内渲染完毕。

  3. HTML 页面会同步到 Amazon S3。

因为这个同步过程在 AWS 内部运行,所以速度也非常快。

doac 0611

Figure 6-11. Hugo 在 AWS 上的持续部署

下面是一个 AWS buildspec.yml 的更模板化版本,你可以用适合你项目的值替换模板化的值:

version: 0.1

environment_variables:
  plaintext:
    HUGO_VERSION: "0.42"

phases:
  install:
    commands:
      - cd /tmp
      - wget https://github.com/gohugoio/hugo/releases/\
      download/v${HUGO_VERSION}/hugo_${HUGO_VERSION}_Linux-64bit.tar.gz
      - tar -xzf hugo_${HUGO_VERSION}_Linux-64bit.tar.gz
      - mv hugo /usr/bin/hugo
      - cd -
      - rm -rf /tmp/*
  build:
    commands:
      - rm -rf public
      - hugo
  post_build:
    commands:
      - aws s3 sync public/ s3://<yourwebsite>.com/ --region us-west-2 --delete
      - aws s3 cp s3://<yourwebsite>.com/\
      s3://<yourwebsite>.com/ --metadata-directive REPLACE \
        --cache-control 'max-age=604800' --recursive
      - aws cloudfront create-invalidation --distribution-id=<YOURID> --paths '/*'
      - echo Build completed on `date`
注意

你可以在 YouTube 上观看完整的 Hugo 持续交付过程的视频 here,也可以在 Pragmatic AI Labs 的 Hugo 网站上查看相关笔记 website

现在我们对纯 AWS 构建解决方案有了更多了解,让我们讨论一下如何在 AWS 上使用第三方构建服务器来处理 .NET。

集成第三方构建服务器

不仅可以使用 AWS 构建服务器构建和部署 .NET 到 AWS,而且还支持包括 JenkinsAzure DevOpsGitHub Actions 在内的第三方构建服务器,让我们主要关注 GitHub Actions,因为它是最广泛使用的托管构建服务。

注意

你可以观看在 YouTube 上设置 C# xUnit 项目与 GitHub Actions 的视频教程。

在使用 GitHub Actions 构建解决方案时,一个理想的代码解决方案是使用 GitHub CodeSpaces,如在 Figure 6-12 中所示。仓库的代码位于这里,通过选择绿色的 Code 按钮,我们可以启动一个具有干净的 Visual Studio 界面的 16 核工作区。

doac 0612

Figure 6-12. GitHub CodeSpaces
注意

GitHub CodeSpaces 是一项付费服务,允许在基于 web 的开发环境中进行开发。如果你的组织无法访问此服务,一个替代选择是 AWS Cloud9,它具有许多相似的功能,但优势在于深度集成 AWS。

注意,我们在路径 .github/workflows 中创建了一个名为 dotnet.yml 的文件,其中包含了构建和测试项目的整个工作流程,如 Figure 6-13 所示。

doac 0613

Figure 6-13. GitHub CodeSpaces 工作流程

dotnet.yml 显示的关键步骤是恢复依赖项、构建项目,然后测试项目:

name: .NET
on:
  push:
    branches: [ "main" ]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
    - name: Setup .NET
      uses: actions/setup-dotnet@v2
      with:
        dotnet-version: 5.0.x
    - name: Restore dependencies ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)
      run: dotnet restore
    - name: Build ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)
      run: dotnet build --no-restore
    - name: Test ![3](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/3.png)
      run: dotnet test --no-build --verbosity normal

1

恢复依赖项。

2

构建项目。

3

测试项目。

要为项目创建结构,首先创建一个目录并 cd 进入其中:

mkdir HelloTests && cd HelloTests

接下来,使用 dotnet new xunit 创建项目。最后,将以下代码块粘贴到你的源代码文件中。让我们分析一下代码的功能:

using Xunit;

namespace MyFirstUnitTests
{
    public class UnitTest1
    {
 [Fact] ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)
        public void PassingTest()
        {
            Assert.Equal(4, Add(2, 2));
        }
        int Add(int x, int y) ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)
        {
            return x + y;
        }
    }
}

1

[Fact] 块是测试 add 函数的单元测试

2

存在一个名为 Add 的“内联”方法,我们在其上运行测试。

要运行此项目,执行以下操作:

  1. 安装依赖项:dotnet restore

  2. 构建项目:dotnet build --no-restore

  3. 测试项目:dotnet test --no-build --verbosity normal

你可以在 Figure 6-14 中看到最终的输出,显示了 GitHub Actions 的成功运行。整个工作流程之所以如此有用,是因为它如何轻松地为项目添加步骤,比如部署到 AWS。AWS 的一篇有帮助的博文展示了如何从 GitHub Actions 部署代码到 AWS 的详细示例。

doac 0614

Figure 6-14. GitHub Actions 构建过程
注意

还值得注意的是,许多 AWS 服务或工具都内置了自动流水线:

AWS App Runner

AWS App Runner 具有从 GitHub 自动部署的功能。当你将 App Runner 连接到你的代码仓库或容器镜像注册表时,App Runner 可以在更新源代码或容器镜像时自动构建和部署你的应用程序。

AWS Copilot

AWS Copilot可以为您配置多个部署环境,如测试和生产环境。此外,Copilot 还可以设置 CI/CD 管道以自动部署。

合作伙伴产品集成

值得注意的是,有许多优秀的第三方合作伙伴产品选择,包括 Jenkins、TeamCity、Azure DevOps 和 Terraform。强调 AWS CodeDeploy 与合作伙伴产品集成的最佳地方是AWS 文档的“与合作伙伴产品和服务集成”部分。以下是突出显示的资源:

Jenkins

Jenkins 是一个开源的构建系统的瑞士军刀,并且 AWS 提供出色的支持。Jenkins 的一个关键优势是能够在 AWS 上挂载网络文件系统,并将其与您的构建和部署流程集成。您可以阅读有关如何使用AWS App2Container 设置 CI/CD 流水线,以及如何使用AWS CodeBuild 与 Jenkins 集成

TeamCity

TeamCity 是许多经验丰富和新手.NET 开发人员喜爱的经典构建系统。使用 TeamCity 的团队可以使用AWS CodeDeploy Runner 插件直接部署到 AWS。

Azure DevOps

AWS App2Container 已与Microsoft Azure DevOps 集成AWS Toolkit for Azure DevOps允许您在不离开现有的构建/发布管道的情况下部署.NET 代码到 AWS。

Terraform

HashiCorp 已经与AWS CodeDeploy集成,使开发人员不仅可以使用Terraform CDK in C#,还可以使用像Consul这样的产品。

现在您已经了解了如何与包括 GitHub Actions 在内的第三方合作伙伴集成进行测试,让我们来讨论 IaC。

在 AWS 上进行 IaC 开发

IaC 是定义基础架构并进行维护的代码。在 AWS 平台上,容器和 IaC 是互补的技术。请注意,在图 6-15 中,GitHub 包含项目的关键元素,包括构建系统文件、IaC 文件、源代码和 Dockerfile。

doac 0615

图 6-15. AWS 风格的容器化 DevOps

在许多场景中,您可以在单个存储库中定义容器化微服务的所有元素,从而便于在本地或新环境中进行调试和构建。IaC 使部分工作流程变得可能。让我们来谈谈 AWS 的 IaC 解决方案 Cloud Development Kit (CDK)如何帮助实现这一点。

在 C#中使用 AWS CDK 开发

AWS CDK 是由 AWS 支持的开源项目。它提供了许多好处,包括更快的开发和丰富的示例。在 AWS CDK 中,部署的基本单位称为堆栈。例如,要创建代表“开发”和“生产”环境的两个堆栈,可以使用以下 C#代码:

var app = new App();

new MyFirstStack(app, "dev");
new MySecondStack(app, "prod");

app.Synth();

要合成一个堆栈,您可以运行 cdk synth dev。在幕后,这将创建 CloudFormation 模板

注意

值得注意的是,构造 是 AWS CDK 应用程序的基本构建块,包含构建资源(比如 S3 存储桶)所需的一切。构造中心包含 600 多个.NET CDK 构造,并且是构建高效解决方案的推荐资源,符合 AWS 最佳实践。

您可以查看在 Figure 6-16 中定义的概念。CDK 的核心思想是编写代码,然后将其转换为基础设施,因为基础设施是一种虚拟资源。请注意,C#语言编译成CloudFormation,然后为资源提供支持。

doac 0616

图 6-16. CDK 架构
注意

AWS CloudFormation 是一种基础设施即代码(IaC)的形式,允许您通过将基础设施视为 JSON 或 YAML 中编写的代码来管理 AWS 资源。一些开发者更喜欢 CDK 而不是常规的 CloudFormation,因为它需要更少的代码来构建相同的解决方案。此外,您可以使用您喜爱的语言,比如 C#,来创建解决方案。这篇博客文章是使用 CloudFormation 和.NET 的完美示例。

在实际操作中,开发者可以使用 CDK 的两种方法之一。第一种方法是使用 C#编写 CDK,其中包括许多精彩的示例在.NET Workshop 页面上。第二种方法是使用高级抽象,它为您生成 CDK 代码,比如 dotnet aws deploy

AWS 部署工具中的一个例子是部署Blazor WebAssembly 应用程序。关键思想是您必须运行 dotnet aws deploy

例如,在您的开发环境中,您可以执行以下操作:

  1. 安装或更新 dotnet AWS 部署工具:dotnet tool install -g aws.deploy.tools

  2. 创建一个新的 Blazor WebAssembly 应用程序:dotnet new blazorserver -o BlazorApp --no-https && BlazorApp

  3. 最后,通过运行命令 dotnet aws deploy 进行部署。

您可以参考Github上的最新文档,了解使用此部署过程的最新选项。

注意

除了 CDK 之外,还有其他 IaC 解决方案。其中较为流行的之一是Terraform,它具有支持 C#的 CDKTF 或云开发工具包。另一个解决方案提供者是 Pulumi,你可以在这里找到一个关于如何发布C# Lambda 的很好的例子

最后要指出的是,一些 AWS 工具可以自动创建和部署 CDK 项目。例如,AWS .NET CLI 的部署工具和 AWS Toolkit for Visual Studio 的Publish to AWS 功能。此外,通过CDK 部署项目,您可以添加额外的 AWS 资源,如 Amazon SQS 队列、Amazon DynamoDB 表等。

现在我们对基础设施即代码有了一个概述,让我们总结一下我们在本章中涵盖的所有内容。

结论

本章涵盖了 DevOps 的历史起源,包括日本汽车工业持续改进的历史。现代 DevOps 的核心是对云的拥抱。云计算使自动化、测试和部署速度进一步耦合。一个这种集成的例子是 IaC,这是 DevOps 工作流的完美工具。DevOps 还通过在软件工程的生命周期关键点上进行人类交互,从代码审查通过拉取请求到与发布经理在生产软件发布上的工作,实现了最佳化。

我们还涵盖了 AWS 如何看待 DevOps 以及在 AWS 上的 DevOps 最佳实践。然后我们使用了构建系统,如 AWS CodeBuild 和第三方系统,如 GitHub Actions。AWS 对每个组件有紧密集成,并可以替换任何第三方工具,如果您的组织选择或与其集成。

最后,我们以 IaC 结束了这一章,这是 DevOps 基础设施自动化的重要工具。我们展示了如何使用 AWS .NET 部署工具对静态网站进行一行命令部署到 AWS S3。这个工具将 CDK 集成并将其作为自动化生命周期的一部分,功能非常强大。

本章的重要收获是 AWS 认真对待 DevOps,并提供一整套托管服务和最佳实践,使您能够构建可维护和敏捷的解决方案。接下来在第七章,我们将讨论.NET 的日志记录、监控和仪表化,这进一步建立在我们对 DevOps 的基础知识之上。在转向该章之前,尝试一些批判性思维的问题和例子,以进一步巩固您对 DevOps 的理解。

批判性思维讨论问题

  • 你对 DevOps 的定义是什么,以及如何利用它来增强组织的成果?

  • 使用.NET AWS CDK 框架定义 AWS 上的云应用资源的优势是什么?

  • 哪种 AWS 部署策略 最符合您组织的最佳工作方式?

  • 为任何 AWS 部署使用 AWS CloudTrail 的重要性是什么?

  • 使用 AWS CodeCommit 相比第三方源代码托管服务的优势是什么?

练习

  • 使用 AWS CodeBuild 部署一个静态 S3 站点,使用 AWS CDK in C#

  • 使用 GitHub Actions 为 .NET 6 项目设置持续集成工作流程,以便在提交后自动测试代码。

  • 使用 AWS CodeBuild 为 .NET 6 项目设置持续集成工作流程。

  • csharp 存储库中找到一个 CDK 应用程序示例,并将其部署到您的 AWS 环境。

  • 持续部署您自己的 Hugo 网站,并使用自己开发的 CMS 在 AWS 上发布关于 .NET 的博客。

第七章:用于.NET 的日志记录、监控和仪器设备

回到 2007 年,当人们正在安装他们的任天堂 Wii 并梦想拥有苹果革命性的新触摸屏手机时,詹姆斯作为一名毕业软件工程师开始了他的第一份工作。然而,当时他并不是在今天工作的基于 Web 的 SaaS 应用程序中工作,而是开始了一段为生产线、油井、游轮和军事编写工业控制软件的工程师职业生涯。在当时詹姆斯有机会参与的所有奇怪和令人兴奋的项目中,最有趣的是为过山车设计控制系统软件。让我们告诉你那是如何工作的。

这是一个主题公园游乐设施,有多辆装有四个人的汽车沿着典型的过山车轨道行驶。每辆汽车从车站发出,沿着链条驱动的斜坡爬升到轨道顶部,然后在重力作用下滚下来,形成激烈而令人兴奋,最重要的是安全的乘车体验。

操控过山车的代码非常简单:软件读取由乘车操作员按下的“GO”按钮的输入。然后,这会命令刹车释放,将车辆从车站发送到斜坡的起始区块。在那里,软件不再需要进一步的控制输入,车辆会被链条升降机制抓住,带到斜坡顶部,然后在轨道上发车。隐藏在轨道下的刹车则会重新装备,准备在车辆返回时轻轻停车。

当然,多辆汽车同时在过山车轨道上飞驰,很容易看出可能出现的问题。如果其中一辆卡住了呢?如果一辆车没有足够的动力爬到顶端,或者轨道上的故障导致它减速或停止了呢?多辆车在轨道上行驶可能会带来实际的危险,甚至可能危及人类生命。如果一辆车停下来,我们就必须停止所有的乘车,直到工程师被派出去修理问题。因此,我们在整个轨道上都安装了传感器和紧急制动装置。我们编写了代码来检测车辆进入轨道周围的“区块”,就像铁路信号系统防止碰撞那样运作。我们在一排中放置了多个传感器,以在轨道周围的多个点测量车辆的速度。¹ 每个速度传感器、运动检测器、紧急制动装置、备用紧急制动装置,所有这些都必须在软件中编码。所有这些代码的量远远超过了我们实际操作乘车所需的代码量。过山车控制系统上运行的代码中,95% 是监控、记录、报警和触发紧急制动。

您的分布式基于云的系统就像过山车一样。执行主要功能可能需要的代码量很少,但是可能出错的事情种类繁多,您可能需要为了维护对用户的高级别服务而将更多复杂性加入系统中。

在本章中,我们将讨论在 AWS 中可以构建、配置或简单启用的内容,这些内容将提高我们监控基于云的应用程序的能力。我们将访问的大多数服务并非特定于 .NET;然而,AWS 为我们即将涵盖的许多工具提供了 .NET SDK。这有助于您深入挖掘 C# 源代码,以识别错误或性能瓶颈的原因。我们将从介绍在管理控制台上最重要的页面开始,当涉及到日志记录和监控时:CloudWatch。

AWS CloudWatch

AWS CloudWatch 是 AWS 提供的一个允许您监控、分析和响应由您的 AWS 资源生成的事件的服务(或者说一系列服务)。如果您在 AWS 上部署了一些代码并运行它们,您可能会使用 CloudWatch 进行监控。图 7-1 展示了 AWS CloudWatch 的四个核心支柱:收集、监控、响应和分析。这些支柱共同工作,使您能够迭代地提高系统的可用性和可伸缩性。

doac 0701

图 7-1. AWS CloudWatch 的四个支柱

您可以收集 AWS 服务发出的日志事件,通过仪表板和指标监控它们,通过警报响应异常情况,并定期分析您的应用程序,以进行架构改进。这四个支柱在 图 7-1 中以一行显示;然而,更准确地说它是一个反馈循环。对日志和指标的分析将允许您改进收集日志的内容,并精调何时触发警报,从而引导您对资源进行更改。这样做的好处是确保系统在最有效的资源余量下运行。您将能够分配足够的资源以保持性能在期望范围内,但不会过度增加成本。

收集 CloudWatch 日志

CloudWatch 从您部署到 AWS 的服务中收集和存储日志消息。许多 AWS 服务会原生地发布包含有关该服务执行信息的 CloudWatch 日志消息。您也可以手动设置日志收集,或者如本章所示,可以使用 AWS .NET SDK 程序化地发布日志消息。

一些原生发布日志到 CloudWatch 的 AWS 服务包括:

API 网关

可配置为发送错误、请求和响应参数、载荷和执行跟踪。

弹性 Beanstalk

您的 Elastic Beanstalk 应用程序的应用程序和系统日志文件可以在 CloudWatch 中读取。

AWS CodeBuild

发送所有云构建的完整详细构建日志。

Amazon Cognito

认证和用户管理指标可以发送到 CloudWatch。

Route 53

亚马逊的域名系统(Domain Name System,DNS)服务可配置记录 DNS 查询等内容。

AWS Lambda

Lambda 函数会自动设置发送指标和执行日志到 CloudWatch。

简单通知服务(Simple Notification Service,SNS)

从 SNS 发送的手机短信(SMS)会自动记录。

这些只是一些示例;AWS 的绝大多数服务都会发布日志消息,要么完全自动,要么稍作配置。要查看可以发布日志到 CloudWatch 的 AWS 服务的完整列表,请访问 AWS 文档页面 “AWS services that publish logs to CloudWatch”。接下来,我们将详细探讨其中一些服务的日志记录。

来自 AWS Lambda 函数的日志

在 “使用 AWS Lambda 和 C# 开发” 的示例中,我们使用 .NET CLI 创建了一个新的 AWS Lambda 函数:

dotnet lambda deploy-function SingleCSharpLambda

如果你遵循这个步骤,你可能已经注意到,除了创建函数外,CLI 还创建了一个 IAM 执行角色,Lambda 函数将在该角色下执行。如果你在 IAM 管理控制台导航到这个执行角色,你会看到默认权限策略之一是 AWSLambdaBasicExecutionRole.² 这个角色由 AWS 管理,旨在为任何新的 Lambda 函数授予权限,以创建日志组、创建流并将日志消息发布到 CloudWatch。Figure 7-2 展示了此策略中包含的策略 JSON。

任何发布日志消息到 AWS CloudWatch 的服务必须在授予这些权限的 IAM 角色下运行。通过它们的名称,这些权限向我们介绍了 CloudWatch 日志记录中的三个重要概念。

doac 0702

图 7-2. 每个新 Lambda 函数添加的 CloudWatch 权限

组、流和事件

CloudWatch 将日志消息(或“事件”)存储在流中,然后按发送它们的服务或实例对每组流进行分组。例如,如果我们记录来自 Lambda 函数执行的消息,则每次 Lambda 调用的事件流将写入同一个 日志流。多个并发的 Lambda 函数调用将创建单独的日志流;然而,这些流仍将被分组在同一个 日志组 下。在这种情况下,日志组对应于单个 Lambda 函数。因此,在 CloudWatch 控制台中,您将为每个 Lambda、EC2 实例、S3 存储桶、CodeBuild 项目或任何您正在记录的服务拥有一个日志组。在每个日志组下将有多个流,每个流包含多条消息。图 7-3 展示了 AWS Lambda 函数调用的日志消息。您可以通过 CloudWatch 管理控制台中的“日志”部分浏览组、流和日志。如果安装了 AWS Toolkit for Visual Studio,还可以直接在 Visual Studio 中查看 CloudWatch 日志。在 AWS Explorer 窗口中导航到 CloudWatch Logs,右键单击日志组,然后选择“查看日志流”。

doac 0703

图 7-3. (1) CloudWatch 日志组,(2) 日志流,和 (3) 日志事件消息

现在我们对 CloudWatch 日志事件的存储和访问有了一些了解,让我们看看如何利用这一点来使用 CloudWatch 保存应用程序中的自定义日志消息。

发送 C# 日志

默认情况下,AWS Lambda 将转发对 Console.WriteLine() 的所有调用到 CloudWatch,但是您还可以通过 AWSSDK.CloudWatchLogs NuGet 包直接从我们的 C# 应用程序向 CloudWatch 推送日志,该包是 AWS SDK for .NET 的一部分。安装该包后,创建一个新的 .NET 6 控制台应用程序来记录测试消息,如 示例 7-1 所示。

示例 7-1. Program.cs
using TestCloudWatchLogPublishing;

await using (var logger = await CloudWatchLogger.CreateNew())
{
    logger.WriteLine("Developing");
    logger.WriteLine("on AWS");
    logger.WriteLine("With C#!");
}

由于我们尚未创建 TestCloudWatchLogPublishing.CloudWatchLogger 类,因此编译将失败,但是您可以看到我们在这里所做的一切只是创建一个新的日志记录器实例并写入几行日志。此示例中的 using 块作为一种方便的方式,用于将我们的日志记录器实例限定在几行代码中。消息将被批处理,然后在调用最后一行的 DisposeAsync() 方法时刷新(发送到 CloudWatch)。³

这里是我们的 TestCloudWatchLogPublishing.CloudWatchLogger 类的代码,它将日志消息批处理并发送到 AWS CloudWatch:

using Amazon.CloudWatchLogs;
using Amazon.CloudWatchLogs.Model;

namespace TestCloudWatchLogPublishing;

public class CloudWatchLogger : IAsyncDisposable
{
    const string LogGroup = "/my-app/process/test-process"; ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)

    private readonly AmazonCloudWatchLogsClient _client;
    private readonly string _logStreamName;
    private readonly List<InputLogEvent> _logs = new List<InputLogEvent>(); ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)

    public CloudWatchLogger(AmazonCloudWatchLogsClient client, string name)
    {
        _client = client;
        _logStreamName = name;
    }

    public async static Task<CloudWatchLogger> CreateNew()
    {
        var client = new AmazonCloudWatchLogsClient();

        var logStreamName = DateTime.UtcNow.ToString("yyyy/MM/dd/")
                            + Guid.NewGuid().ToString(); ![3](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/3.png)

        await client.CreateLogStreamAsync(new CreateLogStreamRequest ![4](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/4.png)
        {
            LogGroupName = LogGroup,
            LogStreamName = logStreamName
        });

        return new CloudWatchLogger(client, logStreamName);
    }

    public void WriteLine(string message)
    {
        _logs.Add(new InputLogEvent
        {
            Message = message,
            Timestamp = DateTime.Now
        });
    }

    public async ValueTask DisposeAsync()
    {
        await _client.PutLogEventsAsync(new PutLogEventsRequest ![5](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/5.png)
        {
            LogEvents = _logs,
            LogGroupName = LogGroup,
            LogStreamName = _logStreamName
        });
    }
}

1

在我们的示例中,日志组名称只是硬编码的。在部署此代码到 AWS 期间,我们很可能希望通过环境变量来创建一个新的日志组。

2

日志消息将写入此列表,然后在类被销毁时“刷新”。

3

日志流应具有唯一的名称。我们在前面加上日期前缀,以便于排序。

4

每个日志记录器类的实例都会创建一个新的日志流。

5

所有批量日志消息都会使用 AWS SDK 中的P⁠u⁠t⁠L⁠o⁠g⁠E⁠v⁠e⁠n⁠t⁠s​A⁠s⁠y⁠n⁠c⁠(⁠)方法发送到 CloudWatch。

运行此控制台应用程序时,查看 AWS 管理控制台 中的日志,将显示 图 7-3 中的输出。

提示

在本地运行 .NET 应用程序时,AWS SDK 会在一系列位置查找您的 AWS 凭据,以便连接到 AWS 并将日志消息发布到 CloudWatch。在 Windows 环境中,有一个名为SDK Store的 JSON 文件,位于%USERPROFILE%\AppData\Local\AWSToolkit\RegisteredAccounts.json,或者您可以使用共享的 AWS 凭据文件。有关配置本地连接到 AWS 的所有可用选项的信息,请访问此文档页面

在前面的示例中,我们直接使用了CloudWatchLogger,但是由于 AWS Lambda 能够将对Console.WriteLine()的调用转发到 CloudWatch,因此我们还可以使用任何流行的 .NET 日志包写入控制台,然后允许 AWS Lambda 为我们转发到 CloudWatch。

例如,这里是一个 Serilog 的配置,添加了一个Serilog.Sinks.Console日志汇,AWS Lambda 将发送到 CloudWatch:

{
  "Serilog": {
    "Using": [ "Serilog.Sinks.Console" ],
    "MinimumLevel": "Debug",
    "WriteTo": [
      { "Name": "Console" }
    ],
    "Properties": {
      "Application": "SerilogLoggingInLambda"
    }
  }
}

我们可以将此 JSON 保存到我们的appsettings.json文件中,并且通过安装了 Serilog 和 Serilog.Sinks.Console 包,我们的 Lambda 函数将向 CloudWatch 发送日志。

您可以在 AWS Logging .NET0 的 GitHub 仓库中找到用于使用其他流行的第三方日志库的插件。

指标

到目前为止,我们只查看了捕获单个事件的日志消息。这对于调试应用程序的执行和捕获特定事件非常有用,但是为了长期监视和分析系统,我们还需要捕获指标。

度量是在一定时间内对特定数据点的测量。例如,您可能有一个记录每个 HTTP 请求响应时间的应用程序,并决定测量每分钟平均响应时间。每分钟平均 HTTP 响应时间的度量是一个指标,它允许您绘制类似于图 7-4 的时间序列。您可以在 CloudWatch 管理控制台的指标 → 所有指标下创建和探索建议的指标。

还可以使用 AWSSDK.CloudWatch NuGet 包中的 AmazonCloudWatchClient 类从您的 C# 代码中发布自定义度量数据点。⁴

doac 0704

图 7-4. API 网关平均延迟度量

示例 7-2 将当前在您的本地计算机上运行的进程数发送到 CloudWatch:

示例 7-2. Program.cs
using Amazon.CloudWatch;
using Amazon.CloudWatch.Model;
using System.Diagnostics;

var client = new AmazonCloudWatchClient();

await client.PutMetricDataAsync(new Amazon.CloudWatch.Model.PutMetricDataRequest
{
    Namespace = "MyApplication",
    MetricData = new List<MetricDatum>
    {
        new MetricDatum
        {
            MetricName = "ProcessCount",
            Value = Process.GetProcesses().Length,
        }
    }
});

您可以在图 7-5 中看到重复运行此操作的结果。请注意,CloudWatch 自动为我们创建了命名空间“MyApplication”和度量“ProcessCount”。

doac 0705

图 7-5. (1) 发送带有命名空间的自定义度量结果,(2) 度量名称,以及 (3) 运行进程数

现在我们的度量数据进入 CloudWatch,接下来我们将看看如何构建 CloudWatch 仪表板以便一目了然地监视我们的系统。随后,我们将看看如何设置警报,以在度量超过一定阈值时触发。

使用 CloudWatch 仪表板监控

仪表板允许您一目了然地监视您的 AWS 资源。您可以通过创建自定义仪表板,选择要快速高效监视的关键指标,一次监视整个 AWS 帐户中的多个资源。CloudWatch 为您的 AWS 服务生成自动仪表板,您可以将其用作创建自己仪表板的起点。

转到管理控制台中的 CloudWatch → 仪表板 → 自动仪表板,查看可用的自动仪表板。您可以从自动仪表板中将小部件添加到新的自定义仪表板,或者通过选择每个小部件的上下文菜单中的“查看指标”来查看和调整底层指标查询。图 7-6 显示了为我们的简单存储服务 (S3) 存储桶生成的自定义仪表板。您可以查看存储桶大小和对象数量的趋势。

doac 0706

图 7-6. 简单存储服务 (S3) 存储桶的自动 CloudWatch 仪表板

仪表板允许您随时间可视化指标的变化,但如果您希望在指标达到某个值时实际执行一些操作呢?在图 7-6 中,您可以看到我们的一个 S3 存储桶的BucketSizeBytes值在长时间内缓慢增长(第一个图表上突然下降的锯齿线)。如果我们希望的话,我们可以设置一个上限,即在这个 S3 存储桶中存储的字节数上限,可能出于成本考虑,然后在存储桶大小超过此限制时执行某些操作。为此,我们使用 CloudWatch 告警。

云监控告警

我们在本章中介绍了监控软件,它保证了过山车的安全运行。在工业控制软件中,例如过山车,如果某个输入读取值在预定的阈值之外超过一定秒数,它将触发告警。在工业应用中,这些告警将连接到发出响亮的听得见的警报声的物理警报,通知附近的操作员条件超出正常范围。例如,可能是温度设定点低于或高于几秒钟,或者是离散输入,如激光束被切断。在您的分布式 AWS 系统中,您可以配置告警以在您认为“正常”的限制之外时触发。告警的目的是触发操作,可以是系统管理员手动操作,也可以是通过触发系统内功能自动执行。

在 CloudWatch 中,您可以为任何单个指标、从多个指标派生的表达式甚至其他告警配置告警。告警可以处于三种可能的状态之一:

OK

此告警的指标在定义的范围内,并且未触发。

IN ALARM

指标超出定义的范围,告警被触发。

INSUFFICIENT_DATA

目前没有足够的数据来确定告警状态。

让我们为我们在图 7-5 中创建的自定义指标添加一个告警。为此,请在管理控制台中导航至 CloudWatch 并创建告警,选择 MyApplication → Metrics with no dimensions → ProcessCount 作为要监视的指标。在图 7-5 的示例中,我们的自定义指标报告了 287 到 292 个进程,因此我们可以将告警阈值设置为 290,这将使测试变得容易。在下一个屏幕上选择“创建新主题”,以便 AWS 创建一个新的简单通知服务(SNS)主题。我们在“使用 SQS 和 SNS 进行开发”中介绍了 SNS;它是一个允许发布者和订阅者分别创建和侦听事件的服务。对于此告警的目的,当ProcessCount指标超过我们的 290 阈值时,我们将向 SNS 发布一条消息,触发告警。

图 7-7 显示了我们创建并等待几分钟以使其具有足够数据以退出INSUFFICIENT_DATA状态后的警报。从这个图表可以看出,我们在 290 处有一条水平线,表示这是我们将触发警报的阈值。

doac 0707

图 7-7. CloudWatch 警报监控我们的自定义指标

我们在本节开头提到,您还可以配置警报以触发多个指标。这可以通过指标数学来实现,我们将在接下来详细讨论。

指标数学

指标是随时间变化的数据点,因此您可以在相同的时间段内对它们执行算术运算。CloudWatch 使您能够使用多种表达式将多个指标组合成一个。在本书中无法涵盖所有内容;然而,您可以在这里找到所有函数和表达式的完整列表。

利用这些算术函数对我们的指标进行操作,我们可以创建的指标,这些指标既可以添加到仪表板中,也可以用于触发警报。在图 7-8 中,我们通过将我们早期示例中的ProcessCount与另一个记录本地机器 CPU 使用率的自定义指标相结合,创建了一个新的表达式。这允许我们为每个进程的平均 CPU创建一个新指标,输入到图 7-8 中表格中可自定义的“标签”字段。

doac 0708

图 7-8. 使用指标数学创建的平均每个进程 CPU 指标

我们从上面的添加数学上下文菜单选项中创建了这个表达式,并选择了“以空表达式开始”。在这个例子中,表达式是 m2 / m1,其中 m1m2 是列表中我们两个自定义指标的默认标识符。您可以通过编辑表格中的“ID”字段查看/更改此指标 ID,如图 7-8 中所示。

通过使用指标数学,我们可以创建复杂的图表和警报条件,以监控系统内部的任何参数。在指标数学中甚至有一个IF(condition, trueValue, falseValue)条件,允许我们执行诸如过滤时间序列中的数据点等操作。也许我们想要在系统重新启动后或软件更新期间过滤掉前 10 分钟的数据点,这是可能的,通过在 AWS CloudWatch 中使用指标数学构建新的指标表达式。

CloudWatch 异常检测

还可以使用称为异常检测的功能触发 CloudWatch 警报。这是一种机器学习算法,持续监视您的度量,并通过一系列预期值确定正常基线。如果度量超出此范围太远,就可以触发警报。CloudWatch 异常检测使用您的度量的历史值来评估每小时、每日和每周模式。这使其能够生成预期值范围,即使是那些随着时间自然变化以遵循正常模式的度量,例如与应用程序使用模式相关的度量。要基于异常检测创建 CloudWatch 警报,请查看警报配置中的“云监控控制台”下的“异常检测”选项(https://oreil.ly/Ag1Tq)。

接下来,我们将从度量指标转移到使用名为 X-Ray 的 AWS 服务跟踪代码执行路径。

使用 X-Ray 进行分布式跟踪

AWS X-Ray 是一个为您的云托管应用程序提供端到端跟踪的服务,使您能够深入了解代码执行方式。例如,您可以为 HTTP 请求设置 X-Ray 跟踪,并查看该 HTTP 请求导致的任何下游服务调用的执行路径。这使您能够在云中运行应用程序时调试应用程序,帮助您理解任何问题的根本原因。您还可以使用 AWS X-Ray 查找代码执行路径中的性能瓶颈。

要利用 AWS X-Ray 提供的工具,您需要设置您的服务以发布跟踪事件到 X-Ray。X-Ray 跟踪事件可以自动配置为许多本地 AWS 服务,如 AWS Lambda 和 DynamoDB。在 Lambda 函数上启用跟踪只需在管理控制台中为任何 Lambda 函数切换“Active tracing”开关(显示在图 7-9)。

doac 0709

图 7-9. 可以在许多本地 AWS 服务的配置设置中启用 X-Ray

如果您的基础架构由 Infrastructure as Code(IaC)框架控制,例如“无服务器应用程序模型(SAM)”,则可以在配置中启用活动跟踪,以便在部署资源更改时设置。以下是启用 Lambda 函数上 X-Ray 的 SAM 配置;我们在属性对象中添加了Tracing:Active

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: My Lambda function with X-Ray active tracing enabled
Resources:
  MyLambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      Handler: MyApp::MyApp.Function::Handler
      Runtime: dotnet6
      Tracing: Active

并非所有的 AWS 服务都可以像这样启用;然而,对于这些服务,我们有使用.NET SDK 的选项。

使用.NET SDK 设置 X-Ray

当启用像 AWS Lambda 这样的本地服务的 X-Ray 时,只需切换一个开关即可(如 图 7-9 所示)。但是,如果你的代码运行在 EC2 容器中(包括 Elastic Beanstalk)或者 App Runner 上,则需要手动配置一些内容。你需要安装或启用 X-Ray 守护程序,该守护程序在后台运行于你的 EC2 实例上,收集 X-Ray 跟踪消息,并将它们批量处理并发送到 AWS X-Ray。此外,你还需要在你的 C# 代码中使用 X-Ray SDK 来将跟踪消息发送给守护程序。

在 EC2 实例上,你可以通过以下命令下载并安装 X-Ray 守护程序,以便在启动实例时自动运行:

#!/bin/bash
curl https://s3.us-east-2.amazonaws.com/aws-xray-assets.us-east-2/
xray-daemon/aws-xray-daemon-3.x.rpm -o /home/ec2-user/xray.rpm
sudo yum install -y /home/ec2-user/xray.rpm

如果你使用 Elastic Beanstalk,你可以在管理控制台的配置 → 软件设置 → X-Ray 守护程序下启用它。在使用 AWS Toolkit for Visual Studio 发布应用程序时,在发布页面上也有一个选项。在发布应用程序到 Elastic Beanstalk 时勾选“启用 AWS X-Ray 跟踪支持”,AWS Toolkit 将会为你启用守护程序。

你也可以在本地开发机上运行 X-Ray 守护程序进程。你可以通过访问 X-Ray 设置指南 来获取在 Windows、Linux、Mac OS 甚至 Docker 容器中本地运行守护程序的指南。

安装了守护程序后,你就可以在应用程序中使用 X-Ray SDK 将跟踪消息发送到 X-Ray。要做到这一点,请首先安装 NuGet 包 AWSXRayRecorder,然后将以下三行代码添加到你的 .NET 6 Web 应用程序的 Program.cs 文件中:

using Amazon.XRay.Recorder.Core;
using Amazon.XRay.Recorder.Handlers.AwsSdk;

AWSXRayRecorder.InitializeInstance(); ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)

AWSSDKHandler.RegisterXRayForAllServices(); ![2](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/2.png)

// ... 
app.UseXRay("ElasticBeanstalkAppExample"); ![3](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/3.png)

app.Run();

1

为这个应用程序初始化 AWS X-Ray 记录器。

2

如果你在应用程序中使用 AWS SDK 调用其他 AWS 服务,请添加此行代码。这将允许 X-Ray 通过这些下游服务跟踪请求。

3

在你的 Web 应用程序路由中添加 X-Ray 跟踪。名称“ElasticBeanstalkAppExample”将用于在 X-Ray 中标识此应用程序。

添加这三行代码将为进入您的 Web 应用程序的每个 HTTP 请求发送跟踪消息到 X-Ray。您将能够测量请求的持续时间,包括任何对下游 AWS 服务的同步调用。X-Ray 有一个称为 “segments” 的概念,它们是代码执行路径的可测量部分。整个 HTTP 请求是父 “segment”,而此请求作为其一部分进行的任何下游调用将作为 “subsegments”。您还可以使用 X-Ray SDK 配置自己的子段,以测量代码的自定义部分的时间。在下面的示例中,我们为控制器动作内代码的持续时间设置了一个子段。然后我们从我们的代码中调用了一个名为 TracedLambdaFunction 的 AWS Lambda 函数。X-Ray 也将为该 Lambda 调用创建一个子段。

示例 7-3. Program.cs
[HttpPost]
public async Task<IActionResult> InvokedMyTracedLambda()
{
    AWSXRayRecorder.Instance.BeginSubsegment("Executing Controller Action");

    var lambdaClient = new AmazonLambdaClient(Amazon.RegionEndpoint.EUWest2);
    await lambdaClient.InvokeAsync(new Amazon.Lambda.Model.InvokeRequest
    {
        FunctionName = "TracedLambdaFunction"
    });

    AWSXRayRecorder.Instance.EndSubsegment();

    return Ok();
}

我们将能够在 CloudWatch 管理控制台的 X-Ray traces 部分查看此示例的跟踪结果。图 7-10. 展示了此控制器方法的跟踪结果。您可以看到 Lambda 函数执行花费了 2.45 秒返回。我们在控制器动作中设置的子段,使用 AWSXRayRecorder.Instance.BeginSubsegment(...),花费了 2.67 秒,整个 HTTP 请求花费了 2.76 秒。

doac 0710

图 7-10. 我们 Web 应用程序控制器调用的跟踪视图

使用 X-Ray 追踪,您可以快速找出代码中的性能瓶颈,并相应地重构或重新设计代码路径。通过为您的应用程序和服务添加跟踪功能,X-Ray 还启用了另一个功能,即 服务映射

X-Ray 服务映射

现在,服务映射位于 AWS CloudWatch 管理控制台的 X-Ray traces 部分,它是所有启用了 X-Ray 的服务及其交互的可视化展示。⁵ Figure 7-11 展示了先前示例的服务映射。你可以看到,我们有我们的 Web 应用 ElasticBeanstalkAppExample,它调用了名为 TracedLambdaFunction 的 Lambda 函数。因为我们在 Web 应用中使用了 X-Ray SDK,并调用了 AWSSDKHandler.RegisterXRayForAllServices(),X-Ray 能够确定此 Lambda 调用来自我们部署到 Elastic Beanstalk 的 .NET Web 应用程序内部。

doac 0711

图 7-11. X-Ray 可以在许多原生 AWS 服务的配置设置中启用

您可以在服务映射界面中单击服务,跟踪它们的执行路径,并查看延迟和 5xx 错误率等性能指标。在 Figure 7-11 中,我们选择了 “ElasticBeanstalkAppExample”。

下面我们将看看如何在 OpenTelemetry 中使用 X-Ray,这是一个开源的可观测性框架,为跨多个云提供商(包括 AWS)提供了一个通用的工具框架。

OpenTelemetry 和 App Runner

对于之前使用的所有服务,我们一直在使用 X-Ray SDK 和/或简单地利用内置的 X-Ray 追踪功能,例如在 AWS Lambda 中。还可以使用 OpenTelemetry 与 X-Ray 一起发送跟踪信息,以减少供应商锁定。如果您希望从容器化应用程序(例如在 Amazon ECS 或 Amazon EKS 上运行的应用程序)中利用 X-Ray 追踪,则可以采用此解决方案。

在您的代码库中添加了 OpenTelemetry SDK 后,可以使用AWS OpenTelemetry 发行版来仪器化您的 .NET 应用程序并将指标发送到 X-Ray。您可以通过访问开始使用 .NET SDK 进行跟踪仪器化了解有关为您的 .NET Core/6+ 应用程序设置 OpenTelemetry 的更多信息。

资源健康和合成金丝雀

如果您已经在 CloudWatch 管理控制台周围探索过,那么您可能已经访问了应用程序监控部分。在这个主题下,有几项来自 AWS 的服务帮助我们监控资源。让我们快速浏览其中的两项:资源健康和合成金丝雀。

资源健康视图以前是 ServiceLens 的一部分,显示您所有运行中 EC2 主机的健康和性能数据。您可以通过从三个维度选择来自定义视图:CPU 利用率、内存利用率和状态检查。管理控制台窗口将为当前区域中的每个 EC2 主机显示彩色方块块。这为您提供了一个易于阅读和访问的视图,显示大多数或所有主机的警报状态。⁶

合成金丝雀是 CloudWatch 应用程序监控部分中的另一个强大工具。在系统监控中,“金丝雀”是一种活跃的监控技术,它使用脚本或其他定期任务定期探测系统的状态。⁷

您可以使用提供的模板(或“蓝图”)在 AWS 中创建多种类型的金丝雀:

  • 心跳监控

  • API 金丝雀

  • 坏链检查器

  • 金丝雀记录器

  • GUI 工作流构建器

  • 可视化监控

合成金丝雀的理念是复制模拟您的应用程序将要为用户执行的实际使用情况。例如,API 金丝雀可以配置为对您的 API 进行系列请求,模拟前端在正常操作下执行的操作。

当创建您的金丝雀时,可以通过选择“使用 AWS X-Ray 追踪我的服务”轻松集成合成与 X-Ray。有关配置和熟悉 X-Ray,请参阅“使用 X-Ray 进行分布式追踪”。

使用 AWS CloudTrail 进行安全审计

到目前为止,我们一直在 AWS CloudWatch 中查看日志,监视您部署的服务以查找并修复问题。然而,在我们离开日志记录和监视主题之前,让我们简要了解一下 AWS 为账户审计提供的功能。

CloudTrail 是 AWS 的一个服务,记录您账户中对 AWS API 的所有请求,并记录下发出请求的用户。这些 API 请求可以从任何地方进行,包括通过 AWS CLI、管理控制台或来自 AWS SDK(例如 AWS SDK for .NET)的 SDK。您甚至可以在 CloudTrail 中监视另一个 AWS 服务发出的请求。CloudTrail 在 AWS 管理控制台中有其自己的部分,因此要访问它并查看您的审计日志,请在搜索栏中搜索“CloudTrail”。

提示

对于 Google Chrome 用户,您可以使用 ALT+S(Mac 上的 Option+S)随时将焦点移动到 AWS 管理控制台中的搜索栏。管理控制台的另一个方便的功能是搜索结果中每个服务旁边的收藏夹“星号”。单击此按钮将导致指向该服务的链接永久显示在管理控制台窗口的标题栏中,并且在您浏览各种 AWS 服务时保持不变。

要开始使用 CloudTrail,请点击“创建跟踪”,并进行设置以配置跟踪的属性,主要是名称和用于存储日志的 S3 存储桶。您可以选择监视任何或所有这些事件类型:

管理事件

在您的 AWS 资源上执行的操作,例如将策略附加到 IAM 角色或在 EC2 中创建子网。

数据事件

对资源实例执行的操作。数据事件包括删除、创建或更新 S3 对象、DynamoDB 记录和执行 Lambda 函数。

洞察事件

当 AWS 检测到您帐户上的异常活动时,AWS 会记录这些事件。它们监视 AWS API 上的写操作,并使用数学模型来检测操作和/或错误的异常水平。

创建跟踪后,AWS 将开始向 S3 存储桶记录日志,并且您可以在管理控制台中查看和分析您的审计日志。

实际示例:一个 CloudWatch 断路器

在本章中,我们已经探讨了 AWS CloudWatch 中的一些可用功能。我们已经研究了从 C#应用程序向 CloudWatch 收集日志、指标、跟踪和发送自定义指标数据。我们还研究了监控仪表板,并设置当指标超出预定阈值时触发警报的功能。让我们通过一个跨越本章涵盖的多个概念的示例来将所有这些联系起来。在此示例中,我们将使用无服务器 AWS 组件和 CloudWatch 实现熔断器模式。

注意

断路器是一个处理应用程序某部分故障的成熟架构模式,引入逻辑来在检测到故障时回退到次要行为。这有助于防止故障传播到下游组件,并允许您即使在一个组件失败时也能维持一定级别的服务向应用程序用户提供。您可以观看一个关于如何实施这种模式的动画示例,链接在YouTube

为了在 AWS 上实施断路器模式,我们首先需要一个可能出现故障的应用程序。在此示例中,我们有一个网站希望向所有访问者显示最新的货币兑换率。在这种情况下,从 USD 到 GBP 的汇率将来自一个外部 API,我们将每次有人加载我们的网页时调用该 API。由于货币在全球范围内交易,汇率会波动,因此通过每次页面加载时调用此 API,我们可以确保向我们的网站访问者展示最新的汇率。图 7-12 展示了我们如何从网站进行这种汇率查询调用。该调用通过我们控制的 API Gateway 实例代理,并直接传递到我们用于检索最新汇率的第三方服务。

doac 0712

图 7-12. 外部 API 调用通过 API Gateway 代理到我们的网站

设置一个简单的 API Gateway 代理到第三方 API 的 CloudFormation 模板将是:

Resources:
  ExchangeRateApi:
    Type: AWS::ApiGatewayV2::Api
    Properties:
      Name: ExchangeRateApi
      ProtocolType: HTTP

  ExternalERServiceIntegration:
    Type: "AWS::ApiGatewayV2::Integration"
    Properties:
      ConnectionType: INTERNET
      IntegrationType: HTTP_PROXY
      IntegrationMethod: GET
      IntegrationUri: https://api.example.com/exchange-rate/
      PayloadFormatVersion: 1.0
      ApiId: !Ref ExchangeRateApi
      RequestParameters:
        "overwrite:header.apikey": "{{API-KEY}}"

  LatestRoute:
    Type: 'AWS::ApiGatewayV2::Route'
    Properties:
      ApiId: !Ref ExchangeRateApi
      RouteKey: 'GET /latest'
      AuthorizationType: NONE
      Target: !Join
        - /
        - - integrations
          - !Ref ExternalERServiceIntegration

会发生什么问题?

每次访问者加载我们的网页时调用此第三方 API,可以很好地显示最新的值;但是,我们现在严重依赖于一个我们不拥有或控制的 API 的可用性。如果这个第三方服务崩溃或开始响应缓慢会发生什么?目前,我们 API 调用的响应时间与外部 API 的响应时间成正比。如果这个外部 API 开始响应非常缓慢,我们的 API 网关调用也将如此,我们的用户将不得不等待他们期望的汇率出现在网站上。也许他们最终会看到一个超时错误,而不是他们预期的 USD 到 GBP 汇率。那我们该怎么办呢?

使用 CloudWatch 我们可以监控此端点的延迟,并设置一个警报,在外部 API 开始响应缓慢时触发。转到 CloudWatch 管理控制台,并为我们 API 网关资源上的延迟指标创建一个新警报,如图 7-13 所示。

警告

在 API 网关中启用“详细路由指标”,以获取每个路由的延迟时间,如图 7-13 所示。

doac 0713

图 7-13. 配置我们 API 网关资源延迟的新警报

对于我们的警报通知动作,我们将设置一个 SNS 主题,并将其链接到名为 TriggerCircuitBreaker 的 Lambda 函数。接下来我们将创建这个 Lambda 函数。

断开电路

现在我们有了一种方法来检测外部 API 的延迟增加,我们需要决定如何保持我们的应用程序运行。对于我们的示例网站,我们已经做出了业务决策,即如果汇率查找 API 的响应不够快,我们将从我们的分布式内存缓存中返回一个 缓存 汇率。也许我们有另一个 Lambda 函数定期运行并将当前汇率保存到此缓存中。对于我们的分布式缓存,我们可以使用 Amazon ElastiCache,它允许我们同时使用 Redis 和 Memcached,这两种极其流行的开源内存存储。在 图 7-14 中,您可以看到我们已经配置了我们的 API 网关,使用了第二个“备用”集成,该集成调用一个 Lambda 函数从缓存中检索最新的汇率。

doac 0714

图 7-14. 将额外的 AWS Lambda 集成添加到 API 网关作为备用

我们的断路器可以通过检测第三方 API 的高延迟并将我们的 API 网关切换到使用此备用集成来运行。这不会影响我们的网站,因为前端的 API 调用仍然进入网关,但我们不再将这些调用代理到我们的第三方服务,而是发送到 Lambda 函数 GetCachedExchangeRate

在 API 网关中进行这种切换可以通过一个简单的 C# Lambda 函数完成,使用 NuGet 包 AWSSDK.ApiGatewayV2 中的 AmazonApiGatewayV2Client

using System.Threading.Tasks;
using Amazon.ApiGatewayV2;
using Amazon.ApiGatewayV2.Model;
using Amazon.Lambda.Core;
using Amazon.Lambda.Serialization.SystemTextJson;

[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]

namespace TriggerCircuitBreaker
{
    public class Function
    {
        public async Task FunctionHandler(object input, ILambdaContext context)
        {
            const string apiGatewayId = "abcabc";
            const string exchangeRateRouteId = "defdef";
            const string fallbackIntegrationId = "xyzxyz";

            var apiGatewayClient = new AmazonApiGatewayV2Client();

            await apiGatewayClient.UpdateRouteAsync(new UpdateRouteRequest
            {
                RouteId = exchangeRateRouteId,
                ApiId = apiGatewayId,
                Target = $"integrations/{fallbackIntegrationId}"
            });
        }
    }
}

在这里,我们已经为我们的 API 网关实例、路由和集成硬编码了 ID;然而,当然可以从环境变量中读取或者在运行时使用 AmazonApiGatewayV2Client 的方法查找它们。

重置触发状态

通过在 API 网关中切换集成,我们已经为用户在网站上提供了一些信息。这可能不是最新的汇率,但应该足够新,关键是,我们已经防范了一个不可预测的慢第三方 API。接下来要考虑的是,当 API 不再经历高延迟时,我们如何重置我们的断路器。

断路器模式提供了一种解决方案,一旦初始条件得到解决,就会定期测试我们的外部 API,这是通过“半开放状态”进行的。我们可以创建另一个 Lambda 函数来执行此操作:

using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;
using Amazon.CloudWatch;
using Amazon.CloudWatch.Model;
using Amazon.Lambda.Core;
using Amazon.Lambda.Serialization.SystemTextJson;

[assembly: LambdaSerializer(typeof(DefaultLambdaJsonSerializer))]

namespace ProbeExchangeRateEndpoint
{
    public class Function
    {
        public async Task FunctionHandler(object input, ILambdaContext context)
        {

            var watch = Stopwatch.StartNew();

            var response = await new HttpClient()
                          .GetAsync("https://external-api.com/latest-rate");

            response.EnsureSuccessStatusCode();

            watch.Stop();

            var cloudWatch = new AmazonCloudWatchClient();
            await cloudWatch.PutMetricDataAsync(new PutMetricDataRequest
            {
                Namespace = "CircuitBreakerExample",
                MetricData = new List<MetricDatum>
                {
                    new MetricDatum
                    {
                        MetricName = "ExchangeRateProbeLatency",
                        Value = watch.ElapsedMilliseconds
                    }
                }
            });

        }
    }
}

在这里,我们有一个函数,它调用我们的第三方汇率 API 并测量响应时间。然后将其作为名为“ExchangeRateProbeLatency”的自定义指标发送回 CloudWatch。然后,我们可以设置第二个警报来跟踪这个新的指标,使用低于阈值。在 CloudWatch 的警报配置窗口中,还有一个“数据点到警报”的设置,我们可以利用它。设置这个(在 图 7-15 中显示)将导致我们的警报在我们的探测 API 的 10 次调用中有超过允许的最大延迟时被触发。

doac 0715

图 7-15. 数据点到报警配置

最后,在 图 7-16 中,这是我们整个示例作为流程图的呈现。我们正在使用 EventBridge 规则来调度调用 ProbeExchangeRateEndpoint Lambda 函数,直到延迟降低到可接受水平。

doac 0716

图 7-16. 带有探测和重置的断路器流程图

结论

没有系统是完美设计或配置的。系统随时间演化,并且随着应用规模的变化,您将需要修改服务间通信的方式。对于从只有少数用户开始,有时以指数方式增长到全球服务数千用户的增长系统来说,这一点尤其重要。当应用程序规模小的时候做出的架构决策可能会在采用增加时阻碍性能。随着用户规模的扩展,您还将更多地了解服务的使用模式,因为更多的用户提供了关于应用程序使用情况和瓶颈出现位置的详细见解。

因此,非常重要的是要有日志记录、监控和仪表化,以便您可以观察这些变化并迅速、高效地响应,并且在您的处置中拥有最大量的有用数据。对于运维工程师、站点可靠性工程师、DevOps 和安全工程师来说,AWS CloudWatch 是一组非常强大的工具,可以为您提供所需的数据,如果投入足够的时间来充分利用它。

对于本书的最后一章,我们将回到应用程序代码中,深入探讨 AWS SDK for .NET。我们还将查看一些能够使与 AWS 交互更加自然和与开发工作流程更加集成的 Visual Studio 工具。

批判性思维讨论问题

  • 工程经理为什么会说日志记录、监控和仪表化对软件工程至关重要?

  • 描述 AWS CloudWatch 的四大支柱的另一种方式是什么?

  • 在构建和部署 AWS Lambda 服务时,您应该关注的三个或四个最重要的度量指标是什么?

  • 类似 X-Ray 这样的服务解决了哪些现实世界的问题?

  • 如何使用 AWS CloudTrail 进行安全审计以帮助防止公司遭受勒索软件攻击?

练习

  • 构建一个 C# 控制台应用程序,查询 AWS CloudTrail。

  • 将自定义 CloudWatch 日志写入构建 .NET 项目的 AWS CodeBuild 作业中。

  • 记录进入 API Gateway 的 POST 请求到一个 .NET Web 服务的 AWS CloudTrail。

  • 记录进入 Elastic Beanstalk 的 POST 请求到一个 .NET Web 服务的 AWS CloudTrail。

  • 构建一个 C# 控制台应用程序,用于查找 AWS CloudTrail 事件中的异常情况。

¹ 乘客的体重会极大地影响重力下过山车车辆的速度。如果你看到四个成年人坐在儿童过山车的车里可能会看起来很有趣,但这辆车进站时增加的动量确实可以测试制动系统的强度。

² 尽管被称为 “AWSLambdaBasicExecutionRole”,但实际上这不是一个角色,而是一个 策略 —— 一组权限设置,可以应用于角色以授予或拒绝对某些资源的访问。

³ IAsyncDisposable 接口是在 C# 8.0 中引入的,允许我们通过在 “using” 语句之前添加 “await” 来调用异步代码中的 DisposeAsync() 方法。

⁴ AWSSDK.CloudWatch 和 AWSSDK.CloudWatchLogs 是两个不同的包,具有不同的客户端;如果您需要向 CloudWatch 发布自定义日志消息 自定义指标数据点,则需要这两个包。

⁵ Service Map 以前是通过 AWS 称为 “Service Lens” 的产品访问的,因此您可能会在在线文档中找到它作为该产品的一部分的引用。

⁶ 您可以使用资源健康窗口在一个视图中查看多达 500 个主机。

⁷ “Canary” 这个术语有着相当阴暗的历史。在电子气体探测器出现之前的日子里,以及动物福利权利出现之前,金丝雀会被放进煤矿的笼子里挂在正在作业的区域。如果这只鸟在矿井中受到有毒气体的影响而死亡,工人们会注意到可怜鸟的死亡,并将其视为发出警报并撤离的信号。

第八章:使用 AWS C# SDK 进行开发

本书始终通过多种方式与您的开发机器进行 AWS 交互。我们一直在使用AWS 管理控制台,并且通过在 Cloud9 上和本地安装 CLI 来使用 AWS CLI。在本章中,我们将介绍一些工具和集成,提供第三种集成和控制 AWS 服务的方式,通过您可能已经熟悉的集成开发环境(IDEs):Visual Studio 和 Visual Studio Code。这两个 IDE 都有 AWS 扩展,允许您查询 AWS 资源,本章我们将对两者进行详细介绍。

在查看了 AWS 的 UI 工具包的一些特性后,我们还将更详细地了解 AWS C# SDK。在本书的整个过程中,我们已广泛使用 SDK,因此您应该对其基础知识很熟悉,但这些 NuGet 包的功能远不止这些。

最后,我们将通过探索 AWS 中的一些人工智能(AI)服务及如何在.NET 中利用这些服务来结束本书。所以让我们直接进入话题,讨论 AWS Toolkit for Visual Studio 以及如何设置您的 Visual Studio 工作流,使得部署到 AWS 感觉无缝。

深入使用 AWS Toolkit for Visual Studio

AWS Toolkit 是一个扩展,通过各种 AWS 相关功能增强了 Visual Studio 界面。工具包的目标是使 Visual Studio 在 AWS 中的集成感与其在微软云服务中的原生集成感一样强。安装了工具包后,您可以使用与“发布”工作流相同的方式快速部署代码到多种 AWS 服务上,这种工作流您可能已经非常熟悉了。

工具包添加了菜单选项、上下文菜单选项、项目模板和 AWS 资源管理器窗口,本章我们将对所有这些功能进行介绍。

配置 Visual Studio 以使用 AWS Toolkit

我们在"使用 Visual Studio 与 AWS 和 AWS Toolkit for Visual Studio"中简要介绍了工具包的安装,因此简要回顾一下:您可以在Visual Studio Marketplace找到工具包扩展,或者通过 AWS 的AWS Toolkit for Visual Studio网页找到,后者还包含指南和文档的链接,帮助您充分利用 Visual Studio 中的 AWS 功能。

工具包有三个版本,适用于五个版本的 Visual Studio,请确保下载适合您版本的正确.msi安装文件。

  • AWS Toolkit for Visual Studio 2013–2015

  • AWS Toolkit for Visual Studio 2017–2019

  • AWS Toolkit for Visual Studio 2022

本书中的示例将使用 AWS Toolkit for Visual Studio 2022;但是,较早版本的 Visual Studio 屏幕并没有根本不同。

安装扩展并打开 Visual Studio 后,您将看到如图 8-1 所示的入门页面。

在我们可以从 Visual Studio 连接到 AWS 之前,我们需要使用一些 AWS 凭据配置工具包,以便它可以代表我们向 AWS 服务发出 API 调用。工具包使用存储在 AWS 凭据文件中的访问密钥和秘密密钥来配置,存储在方括号中的配置文件下。例如:

[default]
aws_access_key_id = <your-access-key>
aws_secret_access_key = <your-secret-key>

doac 0801

图 8-1. 使用 AWS Toolkit 入门

如果您的计算机已经存储了凭据,那么您可以跳过此步骤,因为工具包将在 Visual Studio 内自动获取它们。如果您尚未存储凭据,则可以创建 IAM 用户并下载凭据,然后导入到图 8-1 中的凭据设置部分。

一旦您在计算机上存储了凭据,您就可以开始在 Visual Studio 内探索 AWS Toolkit 的功能,而最好的起点就是 AWS 资源管理器,可以通过新的“工具” → “AWS 资源管理器”菜单选项访问(图 8-2)。

doac 0802

图 8-2. AWS 资源管理器菜单选项

我们在本书开始时介绍了 AWS 资源管理器;你可以在图 1-14 中看到资源管理器窗口的示例,位于“在 Visual Studio 中使用 AWS 和 AWS Toolkit for Visual Studio”下。除了 AWS 资源管理器外,工具包还安装了几个 AWS 特定的项目模板和蓝图。这些不仅可以加速新项目的创建,还可以用来尝试不同的 AWS 部署模型。

AWS 的项目模板

通过在项目类型过滤器中选择“AWS”,从新安装的项目模板中选择一个。这些项目模板适用于基于 AWS Lambda 构建的应用程序,就像我们在本书中许多示例中所涵盖的那样。

doac 0803

图 8-3. Visual Studio 中的项目模板

如果选择模板“AWS 无服务器应用程序与测试(.NET Core - C#)”,那么我们将在下一步选择屏幕上再次选择,从中可以选择 AWS 称为“蓝图”的内容。这些蓝图可以进一步配置模板项目,以最好地反映您正在创建的项目。在这种情况下(图 8-4),我们在前一步中选择了无服务器应用程序,因此我们可以为无服务器应用程序提供几种不同的蓝图选项。

doac 0804

图 8-4. AWS 项目蓝图

选择 ASP.NET Core Web API 蓝图,然后单击“完成”以创建我们将在下一个示例中使用的项目。

从 Visual Studio 发布到 AWS

从我们从蓝图创建的无服务器 ASP.NET Core Web API 项目开始,让我们打开解决方案资源管理器并访问一些已创建的文件:

serverless.template

此文件是我们资源的 Serverless Application Model (SAM) 模板。这个文件允许我们从一个文件中配置我们的基础设施,可以提交到版本控制中。我们在 “Serverless Application Model (SAM)” 中介绍了 SAM。

AWSServerless1.Tests

此测试项目已创建,包括 ValuesControllerTests.cs 中的单个单元测试,作为撰写我们的 API 单元测试的起点。此 AWS 蓝图中包含的测试项目使用了 xUnit 作为 NuGet 依赖项引入。

aws-lambda-tools-defaults.json

这将保存我们项目的发布设置。在下一步添加的设置将作为默认值保存到此文件中,因此您无需每次重新输入它们。

从此蓝图创建的新项目已准备好立即发布 — 我们无需进行任何更改。我们可以使用 AWS Toolkit 直接从 Visual Studio 发布此无服务器项目。要执行此操作,请右键单击项目名称并选择其中一个“发布到 AWS…”选项。如您在 图 8-5 中所见,AWS Toolkit 已添加了两个新的菜单项,供您用于发布项目。

doac 0805

图 8-5. 工具包新增的发布选项

无论您选择如何发布您的应用程序,工具包都将使用之前描述的 serverless.template 文件为您的项目创建一个 CloudFormation 栈,然后构建并将您的代码推送到 AWS。

Visual Studio Code 的 AWS Toolkit

Visual Studio Code 的崛起简直是如同流星一般的。从 2016 年公开发布到 2022 年被超过 70% 的专业软件开发人员 使用,很明显,Visual Studio Code 在开发行业中取代了其他 IDE。C# 开发人员通常将其视为 Visual Studio 的轻量替代品,因此在 2019 年,AWS 宣布发布了他们的 AWS Toolkit for Visual Studio Code

VS Code 工具包包括在 Visual Studio 中找到的 AWS Explorer 版本,允许您从侧边栏的新“AWS”菜单选项下浏览和与您的 AWS 服务交互。图 8-6 显示了 VSCode 中的 AWS Explorer 允许您浏览和与您的资源交互的方式。

doac 0806

图 8-6. Visual Studio Code 中的 AWS Explorer

安装工具包就像在 Visual Studio Code 的扩展市场中搜索“AWS Toolkit”一样简单(在 Windows 上为 Ctrl+Shift+X,或在 macOS 上为 ⌘-Shift-X)。

与 AWS Toolkit for Visual Studio 类似,VS Code 扩展使用存储在~/.aws/credentials(Linux 或 macOS)或%USERPROFILE%.aws\credentials(Windows)下的 AWS 凭证配置文件。您可以通过打开命令面板(在 Windows 上是 Ctrl+Shift+P,或在 macOS 上是⌘-Shift-P)并选择 AWS: Choose AWS Profile 来选择要使用的配置文件。这将显示选择配置文件的选项,或者如果您没有保存任何配置文件,则可以在 Visual Studio Code 内设置一个。

除了 AWS 资源管理器(图 8-6),AWS Toolkit for Visual Studio Code 还提供了一系列可以从命令面板中调用的 AWS 命令。只需搜索“AWS:”即可显示所有带有此前缀的命令。其中一些显示在图 8-7 中。

doac 0807

图 8-7. 在 VS Code 中的 AWS 命令

这就是我们对 AWS Toolkits 的 Visual Studio 和 Visual Studio Code 的概述。接下来,让我们快速看一下 Rider 的 AWS Toolkit。

Rider 的 AWS Toolkit

最后要了解的 AWS Toolkit 扩展是为 JetBrains Rider 提供的。Rider 的 AWS Toolkit提供类似于我们在此处看到的其他 IDE 工具包的功能,包括一个 AWS Lambda 项目模板,可以集成到 Rider 的“新项目”对话框中,并创建一个简单的Hello World项目和测试项目。

AWS Toolkit for Rider 还支持在 Docker 容器内本地运行 AWS Lambda,允许您从 Rider 内部调试 AWS Lambda。在解决方案中的template.yaml文件上还有一个新的“Deploy Serverless Application”上下文菜单项,允许您从 Rider 内部部署 Lambda 函数。

在本章的接下来部分,我们将更详细地查看 AWS .NET SDK,并回顾一些 C#内容。

SDK 主要功能

AWS .NET SDK是一个包含 300 多个 NuGet 包的集合,可让您轻松从运行在.NET 上的应用程序中调用 AWS 服务。SDK 库可以导入到任何.NET 项目中,并且可以从您的代码中进行身份验证调用各种 AWS API。

提示

您可以在.NET on AWS on GitHub页面上探索更多工具、库和其他资源,该页面包含 AWS 维护的所有开源工具的 GitHub 存储库链接,包括 AWS .NET SDK。

所有 SDK 库都遵循包装 AWS API 调用的服务客户端的通用模式。这些客户端在 SDK 中使用强类型的请求和响应类来实现,可供您的 C#代码使用。一个示例是来自AWSSDK.S3包的AmazonS3Client客户端类:

var s3Client = new AmazonS3Client(Amazon.RegionEndpoint.USEast1);

await s3Client.CopyObjectAsync(
        sourceBucket,
        sourceKey,
        destinationBucket,
        destinationKey
);

这里我们正在 AWS 简单存储服务 (S3) 存储桶上调用copy-object来复制一个键到另一个键。我们首先创建服务客户端的新实例(在本例中为AmazonS3Client),并使用一些配置值(AmazonS3Config)。然后,使用服务客户端的实例,我们调用方法CopyObjectAsync()的异步版本。上述代码片段相当于从 AWS 命令行调用以下内容:

aws s3api copy-object --copy-source <source-bucket> --key <key> --bucket <dest>

大多数 AWS 服务遵循此模式,因为所有 AWS 服务客户端都继承自基类Amazon.Runtime.AmazonServiceClient。这使得所有 AWS 服务客户端对象都能访问共享功能,如凭据管理、日志记录、度量、重试和超时,本章后面将详细介绍其中一些功能。

AWS .NET SDK 中的身份验证

要调用 AWS API,您需要向 AWS .NET SDK 传递凭据。有几种方法可以实现这一点,不同的方法适用于不同的场景。例如,如果您在本地运行代码以进行开发,您可能希望使用存储在 AWS 配置文件中的访问密钥和秘密密钥进行身份验证。然而,当您部署代码到 AWS 时,可以使用为特定 EC2 实例、App Runner 或执行代码的 AWS Lambda 函数配置的 IAM 用户。

当您使用服务客户端时,AWS .NET SDK 会按以下顺序查找凭据:

  1. 凭据传递给服务客户端对象的构造函数,例如new AmazonS3Client(awsAccessKeyId, awsSecretAccessKey)

  2. 本地计算机上的命名凭据配置文件,如前文所述,配置文件名称来自 appsettings.{env}.json 中的配置,有关详细信息请参见“配置 Visual Studio 以使用 AWS Toolkit”。

  3. 本地计算机上带有名称的凭据配置文件,配置文件名称存储在名为AWS_PROFILE的环境变量中。

  4. 如果存在带有名称[default]的命名凭据配置文件。

  5. 存储在环境变量AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKEN中的访问密钥、秘密密钥和会话令牌。

  6. 仅在环境变量AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY中包含访问密钥和秘密密钥。

  7. EC2 任务、EC2 实例或其他 .NET 代码执行环境本地的 IAM 角色。

配置 AWS SDK 的最简单和最灵活的方式,也是能够最好地管理不同环境下多个凭据的方式,是使用 appsettings.{env}.json 文件,并通过AWSSDK.Extensions.NETCore.Setup加载,接下来我们将详细了解它。

使用 AWS SDK 进行依赖注入

依赖注入是.NET 应用程序中极为常见的模式,它允许你在一个地方配置所有的依赖关系,并将它们注入到你的.NET 控制器或其他服务中,实现控制反转(IoC)。如果你对依赖注入和 IoC 不太熟悉,可以在YouTube上找到简要概述。

从.NET Core 开始的各个版本支持依赖注入作为框架的一部分,使用在Microsoft.Extensions.DependencyInjection中找到的类。服务(依赖项)在项目的Program.csStartup.cs文件中与容器注册。在这里,在我们的.NET 6 Program.cs文件中,将MyDependency的实现注册为接口IMyDependency的一部分:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();

// Add a dependency to the DI container
builder.Services.AddScoped<IMyDependency, MyDependency>();

当我们在.NET 中使用 AWS SDK 时,我们可以使用类似的语法将 AWS 服务客户端添加到.NET 依赖注入容器中,借助 NuGet 包 AWSSDK.Extensions.NETCore.Setup 的帮助。这个包允许我们做两件重要的事情:注册 AWS 服务客户端作为依赖关系,并使用appsettings.json文件来存储我们的 AWS 配置,正如之前提到的那样。

要加载配置设置,请将 AWSSDK.Extensions.NETCore.Setup NuGet 包添加到您的.NET Core / 6+项目中,然后将以下两行代码添加到您的服务注册代码中:

var awsOptions = builder.Configuration.GetAWSOptions();

builder.Services.AddDefaultAWSOptions(awsOptions);

或者,如果您使用Startup类,那么代码看起来会像这样:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllers();

        var awsOptions = Configuration.GetAWSOptions();

        services.AddDefaultAWSOptions(awsOptions); ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        // ...
    }
}

1

添加配置设置以供所有已解析的服务客户端使用。

您可以使用您的appsettings..json设置文件引用您的 AWS 凭证配置文件的名称,以及其他设置。这允许您在每个环境下使用不同的 AWS 凭证,连接到 AWS 下不同 IAM 用户的角色和权限与该环境定制的内容。一个使用名为“my-profile-name”的本地配置文件appsettings.Development.json的示例如下:

{
  "AWS": {
    "Region": "us-east-1",
    "Profile": "my-profile-name"
  },
  "MyKey": "My appsettings.json Value"
}

在 AWS 节点下的设置映射到类Amazon.AWSConfigs,你可以在Amazon.AWSConfigs 的文档中找到所有可用属性的完整列表。

一旦您在依赖注入容器中添加了默认的 AWS 选项,您可以开始注册 AWS 服务客户端作为应用程序中的依赖关系使用。来自 AWS SDK 的服务客户端将有一个具体的类和一个您可以针对其注册的接口。这使您可以轻松地为单元测试模拟接口,或以其他方式修改您的应用程序的行为,而不需要修改任何调用逻辑。以下是一个使用AWSSDK.Lambda中的AmazonLambdaClient服务客户端的示例。该客户端允许我们从我们的 C#代码调用 AWS Lambda 上的函数:

var awsOptions = builder.Configuration.GetAWSOptions();

builder.Services.AddDefaultAWSOptions(awsOptions);

services.AddAWSService<IAmazonLambda>(); ![1](https://gitee.com/OpenDocCN/ibooker-csharp-zh/raw/master/docs/dev-aws-cs/img/1.png)

1

使用接口注册 AWS Lambda 服务客户端。在运行时,这将作为AmazonLambdaClient的实例注入。

有了这个服务注册后,你可以通过构造函数将其注入到 .NET 控制器中:

[Route("api/[controller]")]
public class ExampleController : ControllerBase
{
    private readonly IAmazonLambda _lambdaClient;

    public ExampleController(IAmazonLambda lambdaClient)
    {
        _lambdaClient = lambdaClient;
    }

    public async Task DoSomething()
    {
        await _lambdaClient.InvokeAsync(new Amazon.Lambda.Model.InvokeRequest
        {
            FunctionName = "MyLambdaFunction",
            InvocationType = InvocationType.Event
        });
    }
}

注意我们在代码中仅引用抽象 IAmazonLambda 并调用 IAmazonLambda.InvokeAsync(...) 来调用 AWS Lambda 函数。这使我们能够通过模拟 IAmazonLambda 来单元测试上面的 DoSomething() 方法,要么通过实现此接口的模拟版本,要么使用 Moq 等工具自动实现。

使用的凭证来调用 AWS Lambda 并调用我们的函数将通过在 appsettings.{env}.json 文件中引用的配置文件中的凭证进行检索,这允许我们为每个环境和/或本地开发用户配置单独的凭证。

重试和超时

由于 AWS SDK 中的方法通过网络对 AWS API 进行 HTTP 调用,因此总会有可能出现问题和调用失败的可能性。所有 SDK 服务客户端继承自的基本 AmazonServiceClient 类包括管理重试功能。可以通过在服务客户端配置上设置两个属性来配置重试行为:

重试模式

设置为 Amazon.Runtime.RequestRetryMode 枚举中的三个值之一:LegacyStandardAdaptive

MaxErrorRetry

在 SDK 服务客户端中失败调用之前重试的次数。

你可以在创建任何 AWS 服务客户端的新实例时单独设置这些值,例如:

要继续之前依赖注入的示例,你可能希望为应用程序中使用的所有服务客户端全局设置它们。你可以通过设置环境变量 AWS_RETRY_MODEAWS_MAX_ATTEMPTS,或者像这样向 AWS 配置文件添加配置键来完成这一点:

[default]
retry_mode = Adaptive
[profile profile-name]
region = eu-west-2
注意

AWS 配置文件位于你的凭证文件 ~/.aws/credentials(Linux 或 macOS)或 %USERPROFILE%.aws\credentials(Windows)旁边。

这些值将加载到你的 AWS 选项对象的 AWSOptions.DefaultClientConfig 属性中:

var awsOptions = builder.Configuration.GetAWSOptions();

// Default retry mode and max error setting for all service clients
var retryMode = awsOptions.DefaultClientConfig.RetryMode;
var maxErrorRetry = awsOptions.DefaultClientConfig.MaxErrorRetry;

builder.Services.AddDefaultAWSOptions(awsOptions);

services.AddAWSService<IAmazonLambda>();

从这里,我们可以使用或修改代码中的值来微调 AWS SDK 中的重试执行方式。

分页器

除了重试和超时,SDK 服务客户端还包括用于返回大量数据数组的服务的分页功能。对于支持它的服务来说,分页是一个非常好的功能,它使用基于对象的新方法替代了继续令牌方法。

首先,让我们看看如何在没有分页器的情况下分页大结果集,通过使用 request.ContinuationToken 属性。我们在响应中有一个继续令牌时,会在 do...while 循环中调用 s3Client.ListObjectsV2Async(...)

public static async IAsyncEnumerable<string>
        GetAllPaginatedKeys(this IAmazonS3 s3Client, string bucketName)
{
    string? continuationToken = null;
    do
    {
        var response = await s3Client.ListObjectsV2Async(
            new ListObjectsV2Request
            {
                BucketName = bucketName,
                ContinuationToken = continuationToken
            });

        foreach (var responseObject in response.S3Objects)
        {
            yield return responseObject.Key;
        }
        continuationToken = response.NextContinuationToken;
    }
    while (continuationToken != null);
}

这很好;但是,尽管我们从返回IAsyncEnumerable<T>获得了优势,要做像链接多个页面结果这样简单的事情仍然需要大量代码。自 AWS SDK for .NET 版本 3.5 以来,我们现在可以在IAmazonS3接口上访问Paginators属性。¹ 这里是同样的方法,但使用分页器。注意调用Paginators.ListObjectsV2同步的,而调用listObjectsV2Paginator.S3Objects是异步的。第一次调用封装了我们的请求对象,但直到我们遍历分页器上的S3Objects属性时,才实际调用 API:

public static async IAsyncEnumerable<string>
        GetAllPaginatedKeys(this IAmazonS3 s3Client, string bucketName)
{
    var listObjectsV2Paginator = s3Client.Paginators.ListObjectsV2(
        new ListObjectsV2Request
        {
            BucketName = bucketName
        });

    await foreach (var s3Object in listObjectsV2Paginator.S3Objects)
    {
        yield return s3Object.Key;
    }
}

此示例中的分页器返回IPaginatedEnumerable<S3Object>,它继承自IAsyncEnumerable<S3Object>,允许我们通过await foreach进行遍历。

这结束了我们对 AWS SDK for .NET 的介绍,以及您如何充分利用 AWS 在其服务客户端类中包含的常用功能。接下来,我们将通过它们的各种 AI 作为服务(AIaaS)产品,来看看 AWS 如何将 AI 引入您的 C#代码库。

使用 AWS AI 服务

在 AWS 中使用高级 AI 服务是一个自然的下一步。使用 AWS 平台的巨大优势之一是可用的高级 AI 和 ML 服务。这些服务允许开发人员快速构建解决方案,涵盖从计算机视觉到自然语言处理等多种服务。这里是AWS 上的 AI 服务完整列表。在本章中,我们将探索其中的两个 AI 服务:亚马逊理解和亚马逊识别。这些服务使用预训练的机器学习模型分析文本和图像,分别进行处理。理解和识别可以覆盖典型.NET 应用程序可能需要的许多 AI 用途。

AWS 理解

让我们通过使用亚马逊理解服务开始探索这些 AI 服务。亚马逊理解是一种利用自然语言处理(NLP)来发现文档内容关键洞察的服务。这种能力对于希望在报告中检测客户情感的公司可能至关重要。您可以同时处理一个或多个文档。可用的服务包括以下项目:

实体

亚马逊理解返回文档实体,包括人物、地点和位置等名词。

关键短语

亚马逊理解从关键文档中提取关键短语以解释文档内容。

PII

此功能检测到个人可识别信息(PII)的存在。

语言

此功能可以成功地将文档中的主要语言分类为多达 100 种语言。

情绪

此功能检测文档的情感倾向,包括积极的、中性的、消极的或混合的情绪。

语法

此函数提取文档中的词性,从形容词到名词等各种词汇。

在 AWS 上启动任何 AI 服务的最佳方法之一是使用 AWS CloudShell 命令行。让我们从可以粘贴到 CloudShell Bash 终端的代码片段开始。该命令使用了 aws 开头,接着是服务名 comprehend,然后是服务的功能 detect-sentiment

aws comprehend detect-sentiment \
    --language-code "en" \
    --text "I love C#."

Figure 8-8 中显示的输出返回了一个包含 SentimentScore 的 JSON 负载,即文本的情感。

doac 0808

图 8-8. 异步列出存储桶

另一种探索此 API 的方法是通过动态地将网站上的文本输入到我们的 CLI 中。此步骤通过使用 lynx 实现。让我们运行这个命令来帮助我们确定维基百科上关于阿尔伯特·爱因斯坦的情感:

  1. 首先,安装 lynx

    sudo yum install lynx
    
  2. 接下来,获取阿尔伯特·爱因斯坦的页面并将其导入到 less 中以便探索:

    lynx -dump https://en.wikipedia.org/wiki/Albert_Einstein | less
    
  3. 通过使用 wc -l,我们得到行数统计:

    lynx -dump https://en.wikipedia.org/wiki/Albert_Einstein | wc -l
    
  4. 要获取字节数,可以使用 wc --bytes

    lynx -dump https://en.wikipedia.org/wiki/Albert_Einstein | wc \
     --bytes
    

    结果显示:

    432232
    

如果运行 aws comprehend detect-sentiment help,它只能处理 5000 字节。因此,我们需要截断输出。通过 head 进行截断,然后将其分配给 Bash 的 TEXT 变量:

TEXT=`lynx -dump https://en.wikipedia.org/wiki/Albert_Einstein | head -c 5000`

接下来,使用 $TEXT 的命令输出显示,维基百科围绕阿尔伯特·爱因斯坦的内容通常是 NEUTRAL,积极情感占 34%:

aws comprehend detect-sentiment --language-code "en" --text "$TEXT"
{
    "Sentiment": "NEUTRAL",
    "SentimentScore": {
        "Positive": 0.3402811586856842,
        "Negative": 0.0033634265419095755,
        "Neutral": 0.6556956768035889,
        "Mixed": 0.0006596834864467382
    }
}

Bash 命令行也常用于构建一个解决方案,并迅速转入另一个常见操作。在下面的示例中,我们切换到从同一块维基百科文本中检测实体。请注意,我们使用 --output text 利用 Bash 的强大功能来过滤输出:

aws comprehend detect-entities \
    --language-code "en" \
    --text "$TEXT" \
    --output text | head

命令的输出显示以下实体:

ENTITIES    20     29     0.632317066192627     Wikipedia          ORGANIZATION
ENTITIES    126    141    0.9918091297149658    Albert Einstein    PERSON
ENTITIES    151    160    0.7205400466918945    Wikipedia          ORGANIZATION
ENTITIES    230    236    0.9783479571342468    German             OTHER
ENTITIES    256    264    0.9940117001533508    Einstein           PERSON
ENTITIES    305    313    0.9899683594703674    Einstein           PERSON
ENTITIES    341    356    0.9821130633354187    Albert Einstein    PERSON
ENTITIES    379    394    0.990595817565918     Albert Einstein    PERSON
ENTITIES    401    409    0.814979076385498     Einstein           PERSON
ENTITIES    410    414    0.9937220215797424    1921               DATE

我们还可以进一步计算使用 Bash 找到的唯一实体的数量。下面的 Bash 管道将实体列转换为小写,然后格式化以便轻松计数不常见的出现。虽然不完美,但足以证明如何在进入 C# 之前使用 Bash 原型 API:

aws comprehend detect-entities \
    --language-code "en" \
    --text "$TEXT" \
    --output text \
    | cut -f 5 \
    | tr -cd "[:alpha:][:space:]" \
    | tr ' [:upper:]' '\n[:lower:]' \
    | tr -s '\n' \
    | sort \
    | uniq -c \
    | sort -nr -k 1 \
    | head

Bash 管道的输出显示我们发现了几个值得进一步探索的实体,至少是从提取的文本的初始部分来看。

     12 einstein
      9 of
      6 university
      4 german
      4 empire
      4 Albert
      3 kingdom
      2 Zurich
      2 wrttemberg
      2 Wikipedia
注意

您还可以观看如何使用 Bash 从头开始提取实体的操作步骤,链接在 O’ReillyYouTube 上。

完成这些探索后,让我们开始用 C# 编写解决方案。

再次选择 Visual Studio 控制台应用程序,并按照 Figure 8-9 中所示的步骤通过 NuGet 安装 Comprehend。

doac 0809

图 8-9. NuGet Comprehend 安装

使用 AWSSDK.Comprehend 包中服务类的 DetectSentimentAsync() 方法进行情感检测如下所示:

using Amazon;
using Amazon.Comprehend;
using Amazon.Comprehend.Model;

// Display title
Console.WriteLine("AWS AI API Sentiment Detector" + Environment.NewLine);

// Ask for phrase
Console.WriteLine("Type in phrase for analysis" + Environment.NewLine);
var phrase = Console.ReadLine();

// Detect Sentiment
var comprehendClient = new AmazonComprehendClient(RegionEndpoint.EUWest1);
Console.WriteLine("Calling DetectSentiment");

var detectSentimentResponse = await
comprehendClient.DetectSentimentAsync(
new DetectSentimentRequest()
{
    Text = phrase,
    LanguageCode = "en"
});
Console.WriteLine(detectSentimentResponse.Sentiment);
Console.WriteLine("Done");

运行控制台应用程序并输入一些文本,如 Figure 8-10 所示,显示我们的陈述是 MIXED 情感。

doac 0810

图 8-10. Comprehend 控制台应用的输出

您还可以观看使用 C# 和 AWS Comprehend 的实例在 O’ReillyYouTube 上的演示。

本例中使用了 DetectSentimentAsync() 来检测文本的 情感(整体情绪);然而,我们也可以通过调用 client.DetectEntities() 在文本中执行实体检测。AWS 在图像中也提供了实体检测服务,名为 AWS Rekognition。

AWS Rekognition

Rekognition 是 AWS 的计算机视觉服务,适用于图像和视频。它提供了多个预训练的机器学习模型,以满足不同的图像识别需求,所有这些都可以通过易于使用的 API 在 .NET SDK 中使用,并采用按使用量付费的模式定价。您可以在 AWS 管理控制台中搜索“Rekognition”,点击“尝试演示”按钮,尝试使用自己的图像测试该服务。从这里,您可以上传来自您的设备的图像(或选择 AWS 提供的示例图像),并从预训练模型中选择一个。图 8-11 显示了 标签检测 算法的结果,正如您所见,AWS Rekognition 识别出这张图像中有 98.1% 的可能性是一只猫。²

doac 0811

图 8-11. 通过管理控制台测试 Rekognition

从您的 C# 代码中调用 AWS Rekognition 就像安装 AWSSDK.Rekognition NuGet 包并利用 Amazon.Rekognition.AmazonRekognitionClient 服务类来调用 API 一样简单。您可以在本章早些部分看到的任何方式中注入或实例化这个服务类;在本例中,我们已经通过 services.AddAWSService<IAmazonRekognition>(); 进行了注册:

using Amazon.Rekognition;
using Amazon.Rekognition.Model;
using Microsoft.AspNetCore.Mvc;

namespace AwsRekognitionExample.Controllers;

[Route("api/[controller]")]
public class ImageController
{
    private readonly IAmazonRekognition _rekognition;

    public ImageController(IAmazonRekognition rekognition)
    {
        _rekognition = rekognition;
    }

 [HttpGet]
    public async Task<string> GetFirstLabel()
    {
        var response = await _rekognition.DetectLabelsAsync(
            new DetectLabelsRequest
            {
                Image = new Image()
                {
                    S3Object = new S3Object()
                    {
                        Name = "cat.jpg",
                        Bucket = "photos-bucket",
                    },
                },
                MaxLabels = 10,
                MinConfidence = 75F,
            });

        return response.Labels.FirstOrDefault()?.Name ?? "None";
    }
}

在这个例子中,我们正在调用 IAmazonRekognitionDetectLabelsAsync(...) 并传递一个指向 S3 存储桶中图像的引用。Rekognition API 的工作方式是从 S3 存储桶加载图像数据,而不是在 HTTP 调用中接受图像数据来调用函数。在云原生 AWS 应用程序中,保存像图片这样的大数据文件到 S3,然后调用 AWS Rekognition 是一个良好的模式,因为它允许您设计一个事件驱动的系统。如果需要首先上传图像,您可以像我们在 “C# 简历上传示例:事件驱动” 中所做的那样利用 S3 触发器。

批判性思维讨论问题

  • 在生产代码中调用 AWS SDK 的超时,会有哪些实际后果?

  • 在生产系统中使用重试通信 AWS SDK 会有哪些实际后果?

  • 使用 AWS SDK 进行异步通信可以启用哪些新的工作流程?

  • 在生产系统中,哪些架构模式最具成本效益,以避免重复的 API 调用?

  • .NET 与 AWS 结合给您带来了哪些独特的优势?

练习

  • 如果您尚未设置 AWS Toolkit for Visual Studio,请进行设置,并使用它创建一个新的 S3 存储桶,在存储桶内创建一个文件夹,最后从您的桌面上传图像或文件。

  • 扩展 AWS Rekognition 示例,并将其转换为部署在 AWS App Runner 上的 Web 服务。

  • 扩展 AWS Comprehend 示例,并将其转换为部署在 AWS App Runner 上的 Web 服务。

  • 创建一个展示完全掌握.NET 在 AWS 上的作品集项目,展示端到端的开发技能,包括 IaC 和前端。

结论

在本书中我们已经多次使用了“云原生”这个术语,而且您在实际应用中也无疑遇到过它,但是让我们想一想它真正意味着什么以及为什么它很重要。云原生代码要么完全在云中运行,要么至少是为云进行了优化。通过以在 AWS 上专用运行代码的意图来编写我们的代码,我们可以更深入地与 AWS 服务集成,例如消息队列、数据库、日志记录和报告。如果我们的 C#代码是“云原生”的(即单词“本地”的词典定义),我们可以编写代码以最大程度地利用其周围的服务。当然,这其中存在一些权衡。如果您将应用程序架构为仅使用 AWS Lambda 函数,则本地开发将更加困难。如果您在每个执行路径中越来越多地集成 AWS X-Ray 跟踪以进行性能监控,则复杂性可能会呈指数增长。不过,通过性能、灵活性和通常情况下的成本获得的优势通常远远超过这些缺点。

请记住,无论您喜欢在 Visual Studio、VS Code 还是 Cloud9 中进行开发,都有适合您的 C# AWS 工作流程。自 Visual Studio 2008 以来,AWS Toolkit for Visual Studio 已经以一种形式或另一种形式存在,并定期更新以跟上 AWS 和 Visual Studio 本身的增强。工具包的最新版本甚至正在推出一种新的“发布到 AWS”体验,旨在使您将代码发布到云更加简单,允许您在将来要使用的环境中快速测试代码:云端。对于我们来说,我们喜欢 Cloud9,因为它可以无缝集成到 AWS 服务中,并且可以通过任何具有 Web 浏览器的地方进行访问。基于 Web 的集成开发环境并非人人都适合,但是如果您经常更换开发设备或使用便携或借用设备进行开发,那么能够从云中的任何地方访问您的代码可能会改变游戏规则。

亚马逊网络服务是全球范围内使用最广泛的云平台,自 2008 年以来一直在云中支持.NET。通过提供超过 200 种服务,从可以为任何想象的任务创建的简单 Windows 虚拟机,到本章中访问的机器学习服务,AWS 支持 485 种实例类型的.NET,255 种不同的适用于 Windows 工作负载的 AMI,以及 40 种预配置了.NET 或 SQL Server 的不同 Linux AMI。这意味着从您的 C#代码库部署到 AWS 并与之集成从未如此简单过。

在本书中,我们涵盖了许多您可以在应用程序中利用 AWS 的方式,但还有许多我们没有时间涉及的方式。有开源的.NET 工具,适用于 AWS CodeBuild 的 Windows 示例,用于利用物联网(IoT)服务的 MQTT 客户端,以及仅用于机器学习的 20 多种 AWS 服务。在这些服务中,AWS SageMaker 是一项允许您训练和部署自己的 ML 模型的服务,这个话题本身就足以填写一整本书。

然而,我们所涵盖的内容应该为您进入亚马逊的云服务提供了一个入口,并帮助您规划您的.NET 代码库的下一阶段开发。无论是迁移、容器化还是从头开始重写,我们希望这些页面上的主题能够为您提供足够的思考。

¹ 此处的示例仅使用了简单存储服务(S3)客户端;然而,这个同样适用于许多不同的 AWS 服务客户端,这些服务可能返回大量项目的方法上有Paginators属性。

² 我们几乎在没有包含詹姆斯的猫的照片的情况下完成了这本书。

附录 A. AWS 的基准测试

深入了解 AWS 的一种方法是对不同机器的性能进行基准测试。 AWS 的价值之一是能够使用许多不同的服务和其他抽象化方法来以最有效的方式解决问题。 您可以在这个基准测试的示例 GitHub 仓库中查看许多代码示例。 请注意,从 1 核心到 36 核心再到 96 核心的 sysbench 基准测试显示了显著差异,如图 A-1 所示。

doac ab01

图 A-1. AWS 实例的基准测试

当您的组织确定在 Amazon Linux 2 上运行 .NET Core 的正确机器时,最好进行一些初始基准测试。 要在 Amazon Linux 2 上以可重复的方式运行这些基准测试,可以参考这个Makefile。 请注意,sysbench 只需要两行来安装和运行 ./benchmark.sh 脚本。

实际的基准测试脚本如下:

#!/usr/bin/env bash
CPU=`python -c "import multiprocessing as mp;print(f'{mp.cpu_count()}')"`
echo "Running with CPU Count and Threads: " $CPU
sysbench cpu --threads=$CPU run

makefile 命令可以作为 make benchmark-sysbench-amazon 运行:

benchmark-sysbench-amazon:
	#install sysbench
	curl -s https://packagecloud.io/install/repositories/akopytov/\
	sysbench/script.rpm.sh | sudo bash
	sudo yum -y install sysbench
	#run CPU benchmark
	./benchmark.sh

基准测试脚本使用“单行命令”来确定可用的核心数,然后将其传递给 CPU 基准测试:

#!/usr/bin/env bash

CPU=`python -c "import multiprocessing as mp;print(f'{mp.cpu_count()}')"`
echo "Running with CPU Count and Threads: " $CPU
sysbench cpu --threads=$CPU run

Cinebench这样的免费工具可用于对 Windows 操作系统进行基准测试。 在实际部署应用程序的环境中执行多次测量,包括操作系统级别的基准测试和应用程序负载测试,总是一个好主意。

附录 B. 使用 .NET 入门

从浏览器开始交互式文档的 Hello World

GitHub Codespaces 中的 C# Hello World

接下来,尝试使用 GitHub Codespaces 和 Visual Studio Code 教程

首先,从 CLI 创建一个新的控制台应用,并命名为 HelloWorld

dotnet new console -n HelloWorld

这一步创建了一个控制台项目。接下来,将 Program.cs 中的代码更改为以下示例:

using System;

namespace HelloWorld
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World C# on AWS!");
        }
    }
}

接下来,进入目录(cd HelloWorld)并运行代码 dotnet run。该命令的输出如下:

Hello World C# on AWS!

GitHub Codespaces 中的 F# Hello World

创建一个新的 F# 控制台应用:

dotnet new console -lang "F#" -n FSharpHelloWorld

将代码更改为以下内容:

open System

// Define a function to construct a message to print
let from whom =
    sprintf "from %s" whom

[<EntryPoint>]
let main argv =
    let message = from "F# on AWS" // Call the function
    printfn "Hello world %s" message
    0 // return an integer exit code

接下来,进入目录(cd FSharpHelloWorld)并运行 dotnet run。输出将是:Hello world from F# on AWS

注意

您可以在 O'Reilly 平台YouTube 上观看此过程的演示。

posted @   绝不原创的飞龙  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
历史上的今天:
2022-06-18 ApacheCN 校对活动参与手册
2022-06-18 # ApacheCN 校对活动参与手册
点击右上角即可分享
微信分享提示