

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

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

# 使用 AWS CDK 开发和部署云基础架构的最佳实践
<a name="best-practices"></a>

借助 AWS CDK，开发人员或管理员可以使用支持的编程语言来定义其云基础架构。CDK 应用程序应组织为逻辑单位，例如 API、数据库和监控资源，并且可以选择针对自动部署配备管线。逻辑单位应作为构造加以实现，包括以下内容：
+ 基础设施（例如 Amazon S3 存储桶、Amazon RDS 数据库或 Amazon VPC 网络）
+ 运行时代码（例如 AWS Lambda 函数）
+ 配置代码

堆栈定义了这些逻辑单位的部署模型。有关 CDK 背后概念的更详细介绍，请参阅 CDK [入 AWS 门](getting-started.md)。

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

![代表基础设施、应用程序、源代码、配置和部署的软件开发生命周期图标。](http://docs.aws.amazon.com/zh_cn/cdk/v2/guide/images/all-in-one.jpg)


在部署时， AWS CDK 会合成一个包含以下内容的云程序集：
+  AWS CloudFormation 描述您在所有目标环境中的基础架构的模板
+ 包含您的运行时代码及其支持文件的文件资产

借助 CDK，应用程序主版本控制分支每次均可提交完整、一致且可部署的应用程序版本。然后，只要进行更改，都能自动部署您的应用程序。

 AWS CDK 背后的理念促成了我们推荐的最佳实践，我们将其分为四大类。
+  [组织最佳实践](#best-practices-organization) 
+  [编码最佳实践](#best-practices-code) 
+  [构建最佳实践](#best-practices-constructs) 
+  [应用程序最佳实践](#best-practices-apps) 

**提示**  
还要考虑适用[于 CDK 定义的基础架构的最佳实践 AWS CloudFormation](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/best-practices.html)和您使用的各项 AWS 服务。

## 组织最佳实践
<a name="best-practices-organization"></a>

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

 CCoE 可能会就云基础设施应使用哪些编程语言提供指导。各组织的详细情况有所不同，但良好的策略有助于确保开发人员能够了解和维护公司的云基础设施。

 CCoE 还会创建一个 “landing zone”，用于定义您在其中的组织单位 AWS。landing zone 是一个基于最佳实践蓝图的预配置、安全、可扩展的多账户 AWS 环境。要整合构成登录区的服务，您可以使用 [AWS Control Tower](https://aws.amazon.com/controltower/)，其可通过单个用户界面配置和管理整个多账户系统。

开发团队应能使用自己的账户进行测试，并按需在这些账户中部署新资源。个人开发人员可将这些资源视为其自己的开发工作站的扩展。使用 [CDK](cdk-pipeline.md) Pipelin CI/CD es后， AWS 可以通过账户将CDK应用程序部署到测试、集成和生产环境（每个环境都隔离在 AWS 自己的区域或账户中）。具体方式是将开发人员的代码合并到您组织的规范存储库中。

![该图显示了通过 CI/CD 管道从开发者账户部署到多个目标账户的过程。](http://docs.aws.amazon.com/zh_cn/cdk/v2/guide/images/best-practice-deploy-to-multiple-accounts.png)


## 编码最佳实践
<a name="best-practices-code"></a>

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

![该图显示了团队的代码组织：存储库、程序包、CDK 应用程序或构造库。](http://docs.aws.amazon.com/zh_cn/cdk/v2/guide/images/code-organization.jpg)
<a name="best-practices-code-kiss"></a>

 **从简单开始，只有在需要时才增加复杂性**   
我们大多数最佳实践的指导原则是让事情尽可能简单，而不过度简化。只有当您要求采用更加复杂的解决方案时，才会增加复杂性。使用 AWS CDK，您可以根据需要重构代码以支持新的需求。您不必预先为所有可能的场景进行架构设计。<a name="best-practices-code-well-architected"></a>

 **与 Well-Architect AWS ed 框架保持一致**   
Wel [AWS l](https://aws.amazon.com/architecture/well-architected/)-*A* rchitected Framework 将组件定义为根据需求共同交付的代码、配置 AWS 和资源。组件通常是技术处理单元，与其他组件分离。*工作负载*一词是指共同提供业务价值的组件集合。工作负载通常是业务和技术领导者沟通的细节层次。  
 AWS CDK 应用程序映射到 Well-Architected Framework 定义 AWS 的组件。 AWS CDK 应用程序是一种编纂和交付 Well-Architected 云应用程序最佳实践的机制。您还可以通过构件存储库（例如 AWS CodeArtifact）将组件作为可重用代码库进行创建和共享。<a name="best-practices-code-package"></a>

 **每个应用程序都从一个存储库中的单个软件包开始**   
单个软件包是您的 AWS CDK 应用程序的入口点。在这里，您可以定义应用程序不同逻辑单位的部署方式和位置。您还可以定义用于部署应用程序的 CI/CD 管道。应用程序的构造定义了解决方案的逻辑单位。  
对于在多个应用程序中使用的构造，请使用其他软件包。（共享构造也应有自己的生命周期和测试策略。） 同一存储库中软件包之间的依赖关系由存储库的构建工具管理。  
尽管可以这样做，但我们不建议将多个应用程序放在同一存储库中，尤其是在使用自动部署管线时。这样做会在部署期间扩大更改的“影响范围”。若存储库中有多个应用程序，更改一个应用程序会触发其他应用程序的部署（即使其他应用程序并无更改）。此外，一个应用程序中断会妨碍其他应用程序的部署。<a name="best-practices-code-repo"></a>

 **根据代码生命周期或团队所有权将代码移入存储库**   
当软件包开始在多个应用程序中使用时，请将其移至相应存储库中。这样，使用软件包的应用程序构建系统就可以引用这些软件包，并且以一定间隔进行更新，不受应用程序生命周期影响。但是，起初可以将所有共享构造放在一个存储库中。  
此外，不同团队在处理软件包时，请将软件包移至相应存储库中。这有助于实施访问控制。  
要跨存储库边界使用软件包，你需要一个私有软件包存储库，类似于 NPM 或 Maven Central PyPi，但位于组织内部。您还需要一个发布流程，用于构建、测试软件包并将其发布到私有软件包存储库。 [CodeArtifact](https://docs.aws.amazon.com/codeartifact/latest/ug/)可以托管大多数流行编程语言的软件包。  
对包存储库中软件包的依赖由您的语言的包管理器管理，例如 TypeScript 或 JavaScript 应用程序的 NPM。软件包管理器有助于确保构建的可重复性。其通过记录应用程序所依赖的所有特定版本软件包实现这一点。此外，软件包管理器还能让您以受控方式升级这些依赖项。  
共享软件包需要不同的测试策略。若为单个应用程序，将应用程序部署到测试环境中并确认其仍可运行即可。但共享软件包的测试必须独立于使用的应用程序进行，如同公开发布的程序包一样。（您的组织可能会选择实际公开发布一些共享软件包。）  
请记住，构造的简单或复杂程度不做要求。`Bucket` 是构造，但 `CameraShopWebsite` 也可以是构造。<a name="best-practices-code-all"></a>

 **基础架构和运行时代码位于同一个包中**   
除了生成用于部署基础设施的 AWS CloudFormation 模板外， AWS CDK 还捆绑了 Lambda 函数和 Docker 镜像等运行时资产，并将它们与您的基础设施一起部署。这样可以将定义基础设施的代码和实现运行时逻辑的代码合并到一个构造中。此为最佳实践。这两种代码不需要存放在单独的存储库中，甚至不需要放在单独的软件包中。  
要同时改进两种代码，可以使用完整描述一项功能（包括其基础设施和逻辑）的独立构造。借助独立构造，您可以单独测试这两种代码，跨项目共享和重用代码，并同步对所有代码进行版本控制。

## 构造最佳实践
<a name="best-practices-constructs"></a>

本节包含开发构造的最佳实践。构造是封装资源的可重用、可组合模块。它们是 AWS CDK 应用程序的基石。<a name="best-practices-constructs-model"></a>

 **使用构造建模，使用堆栈进行部署**   
堆栈是部署单位：堆栈中的各个部分一同部署。因此，在使用多个 AWS 资源构建应用程序的高级逻辑单元时，请将每个逻辑单元表示为[构造](https://docs.aws.amazon.com/cdk/api/v2/docs/constructs.Construct.html)，而不是[堆栈](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.Stack.html)。仅使用堆栈来描述在各种部署场景中应如何组合和连接构造。  
例如，如果逻辑单位是网站，则构成该网站的构造（例如 Amazon S3 存储桶、API Gateway、Lambda 函数或 Amazon RDS 表）应组合成单个高级别构造。然后，应将该构造实例化为一个或多个堆栈进行部署。  
通过将构造用于构建、将堆栈用于部署，可以提高基础设施的重用潜力，并增加部署方式的灵活性。<a name="best-practices-constructs-config"></a>

 **使用属性和方法进行配置，而不是使用环境变量进行配置**   
在构造和堆栈中查找环境变量是一种常见的反模式。构造和堆栈均应接受属性对象，以便可以完全使用代码进行配置。否则会引入对运行代码的计算机的依赖，这会创建更多需要跟踪和管理的配置信息。  
通常，环境变量查找应限制在 AWS CDK 应用程序的顶层。查询还应用于传递在开发环境中运行所需的信息。有关更多信息，请参阅 [AWS CDK 的环境](environments.md)。<a name="best-practices-constructs-test"></a>

 **对您的基础架构进行单元测试**   
要在所有环境中在构建时一致地运行全套单元测试，请避免在合成过程中进行网络查询，并使用代码对所有生产阶段进行建模。（后文将介绍这些最佳实践。） 如果任何一次提交总是产生相同的生成模板，则可以信任您编写的单元测试，以此确认已生成模板是否符合您的预期。有关更多信息，请参阅[测试 AWS CDK 应用程序。](testing.md)<a name="best-practices-constructs-logicalid"></a>

 **请勿更改有状态资源的逻辑 ID**   
更改资源的逻辑 ID 会导致该资源在下次部署时替换成新资源。对于数据库和 S3 存储桶等有状态资源，或者类似 Amazon VPC 的持久基础设施，您通常不希望发生这种情况。请谨慎对待可能导致 ID 更改的 AWS CDK 代码的任何重构。编写断言有状态资源的逻辑保持静态 IDs 的单元测试。逻辑 ID 源自您在实例化构造时指定的 `id` 以及构造在构造树中的位置。有关更多信息，请参阅[逻辑 IDs](identifiers.md#identifiers-logical-ids)。<a name="best-practices-constructs-compliance"></a>

 **构造不足以保证合规**   
许多企业客户为 L2 构造（代表具有内置合理默认值和最佳实践的单个 AWS 资源的 “精选” 结构）编写自己的包装器。这些包装器强制执行安全最佳实践，例如静态加密和特定 IAM 策略。例如，您可以创建 `MyCompanyBucket`，然后在应用程序中用其代替常见 Amazon S3 `Bucket` 构造。该模式对于在软件开发生命周期初期提出安全指导非常有用，但不要将其作为唯一的执行手段。  
取而代之的是，使用[服务控制策略](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html)和[权限边界](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html)等 AWS 功能在组织层面强制实施安全防护措施。[在部署之前，使用 Aspects 和 AWS CDK](aspects.md) 或 [CloudFormation Guard](https://github.com/aws-cloudformation/cloudformation-guard) 等工具对基础架构元素的安全属性做出断言。使用 AWS CDK 来做它最擅长的事情。  
最后，请记住，编写自己的 “L2\+” 构造可能会使您的开发人员无法利用 AWS CDK 包（例如 S [AWS olutions Constructs）或 Construct Hub 中的第三方构造](https://docs.aws.amazon.com/solutions/latest/constructs/welcome.html)。这些包通常基于标准 AWS CDK 构造构造，无法使用您的包装器构造。

## 应用程序最佳实践
<a name="best-practices-apps"></a>

在本节中，我们将讨论如何编写 AWS CDK 应用程序，通过组合结构来定义 AWS 资源的连接方式。<a name="best-practices-apps-synth"></a>

 **在综合时做出决定**   
尽管 AWS CloudFormation 允许您在部署时做出决策（使用`Conditions``{ Fn::If }`、和`Parameters`），并且 AWS CDK 允许您访问这些机制，但我们建议您不要使用它们。与通用编程语言中可用的值相比，可以使用的值类型和可对其执行的操作类型相对有限。  
相反，请尝试使用编程语言的语`if`句和其他功能在 AWS CDK 应用程序中做出所有决定，例如要实例化哪个构造。例如，一个常见的 CDK 成语，即遍历列表并用列表中每项的值实例化构造，根本不可能使用表达式。 AWS CloudFormation   
 AWS CloudFormation 将其视为 AWS CDK 用于强大云部署的实现细节，而不是语言目标。你不是在 TypeScript 用 Python 编写 AWS CloudFormation 模板，而是在编写恰好 CloudFormation 用于部署的 CDK 代码。<a name="best-practices-apps-names"></a>

 **使用生成的资源名称，而不是物理名称**   
名称属于宝贵资源。每个名称只能使用一次。因此，如果您将表名或存储桶名称硬编码至基础设施和应用程序中，则不能在同一账户中两次部署该基础设施。（此处所述名称是指由 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`部分检索该值。<a name="best-practices-apps-removal-logs"></a>

 **定义移除策略和日志留存**   
 AWS CDK 试图通过默认使用保留您创建的所有内容的策略来防止您丢失数据。例如，对于包含数据的资源（例如 Amazon S3 存储桶和数据库表），默认移除策略是资源从堆栈中移除时，不会被删除。相反，该资源从堆栈中孤立出来。同样，CDK 默认永久保留所有日志。在生产环境中，这些默认设置很快就会导致存储大量您实际上并不需要的数据，并产生相应的 AWS 账单。  
请仔细考虑您希望各生产资源采用何种策略，并相应指定策略。使用 [Aspects 和 AWS CDK](aspects.md) 来验证堆栈中的删除和日志策略。<a name="best-practices-apps-separate"></a>

 **根据部署要求将您的应用程序分成多个堆栈**   
应用程序所需堆栈数量未作硬性规定。通常您最终会根据自己的部署模式做出决策。记住以下准则：  
+ 尽可能将更多资源放在同一堆栈中通常更简单，因此，除非知道要将其分开，否则请将资源放在一起。
+ 考虑将有状态资源（如数据库）与无状态资源分开保存在不同堆栈中。然后，您可以在有状态堆栈上开启终止保护。这样，您就可以在不面临数据丢失风险的情况下自由销毁或创建多个无状态堆栈的副本。
+ 有状态资源对构造重命名更为敏感，重命名会导致资源替换。因此，请勿将有状态资源嵌套在可能被移动或重命名的构造中（除非状态丢失后可以重建，例如缓存）。这是将有状态资源放在相应堆栈中的又一充分理由。<a name="best-practices-apps-context"></a>

 **承诺`cdk.context.json`避免非确定性行为**   
确定性是成功 AWS 部署 CDK 的关键。无论何时将 AWS CDK 应用程序部署到给定环境，其结果都应基本相同。  
由于您的 AWS CDK 应用程序是用通用编程语言编写的，因此它可以执行任意代码、使用任意库和进行任意网络调用。例如，在合成应用程序时，你可以使用 AWS SDK 从你的 AWS 账户中检索一些信息。要知道，这样做会额外引发凭证设置需求，增加延迟，并且每次运行 `cdk synth` 时均有可能出现故障（无论多小）。  
在合成过程中，切勿修改您的 AWS 账户或资源。应用程序合成不得产生副作用。只有在 AWS CloudFormation 模板生成之后，才应在部署阶段对基础架构进行更改。这样，如果出现问题， AWS CloudFormation 可以自动回滚更改。要进行在 AWS CDK 框架中无法轻易进行的更改，请在部署时使用[自定义资源](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html)执行任意代码。  
即使是严格的只读调用，也未必安全。考虑一下如果网络调用返回值变更会发生什么。这将影响基础设施的哪一部分？ 已部署的资源会如何？ 以下是两个示例情况，其中值突然发生变化可能会导致问题。  
+ 如果您将 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](context.md)。  
如果您需要一些没有原生 CDK 上下文提供程序的值（来自 AWS 或其他地方），我们建议您编写一个单独的脚本。该脚本应检索该值并将其写入文件，然后在 CDK 应用程序中读取文件。仅在您想要刷新存储值时运行脚本，请勿将其作为常规构建过程的一部分。<a name="best-practices-apps-roles"></a>

 **让 AWS CDK 管理角色和安全组**   
借助 AWS CDK 构造库的`grants`属性及其便捷方法，您可以创建 Ident AWS ity and Access Management 角色，这些角色使用最小范围的权限授予另一种资源的访问权限。例如，请考虑以下行：  

```
amzn-s3-demo-bucket.grants.read(myLambda)
```
此行向 Lambda 函数的角色添加了策略（该角色同样是为您创建）。这个角色及其政策有十几 CloudFormation 行你不必写。 AWS CDK 仅授予函数从存储桶读取所需的最低权限。  
如果您要求开发人员始终使用安全团队创建的预定义角色，那么 AWS CDK 编码就会变得更加复杂。您的团队会在设计应用程序方面面临灵活性不足的问题。一种更好的替代方法是使用[服务控制策略](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html)和[权限边界](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_boundaries.html)，确保开发人员始终处于防护机制内。<a name="best-practices-apps-stages"></a>

 **用代码对所有生产阶段进行建模**   
在传统 AWS CloudFormation 场景中，您的目标是生成一个参数化的工件，以便在应用特定于这些环境的配置值后，可以将其部署到各种目标环境。在 CDK 中，您可以且应该将该配置构建到源代码中。为您的生产环境创建一个堆栈，并为其他各阶段单独创建一个堆栈。然后在代码中输入各堆栈的配置值。使用诸如 S [ecrets Manag](https://aws.amazon.com/secrets-manager/) [er 和 System](https://aws.amazon.com/systems-manager/) s Manager Parameter Store 之类的服务来获取你不想在源代码管理中签入 ARNs 的敏感值，使用这些资源的名称或名称。  
合成应用程序时，在 `cdk.out` 文件夹中创建的云程序集包含各环境的单独模板。整个构建具有确定性。您的应用程序没有任何 out-of-band更改，并且任何给定的提交始终会生成完全相同的 AWS CloudFormation 模板和随附的资产。单元测试因此能更加可靠。<a name="best-practices-apps-measure"></a>

 **测量一切**   
在没有人工干预的情况下，要实现全面持续部署的目标需要高度自动化。只有通过大量监控才能实现自动化。要测量已部署资源的所有方面，请创建指标、警报和仪表板。请勿局限于测量 CPU 使用率和磁盘空间等数据。还要记录业务指标，并使用测量结果自动执行回滚等部署决策。 AWS CDK 中的大多数 L2 构造都有方便的方法来帮助您创建指标，例如类上的`metricUserErrors()`方法。[https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb.Table.html](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_dynamodb.Table.html)