AWS CDK の3種類の Construct を使ってデプロイしてみた
はじめに
CX事業本部@札幌の佐藤です。この記事では、AWS CDKのConstructについて説明していきます。
この記事に出てくるサンプルコードは以下のリポジトリにありますので、参考にしてみてください。
https://github.com/briete/aws-cdk-construct-library-sample
前提
AWS CDK Workshopを試した程度の知識を前提にしています。まだやっていないという方、とてもわかりやすいWorkshopなのでぜひやってみてください。
概要
AWS CDKは、基本概念として以下の3つの要素によって構成されています。
App
AWS CDKの最上位層。複数スタックの依存関係などを定義します。
Stack
CloudFormationスタックと1:1で対応します。AWSへのデプロイはこのスタック単位で行います。
Construct
デプロイするAWSリソース。上記のStack層に、Construct Library を使用してAWSリソースを定義していきます。
今回は、この中のConstructについて焦点を当てていきます。
3種類のConstruct
AWS CDKでは、Construct Libraryというライブラリを使ってAWSリソースを作成していきます。このConstruct Libraryには以下の3つの種類があります。
High Level Construct
- Function クラスのように、AWS CDKがCloudFormationの各リソースを抽象化してくれているライブラリです。クラスには、
addEventSource(source)
のような直感的にリソースを定義できる便利なメソッドがあり、CloudFormationを使うよりも簡単に各リソースを定義することができます。 - ほとんどの場合は、こちらを使った方が便利です。
- ただ、すべてのリソースがHigh Level Constructに対応しているわけではなく、
developer preview
となっているリソースなどはまだまだ対応されていない状況です(aws-iot
、aws-greengrass
など)。ただ、AWS CDKは頻繁にアップデートされているため、High Level Constructが提供されるのも時間の問題かと思います。 developer preview
のリソースに関しては、下記のLow Level Constractを使って定義することになります。
Low Level Construct
- CfnFunction クラスのように、High Lebel Constructsより低レベルなライブラリで、CloudFormationの各リソースと1:1の関係になっています。クラスに
Cfn
プレフィックスがついているものがこれに該当します。 - CloudFormationリソースと1:1で対応しているため、もし既存のCloudFormationテンプレートをどうしてもCDKに移行したいなどの要件がある場合は、Low Level Constructを使って、CloudFormationのYAML/JSONを手動でTypeScriptやPythonに変換することもできます。
- 作成しようとしているAWSリソースに、High Level Constructがあるならばそちらを使った方が簡単にリソースを定義できるためオススメです。より細かいパラメーターの設定をしたいなどの要件がある場合にこちらを使えば良いと思います。
Patterns
- aws-ecs-patterns のように、複数のリソースを含む一般的なパターンを定義しているライブラリです。このクラスのコンストラクタに、必要なリソースのインスタンスを渡すだけで、一般的なパターンのインフラ環境を簡単に作成することができます。
High Level Construct、Low Level Construct、Patternでそれぞれデプロイしてみた
実際に、上記の3つのライブラリを使用して、AWSリソースを作成してみたいとおもいます。
環境
項目 | バージョン |
---|---|
macOS | Mojave 10.14.5 |
node | v10.16.0 |
npm | 6.9.0 |
aws-cdk | 1.11.0 (build 4ed4d96) |
CDKプロジェクトと必要なライブラリのインストール
$ mkdir aws-cdk-construct-library-sample $ cd aws-cdk-construct-library-sample $ cdk init app --language=typescript $ npm install --save @aws-cdk/aws-lambda @aws-cdk/aws-apigateway @aws-cdk/aws-iam @aws-cdk/aws-ecs-patterns @aws-cdk/aws-ecs
High Level Constructで実装
まずは、High Level Constructで実装します。Lambda + API Gatewayのリソースを作成します。
サンプル用のLambdaのコード
export async function handler(event: any) { return { statusCode: 200, body: 'Sample Lambda Response' } }
CDKでインフラを定義
import cdk = require('@aws-cdk/core'); import lambda = require('@aws-cdk/aws-lambda'); import apigateway = require('@aws-cdk/aws-apigateway'); export class AwsCdkHighLevelConstructLibrarySampleStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const sampleLambda = new lambda.Function(this, 'sample-lambda', { code: lambda.Code.asset('src/handler'), handler: 'app.handler', runtime: lambda.Runtime.NODEJS_10_X, }); const api = new apigateway.RestApi(this, 'api', { restApiName: 'SampleApi' }); const integration = new apigateway.LambdaIntegration(sampleLambda, { proxy: true }); const resource = api.root.addResource('sample'); resource.addMethod('POST', integration); } }
Lambda + API Gatewayの構成ならば、High Level Constructを使えば30行ほどで書けてしまいます。
デプロイ
$ cdk deploy AwsCdkHighLevelConstructLibrarySampleStack
動作確認
$ curl -sS -X POST https://XXXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/sample Sample Lambda Response
うまく動いているようです。
Low Level Constructで実装
次に、Low Level ConstructでLambda + APi Gatewayを実装します。こちらの場合は、上記に比べて抽象化がなされていないためCloudFormationと同じように、一つ一つ定義していくことになります。
CDKでインフラを定義
import cdk = require('@aws-cdk/core'); import lambda = require('@aws-cdk/aws-lambda'); import apigateway = require('@aws-cdk/aws-apigateway'); import iam = require('@aws-cdk/aws-iam'); export class AwsCdkLowLevelConstructLibrarySampleStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const region: string = cdk.Stack.of(this).region; const accountId: string = cdk.Stack.of(this).account; const lambdaServiceRole = new iam.CfnRole(this, 'sampleLambdaServiceRole', { assumeRolePolicyDocument: { "Statement": [ { "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" } } ], "Version": "2012-10-17" }, managedPolicyArns: [ "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" ] }); const lambdaFunction = new lambda.CfnFunction(this, 'SampleLambdaFunction', { code: { s3Bucket: "s3bucket", s3Key: "s3key" }, handler: "app.handler", role: lambdaServiceRole.attrArn, runtime: "nodejs10.x" }); lambdaFunction.addDependsOn(lambdaServiceRole); const api = new apigateway.CfnRestApi(this, 'SampleRestApi', { name: "SampleApi" }); const cloudwatchRole = new iam.CfnRole(this, 'CloudWatchRole', { assumeRolePolicyDocument: { "Statement": [ { "Action": "sts:AssumeRole", "Effect": "Allow", "Principal": { "Service": "apigateway.amazonaws.com" } } ], "Version": "2012-10-17" }, managedPolicyArns: [ "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs" ] }); const apiAccount = new apigateway.CfnAccount(this, 'SampleApiAccount', { cloudWatchRoleArn: cloudwatchRole.attrArn }); apiAccount.addDependsOn(api); const apiResource = new apigateway.CfnResource(this, 'SampleApiResource', { parentId: api.attrRootResourceId, pathPart: "sample", restApiId: api.ref }); const apiMethod = new apigateway.CfnMethod(this, 'SampleApiMethod', { httpMethod: "POST", resourceId: apiResource.ref, restApiId: api.ref, authorizationType: "NONE", integration: { integrationHttpMethod: "POST", type: "AWS_PROXY", uri: `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${lambdaFunction.attrArn}/invocations` } }); const apiDeployment = new apigateway.CfnDeployment(this, 'Deployment', { restApiId: api.ref }); apiDeployment.addDependsOn(apiMethod); apiDeployment.addDependsOn(apiResource); const apiStage = new apigateway.CfnStage(this, 'SampleStage', { restApiId: api.ref, deploymentId: apiDeployment.ref, stageName: "prod" }); new lambda.CfnPermission(this, 'SampleLambdaPermission', { action: "lambda:InvokeFunction", functionName: lambdaFunction.attrArn, principal: "apigateway.amazonaws.com", sourceArn: `arn:aws:execute-api:${region}:${accountId}:${api.ref}/${apiStage.ref}/POST/sample` }); new lambda.CfnPermission(this, 'TestSamleLambdaPermission', { action: "lambda:InvokeFunction", functionName: lambdaFunction.attrArn, principal: "apigateway.amazonaws.com", sourceArn: `arn:aws:execute-api:${region}:${accountId}:${api.ref}/test-invoke-stage/POST/sample` }); } }
Low Level Constractでは120行ぐらい書かなければいけませんでした。High Level Constructと比べて、コード量は4倍近くになっています。CloudFormationと1:1なので、リソースのARNを直接指定しなければならない場面や、IAMのポリシーを自分でガリガリ書いていく必要があるので、High Level Constractに比べて冗長な記述になってしまいます。
デプロイ
$ cdk deploy AwsCdkLowLevelConstructLibrarySampleStack
動作確認
$ curl -sS -X POST https://XXXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/prod/sample Sample Lambda Response
先ほどと同じく動作しています。
High Level ConstructとLow Level Constructの比較
実際に同じAWSリソースを実装してみて、High Level ConstructとLow Level Constructで、かなり行数が違っていることがわかると思います。High Level Constructでは、AWS CDKが抽象化してくれているので、内部で色々なリソースを作ってくれています。そのため行数を少なく記述できます。リソースごとに細かいパラメータの設定が必要でない限りは、High Level Constructを使用するのがオススメです。
Patterns で実装
High Level Constructの他にも、Patternsというさらに一般的なパターンとして抽象化されているライブラリがあります。現状、AWS CDK公式で提供しているPatternsは以下の2つがあります。
- aws-ecs-patterns
- aws-route53-patterns
今回は、aws-ecs-patterns
ライブラリを使用して、ECS、Fargate、Application Load Balancerのインフラ環境を作ってみます。
CDKでインフラを定義
import cdk = require('@aws-cdk/core'); import ecs = require('@aws-cdk/aws-ecs'); import ecsPatterns = require('@aws-cdk/aws-ecs-patterns'); export class AwsCdkPatternsConstructLibrarySampleStack extends cdk.Stack { constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) { super(scope, id, props); const cluster = new ecs.Cluster(this, 'SampleCluster', { clusterName: 'SampleCluster', }); const taskDefinition = new ecs.FargateTaskDefinition(this, 'TaskDefinition'); const container = taskDefinition.addContainer('SampleContainer', { image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), }); container.addPortMappings({ containerPort: 80 }); const appLoadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'SampleService', { cluster: cluster, memoryLimitMiB: 1024, cpu: 512, desiredCount: 1, taskDefinition: taskDefinition }); } }
cdk synthコマンドで、生成されたCloudFormationテンプレートを確認してみます。
$ cdk synth AwsCdkPatternsConstructLibrarySampleStack
Resources: SampleClusterB4B72990: Type: AWS::ECS::Cluster Properties: ClusterName: SampleCluster Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Resource SampleClusterVpcD1C6ABD9: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsHostnames: true EnableDnsSupport: true InstanceTenancy: default Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/Resource SampleClusterVpcPublicSubnet1SubnetE377A512: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.0.0/18 VpcId: Ref: SampleClusterVpcD1C6ABD9 AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" MapPublicIpOnLaunch: true Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1 - Key: aws-cdk:subnet-name Value: Public - Key: aws-cdk:subnet-type Value: Public Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/Subnet SampleClusterVpcPublicSubnet1RouteTable7114D244: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: SampleClusterVpcD1C6ABD9 Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/RouteTable SampleClusterVpcPublicSubnet1RouteTableAssociation0B5402E3: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: SampleClusterVpcPublicSubnet1RouteTable7114D244 SubnetId: Ref: SampleClusterVpcPublicSubnet1SubnetE377A512 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/RouteTableAssociation SampleClusterVpcPublicSubnet1DefaultRoute28A82BC4: Type: AWS::EC2::Route Properties: RouteTableId: Ref: SampleClusterVpcPublicSubnet1RouteTable7114D244 DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: SampleClusterVpcIGW21649D5C DependsOn: - SampleClusterVpcVPCGW39AFB859 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/DefaultRoute SampleClusterVpcPublicSubnet1EIPD2C3FC83: Type: AWS::EC2::EIP Properties: Domain: vpc Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/EIP SampleClusterVpcPublicSubnet1NATGateway715FE613: Type: AWS::EC2::NatGateway Properties: AllocationId: Fn::GetAtt: - SampleClusterVpcPublicSubnet1EIPD2C3FC83 - AllocationId SubnetId: Ref: SampleClusterVpcPublicSubnet1SubnetE377A512 Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet1/NATGateway SampleClusterVpcPublicSubnet2SubnetB88D2B08: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.64.0/18 VpcId: Ref: SampleClusterVpcD1C6ABD9 AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" MapPublicIpOnLaunch: true Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2 - Key: aws-cdk:subnet-name Value: Public - Key: aws-cdk:subnet-type Value: Public Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/Subnet SampleClusterVpcPublicSubnet2RouteTable8A11EEAD: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: SampleClusterVpcD1C6ABD9 Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/RouteTable SampleClusterVpcPublicSubnet2RouteTableAssociation857BF408: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: SampleClusterVpcPublicSubnet2RouteTable8A11EEAD SubnetId: Ref: SampleClusterVpcPublicSubnet2SubnetB88D2B08 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/RouteTableAssociation SampleClusterVpcPublicSubnet2DefaultRouteFD4087CF: Type: AWS::EC2::Route Properties: RouteTableId: Ref: SampleClusterVpcPublicSubnet2RouteTable8A11EEAD DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: SampleClusterVpcIGW21649D5C DependsOn: - SampleClusterVpcVPCGW39AFB859 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/DefaultRoute SampleClusterVpcPublicSubnet2EIPCB2281EA: Type: AWS::EC2::EIP Properties: Domain: vpc Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/EIP SampleClusterVpcPublicSubnet2NATGatewayB385D543: Type: AWS::EC2::NatGateway Properties: AllocationId: Fn::GetAtt: - SampleClusterVpcPublicSubnet2EIPCB2281EA - AllocationId SubnetId: Ref: SampleClusterVpcPublicSubnet2SubnetB88D2B08 Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PublicSubnet2/NATGateway SampleClusterVpcPrivateSubnet1Subnet24256A44: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.128.0/18 VpcId: Ref: SampleClusterVpcD1C6ABD9 AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" MapPublicIpOnLaunch: false Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1 - Key: aws-cdk:subnet-name Value: Private - Key: aws-cdk:subnet-type Value: Private Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1/Subnet SampleClusterVpcPrivateSubnet1RouteTable55080EB4: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: SampleClusterVpcD1C6ABD9 Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1/RouteTable SampleClusterVpcPrivateSubnet1RouteTableAssociationBC171CD8: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: SampleClusterVpcPrivateSubnet1RouteTable55080EB4 SubnetId: Ref: SampleClusterVpcPrivateSubnet1Subnet24256A44 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1/RouteTableAssociation SampleClusterVpcPrivateSubnet1DefaultRouteB1C5B147: Type: AWS::EC2::Route Properties: RouteTableId: Ref: SampleClusterVpcPrivateSubnet1RouteTable55080EB4 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: SampleClusterVpcPublicSubnet1NATGateway715FE613 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet1/DefaultRoute SampleClusterVpcPrivateSubnet2Subnet25DCB36D: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.0.192.0/18 VpcId: Ref: SampleClusterVpcD1C6ABD9 AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" MapPublicIpOnLaunch: false Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2 - Key: aws-cdk:subnet-name Value: Private - Key: aws-cdk:subnet-type Value: Private Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2/Subnet SampleClusterVpcPrivateSubnet2RouteTable35B9289E: Type: AWS::EC2::RouteTable Properties: VpcId: Ref: SampleClusterVpcD1C6ABD9 Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2/RouteTable SampleClusterVpcPrivateSubnet2RouteTableAssociationC174EB56: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: SampleClusterVpcPrivateSubnet2RouteTable35B9289E SubnetId: Ref: SampleClusterVpcPrivateSubnet2Subnet25DCB36D Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2/RouteTableAssociation SampleClusterVpcPrivateSubnet2DefaultRoute74AE4D72: Type: AWS::EC2::Route Properties: RouteTableId: Ref: SampleClusterVpcPrivateSubnet2RouteTable35B9289E DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: SampleClusterVpcPublicSubnet2NATGatewayB385D543 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/PrivateSubnet2/DefaultRoute SampleClusterVpcIGW21649D5C: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/IGW SampleClusterVpcVPCGW39AFB859: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: Ref: SampleClusterVpcD1C6ABD9 InternetGatewayId: Ref: SampleClusterVpcIGW21649D5C Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleCluster/Vpc/VPCGW TaskDefinitionTaskRoleFD40A61D: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: ecs-tasks.amazonaws.com Version: "2012-10-17" Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/TaskDefinition/TaskRole/Resource TaskDefinitionB36D86D9: Type: AWS::ECS::TaskDefinition Properties: ContainerDefinitions: - Essential: true Image: amazon/amazon-ecs-sample Name: SampleContainer PortMappings: - ContainerPort: 80 Protocol: tcp Cpu: "256" Family: AwsCdkPatternsConstructLibrarySampleStackTaskDefinitionDFEE4CCD Memory: "512" NetworkMode: awsvpc RequiresCompatibilities: - FARGATE TaskRoleArn: Fn::GetAtt: - TaskDefinitionTaskRoleFD40A61D - Arn Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/TaskDefinition/Resource SampleServiceLBF4CAFE0D: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Scheme: internet-facing SecurityGroups: - Fn::GetAtt: - SampleServiceLBSecurityGroup3D8CF0A2 - GroupId Subnets: - Ref: SampleClusterVpcPublicSubnet1SubnetE377A512 - Ref: SampleClusterVpcPublicSubnet2SubnetB88D2B08 Type: application DependsOn: - SampleClusterVpcPublicSubnet1DefaultRoute28A82BC4 - SampleClusterVpcPublicSubnet2DefaultRouteFD4087CF Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/Resource SampleServiceLBSecurityGroup3D8CF0A2: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Automatically created Security Group for ELB AwsCdkPatternsConstructLibrarySampleStackSampleServiceLB60DF5B18 SecurityGroupIngress: - CidrIp: 0.0.0.0/0 Description: Allow from anyone on port 80 FromPort: 80 IpProtocol: tcp ToPort: 80 VpcId: Ref: SampleClusterVpcD1C6ABD9 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/SecurityGroup/Resource SampleServiceLBSecurityGrouptoAwsCdkPatternsConstructLibrarySampleStackSampleServiceSecurityGroup3FF4258B809AF497E9: Type: AWS::EC2::SecurityGroupEgress Properties: GroupId: Fn::GetAtt: - SampleServiceLBSecurityGroup3D8CF0A2 - GroupId IpProtocol: tcp Description: Load balancer to target DestinationSecurityGroupId: Fn::GetAtt: - SampleServiceSecurityGroupF3490F3B - GroupId FromPort: 80 ToPort: 80 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/SecurityGroup/to AwsCdkPatternsConstructLibrarySampleStackSampleServiceSecurityGroup3FF4258B:80 SampleServiceLBPublicListenerA227ACD3: Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: - TargetGroupArn: Ref: SampleServiceLBPublicListenerECSGroup1AB7CB78 Type: forward LoadBalancerArn: Ref: SampleServiceLBF4CAFE0D Port: 80 Protocol: HTTP Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/PublicListener/Resource SampleServiceLBPublicListenerECSGroup1AB7CB78: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Port: 80 Protocol: HTTP TargetType: ip VpcId: Ref: SampleClusterVpcD1C6ABD9 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/LB/PublicListener/ECSGroup/Resource SampleService263FFCD0: Type: AWS::ECS::Service Properties: TaskDefinition: Ref: TaskDefinitionB36D86D9 Cluster: Ref: SampleClusterB4B72990 DeploymentConfiguration: MaximumPercent: 200 MinimumHealthyPercent: 50 DesiredCount: 1 EnableECSManagedTags: false HealthCheckGracePeriodSeconds: 60 LaunchType: FARGATE LoadBalancers: - ContainerName: SampleContainer ContainerPort: 80 TargetGroupArn: Ref: SampleServiceLBPublicListenerECSGroup1AB7CB78 NetworkConfiguration: AwsvpcConfiguration: AssignPublicIp: DISABLED SecurityGroups: - Fn::GetAtt: - SampleServiceSecurityGroupF3490F3B - GroupId Subnets: - Ref: SampleClusterVpcPrivateSubnet1Subnet24256A44 - Ref: SampleClusterVpcPrivateSubnet2Subnet25DCB36D DependsOn: - SampleServiceLBPublicListenerECSGroup1AB7CB78 - SampleServiceLBPublicListenerA227ACD3 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/Service/Service SampleServiceSecurityGroupF3490F3B: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: AwsCdkPatternsConstructLibrarySampleStack/SampleService/Service/SecurityGroup SecurityGroupEgress: - CidrIp: 0.0.0.0/0 Description: Allow all outbound traffic by default IpProtocol: "-1" VpcId: Ref: SampleClusterVpcD1C6ABD9 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/Service/SecurityGroup/Resource SampleServiceSecurityGroupfromAwsCdkPatternsConstructLibrarySampleStackSampleServiceLBSecurityGroup3BC91F0D80BE55E0A3: Type: AWS::EC2::SecurityGroupIngress Properties: IpProtocol: tcp Description: Load balancer to target FromPort: 80 GroupId: Fn::GetAtt: - SampleServiceSecurityGroupF3490F3B - GroupId SourceSecurityGroupId: Fn::GetAtt: - SampleServiceLBSecurityGroup3D8CF0A2 - GroupId ToPort: 80 Metadata: aws:cdk:path: AwsCdkPatternsConstructLibrarySampleStack/SampleService/Service/SecurityGroup/from AwsCdkPatternsConstructLibrarySampleStackSampleServiceLBSecurityGroup3BC91F0D:80 CDKMetadata: Type: AWS::CDK::Metadata Properties: Modules: aws-cdk=1.11.0,@aws-cdk/assets=1.11.0,@aws-cdk/aws-apigateway=1.11.0,@aws-cdk/aws-applicationautoscaling=1.11.0,@aws-cdk/aws-autoscaling=1.11.0,@aws-cdk/aws-autoscaling-common=1.11.0,@aws-cdk/aws-autoscaling-hooktargets=1.11.0,@aws-cdk/aws-certificatemanager=1.11.0,@aws-cdk/aws-cloudformation=1.11.0,@aws-cdk/aws-cloudwatch=1.11.0,@aws-cdk/aws-ec2=1.11.0,@aws-cdk/aws-ecr=1.11.0,@aws-cdk/aws-ecr-assets=1.11.0,@aws-cdk/aws-ecs=1.11.0,@aws-cdk/aws-ecs-patterns=1.11.0,@aws-cdk/aws-elasticloadbalancingv2=1.11.0,@aws-cdk/aws-events=1.11.0,@aws-cdk/aws-events-targets=1.11.0,@aws-cdk/aws-iam=1.11.0,@aws-cdk/aws-kms=1.11.0,@aws-cdk/aws-lambda=1.11.0,@aws-cdk/aws-logs=1.11.0,@aws-cdk/aws-route53=1.11.0,@aws-cdk/aws-route53-targets=1.11.0,@aws-cdk/aws-s3=1.11.0,@aws-cdk/aws-s3-assets=1.11.0,@aws-cdk/aws-servicediscovery=1.11.0,@aws-cdk/aws-sns=1.11.0,@aws-cdk/aws-sns-subscriptions=1.11.0,@aws-cdk/aws-sqs=1.11.0,@aws-cdk/aws-ssm=1.11.0,@aws-cdk/core=1.11.0,@aws-cdk/cx-api=1.11.0,@aws-cdk/region-info=1.11.0,jsii-runtime=node.js/v10.16.0 Condition: CDKMetadataAvailable Outputs: SampleServiceLoadBalancerDNS54524680: Value: Fn::GetAtt: - SampleServiceLBF4CAFE0D - DNSName SampleServiceServiceURLA7529434: Value: Fn::Join: - "" - - http:// - Fn::GetAtt: - SampleServiceLBF4CAFE0D - DNSName Conditions: CDKMetadataAvailable: Fn::Or: - Fn::Or: - Fn::Equals: - Ref: AWS::Region - ap-east-1 - Fn::Equals: - Ref: AWS::Region - ap-northeast-1 - Fn::Equals: - Ref: AWS::Region - ap-northeast-2 - Fn::Equals: - Ref: AWS::Region - ap-south-1 - Fn::Equals: - Ref: AWS::Region - ap-southeast-1 - Fn::Equals: - Ref: AWS::Region - ap-southeast-2 - Fn::Equals: - Ref: AWS::Region - ca-central-1 - Fn::Equals: - Ref: AWS::Region - cn-north-1 - Fn::Equals: - Ref: AWS::Region - cn-northwest-1 - Fn::Equals: - Ref: AWS::Region - eu-central-1 - Fn::Or: - Fn::Equals: - Ref: AWS::Region - eu-north-1 - Fn::Equals: - Ref: AWS::Region - eu-west-1 - Fn::Equals: - Ref: AWS::Region - eu-west-2 - Fn::Equals: - Ref: AWS::Region - eu-west-3 - Fn::Equals: - Ref: AWS::Region - me-south-1 - Fn::Equals: - Ref: AWS::Region - sa-east-1 - Fn::Equals: - Ref: AWS::Region - us-east-1 - Fn::Equals: - Ref: AWS::Region - us-east-2 - Fn::Equals: - Ref: AWS::Region - us-west-1 - Fn::Equals: - Ref: AWS::Region - us-west-2
たった30行ほどのコードですが、500行越えのCloudFormationテンプレートが出力されました。以下のリソースが作成されているのが確認できます。
- VPC
- Public Subnet
- Private Subnet
- NAT Gateway
- Route Table
- Security Group
- ELB
- ECS Cluster
- ECS Service
- ECS Task Definition
Patternライブラリが、いかにCloudFormationテンプレートを抽象化してくれているかがわかるかと思います。20 ~ 30行ほどのコードであらかじめ定義された一般的なパターンのインフラ環境をデプロイできるというのは、とても強力な機能です。標準のpatternライブラリ自体は2種類とまだ少ないですが、今後のアップデートに期待です。
デプロイ
$ cdk deploy AwsCdkPatternsConstructLibrarySampleStack
動作確認
デプロイできたらCloudFormationの出力タブにELBのURLが出力されているため、それをクリックしてみます。以下のように、ECS、Fargate上で動いているPHPのサンプルアプリケーションが表示されれば成功です。
終わりに
以上、AWS CDKのConstructs Libraryを理解するための記事でした。先日行われていたAWS DevDayでもCDKのセッションがあったりと、CDK界隈が盛り上がってきているなと感じています。本当に便利なのでぜひみなさんも触ってみてください!