概要
前回の記事でのこの宣言通り
cdkのL1とL2(そしてなんとL3も)について書いていきます。
もう少し深く(cdkのL1とL2について)書こうと思いましたが
予想より文量が多くなってしまったので次回に回そうと思います。
主にCfnとcdkの関係の話になります。
結論
一口にaws-cdkと言ってもその中には3つのレイヤーが存在します。
- L1:ほとんどそのままCfnのテンプレートを記述するもの→書いたものがそのままリソースとなる
- L2:設定しなくてもデフォルトで設定をいい感じにしてくれる
- L3:単一のリソースではなくAWSが設定したタスク群を設定できる。
レイヤーが高くなるほど便利になる(コード量や設定箇所)一方、設定の自由度が下がると言ったイメージ。
環境
操作する環境は以下の通りです。
% node -v //Node.jsのバージョン
v14.16.0
% tsc -v //TypeScriptのバージョン
Version 4.5.5
% cdk --version //aws-cdkのバージョン
1.142.0
L1
AWSの説明はこちら
beginning with low-level constructs, which we call CFN Resources (or L1, short for "layer 1") or Cfn (short for CloudFormation) resources. These constructs directly represent all resources available in AWS CloudFormation. CFN Resources are periodically generated from the AWS CloudFormation Resource Specification. They are named CfnXyz, where Xyz is name of the resource.
「頭にCfnとついているクラス(L1のクラス)はCfnを直接表現でき、Cfnで扱えるものは全てこのレイヤーで扱える。」とあります。
cdkの中でも低レイヤーのクラスであるということのようです。
実践
実際にやってみましょう。
CfnVPC
クラスを例にしてみます。リファレンスはこちら
//lib/cdk-stack.ts
+import { CfnVPC } from "@aws-cdk/aws-ec2";
import * as cdk from "@aws-cdk/core";
export class CdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
+ new CfnVPC(this, "VPC", {
+ cidrBlock: "10.0.0.0/16",
});
}
}
必須要素のcidrBlock
のみ指定してみます。
cdk synth
にて出力したCfnのテンプレートはこちら。
% cdk synth CdkStack --profile test
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Metadata:
aws:cdk:path: CdkStack/VPC
CDKMetadata:
Type: AWS::CDK::Metadata
Properties:
Analytics: v2:deflate64:H4sIAAAAAAAA/yWMQQqAIBAA39Jdt4zoHPiBKOge6wYWKbhaB/HvFZ0GhmEUqK6FphrWmyWao87oA0Ge44qH0N5xDAmj0JubiH0KSEV8LWELr1xGXYTzhmDn+lIdqP697WytDMlFexJMPx/x8RwRagAAAA==
Metadata:
aws:cdk:path: CdkStack/CDKMetadata/Default
指定した通り、CidrBlockが10.0.0.0/16のAWS::EC2::VPC
が作成されてます。(deployはスキップします。)
指定した分だけ反映されるというのがミソですね。
L2
少し長いですが、こちらも公式ドキュメントから
The next level of constructs, L2, also represent AWS resources, but with a higher-level, intent-based API. They provide similar functionality, but provide the defaults, boilerplate, and glue logic you'd be writing yourself with a CFN Resource construct. AWS constructs offer convenient defaults and reduce the need to know all the details about the AWS resources they represent, while providing convenience methods that make it simpler to work with the resource.
「L1と同様AWSリソースを表現するが、AWSリソースの全ての知識がなくても詳細まで表現できる」とのことですね。
細かい設定は知らなくでも勝手にやってくれるイメージでしょうか。
実践
ピンと来なければやってみればいいんです。
//lib/cdk-stack.ts
+import { Vpc } from "@aws-cdk/aws-ec2";
import * as cdk from "@aws-cdk/core";
export class CdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
+ new Vpc(this, "VPC", {
+ cidr: "10.0.0.0/16",
});
}
}
今度はVpc
クラスで構築してみます。
リファレンス置いときます。
必須ではないですが(この時点でL1と違いますね)先ほどと同様にcidrを設定します。
さあ、どうなるんでしょうか。
% cdk synth CdkStack --profile test
Resources:
VPCB9E5F0B4:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsHostnames: true
EnableDnsSupport: true
InstanceTenancy: default
Tags:
- Key: Name
Value: CdkStack/VPC
Metadata:
aws:cdk:path: CdkStack/VPC/Resource
VPCPublicSubnet1SubnetB4246D30:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.0.0/19
VpcId:
Ref: VPCB9E5F0B4
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: true
Tags:
- Key: aws-cdk:subnet-name
Value: Public
- Key: aws-cdk:subnet-type
Value: Public
- Key: Name
Value: CdkStack/VPC/PublicSubnet1
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet1/Subnet
VPCPublicSubnet1RouteTableFEE4B781:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPCB9E5F0B4
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet1
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet1/RouteTable
VPCPublicSubnet1RouteTableAssociation0B0896DC:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCPublicSubnet1RouteTableFEE4B781
SubnetId:
Ref: VPCPublicSubnet1SubnetB4246D30
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet1/RouteTableAssociation
VPCPublicSubnet1DefaultRoute91CEF279:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: VPCPublicSubnet1RouteTableFEE4B781
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: VPCIGWB7E252D3
DependsOn:
- VPCVPCGW99B986DC
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet1/DefaultRoute
VPCPublicSubnet1EIP6AD938E8:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet1
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet1/EIP
VPCPublicSubnet1NATGatewayE0556630:
Type: AWS::EC2::NatGateway
Properties:
SubnetId:
Ref: VPCPublicSubnet1SubnetB4246D30
AllocationId:
Fn::GetAtt:
- VPCPublicSubnet1EIP6AD938E8
- AllocationId
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet1
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet1/NATGateway
VPCPublicSubnet2Subnet74179F39:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.32.0/19
VpcId:
Ref: VPCB9E5F0B4
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: true
Tags:
- Key: aws-cdk:subnet-name
Value: Public
- Key: aws-cdk:subnet-type
Value: Public
- Key: Name
Value: CdkStack/VPC/PublicSubnet2
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet2/Subnet
VPCPublicSubnet2RouteTable6F1A15F1:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPCB9E5F0B4
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet2
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet2/RouteTable
VPCPublicSubnet2RouteTableAssociation5A808732:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCPublicSubnet2RouteTable6F1A15F1
SubnetId:
Ref: VPCPublicSubnet2Subnet74179F39
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet2/RouteTableAssociation
VPCPublicSubnet2DefaultRouteB7481BBA:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: VPCPublicSubnet2RouteTable6F1A15F1
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: VPCIGWB7E252D3
DependsOn:
- VPCVPCGW99B986DC
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet2/DefaultRoute
VPCPublicSubnet2EIP4947BC00:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet2
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet2/EIP
VPCPublicSubnet2NATGateway3C070193:
Type: AWS::EC2::NatGateway
Properties:
SubnetId:
Ref: VPCPublicSubnet2Subnet74179F39
AllocationId:
Fn::GetAtt:
- VPCPublicSubnet2EIP4947BC00
- AllocationId
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet2
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet2/NATGateway
VPCPublicSubnet3Subnet631C5E25:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.64.0/19
VpcId:
Ref: VPCB9E5F0B4
AvailabilityZone: ap-northeast-1d
MapPublicIpOnLaunch: true
Tags:
- Key: aws-cdk:subnet-name
Value: Public
- Key: aws-cdk:subnet-type
Value: Public
- Key: Name
Value: CdkStack/VPC/PublicSubnet3
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet3/Subnet
VPCPublicSubnet3RouteTable98AE0E14:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPCB9E5F0B4
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet3
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet3/RouteTable
VPCPublicSubnet3RouteTableAssociation427FE0C6:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCPublicSubnet3RouteTable98AE0E14
SubnetId:
Ref: VPCPublicSubnet3Subnet631C5E25
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet3/RouteTableAssociation
VPCPublicSubnet3DefaultRouteA0D29D46:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: VPCPublicSubnet3RouteTable98AE0E14
DestinationCidrBlock: 0.0.0.0/0
GatewayId:
Ref: VPCIGWB7E252D3
DependsOn:
- VPCVPCGW99B986DC
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet3/DefaultRoute
VPCPublicSubnet3EIPAD4BC883:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet3
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet3/EIP
VPCPublicSubnet3NATGatewayD3048F5C:
Type: AWS::EC2::NatGateway
Properties:
SubnetId:
Ref: VPCPublicSubnet3Subnet631C5E25
AllocationId:
Fn::GetAtt:
- VPCPublicSubnet3EIPAD4BC883
- AllocationId
Tags:
- Key: Name
Value: CdkStack/VPC/PublicSubnet3
Metadata:
aws:cdk:path: CdkStack/VPC/PublicSubnet3/NATGateway
VPCPrivateSubnet1Subnet8BCA10E0:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.96.0/19
VpcId:
Ref: VPCB9E5F0B4
AvailabilityZone: ap-northeast-1a
MapPublicIpOnLaunch: false
Tags:
- Key: aws-cdk:subnet-name
Value: Private
- Key: aws-cdk:subnet-type
Value: Private
- Key: Name
Value: CdkStack/VPC/PrivateSubnet1
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet1/Subnet
VPCPrivateSubnet1RouteTableBE8A6027:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPCB9E5F0B4
Tags:
- Key: Name
Value: CdkStack/VPC/PrivateSubnet1
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet1/RouteTable
VPCPrivateSubnet1RouteTableAssociation347902D1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCPrivateSubnet1RouteTableBE8A6027
SubnetId:
Ref: VPCPrivateSubnet1Subnet8BCA10E0
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet1/RouteTableAssociation
VPCPrivateSubnet1DefaultRouteAE1D6490:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: VPCPrivateSubnet1RouteTableBE8A6027
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: VPCPublicSubnet1NATGatewayE0556630
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet1/DefaultRoute
VPCPrivateSubnet2SubnetCFCDAA7A:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.128.0/19
VpcId:
Ref: VPCB9E5F0B4
AvailabilityZone: ap-northeast-1c
MapPublicIpOnLaunch: false
Tags:
- Key: aws-cdk:subnet-name
Value: Private
- Key: aws-cdk:subnet-type
Value: Private
- Key: Name
Value: CdkStack/VPC/PrivateSubnet2
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet2/Subnet
VPCPrivateSubnet2RouteTable0A19E10E:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPCB9E5F0B4
Tags:
- Key: Name
Value: CdkStack/VPC/PrivateSubnet2
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet2/RouteTable
VPCPrivateSubnet2RouteTableAssociation0C73D413:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCPrivateSubnet2RouteTable0A19E10E
SubnetId:
Ref: VPCPrivateSubnet2SubnetCFCDAA7A
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet2/RouteTableAssociation
VPCPrivateSubnet2DefaultRouteF4F5CFD2:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: VPCPrivateSubnet2RouteTable0A19E10E
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: VPCPublicSubnet2NATGateway3C070193
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet2/DefaultRoute
VPCPrivateSubnet3Subnet3EDCD457:
Type: AWS::EC2::Subnet
Properties:
CidrBlock: 10.0.160.0/19
VpcId:
Ref: VPCB9E5F0B4
AvailabilityZone: ap-northeast-1d
MapPublicIpOnLaunch: false
Tags:
- Key: aws-cdk:subnet-name
Value: Private
- Key: aws-cdk:subnet-type
Value: Private
- Key: Name
Value: CdkStack/VPC/PrivateSubnet3
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet3/Subnet
VPCPrivateSubnet3RouteTable192186F8:
Type: AWS::EC2::RouteTable
Properties:
VpcId:
Ref: VPCB9E5F0B4
Tags:
- Key: Name
Value: CdkStack/VPC/PrivateSubnet3
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet3/RouteTable
VPCPrivateSubnet3RouteTableAssociationC28D144E:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
RouteTableId:
Ref: VPCPrivateSubnet3RouteTable192186F8
SubnetId:
Ref: VPCPrivateSubnet3Subnet3EDCD457
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet3/RouteTableAssociation
VPCPrivateSubnet3DefaultRoute27F311AE:
Type: AWS::EC2::Route
Properties:
RouteTableId:
Ref: VPCPrivateSubnet3RouteTable192186F8
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId:
Ref: VPCPublicSubnet3NATGatewayD3048F5C
Metadata:
aws:cdk:path: CdkStack/VPC/PrivateSubnet3/DefaultRoute
VPCIGWB7E252D3:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: CdkStack/VPC
Metadata:
aws:cdk:path: CdkStack/VPC/IGW
VPCVPCGW99B986DC:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId:
Ref: VPCB9E5F0B4
InternetGatewayId:
Ref: VPCIGWB7E252D3
Metadata:
aws:cdk:path: CdkStack/VPC/VPCGW
CDKMetadata:
Type: AWS::CDK::Metadata
Properties:
Analytics: v2:deflate64:H4sIAAAAAAAA/0WPTw+CMAzFP4v3MYQQzxJiDBdDwHAfpcbxZzNbpzGE7y6IyKm/vr42fQEPopDvd0fxsh7UrT+ANsiHggS0LEernQFkiVaWjANiyU2t6sjmJYSQD+UD5kmZJSxzVSehcJXCr3ujXDvCq6g63PRNi63VIAVJrf7mGU5pNpeLoLMgfIk3y4x8TrgdThWhmXg1LJ/8upimKPceFY0jU7pG3lj/GUQ8OEy5GyulZ5wi2SPPl/oBpmTsORQBAAA=
Metadata:
aws:cdk:path: CdkStack/CDKMetadata/Default
な、長い。。。
よく見てみると10.0.0.0/16のAWS::EC2::VPC
だけでなく
Subnet、IGW、NATGW、RouteTableまでできてますね。
これが
but provide the defaults, boilerplate, and glue logic you'd be writing yourself with a CFN Resource construct.
ということなんでしょうね。
設定をしていなくても「いい感じに」設定を入れてくれるってとこでしょうか。
確かに勝手にサービスを作られているのは「えっ?」って感じですが、
いざEC2を立てようとするときVPCだけじゃうまくいかないですもんね。
EC2に詳しければ、Subnetを立てて、IGWとRTを設定して、、とやっていけますがそこまで詳しくないと厳しいかも。。
それをAWSにそこまで詳しくなくても
「取り合えす動く状態」にしてくれるのがL2の良さってことでしょうね。
(ちなみにL1だと全て自分で設定が必要です)
※もちろん詳細な設定はL2でも可能です。「詳細な設定をしなくても動く」というのが強みですね
L3
こちらより引用
the AWS Construct Library includes L3 constructs, which we call patterns. These constructs are designed to help you complete common tasks in AWS, often involving multiple kinds of resources.
L2までと違い
リソース単体ではなく、よく使われるリソースの組み合わせを提供してくれるようです。
これは便利そう。
一つのクラスでよく使う構成が作れるということですね。
実践
ということで実践。
ドキュメントにあったこちらのクラスを構築していこうと思います。
よくあるLambda+APIGatewayの組み合わせを構築できるということのようです。
が、結論から言うとこのL3はあまりイケてない。。
書きたいこと(L1とL2の抽象度の違い)から外れるのと
L1→L2の時にあった「自動で勝手に補完してくれる」と言う感動もないので軽く触れて終わります。
//lib/cdk-stack.ts
+import { LambdaRestApi } from "@aws-cdk/aws-apigateway";
import * as cdk from "@aws-cdk/core";
export class CdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
+ new LambdaRestApi(this, "LambdaRestApi", {
+ handler: ,
+ });
}
}
handler
は必須パラメータなのですが、このhandler
の型がIFunction
型。つまりlambda functionを割り当てないといけないわけです。
早い話が「自分でlambda」を作らないといけないってことです。
まあ、いい感じに勝手に作れ!ってのも無茶な話ですが。。。
作りました。
//lib/cdk-stack.ts
import { LambdaRestApi } from "@aws-cdk/aws-apigateway";
+import { Code, Function, Runtime } from "@aws-cdk/aws-lambda";
import * as cdk from "@aws-cdk/core";
export class CdkStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
+ const func = new Function(this, "Function", {
+ code: Code.fromAsset(`${__dirname}/../lambdas/src`),
+ handler: "index.handler",
+ runtime: Runtime.PYTHON_3_8,
});
new LambdaRestApi(this, "LambdaRestApi", {
- handler:
+ handler: func,
});
}
}
Lambda+APIGatewayの組み合わせを構築するのにL2でlambdaとAPIGatewayを作りました、
と言う話です。
(連携は自動でしてくれますが。。。)
L3を使うメリットがあまり感じないというより、
やっていることはL2の構築なのでL3を気にする必要はないのかなという印象です。
まとめ
- L1
- メリット
- 基本的にCfnと同じなので記述した以外のリソースを勝手に作成されない。
- Cfnに対応しているリソースであれば全てこのレイヤーで扱える。
- デメリット
- リソースの深い知識や理解がないと稼働するリソースを作成するハードルが高い。
- 必然的にコード量が多くなる。
- メリット
- L2(&L3)
- メリット
- 必須プロパティさえ設定すれば、自動で稼働するリソースを作成してくれる
- 少ないコード量でリソースを定義できる
- デメリット
- 対応していないリソースがある
- 思わぬリソースや意図しない設定で構築されることに注意しないといけない。
- メリット
あるあるですが、メリットとデメリットが逆になっている感じですね。
L2(L3)でも詳細な設定ができないわけではないので、
基本はL2で構築が良いかなと思います。
ただ、
「設定項目がない」だとか、「そもそもリソースが対応していない」って場合はL1で構築をチャレンジということになるかなと思います。
補足
同一スタック内でもL1とL2、L3を混在させて記述することは可能です。
が、propsは共有できないので連携させる際には注意が必要です。
さいごに
L1とL2を整理することで
構築で利用するクラスを意識しながら構築を進められるといいなと思います。
特に、
L2を利用しているときに、どういった設定が自動でされているのか意識することでより詳細な設計に活かせる気がします。
次回はまあ単純にEC2でも立ててみます。