CDK（Python）を使ってEC2作成してみた

CloudFormationを最近触り始めた私がAWS CDKを使ってEC2作成まで挑戦します

# 前提条件
以下インストールされていること
Python3
aws cli
aws cdk

# app作成
以下を参考にapp作成していきます

https://docs.aws.amazon.com/cdk/latest/guide/hello_world.html

```shell
$ mkdir cdk-app
$ cd cdk-app

$ cdk init app --language python

$ source .venv/bin/activate
$ python -m pip install -r requirements.txt
$ cdk bootstrap

```

成功するとこんな感じにファイルができます

```shell
$ ls -a
.			.venv			cdk_app			tests
..			README.md		requirements-dev.txt
.git			app.py			requirements.txt
.gitignore		cdk.json		source.bat
```

# 実装

どこに処理を書いていけばいいんじゃ.....？

生成されたファイルの中に「cdk_app_stack.py」というファイルがありました
どうやらここに処理を書いていくようです

```shell
$ cd cdk_app/
$ ls -a
.			..			__init__.py		cdk_app_stack.py
$ cat cdk_app_stack.py
from aws_cdk import (
    # Duration,
    Stack,
    # aws_sqs as sqs,
)
from constructs import Construct

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here

        # example resource
        # queue = sqs.Queue(
        #     self, "CdkAppQueue",
        #     visibility_timeout=Duration.seconds(300),
        # )
```

## VPC作成

さっそくVPCとサブネットの部分を実装していきます

```python
from aws_cdk import (
    # Duration,
    aws_ec2 as ec2,
    Stack,
)
from constructs import Construct

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Create VPC
        vpc = ec2.Vpc(
            self,
            id="vpc",
            cidr="10.0.0.0/16",
            nat_gateways=0, # NatGatewayを作成しない指定
            # Create Private Subnet
            subnet_configuration=[
                ec2.SubnetConfiguration(
                    name="subnet",
                    subnet_type=ec2.SubnetType.PUBLIC,
                    cidr_mask=24
                )
            ]
        )
```

以下を実行することで実装した内容が正しく動くのかチェックできるのと、作成するリソースの内容が見れます

```shell
$ cdk diff
```

作成されるリソース

```shell
〜〜〜省略〜〜〜

Resources
[+] AWS::EC2::VPC vpc vpcA2121C38 
[+] AWS::EC2::Subnet vpc/subnetSubnet1/Subnet vpcsubnetSubnet1SubnetED4FAF75 
[+] AWS::EC2::RouteTable vpc/subnetSubnet1/RouteTable vpcsubnetSubnet1RouteTable540CA842 
[+] AWS::EC2::SubnetRouteTableAssociation vpc/subnetSubnet1/RouteTableAssociation vpcsubnetSubnet1RouteTableAssociation8AB5D956 
[+] AWS::EC2::Route vpc/subnetSubnet1/DefaultRoute vpcsubnetSubnet1DefaultRoute693F9670 
[+] AWS::EC2::Subnet vpc/subnetSubnet2/Subnet vpcsubnetSubnet2Subnet1FDD234B 
[+] AWS::EC2::RouteTable vpc/subnetSubnet2/RouteTable vpcsubnetSubnet2RouteTable76DD52F4 
[+] AWS::EC2::SubnetRouteTableAssociation vpc/subnetSubnet2/RouteTableAssociation vpcsubnetSubnet2RouteTableAssociationE50AAA12 
[+] AWS::EC2::Route vpc/subnetSubnet2/DefaultRoute vpcsubnetSubnet2DefaultRouteD284164C 
[+] AWS::EC2::InternetGateway vpc/IGW vpcIGWE57CBDCA 
[+] AWS::EC2::VPCGatewayAttachment vpc/VPCGW vpcVPCGW7984C166 

〜〜〜省略〜〜〜
```

ではデプロイしてみましょう

```shell
$ cdk deploy
```

VPCとサブネットが作成されてました！

![VPC作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276166/8b6ad21d-816e-f676-564d-48eb0f4e7c27.png)

![サブネット作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276166/fb6235bf-3ab2-5a57-639d-0d7c3f49a7f0.png)

ルートテーブルやインターネットゲートウェイも作られてました！
ここはCloudFormationだと書かないと作られないので楽ですね
（自分で作ることもできそうです）

## EC2作成

セキュリティグループもあわせて実装します

```python
from aws_cdk import (
    # Duration,
    aws_ec2 as ec2,
    Stack,
)
from constructs import Construct

class CdkAppStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # Create VPC
        vpc = ec2.Vpc(
            self,
            id="vpc",
            cidr="10.0.0.0/16",
            nat_gateways=0, # NatGatewayを作成しない指定
            # Create Private Subnet
            subnet_configuration=[
                ec2.SubnetConfiguration(
                    name="subnet",
                    subnet_type=ec2.SubnetType.PUBLIC,
                    cidr_mask=24
                )
            ]
        )

        # Create SecurityGroup
        security_group = ec2.SecurityGroup(
            self,
            id="ec2-sg",
            vpc=vpc,
            allow_all_outbound=True,
            security_group_name="ec2-sg"
        )

        # Add Ingress Rule
        security_group.add_ingress_rule(
            peer=ec2.Peer.ipv4("**********/32"),
            connection=ec2.Port.tcp(22),
            description="allow ssh access"
        )

        # Create EC2
        ec2_instance = ec2.Instance(
            self,
            id="ec2-instance",
            instance_type=ec2.InstanceType.of(
                ec2.InstanceClass.BURSTABLE2,
                ec2.InstanceSize.MICRO
            ),
            machine_image=ec2.AmazonLinuxImage(),
            vpc=vpc,
            vpc_subnets=ec2.SubnetSelection(
                subnet_type=ec2.SubnetType.PUBLIC
            ),
            instance_name="ec2-instance",
            security_group=security_group
        )

```

できたので再度diff...

```shell
$ cdk diff
```

ちゃんと差分のみ表示してくれますね！
とても見やすいです！

```shell
IAM Statement Changes
┌───┬──────────────────────────────┬────────┬────────────────┬──────────────────────────────┬───────────┐
│   │ Resource                     │ Effect │ Action         │ Principal                    │ Condition │
├───┼──────────────────────────────┼────────┼────────────────┼──────────────────────────────┼───────────┤
│ + │ ${ec2-instance/InstanceRole. │ Allow  │ sts:AssumeRole │ Service:ec2.${AWS::URLSuffix │           │
│   │ Arn}                         │        │                │ }                            │           │
└───┴──────────────────────────────┴────────┴────────────────┴──────────────────────────────┴───────────┘
Security Group Changes
┌───┬───────────────────┬─────┬────────────┬────────────────────┐
│   │ Group             │ Dir │ Protocol   │ Peer               │
├───┼───────────────────┼─────┼────────────┼────────────────────┤
│ + │ ${ec2-sg.GroupId} │ In  │ TCP 22     │ **********/32 │
│ + │ ${ec2-sg.GroupId} │ Out │ Everything │ Everyone (IPv4)    │
└───┴───────────────────┴─────┴────────────┴────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Parameters
[+] Parameter SsmParameterValue:--aws--service--ami-amazon-linux-latest--amzn-ami-hvm-x86_64-gp2:C96584B6-F00A-464E-AD19-53AFF4B05118.Parameter SsmParameterValueawsserviceamiamazonlinuxlatestamznamihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter: {"Type":"AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>","Default":"/aws/service/ami-amazon-linux-latest/amzn-ami-hvm-x86_64-gp2"}

Resources
[+] AWS::EC2::SecurityGroup ec2-sg ec2sg81ADD929 
[+] AWS::IAM::Role ec2-instance/InstanceRole ec2instanceInstanceRoleCA97C688 
[+] AWS::IAM::InstanceProfile ec2-instance/InstanceProfile ec2instanceInstanceProfile9BCE9015 
[+] AWS::EC2::Instance ec2-instance ec2instance42082E81 
```

そしてデプロイ

```shell
$ cdk deploy
```

EC2作成されてました！

![EC2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276166/f631c3f7-06ce-1653-29b8-1315b2f05d00.png)

# メソッド化

せっかくyamlでなくPythonで書いているので、プログラムみたいにしたい！
ということでメソッドを作ってみます


```Python
　　　　　　　　def create_ec2(self, id, vpc, security_group):
        return ec2.Instance(
            self,
            id=id,
            instance_type=ec2.InstanceType.of(
                ec2.InstanceClass.BURSTABLE2,
                ec2.InstanceSize.MICRO
            ),
            machine_image=ec2.AmazonLinuxImage(),
            vpc=vpc,
            vpc_subnets=ec2.SubnetSelection(
                subnet_type=ec2.SubnetType.PUBLIC
            ),
            instance_name=id,
            security_group=security_group
        )
```

単純ですがEC2を作成するメソッドを作成しました

idを変えて呼び出す
このidの部分がCloudFormationでいうResourcesIdの部分なので同じ値は指定できません

```Python
        # EC2インスタンスを作成
        ec2_instance = self.create_ec2("ec2-instance", vpc, security_group)
        ec2_instance2 = self.create_ec2("ec2-instance2", vpc, security_group)
```

これでデプロイしてみると.....

![EC2_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/276166/77a18317-8572-c193-5afd-edd4f4f03605.png)


2つ作成されてました！
素敵ですね！！

# スタック削除

一通り試せたので次はスタックの削除をしてみます
削除するときはdestroyコマンドです

```shell
$ cdk destroy
```

消す前にスタック名の確認をしてくれるので精神的に安心ですｗ

```shell
Are you sure you want to delete: CdkAppStack (y/n)? 
```

最初に作成したVPCまで削除されてました

# 感想

CDKで実行するとターミナル上で見れる情報が多く、コマンド実行してからの不安が軽減された感じがしました

あとやっぱりメソッド化できるのがいいですね！
CloudFormationだとどうしても冗長化したソースになってしまい読みづらくなってしまうので、、、

今後構築する案件ではCDK使って行きたいなととても感じました

以上

# Tips

公式サンプル

https://docs.aws.amazon.com/cdk/api/latest/docs/aws-construct-library.html

app作成

https://docs.aws.amazon.com/cdk/latest/guide/hello_world.html