aws-cdkの凄さとL1・L2の違い aws-cdk#2

# 概要
巷で話題の(私だけ？)aws-cdkですが
「実際何がすごいの？」という疑問に自分ならこう答える！
というものを書いていこうかなと。
~~派生してaws-cdkのL1とL2の違いについても少しだけ書きます。~~
↑次回書きます。。
[書きました](https://qiita.com/KbSota/items/be3b4063deea52815d50)

# 結論
aws-cdkは
①普段利用しているプログラミング言語を使って
②少ないコード量で
③Cfnを利用することができる
からすごい！！

これについて少しずつみていきます。

# 環境
操作する環境は以下の通りです。
```
% node -v //Node.jsのバージョン
v14.16.0

% tsc -v //TypeScriptのバージョン
Version 4.5.5

% cdk --version //aws-cdkのバージョン
1.142.0
```

# 本題の前に
早速本題に入っていく前にAWS SDKとCloudFormation(Cfn)について触れます。
[以前の投稿](https://qiita.com/KbSota/items/48d9d8ffaeccd775714e)でも少し触れたのですが
もう少しだけ詳しく。

### AWS SDKとは
[公式](https://docs.aws.amazon.com/ja_jp/sdk-for-javascript/v2/developer-guide/welcome.html)はこのように言っています。
>AWS SDK for JavaScript は AWS のサービス用の JavaScript API を提供します。JavaScript API を使用して、Node.js またはブラウザ用のライブラリまたはアプリケーションを構築できます。

つまり、AWSを操作するAPIをプログラミング言語(C++,Go,Java,JavaScript,.NET,Node.js,PHP,Python,Ruby)から叩けるというツールなんですね。
**普段使い慣れた言語で直接AWSも触れる、システムに組み込みやすい**というのが大きなメリットです。

AWS CLIも`aws ...`とコマンドラインからAPIを実行できますが
上記のような**高級言語でAPIを扱える**のが非常に便利でイケている点です。

### CloudFormationとは
[公式](https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html)はだらだらとわかりにくいので、、簡単にまとめると
**テンプレートと言われるyamlやjson形式のファイルを元にAWSのリソースを自動で作成してくれるサービス**
というものです。
ちなみにテンプレートファイルの例がこちら
```
{
  "AWSTemplateFormatVersion" : "2010-09-09",
  "Description" : "A simple EC2 instance",
  "Resources" : {
    "MyEC2Instance" : {
      "Type" : "AWS::EC2::Instance",
      "Properties" : {
        "ImageId" : "ami-0ff8a91507f77f867",
        "InstanceType" : "t2.micro"
      }
    }
  }
}
```
ふ〜んと言った感じ。。。
##### CloudFormationの凄さ
では何がそんなに便利なのか。
主に次の２点なのかなと思います。

###### ①スタック単位で構築できる
インスタンスやサービス単位ではなく**スタック単位で**リソースを作成してくれるので自分の好きなまとまりごとに更新や削除・構築が可能な点です。
前述のAWS SDKだとAPIを叩くのはサービスごとになるのでコンソールぽちぽちとやっていることは変わらないんですね。(スクリプトで自動化できるなどのメリットはあるものの)

###### ②依存関係を解消してくれる
「SDKのコードをまとめて実行させる」というのはリソース構築という観点からはNGです。
というのもその方法では依存関係を解消できないからです。

AWSリソースを構築するにあたっては依存関係があったりします。
例えばEC2インスタンスはVPCがないと構築出来ません。つまりEC2はVPCに依存しているということです。
ですので、EC2を構築する前にVPCを(もっといえばSubnetも)構築しておかなければいけないということです。

システムが複雑になってくると思わぬところに依存関係が隠れていてそれを手で解消するのはAWSのスペシャリストでも苦労するかもしれません。
(SDKで構築するとこれをdeployのたびにこれをするハメになります)

Cfnは同一スタック内の依存関係を自動で検出して**イイカンジに**構築してくれるのです。
しかも万一エラーが起きて構築出来なかった場合、**ROLLBACK**して変更を全て消してくれます。
変なゴミが残ってないかなんて心配は必要ないわけです。
**deploy時の不安解消、手間解消**がCfnのメリットですね。

###### 補足
SDKはcdkに対して劣っているということではなく、**使うシチュエーションが異なる**ということです。
AWSリソースの構築はcdkが得意ですし、操作(S3にログをプッシュするなど)はSDKの方が合っているかもしれません。
この記事ではAWSリソースの構築ということについてお話ししているのでSDKを軽く扱っています。


# aws-cdkとは
長々とAWS SDKとCfnについて書いてきましたが、aws-cdkは**両方のメリットを兼ね備えている**というイメージです。

### ①普段利用しているプログラミング言語で開発可能
[こちら](https://aws.amazon.com/jp/cdk/faqs/)にあるようにSDKと同様、普段利用している高級言語で扱うことができるのです。(AWS SDKのメリット)
>Q: AWS CDK ではどのプログラム言語がサポートされていますか?
AWS CDK は、JavaScript、TypeScript、Python、Java、C# が一般公開されており、開発者プレビューでは Go がサポートされています。将来的にはほかの言語用の AWS CDK バインディングも計画しています。GitHub でお気に入りを投票してください。

### ②コード量の少なさ
しかもそのコード量は少ないです。
実際に書いてみましょう。
cdkでVPCを作成してみます。
```diff_typescript
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");
  }
}
```
この一行でVPCが作成できます。
**この一行だけで**です！！

そして、作成されるCfnのテンプレートがこちら。(長いですが略さず書きますので流し読みしてください)
```
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
```
Cfnのテンプレートファイルに比べて以下に少ないコード量で(=手軽に)リソースを記述できるか理解してもらえたでしょうか？
これがメリットの2点目です。

### ③Cfnを利用することができる
aws-cdkはそのデプロイの過程でCfnのテンプレートファイルを生成し、
Cfnを利用してdeployします。
当然、Cfnのメリットを享受できます。
>Q: AWS CDK と CloudFormation にはどのような関係がありますか?
AWS CDK は、最新のプログラミング言語のすべての機能を活用して AWS Infrastructure as Code を定義する、デベロッパー中心のツールキットと考えることができます。AWS CDK アプリケーションが実行されると、それらは完全に形成された CloudFormation JSON/YAML テンプレートにコンパイルされ、プロビジョニング用に CloudFormation サービスに送信されます。AWS CDK は CloudFormation を利用しているため、CloudFormation の安全なデプロイ、自動ロールバック、ドリフト検出などのすべての利点がそのまま提供されます。


VPCだけでこの量になるCfnのテンプレートを他のサービスの分も手で書いていけますか？
Cfnを使わずに依存関係を解消しながら簡単にdeployできますか？

**この両方の難題に「YES」と答えるToolがaws-cdkなんです。**

# まとめ
aws-cdkは
**①使い慣れた言語でAWSリソースを、(直接APIを実行するわけではないので注意)
②比較的少量のコードで気軽に記述でき
③Cfnを利用して依存関係の解消やエラー時のROLLBACKなどを行える。**
というのがaws-cdkの凄さでした。

AWSリソース構築という段においてはaws-cdkはかなり便利で替えの利かないツールなのではと思っています。
特にIaCの文脈でインフラのコード管理を考えた時に避けては通れないツールですね。



もう少し深く(cdkのL1とL2について)書こうと思いましたが
予想より文量が多くなってしまったので次回に回そうと思います。
主にCfnとcdkの関係の話になります。

[次回投稿](https://qiita.com/KbSota/items/be3b4063deea52815d50)