自定义 AWS 构造库中的构造 - AWS Cloud Development Kit (AWS CDK) v2

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

自定义 AWS 构造库中的构造

通过转义孵化、原始覆盖和自定义资源自定义 AWS 构造库中的构造。

使用转义孵化

AWS 构造库提供了不同抽象级别的构造

在最高级别上,AWS CDK 应用程序及其中的堆栈本身就是整个云基础设施或其很大一部分的抽象。可以对它们进行参数化以将其部署在不同的环境中或满足不同的需求。

抽象是用于设计和实现云应用程序的强大工具。AWS CDK 使您不仅可以利用其抽象进行构建,还可以创建新的抽象。以现有的开源 L2 和 L3 构造为指导,您可以构建自己的 L2 和 L3 构造,以反映自己组织的最佳实践和观点。

没有哪个抽象是完美的,即使是良好的抽象也无法涵盖所有可能的使用案例。在开发过程中,您可能会发现一种几乎符合您的需求的构造,需要进行小规模或大规模的自定义。

出于这个原因,AWS CDK 提供了突破构造模型的方法。这包括改为使用较低级别的抽象或完全不同的模型。转义孵化可让您转义 AWS CDK 范式,并以适合您需求的方式对其进行自定义。然后,您可以将更改封装在一个新的构造中,以抽象出底层的复杂性,并为其他开发人员提供一个干净的 API。

以下是您可以使用转义孵化的情况示例:

  • AWS CloudFormation 提供了 AWS 服务功能,但没有适用于它的 L2 构造。

  • AWS CloudFormation 提供了 AWS 服务功能,并且有适用于该服务的 L2 构造,但这些构造尚未公开此功能。由于 L2 构造由 CDK 团队策管,因此它们可能无法立即用于新功能。

  • AWS CloudFormation 尚未提供此功能。

    要确定某项功能是否可通过 AWS CloudFormation 提供,请参阅《AWS 资源和属性类型参考》。

为 L1 构造开发转义孵化

如果 L2 构造不可用于该服务,则可以使用自动生成的 L1 构造。这些资源可以按其名称(以 Cfn 开头)进行识别,例如 CfnBucketCfnRole。您可以完全按照使用等效 AWS CloudFormation 资源的方式来实例化这些资源。

例如,要实例化启用了分析功能的低级别 Amazon S3 存储桶 L1,您需要编写如下内容。

TypeScript
new s3.CfnBucket(this, 'amzn-s3-demo-bucket', { analyticsConfigurations: [ { id: 'Config', // ... } ] });
JavaScript
new s3.CfnBucket(this, 'amzn-s3-demo-bucket', { analyticsConfigurations: [ { id: 'Config' // ... } ] });
Python
s3.CfnBucket(self, "amzn-s3-demo-bucket", analytics_configurations: [ dict(id="Config", # ... ) ] )
Java
CfnBucket.Builder.create(this, "amzn-s3-demo-bucket") .analyticsConfigurations(Arrays.asList(java.util.Map.of( // Java 9 or later "id", "Config", // ... ))).build();
C#
new CfnBucket(this, 'amzn-s3-demo-bucket', new CfnBucketProps { AnalyticsConfigurations = new Dictionary<string, string> { ["id"] = "Config", // ... } });

在极少数情况下,您可能希望定义一个没有对应 CfnXxx 类的资源。这可能是尚未在 AWS CloudFormation 资源规范中发布的新资源类型。在这种情况下,您可以直接实例化 cdk.CfnResource 并指定资源类型和属性。如以下示例所示。

TypeScript
new cdk.CfnResource(this, 'amzn-s3-demo-bucket', { type: 'AWS::S3::Bucket', properties: { // Note the PascalCase here! These are CloudFormation identifiers. AnalyticsConfigurations: [ { Id: 'Config', // ... } ] } });
JavaScript
new cdk.CfnResource(this, 'amzn-s3-demo-bucket', { type: 'AWS::S3::Bucket', properties: { // Note the PascalCase here! These are CloudFormation identifiers. AnalyticsConfigurations: [ { Id: 'Config' // ... } ] } });
Python
cdk.CfnResource(self, 'amzn-s3-demo-bucket', type="AWS::S3::Bucket", properties=dict( # Note the PascalCase here! These are CloudFormation identifiers. "AnalyticsConfigurations": [ { "Id": "Config", # ... } ] } )
Java
CfnResource.Builder.create(this, "amzn-s3-demo-bucket") .type("AWS::S3::Bucket") .properties(java.util.Map.of( // Map.of requires Java 9 or later // Note the PascalCase here! These are CloudFormation identifiers "AnalyticsConfigurations", Arrays.asList( java.util.Map.of("Id", "Config", // ... )))) .build();
C#
new CfnResource(this, "amzn-s3-demo-bucket", new CfnResourceProps { Type = "AWS::S3::Bucket", Properties = new Dictionary<string, object> { // Note the PascalCase here! These are CloudFormation identifiers ["AnalyticsConfigurations"] = new Dictionary<string, string>[] { new Dictionary<string, string> { ["Id"] = "Config" } } } });

为 L2 构造开发转义孵化

如果 L2 构造缺少功能或您正在尝试解决问题,则可以修改由 L2 构造封装的 L1 构造。

所有 L2 构造都包含在对应的 L1 构造内。例如,高级别 Bucket 构造封装低级别 CfnBucket 构造。由于 CfnBucket 直接对应于 AWS CloudFormation 资源,因此它公开了可通过 AWS CloudFormation 提供的所有功能。

访问 L1 构造的基本方法是使用 construct.node.defaultChild(Python:default_child),将其转换为正确的类型(如有必要),然后修改其属性。我们再次以 Bucket 为例。

TypeScript
// Get the CloudFormation resource const cfnBucket = bucket.node.defaultChild as s3.CfnBucket; // Change its properties cfnBucket.analyticsConfiguration = [ { id: 'Config', // ... } ];
JavaScript
// Get the CloudFormation resource const cfnBucket = bucket.node.defaultChild; // Change its properties cfnBucket.analyticsConfiguration = [ { id: 'Config' // ... } ];
Python
# Get the CloudFormation resource cfn_bucket = bucket.node.default_child # Change its properties cfn_bucket.analytics_configuration = [ { "id": "Config", # ... } ]
Java
// Get the CloudFormation resource CfnBucket cfnBucket = (CfnBucket)bucket.getNode().getDefaultChild(); cfnBucket.setAnalyticsConfigurations( Arrays.asList(java.util.Map.of( // Java 9 or later "Id", "Config", // ... ));
C#
// Get the CloudFormation resource var cfnBucket = (CfnBucket)bucket.Node.DefaultChild; cfnBucket.AnalyticsConfigurations = new List<object> { new Dictionary<string, string> { ["Id"] = "Config", // ... } };

您也可以使用此对象来更改 MetadataUpdatePolicy 等 AWS CloudFormation 选项。

TypeScript
cfnBucket.cfnOptions.metadata = { MetadataKey: 'MetadataValue' };
JavaScript
cfnBucket.cfnOptions.metadata = { MetadataKey: 'MetadataValue' };
Python
cfn_bucket.cfn_options.metadata = { "MetadataKey": "MetadataValue" }
Java
cfnBucket.getCfnOptions().setMetadata(java.util.Map.of( // Java 9+ "MetadataKey", "Metadatavalue"));
C#
cfnBucket.CfnOptions.Metadata = new Dictionary<string, object> { ["MetadataKey"] = "Metadatavalue" };

使用取消转义孵化

AWS CDK 还提供了上升抽象级别的功能,我们可以将其称为“取消转义”孵化。如果您有 L1 构造(例如 CfnBucket),则可以创建一个新的 L2 构造(在本例中为 Bucket)来封装 L1 构造。

当您创建 L1 资源但又想将其与需要 L2 资源的构造结合使用时,这会很方便。当您希望使用 L1 构造中没有的便捷方法(例如 .grantXxxxx())时,这也会很有用。

您可以在调用 .fromCfnXxxxx()(例如,对于 Amazon S3 存储桶为 Bucket.fromCfnBucket())的 L2 类上使用静态方法移至更高抽象级别。L1 资源是唯一的参数。

TypeScript
b1 = new s3.CfnBucket(this, "buck09", { ... }); b2 = s3.Bucket.fromCfnBucket(b1);
JavaScript
b1 = new s3.CfnBucket(this, "buck09", { ...} ); b2 = s3.Bucket.fromCfnBucket(b1);
Python
b1 = s3.CfnBucket(self, "buck09", ...) b2 = s3.from_cfn_bucket(b1)
Java
CfnBucket b1 = CfnBucket.Builder.create(this, "buck09") // .... .build(); IBucket b2 = Bucket.fromCfnBucket(b1);
C#
var b1 = new CfnBucket(this, "buck09", new CfnBucketProps { ... }); var v2 = Bucket.FromCfnBucket(b1);

根据 L1 构造创建的 L2 构造是引用 L1 资源的代理对象,类似于根据资源名称、ARN 或查找创建的构造。对这些构造的修改不会影响最终合成的 AWS CloudFormation 模板(但是,由于您拥有 L1 资源,因此可以改为对其进行修改)。有关代理对象的更多信息,请参阅 引用 AWS 账户中的资源

为避免混淆,请勿创建引用同一 L1 构造的多个 L2 构造。例如,如果您使用上一节中的方法从 Bucket 中提取 CfnBucket,则不应使用该 CfnBucket 调用 Bucket.fromCfnBucket() 来创建第二个 Bucket 实例。它实际上可以按预期工作(只有一个 AWS::S3::Bucket 是合成的),但它会使代码更难维护。

使用原始覆盖

如果 L1 构造中缺少某些属性,则可以使用原始覆盖来绕过所有输入。这也可以删除合成的属性。

使用其中一种 addOverride 方法(Python:add_override),如以下示例所示。

TypeScript
// Get the CloudFormation resource const cfnBucket = bucket.node.defaultChild as s3.CfnBucket; // Use dot notation to address inside the resource template fragment cfnBucket.addOverride('Properties.VersioningConfiguration.Status', 'NewStatus'); cfnBucket.addDeletionOverride('Properties.VersioningConfiguration.Status'); // use index (0 here) to address an element of a list cfnBucket.addOverride('Properties.Tags.0.Value', 'NewValue'); cfnBucket.addDeletionOverride('Properties.Tags.0'); // addPropertyOverride is a convenience function for paths starting with "Properties." cfnBucket.addPropertyOverride('VersioningConfiguration.Status', 'NewStatus'); cfnBucket.addPropertyDeletionOverride('VersioningConfiguration.Status'); cfnBucket.addPropertyOverride('Tags.0.Value', 'NewValue'); cfnBucket.addPropertyDeletionOverride('Tags.0');
JavaScript
// Get the CloudFormation resource const cfnBucket = bucket.node.defaultChild ; // Use dot notation to address inside the resource template fragment cfnBucket.addOverride('Properties.VersioningConfiguration.Status', 'NewStatus'); cfnBucket.addDeletionOverride('Properties.VersioningConfiguration.Status'); // use index (0 here) to address an element of a list cfnBucket.addOverride('Properties.Tags.0.Value', 'NewValue'); cfnBucket.addDeletionOverride('Properties.Tags.0'); // addPropertyOverride is a convenience function for paths starting with "Properties." cfnBucket.addPropertyOverride('VersioningConfiguration.Status', 'NewStatus'); cfnBucket.addPropertyDeletionOverride('VersioningConfiguration.Status'); cfnBucket.addPropertyOverride('Tags.0.Value', 'NewValue'); cfnBucket.addPropertyDeletionOverride('Tags.0');
Python
# Get the CloudFormation resource cfn_bucket = bucket.node.default_child # Use dot notation to address inside the resource template fragment cfn_bucket.add_override("Properties.VersioningConfiguration.Status", "NewStatus") cfn_bucket.add_deletion_override("Properties.VersioningConfiguration.Status") # use index (0 here) to address an element of a list cfn_bucket.add_override("Properties.Tags.0.Value", "NewValue") cfn_bucket.add_deletion_override("Properties.Tags.0") # addPropertyOverride is a convenience function for paths starting with "Properties." cfn_bucket.add_property_override("VersioningConfiguration.Status", "NewStatus") cfn_bucket.add_property_deletion_override("VersioningConfiguration.Status") cfn_bucket.add_property_override("Tags.0.Value", "NewValue") cfn_bucket.add_property_deletion_override("Tags.0")
Java
// Get the CloudFormation resource CfnBucket cfnBucket = (CfnBucket)bucket.getNode().getDefaultChild(); // Use dot notation to address inside the resource template fragment cfnBucket.addOverride("Properties.VersioningConfiguration.Status", "NewStatus"); cfnBucket.addDeletionOverride("Properties.VersioningConfiguration.Status"); // use index (0 here) to address an element of a list cfnBucket.addOverride("Properties.Tags.0.Value", "NewValue"); cfnBucket.addDeletionOverride("Properties.Tags.0"); // addPropertyOverride is a convenience function for paths starting with "Properties." cfnBucket.addPropertyOverride("VersioningConfiguration.Status", "NewStatus"); cfnBucket.addPropertyDeletionOverride("VersioningConfiguration.Status"); cfnBucket.addPropertyOverride("Tags.0.Value", "NewValue"); cfnBucket.addPropertyDeletionOverride("Tags.0");
C#
// Get the CloudFormation resource var cfnBucket = (CfnBucket)bucket.node.defaultChild; // Use dot notation to address inside the resource template fragment cfnBucket.AddOverride("Properties.VersioningConfiguration.Status", "NewStatus"); cfnBucket.AddDeletionOverride("Properties.VersioningConfiguration.Status"); // use index (0 here) to address an element of a list cfnBucket.AddOverride("Properties.Tags.0.Value", "NewValue"); cfnBucket.AddDeletionOverride("Properties.Tags.0"); // addPropertyOverride is a convenience function for paths starting with "Properties." cfnBucket.AddPropertyOverride("VersioningConfiguration.Status", "NewStatus"); cfnBucket.AddPropertyDeletionOverride("VersioningConfiguration.Status"); cfnBucket.AddPropertyOverride("Tags.0.Value", "NewValue"); cfnBucket.AddPropertyDeletionOverride("Tags.0");

使用自定义资源

如果该功能无法通过 AWS CloudFormation 提供,而只能通过直接 API 调用,则必须编写 AWS CloudFormation 自定义资源来进行所需的 API 调用。您可以使用 AWS CDK 来编写自定义资源并将其封装到常规构造接口中。从构造使用者的角度来看,体验会让人感觉很原生。

构建自定义资源涉及编写 Lambda 函数来响应资源的 CREATEUPDATEDELETE 生命周期事件。如果自定义资源只能进行一次 API 调用,请考虑使用 AwsCustomResource。这使得在 AWS CloudFormation 部署期间可以执行任意 SDK 调用。否则,您应该编写自己的 Lambda 函数来执行需要完成的工作。

主题过于宽泛,无法在此处全面介绍,但是以下链接应该可以帮助您入门: