View a markdown version of this page

第 3 层构造 - AWS 规范性指导

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

第 3 层构造

如果 L1 构造将 CloudFormation 资源直译为编程代码,而 L2 构造用辅助方法和自定义逻辑替换了大部分冗余 CloudFormation 语法,那么 L3 构造会做什么? 答案只受您的想象力的限制。您可以创建第 3 层以适应任何特定的使用案例。如果您的项目需要具有特定属性子集的资源,则可以创建可重复使用的 L3 构造来满足该需求。

L3 构造在 AWS CDK内称为模式。模式是指扩展中的Construct类 AWS CDK (或扩展扩展该类的Construct 类)以执行第 2 层以外的任何抽象逻辑的任何对象。使用 AWS CDK CLI 运行 cdk ini t 启动新 AWS CDK 项目时,必须从三种 AWS CDK 应用程序类型中进行选择:applib、和。sample-app

AWS CDK 应用程序类型

appsample-app两者都代表经典 AWS CDK 应用程序,您可以在其中构建 CloudFormation 堆栈并将其部署到 AWS 环境。当你做出选择时lib,你就是在选择建造一个全新的三级建筑。 appsample-app允许你选择他们 AWS CDK 支持的任何语言,但你只能选择 TypeScript lib。这是因为 AWS CDK 是原生编写的, TypeScript 并使用名为 jsiI 的开源系统将原始代码翻译成其他支持的语言。选择 lib 启动项目时,您就选择了构建 AWS CDK的扩展。

任何可扩展 Construct 类别的类别都可以是 L3 构造,但第 3 层最常见的使用案例是资源交互、资源扩展和自定义资源。大多数 L3 构造使用这三种情况中的一种或多种来扩展 AWS CDK 功能。

资源交互

一个解决方案通常使用多个协同工作的 AWS 服务。例如,Amazon CloudFront 分发通常使用 S3 存储桶作为其来源, AWS WAF 以防范常见漏洞。 AWS AppSync 而且 Amazon API Gateway 经常使用亚马逊 DynamoDB 表作为其 API 的数据源。中的管道 AWS CodePipeline 通常使用 Amazon S3 作为其源代码和 AWS CodeBuild 构建阶段。在这些情况下,创建一个 L3 构造来处理两个或多个互连的 L2 构造的预调配通常很有用。

以下是 L3 构造的示例,该结构预置了 CloudFront 分配及其前面的 S3 来源、Amazon Route 53 记录以及 AWS WAF 用于在传输中添加带有加密功能的自定义终端节点的 AWS Certificate Manager (ACM) 证书,所有这些都在一个可重复使用的结构中:

// Define the properties passed to the L3 construct export interface CloudFrontWebsiteProps { distributionProps: DistributionProps bucketProps: BucketProps wafProps: CfnWebAclProps zone: IHostedZone } // Define the L3 construct export class CloudFrontWebsite extends Construct { public distribution: Distribution constructor( scope: Construct, id: string, props: CloudFrontWebsiteProps ) { super(scope, id); const certificate = new Certificate(this, "Certificate", { domainName: props.zone.zoneName, validation: CertificateValidation.fromDns(props.zone) }); const defaultBehavior = { origin: new S3Origin(new Bucket(this, "bucket", props.bucketProps)) } const waf = new CfnWebACL(this, "waf", props.wafProps); this.distribution = new Distribution(this, id, { ...props.distributionProps, defaultBehavior, certificate, domainNames: [this.domainName], webAclId: waf.attrArn, }); } }

请注意 CloudFront,Amazon S3、Route 53 和 ACM 都使用 L2 结构,但是 Web ACL(定义处理网络请求的规则)使用 L1 结构。这是因为 AWS CDK 这是一个不断演变的开源软件包,WebAcl尚未完全完成,而且还没有 L2 构造。但是,任何人都可以 AWS CDK 通过创建新的 L2 构造来为其做出贡献。因此,在 AWS CDK 为提供 L2 构造之前WebAcl,您必须使用 L1 构造。要使用 L3 构造 CloudFrontWebsite 创建新网站,请使用以下代码:

const siteADotCom = new CloudFrontWebsite(stack, "siteA", siteAProps); const siteBDotCom = new CloudFrontWebsite(stack, "siteB", siteBProps); const siteCDotCom = new CloudFrontWebsite(stack, "siteC", siteCProps);

在此示例中, CloudFront DistributionL2 构造作为 L3 构造的公共属性公开。在某些情况下,您仍然需要根据需要公开 L3 属性。实际上,我们稍后会在自定义资源部分中再次看到 Distribution

AWS CDK 包括一些资源交互模式的示例,例如此示例。除了包含 Amazon Elastic Container Service(Amazon ECS)的 L2 构造的 aws-ecs 软件包之外, AWS CDK 还有一个名为 aws-ecs-patterns 的软件包。此软件包包含多个 L3 构造,它们将 Amazon ECS 与应用程序负载均衡器、网络负载均衡器和目标组相结合,同时提供针对 Amazon Elastic Compute Cloud(Amazon EC2)和 AWS Fargate预设的不同版本。由于许多无服务器应用程序仅将 Amazon ECS 与 Fargate 配合使用,因此这些 L3 构造提供了便利,可以为开发者节省时间,为客户节省金钱。

资源扩展

某些使用案例要求资源具有特定的默认设置,而这些设置不是 L2 构造的原生设置。在堆栈级别,这可以通过使用方面来处理,但为 L2 构造赋予新默认值的另一种便捷方法是扩展第 2 层。由于构造是继承了 Construct 类别的任何类别,而 L2 构造又扩展了该类别,因此您也可以通过直接扩展 L2 构造来创建 L3 构造。

这对于支持客户单一需求的自定义业务逻辑特别有用。假设一家公司有一个存储库,该存储库将其所有 AWS Lambda 函数代码存储在名为的单个目录中,src/lambda并且大多数 Lambda 函数每次都重复使用相同的运行时和处理程序名称。您可以创建一个新的 L3 构造,而不必在每次配置新的 Lambda 函数时都配置代码路径:

export class MyCompanyLambdaFunction extends Function { constructor( scope: Construct, id: string, props: Partial<FunctionProps> = {} ) { super(scope, id, { handler: 'index.handler', runtime: Runtime.NODEJS_LATEST, code: Code.fromAsset(`src/lambda/${props.functionName || id}`), ...props }); }

然后,您可以按如下方式替换存储库中任何位置的 L2 Function 构造:

new MyCompanyLambdaFunction(this, "MyFunction"); new MyCompanyLambdaFunction(this, "MyOtherFunction"); new MyCompanyLambdaFunction(this, "MyThirdFunction", { runtime: Runtime.PYTHON_3_11 });

默认值允许您在单行上创建新的 Lambda 函数,且 L3 构造已设置,因此您仍然可以在需要时覆盖默认属性。

当您只想为现有 L2 构造添加新的默认值时,直接扩展 L2 构造效果最佳。如果您还需要其他自定义逻辑,最好扩展 Construct 类别。其原因源于在构造函数内调用的 super 方法。在可扩展其他类别的类别中,super 方法用于调用父类别的构造函数,并且这必须是构造函数内发生的第一件事。这意味着只有在创建了原始 L2 构造之后,才能对传递的参数或其他自定义逻辑进行任何操作。如果您需要在实例化 L2 构造之前执行任何自定义逻辑,则最好遵循之前在资源交互部分中概述的模式。

自定义资源

自定义资源是一项强大功能 CloudFormation ,可让您通过堆栈部署期间激活的 Lambda 函数运行自定义逻辑。每当您在部署期间需要任何不直接支持的流程时 CloudFormation,都可以使用自定义资源来实现。 AWS CDK 提供的类别还允许您以编程方式创建自定义资源。通过在 L3 构造函数内使用自定义资源,您几乎可以用任何东西制作一个构造。

使用 Amazon 的优势之一 CloudFront 是其强大的全球缓存功能。如果您想手动重置该缓存,以便您的网站立即反映对来源所做的新更改,则可以使用CloudFront 失效。但是,失效是指在分配上运行的进程,而不是 CloudFront分配的 CloudFront 属性。它们可以随时创建并应用于现有分发,因此它们并非预调配和部署过程的固有组成部分。

在这种情况下,您可能希望在每次更新分发来源后创建并运行失效。由于有自定义资源,您可以创建如下所示的 L3 构造:

export interface CloudFrontInvalidationProps { distribution: Distribution region?: string paths?: string[] } export class CloudFrontInvalidation extends Construct { constructor( scope: Construct, id: string, props: CloudFrontInvalidationProps ) { super(scope, id); const policy = AwsCustomResourcePolicy.fromSdkCalls({ resources:AwsCustomResourcePolicy.ANY_RESOURCE }); new AwsCustomResource(scope, `${id}Invalidation`, { policy, onUpdate: { service: 'CloudFront', action: 'createInvalidation', region: props.region || 'us-east-1', physicalResourceId: PhysicalResourceId.fromResponse('Invalidation.Id'), parameters: { DistributionId: props.distribution.distributionId, InvalidationBatch: { Paths: { Quantity: props.paths?.length || 1, Items: props.paths || ['/*'] }, CallerReference: crypto.randomBytes(5).toString('hex') } } } } } }

使用我们之前在 CloudFrontWebsite L3 构造中创建的分发,您可以很容易地做到这一点:

new CloudFrontInvalidation(this, 'MyInvalidation', { distribution: siteADotCom.distribution });

此 L3 构造使用名为的 AWS CDK L3 构造AwsCustomResource来创建执行自定义逻辑的自定义资源。 AwsCustomResource当您只需要调用一个 AWS SDK 时,它非常方便,因为它允许您无需编写任何 Lambda 代码即可执行此操作。如果您有更复杂的要求并想要实现自己的逻辑,则可以直接使用基本CustomResource类。

AWS CDK 使用自定义资源 L3 结构的另一个很好的例子是 S3 存储桶部署。由此 L3 构造的构造函数中的自定义资源创建的 Lambda 函数添加了原本 CloudFormation 无法处理的功能:它在 S3 存储桶中添加和更新对象。如果没有 S3 存储桶部署,您将无法将内容放入作为堆栈一部分刚刚创建的 S3 存储桶中,这将非常不方便。

AWS CDK 无需写出大量 CloudFormation 语法的最好例子就是这样一个基本S3BucketDeployment的例子:

new BucketDeployment(this, 'BucketObjects', { sources: [Source.asset('./path/to/amzn-s3-demo-bucket')], destinationBucket: amzn-s3-demo-bucket });

将其与你为完成同样的事情而必须编写的 CloudFormation 代码进行比较:

"lambdapolicyA5E98E09": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ { "Action": "lambda:UpdateFunctionCode", "Effect": "Allow", "Resource": "arn:aws:lambda:us-east-1:123456789012:function:my-function" } ], "Version": "2012-10-17" }, "PolicyName": "lambdaPolicy", "Roles": [ { "Ref": "myiamroleF09C7974" } ] }, "Metadata": { "aws:cdk:path": "CdkScratchStack/lambda-policy/Resource" } }, "BucketObjectsAwsCliLayer8C081206": { "Type": "AWS::Lambda::LayerVersion", "Properties": { "Content": { "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, "S3Key": "e2277687077a2abf9ae1af1cc9565e6715e2ebb62f79ec53aa75a1af9298f642.zip" }, "Description": "/opt/awscli/aws" }, "Metadata": { "aws:cdk:path": "CdkScratchStack/BucketObjects/AwsCliLayer/Resource", "aws:asset:path": "asset.e2277687077a2abf9ae1af1cc9565e6715e2ebb62f79ec53aa75a1af9298f642.zip", "aws:asset:is-bundled": false, "aws:asset:property": "Content" } }, "BucketObjectsCustomResourceB12E6837": { "Type": "Custom::CDKBucketDeployment", "Properties": { "ServiceToken": { "Fn::GetAtt": [ "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536", "Arn" ] }, "SourceBucketNames": [ { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" } ], "SourceObjectKeys": [ "f888a9d977f0b5bdbc04a1f8f07520ede6e00d4051b9a6a250860a1700924f26.zip" ], "DestinationBucketName": { "Ref": "amzn-s3-demo-bucket77F80CC0" }, "Prune": true }, "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete", "Metadata": { "aws:cdk:path": "CdkScratchStack/BucketObjects/CustomResource/Default" } }, "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265": { "Type": "AWS::IAM::Role", "Properties": { "AssumeRolePolicyDocument": { "Statement": [ { "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" } } ], "Version": "2012-10-17" }, "ManagedPolicyArns": [ { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ] ] } ] }, "Metadata": { "aws:cdk:path": "CdkScratchStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/Resource" } }, "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF": { "Type": "AWS::IAM::Policy", "Properties": { "PolicyDocument": { "Statement": [ { "Action": [ "s3:GetBucket*", "s3:GetObject*", "s3:List*" ], "Effect": "Allow", "Resource": [ { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, ":s3:::", { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, "/*" ] ] }, { "Fn::Join": [ "", [ "arn:", { "Ref": "AWS::Partition" }, ":s3:::", { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" } ] ] } ] }, { "Action": [ "s3:Abort*", "s3:DeleteObject*", "s3:GetBucket*", "s3:GetObject*", "s3:List*", "s3:PutObject", "s3:PutObjectLegalHold", "s3:PutObjectRetention", "s3:PutObjectTagging", "s3:PutObjectVersionTagging" ], "Effect": "Allow", "Resource": [ { "Fn::GetAtt": [ "amzns3demobucket77F80CC0", "Arn" ] }, { "Fn::Join": [ "", [ { "Fn::GetAtt": [ "amzns3demobucket77F80CC0", "Arn" ] }, "/*" ] ] } ] } ], "Version": "2012-10-17" }, "PolicyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", "Roles": [ { "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" } ] }, "Metadata": { "aws:cdk:path": "CdkScratchStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/ServiceRole/DefaultPolicy/Resource" } }, "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536": { "Type": "AWS::Lambda::Function", "Properties": { "Code": { "S3Bucket": { "Fn::Sub": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}" }, "S3Key": "9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd.zip" }, "Role": { "Fn::GetAtt": [ "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265", "Arn" ] }, "Environment": { "Variables": { "AWS_CA_BUNDLE": "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem" } }, "Handler": "index.handler", "Layers": [ { "Ref": "BucketObjectsAwsCliLayer8C081206" } ], "Runtime": "python3.9", "Timeout": 900 }, "DependsOn": [ "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF", "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265" ], "Metadata": { "aws:cdk:path": "CdkScratchStack/Custom::CDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C/Resource", "aws:asset:path": "asset.9eb41a5505d37607ac419321497a4f8c21cf0ee1f9b4a6b29aa04301aea5c7fd", "aws:asset:is-bundled": false, "aws:asset:property": "Code" } }

4 行与 241 行差异很大!这只是利用第 3 层自定义堆栈所能实现的功能的一个示例。