使用 AWS CDK 开发和部署云基础架构的最佳实践 - AWS Cloud Development Kit (AWS CDK) v2

这是 AWS CDK v2 开发者指南。旧版 CDK v1 于 2022 年 6 月 1 日进入维护阶段,并于 2023 年 6 月 1 日终止支持。

本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。

使用 AWS CDK 开发和部署云基础架构的最佳实践

借助 AWS CDK,开发人员或管理员可以使用支持的编程语言来定义其云基础架构。CDK 应用程序应组织为逻辑单位,例如 API、数据库和监控资源,并且可以选择针对自动部署配备管线。逻辑单位应作为构造加以实现,包括以下内容:

  • 基础设施(例如 Amazon S3 存储桶、Amazon RDS 数据库或 Amazon VPC 网络)

  • 运行时代码(例如 AWS Lambda 函数)

  • 配置代码

堆栈定义了这些逻辑单位的部署模型。有关 CDK 背后概念的更详细介绍,请参阅 CDK 入 AWS 门

AWS CDK 反映了对客户和内部团队需求的仔细考虑,也反映了复杂云应用程序的部署和持续维护过程中经常出现的故障模式。我们发现,故障通常与未经全面测试的应用程序的 out-of-band “” 更改有关,例如配置更改。因此,我们围绕一个模型开发了 AWS CDK,在这个模型中,您的整个应用程序都是用代码定义的,不仅是业务逻辑,还包括基础设施和配置。这样即可仔细查看提议的更改,在类似于生产的环境中对更改进行不同程度的全面测试,如果出现问题则可完全回滚。

代表基础架构、应用程序、源代码、配置和部署的软件开发生命周期图标。

在部署时, AWS CDK 会合成一个包含以下内容的云程序集:

  • AWS CloudFormation 描述您在所有目标环境中的基础架构的模板

  • 包含您的运行时代码及其支持文件的文件资产

使用 CDK,应用程序主版本控制分支中的每一次提交都可以代表应用程序的完整、一致、可部署的版本。然后,只要进行更改,都能自动部署您的应用程序。

AWS CDK 背后的理念促成了我们推荐的最佳实践,我们将其分为四大类。

组织最佳实践

在采用 AWS CDK的初始阶段,重要的是要考虑如何为组织做好准备,以取得成功。最好的做法是让一支专家团队负责培训和指导公司其他成员采用CDK。该团队的规模可能各不相同,从小型公司的一两个人到大型公司的全面卓越云中心 (CCoE)。该团队负责为您公司的云基础设施制定标准和策略,并负责培训和指导开发人员。

CCoE 可能会就云基础设施应使用哪些编程语言提供指导。每个组织的细节会有所不同,但是良好的政策有助于确保开发人员能够了解和维护公司的云基础架构。

CCoE 还会创建一个 “landing zone”,用于定义您在其中的组织单位 AWS。landing zone 是一个基于最佳实践蓝图的预配置、安全、可扩展的多账户 AWS 环境。要整合构成着陆区的服务,你可以使用 Cont AWS rol Tower,它通过单个用户界面配置和管理整个多账户系统。

开发团队应能使用自己的账户进行测试,并按需在这些账户中部署新资源。个人开发人员可将这些资源视为其自己的开发工作站的扩展。使用 CDK Pipelines, AWS 可以通过 CI/CD 账户将 CDK 应用程序部署到测试、集成和生产环境(每个环境都隔离在 AWS 自己的区域或账户中)。这是通过将开发者的代码合并到您组织的规范存储库中来完成的。

该图显示了通过 CI/CD 管道从开发者账户部署到多个目标账户的过程。

编码最佳实践

本节介绍组织 AWS CDK 代码的最佳实践。下图显示了团队与该团队的代码存储库、包、应用程序和构造库之间的关系。

该图显示了团队的代码组织:存储库、包、CDK 应用程序或构造库。
从简单开始,只有在需要时才增加复杂性

我们大多数最佳实践的指导原则是让事情尽可能简单,但不要更简单。只有当您要求采用更加复杂的解决方案时,才会增加复杂性。使用 AWS CDK,您可以根据需要重构代码以支持新的需求。您不必预先为所有可能的场景进行架构设计。

与 Well-Architect AWS ed 框架保持一致

Wel AWS l-A rchitected Framework 将组件定义为根据需求共同交付的代码、配置 AWS 和资源。组件通常是技术处理单元,与其他组件分离。工作负载一词是指共同提供业务价值的组件集合。工作负载通常是业务和技术领导者沟通的细节层次。

AWS CDK 应用程序映射到 Well-Architected Framework 定义 AWS 的组件。 AWS CDK 应用程序是一种编纂和交付 Well-Architected 云应用程序最佳实践的机制。您还可以通过构件存储库(例如 AWS CodeArtifact)将组件作为可重用代码库进行创建和共享。

每个应用程序都从一个存储库中的单个软件包开始

单个软件包是您的 AWS CDK 应用程序的入口点。在这里,您可以定义应用程序不同逻辑单位的部署方式和位置。您还可以通过定义 CI/CD 管线来部署应用程序。应用程序的构造定义了解决方案的逻辑单元。

对于在多个应用程序中使用的构造,请使用其他软件包。(共享构造也应有自己的生命周期和测试策略。) 同一存储库中软件包之间的依赖关系由您的 repo 的构建工具管理。

尽管这是可能的,但我们不建议将多个应用程序放在同一个存储库中,尤其是在使用自动部署管道时。这样做会在部署期间扩大更改的“影响范围”。当存储库中有多个应用程序时,对一个应用程序的更改会触发其他应用程序的部署(即使其他应用程序没有更改)。此外,一个应用程序中断会妨碍其他应用程序的部署。

根据代码生命周期或团队所有权将代码移入存储库

当软件包开始在多个应用程序中使用时,请将其移至相应存储库中。这样,使用软件包的应用程序构建系统就可以引用这些软件包,并且以一定间隔进行更新,不受应用程序生命周期影响。但是,起初可以将所有共享构造放在一个存储库中。

此外,不同团队在处理软件包时,请将软件包移至相应存储库中。这有助于实施访问控制。

要跨存储库边界使用软件包,你需要一个私有软件包存储库,类似于 NPM 或 Maven Central PyPi,但位于组织内部。您还需要一个发布流程,用于构建、测试软件包并将其发布到私有软件包存储库。 CodeArtifact可以托管大多数流行编程语言的软件包。

对包存储库中软件包的依赖由您的语言的包管理器管理,例如 TypeScript 或 JavaScript 应用程序的 NPM。软件包管理器有助于确保构建的可重复性。其通过记录应用程序所依赖的所有特定版本软件包实现这一点。此外,软件包管理器还能让您以受控方式升级这些依赖项。

共享软件包需要不同的测试策略。若为单个应用程序,将应用程序部署到测试环境中并确认其仍可运行即可。但共享软件包的测试必须独立于使用的应用程序进行,如同公开发布的程序包一样。(您的组织可能会选择实际公开发布一些共享软件包。)

请记住,构造的简单或复杂程度不做要求。Bucket 是构造,但 CameraShopWebsite 也可以是构造。

基础架构和运行时代码位于同一个包中

除了生成用于部署基础设施的 AWS CloudFormation 模板外, AWS CDK 还捆绑了 Lambda 函数和 Docker 镜像等运行时资产,并将它们与您的基础设施一起部署。这样可以将定义基础设施的代码和实现运行时逻辑的代码合并到一个构造中。这是执行此操作的最佳实践。这两种代码不需要放在单独的存储库中,甚至不需要放在单独的包中。

要同时改进两种代码,可以使用完整描述一项功能(包括其基础设施和逻辑)的独立构造。借助独立构造,您可以单独测试这两种代码,跨项目共享和重用代码,并同步对所有代码进行版本控制。

构造最佳实践

本节包含开发构造的最佳实践。构造是封装资源的可重用、可组合模块。它们是 AWS CDK 应用程序的基石。

使用构造建模,使用堆栈进行部署

堆栈是部署单位:堆栈中的各个部分一同部署。因此,在使用多个 AWS 资源构建应用程序的高级逻辑单元时,请将每个逻辑单元表示为构造,而不是堆栈。仅使用堆栈来描述在各种部署场景中应如何组合和连接构造。

例如,如果逻辑单位是网站,则构成该网站的构造(例如 Amazon S3 存储桶、API Gateway、Lambda 函数或 Amazon RDS 表)应组合成单个高级别构造。然后,应将该构造实例化为一个或多个堆栈进行部署。

通过使用构造进行构建,使用堆栈进行部署,可以提高基础架构的重复使用潜力,并使自己在部署方式上更加灵活。

使用属性和方法进行配置,而不是使用环境变量进行配置

在构造和堆栈中查找环境变量是一种常见的反模式。构造和堆栈均应接受属性对象,以便可以完全使用代码进行配置。否则会引入对运行代码的计算机的依赖,这会创建更多需要跟踪和管理的配置信息。

通常,环境变量查找应限制在 AWS CDK 应用程序的顶层。它们还应该用于传递在开发环境中运行所需的信息。有关更多信息,请参阅 AWS CDK 的环境

对您的基础架构进行单元测试

要在所有环境中在构建时一致地运行全套单元测试,请避免在合成过程中进行网络查询,并使用代码对所有生产阶段进行建模。(后文将介绍这些最佳实践。) 如果任何一次提交总是产生相同的生成模板,则可以信任您编写的单元测试,以此确认已生成模板是否符合您的预期。有关更多信息,请参阅测试 AWS CDK 应用程序。

不要更改有状态资源的逻辑 ID

更改资源的逻辑 ID 会导致该资源在下次部署时替换成新资源。对于数据库和 S3 存储桶等有状态资源,或者类似 Amazon VPC 的持久基础设施,您通常不希望发生这种情况。请谨慎对待可能导致 ID 更改的 AWS CDK 代码的任何重构。编写断言有状态资源的逻辑保持静态 IDs 的单元测试。逻辑 ID 源自id您在实例化构造时指定的以及构造在构造树中的位置。有关更多信息,请参阅逻辑 IDs

结构不足以保证合规性

许多企业客户为 L2 构造(代表具有内置合理默认值和最佳实践的单个 AWS 资源的 “精选” 结构)编写自己的包装器。这些包装器强制执行安全最佳实践,例如静态加密和特定 IAM 策略。例如,您可以创建 MyCompanyBucket,然后在应用程序中用其代替常见 Amazon S3 Bucket 构造。这种模式对于在软件开发生命周期的早期显示安全指导很有用,但不要依赖它作为唯一的强制执行手段。

取而代之的是,使用服务控制策略权限边界等 AWS 功能在组织层面强制实施安全防护措施。在部署之前,使用 Aspects 和 AWS CDKCloudFormation Guard 等工具对基础架构元素的安全属性做出断言。使用 AWS CDK 来做它最擅长的事情。

最后,请记住,编写自己的 “L2+” 构造可能会使您的开发人员无法利用 AWS CDK 包(例如 S AWS olutions Constructs)或 Construct Hub 中的第三方构造。这些包通常基于标准 AWS CDK 构造构造,无法使用您的包装器构造。

应用程序最佳实践

在本节中,我们将讨论如何编写 AWS CDK 应用程序,通过组合结构来定义 AWS 资源的连接方式。

在综合时做出决定

尽管 AWS CloudFormation 允许您在部署时做出决策(使用Conditions{ Fn::If }、和Parameters),并且 AWS CDK 允许您访问这些机制,但我们建议您不要使用它们。与通用编程语言中可用的值相比,您可以使用的值类型和可以对其执行的操作类型是有限的。

相反,请尝试使用编程语言的语if句和其他功能在 AWS CDK 应用程序中做出所有决定,例如要实例化哪个构造。例如,一个常见的 CDK 成语,即遍历列表并用列表中每项的值实例化构造,根本不可能使用表达式。 AWS CloudFormation

AWS CloudFormation 将其视为 AWS CDK 用于强大云部署的实现细节,而不是语言目标。你不是在 TypeScript 用 Python 编写 AWS CloudFormation 模板,而是在编写恰好 CloudFormation 用于部署的 CDK 代码。

使用生成的资源名称,而不是物理名称

名称属于宝贵资源。每个名称只能使用一次。因此,如果您将表名或存储桶名称硬编码到基础设施和应用程序中,则不能在同一个账户中两次部署该基础架构。(我们这里所说的名称是由 Amazon S3 存储桶构造中的bucketName属性指定的名称。)

更糟糕的是,您无法对需要替换的资源进行更改。如果只能在创建资源时设置属性,例如 Amazon DynamoDB 表的 KeySchema,则该属性不可变。更改此属性需要新资源。然而,新资源必须名称相同才能真正替换。但是,当现有资源仍在使用该名称时,它不能使用相同的名称。

更好的方法是尽可能少地指定名称。如果您省略资源名称, AWS CDK 将以不会导致问题的方式为您生成它们。假设您有一张表作为资源。然后,您可以将生成的表名称作为环境变量传递到您的 AWS Lambda 函数中。在您的 AWS CDK 应用程序中,您可以将表名引用为table.tableName。或者,您可以在启动时在您的 Amazon EC2 实例上生成配置文件,或者将实际的表名写入 S AWS ystems Manager Parameter Store,以便您的应用程序可以从那里读取它。

如果你需要它的地方是另一个 AWS CDK 堆栈,那就更简单了。假设一个堆栈定义了资源,而另一堆栈需要使用该资源,则以下情况适用:

  • 如果两个堆栈位于同一 AWS CDK 应用程序中,则在两个堆栈之间传递一个引用。例如,将对资源构造的引用保存为定义堆栈的属性 (this.stack.uploadBucket = amzn-s3-demo-bucket)。然后,将该属性传递给需要资源的堆栈的构造函数。

  • 当两个堆栈位于不同的 AWS CDK 应用程序中时,使用静态from方法根据其 ARN、名称或其他属性使用外部定义的资源。(例如,将 Table.fromArn() 用于 DynamoDB 表)。使用该CfnOutput结构在的输出中打印 ARN 或其他必填值cdk deploy,或者在 AWS 管理控制台中查看。或者,第二个应用程序可以读取第一个应用程序生成的 CloudFormation 模板并从该Outputs部分检索该值。

定义删除策略和日志保留

AWS CDK 试图通过默认使用保留您创建的所有内容的策略来防止您丢失数据。例如,对于包含数据的资源(例如 Amazon S3 存储桶和数据库表),默认移除策略是资源从堆栈中移除时,不会被删除。相反,该资源从堆栈中孤立出来。同样,CDK 的默认设置是永久保留所有日志。在生产环境中,这些默认设置很快就会导致存储大量您实际上并不需要的数据,并产生相应的 AWS 账单。

请仔细考虑您希望各生产资源采用何种策略,并相应指定策略。使用 Aspects 和 AWS CDK 来验证堆栈中的删除和日志策略。

根据部署要求将您的应用程序分成多个堆栈

应用程序所需堆栈数量未作硬性规定。通常,您最终会根据自己的部署模式做出决定。记住以下准则:

  • 将尽可能多的资源放在同一个堆栈中通常更简单,因此,除非你知道要将它们分开,否则请将它们放在一起。

  • 考虑将有状态资源(如数据库)与无状态资源分开保存在不同堆栈中。然后,您可以在有状态堆栈上开启终止保护。这样,您就可以在不面临数据丢失风险的情况下自由销毁或创建多个无状态堆栈的副本。

  • 有状态的资源对构造重命名更为敏感——重命名会导致资源替换。因此,不要将有状态的资源嵌套在可能被移动或重命名的构造中(除非状态丢失后可以重建,比如缓存)。这是将有状态资源放在相应堆栈中的又一充分理由。

承诺cdk.context.json避免非确定性行为

确定性是成功 AWS 部署 CDK 的关键。无论何时将 AWS CDK 应用程序部署到给定环境,其结果都应基本相同。

由于您的 AWS CDK 应用程序是用通用编程语言编写的,因此它可以执行任意代码、使用任意库和进行任意网络调用。例如,在合成应用程序时,你可以使用 AWS SDK 从你的 AWS 账户中检索一些信息。要知道,这样做会额外引发凭证设置需求,增加延迟,并且每次运行 cdk synth 时均有可能出现故障(无论多小)。

在合成过程中,切勿修改您的 AWS 账户或资源。应用程序合成不得产生副作用。只有在 AWS CloudFormation 模板生成之后,才应在部署阶段对基础架构进行更改。这样,如果出现问题, AWS CloudFormation 可以自动回滚更改。要进行在 AWS CDK 框架中无法轻易进行的更改,请在部署时使用自定义资源执行任意代码。

即使是严格的只读调用,也未必安全。考虑一下如果网络调用返回值变更会发生什么。这将影响基础设施的哪一部分? 已部署的资源会如何? 以下是两个示例情况,其中值突然发生变化可能会导致问题。

  • 如果您将 Amazon VPC 配置到指定区域的所有可用区域,并且部署当天的可用区数量 AZs 为两个,则您的 IP 空间将被分成两半。如果在第二天 AWS 启动新的可用区,则之后的下一次部署会尝试将您的 IP 空间分成三分之二,要求重新创建所有子网。这可能是不可能的,因为你的 Amazon EC2 实例仍在运行,你必须手动清理它。

  • 如果您查询最新的 Amazon Linux 计算机映像并部署了 Amazon EC2 实例,而第二天又发布了新映像,则后续部署会选择新的 AMI 并替换您的所有实例。这可能不符合您的期望。

这些情况可能是有害的,因为 AWS-side 更改可能会在成功部署数月或数年后发生。您的部署会顷刻之间“无故”崩盘,而您早已忘记了已执行的操作和原因。

幸运的是, AWS CDK 包含一种名为上下文提供者的机制,用于记录非确定性值的快照。这样未来合成操作即可生成与首次部署时完全相同的模板。新模板的唯一更改即是对代码所做更改。当你使用构造的.fromLookup()方法时,调用的结果会被缓存在中cdk.context.json。您应将其与其余代码一起提交版本控制,确保未来 CDK 应用程序的执行使用相同值。CDK Toolkit 包含用于管理上下文缓存的命令,您可以按需刷新特定条目。有关更多信息,请参阅上下文值和 AWS CDK

如果您需要一些没有原生 CDK 上下文提供程序的值(来自 AWS 或其他地方),我们建议您编写一个单独的脚本。该脚本应检索该值并将其写入文件,然后在 CDK 应用程序中读取文件。仅在您想要刷新存储值时运行脚本,请勿将其作为常规构建过程的一部分。

让 AWS CDK 管理角色和安全组

使用 AWS CDK 构造库的grant()便捷方法,您可以创建 Ident AWS ity and Access Management 角色,这些角色使用最小范围的权限授予另一种资源的访问权限。例如,请考虑以下行:

amzn-s3-demo-bucket.grantRead(myLambda)

这一行向 Lambda 函数的角色添加了一个策略(该角色也是为您创建的)。这个角色及其政策有十几 CloudFormation 行你不必写。 AWS CDK 仅授予函数从存储桶读取所需的最低权限。

如果您要求开发人员始终使用安全团队创建的预定义角色,那么 AWS CDK 编码就会变得更加复杂。您的团队会在设计应用程序方面面临灵活性不足的问题。一种更好的替代方法是使用服务控制策略权限边界,确保开发人员始终处于防护机制内。

用代码对所有生产阶段进行建模

在传统 AWS CloudFormation 场景中,您的目标是生成一个参数化的工件,以便在应用特定于这些环境的配置值后,可以将其部署到各种目标环境。在 CDK 中,您可以且应该将该配置构建到源代码中。为您的生产环境创建一个堆栈,并为其他各阶段单独创建一个堆栈。然后在代码中输入各堆栈的配置值。使用诸如 S ecrets Manag er 和 System s Manager Parameter Store 之类的服务来获取你不想在源代码管理中签入 ARNs 的敏感值,使用这些资源的名称或名称。

合成应用程序时,在 cdk.out 文件夹中创建的云程序集包含各环境的单独模板。整个构建具有确定性。您的应用程序没有任何 out-of-band更改,并且任何给定的提交始终会生成完全相同的 AWS CloudFormation 模板和随附的资产。单元测试因此能更加可靠。

测量一切

在没有人工干预的情况下,要实现全面持续部署的目标需要高度自动化。只有通过大量监控才能实现自动化。要测量已部署资源的所有方面,请创建指标、警报和仪表板。不要停下来衡量 CPU 使用率和磁盘空间之类的东西。还要记录业务指标,并使用测量结果自动执行回滚等部署决策。 AWS CDK 中的大多数 L2 构造都有方便的方法来帮助您创建指标,例如类上的metricUserErrors()方法。dynamodb.Table