Help us understand the problem. What is going on with this article?

AWS CDKなら、たった1行で高可用なVPCを構築できる

AWS CDK(Python3)でVPCを作るため、aws_cdk.aws_ec2.Vpcのオブジェクトを生成する (= 下記testinfra.stack.pyのec2.Vpc()の部分を実行する) 時、何が起きるのかを観察してみました。

CloudFormationでは、作ろうとするリソースに対応するコードの殆どを自分で書く必要がありますが、AWS CDKではわずか1行でデフォルトでいろんなものを作れました。

0. 前提知識

以下をお読みいただくと、Python3でのAWS CDK開発のイメージがわくはずなので、きっとスムーズに読めるはずですよ。(保証はしません)
Cloud9にAWS CDK (Python3) をインストールする方法
AWS CDK (Python3) でS3 Bucketを1個作って感じたこと

1. テストコード

以下は、スタックを表すクラスです。
VPCのCidrには10.0.0.0/24を指定しています。
端的に言えば、わざわざ指定したパラメータは、このVPCのCidrだけです。

testinfra_stack.py
from aws_cdk import core
from aws_cdk import (
    aws_ec2 as ec2
)

class CodeinfraStack(core.Stack):

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

        # Parameter
        vpcName = "test-vpc"
        vpcCidrBlock = "10.0.0.0/24"

        self.createVpc(vpcCidrBlock=vpcCidrBlock, vpcName=vpcName)

    # VPC
    def createVpc(self, vpcCidrBlock, vpcName):
        vpcNetworkAddress = ipaddress.ip_network(address=vpcCidrBlock)

        vpcNetwork = ec2.Vpc(self, vpcName, cidr=vpcCidrBlock)

以下は、app.pyになります。

app.py
#!/usr/bin/env python3

from aws_cdk import core
from test.testinfra_stack import CodeinfraStack

app = core.App()
CodeinfraStack(app, "Test-Infra")

app.synth()

CloudFormationであれば、VPCそのものしか書いていないイメージです。

2. cdk synthしてできたもの

このコードでcdk synthしてみると、以下のCloudFormationテンプレートができました。
testinfra_stack.pyが約20行ですが、CloudFormationテンプレートは約440行でした。

Test-Infra.template.json
{
  "Resources": {
    "testvpc8985080E": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": "10.0.0.0/24",
        "EnableDnsHostnames": true,
        "EnableDnsSupport": true,
        "InstanceTenancy": "default",
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/Resource"
      }
    },
    "testvpcPublicSubnet1Subnet01CF7554": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "CidrBlock": "10.0.0.0/26",
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "AvailabilityZone": {
          "Fn::Select": [
            0,
            {
              "Fn::GetAZs": ""
            }
          ]
        },
        "MapPublicIpOnLaunch": true,
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PublicSubnet1"
          },
          {
            "Key": "aws-cdk:subnet-name",
            "Value": "Public"
          },
          {
            "Key": "aws-cdk:subnet-type",
            "Value": "Public"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet1/Subnet"
      }
    },
    "testvpcPublicSubnet1RouteTable180BB588": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PublicSubnet1"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet1/RouteTable"
      }
    },
    "testvpcPublicSubnet1RouteTableAssociation14A2D92F": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "RouteTableId": {
          "Ref": "testvpcPublicSubnet1RouteTable180BB588"
        },
        "SubnetId": {
          "Ref": "testvpcPublicSubnet1Subnet01CF7554"
        }
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet1/RouteTableAssociation"
      }
    },
    "testvpcPublicSubnet1DefaultRouteB1E474AB": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "testvpcPublicSubnet1RouteTable180BB588"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "GatewayId": {
          "Ref": "testvpcIGW2C2BA83F"
        }
      },
      "DependsOn": [
        "testvpcVPCGW7060AA15"
      ],
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet1/DefaultRoute"
      }
    },
    "testvpcPublicSubnet1EIP84634DA0": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "Domain": "vpc"
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet1/EIP"
      }
    },
    "testvpcPublicSubnet1NATGateway50787A07": {
      "Type": "AWS::EC2::NatGateway",
      "Properties": {
        "AllocationId": {
          "Fn::GetAtt": [
            "testvpcPublicSubnet1EIP84634DA0",
            "AllocationId"
          ]
        },
        "SubnetId": {
          "Ref": "testvpcPublicSubnet1Subnet01CF7554"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PublicSubnet1"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet1/NATGateway"
      }
    },
    "testvpcPublicSubnet2Subnet4E9D9728": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "CidrBlock": "10.0.0.64/26",
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "AvailabilityZone": {
          "Fn::Select": [
            1,
            {
              "Fn::GetAZs": ""
            }
          ]
        },
        "MapPublicIpOnLaunch": true,
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PublicSubnet2"
          },
          {
            "Key": "aws-cdk:subnet-name",
            "Value": "Public"
          },
          {
            "Key": "aws-cdk:subnet-type",
            "Value": "Public"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet2/Subnet"
      }
    },
    "testvpcPublicSubnet2RouteTable28A079F9": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PublicSubnet2"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet2/RouteTable"
      }
    },
    "testvpcPublicSubnet2RouteTableAssociationACF92511": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "RouteTableId": {
          "Ref": "testvpcPublicSubnet2RouteTable28A079F9"
        },
        "SubnetId": {
          "Ref": "testvpcPublicSubnet2Subnet4E9D9728"
        }
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet2/RouteTableAssociation"
      }
    },
    "testvpcPublicSubnet2DefaultRoute39BC0F35": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "testvpcPublicSubnet2RouteTable28A079F9"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "GatewayId": {
          "Ref": "testvpcIGW2C2BA83F"
        }
      },
      "DependsOn": [
        "testvpcVPCGW7060AA15"
      ],
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet2/DefaultRoute"
      }
    },
    "testvpcPublicSubnet2EIP6819FC49": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "Domain": "vpc"
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet2/EIP"
      }
    },
    "testvpcPublicSubnet2NATGateway8D7A9976": {
      "Type": "AWS::EC2::NatGateway",
      "Properties": {
        "AllocationId": {
          "Fn::GetAtt": [
            "testvpcPublicSubnet2EIP6819FC49",
            "AllocationId"
          ]
        },
        "SubnetId": {
          "Ref": "testvpcPublicSubnet2Subnet4E9D9728"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PublicSubnet2"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PublicSubnet2/NATGateway"
      }
    },
    "testvpcPrivateSubnet1Subnet865FB50A": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "CidrBlock": "10.0.0.128/26",
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "AvailabilityZone": {
          "Fn::Select": [
            0,
            {
              "Fn::GetAZs": ""
            }
          ]
        },
        "MapPublicIpOnLaunch": false,
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PrivateSubnet1"
          },
          {
            "Key": "aws-cdk:subnet-name",
            "Value": "Private"
          },
          {
            "Key": "aws-cdk:subnet-type",
            "Value": "Private"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PrivateSubnet1/Subnet"
      }
    },
    "testvpcPrivateSubnet1RouteTableC6BCA266": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PrivateSubnet1"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PrivateSubnet1/RouteTable"
      }
    },
    "testvpcPrivateSubnet1RouteTableAssociation0E625B49": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "RouteTableId": {
          "Ref": "testvpcPrivateSubnet1RouteTableC6BCA266"
        },
        "SubnetId": {
          "Ref": "testvpcPrivateSubnet1Subnet865FB50A"
        }
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PrivateSubnet1/RouteTableAssociation"
      }
    },
    "testvpcPrivateSubnet1DefaultRouteF07B0F68": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "testvpcPrivateSubnet1RouteTableC6BCA266"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "NatGatewayId": {
          "Ref": "testvpcPublicSubnet1NATGateway50787A07"
        }
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PrivateSubnet1/DefaultRoute"
      }
    },
    "testvpcPrivateSubnet2Subnet23D3396F": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "CidrBlock": "10.0.0.192/26",
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "AvailabilityZone": {
          "Fn::Select": [
            1,
            {
              "Fn::GetAZs": ""
            }
          ]
        },
        "MapPublicIpOnLaunch": false,
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PrivateSubnet2"
          },
          {
            "Key": "aws-cdk:subnet-name",
            "Value": "Private"
          },
          {
            "Key": "aws-cdk:subnet-type",
            "Value": "Private"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PrivateSubnet2/Subnet"
      }
    },
    "testvpcPrivateSubnet2RouteTable26C5E053": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc/PrivateSubnet2"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PrivateSubnet2/RouteTable"
      }
    },
    "testvpcPrivateSubnet2RouteTableAssociationB60494EA": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "RouteTableId": {
          "Ref": "testvpcPrivateSubnet2RouteTable26C5E053"
        },
        "SubnetId": {
          "Ref": "testvpcPrivateSubnet2Subnet23D3396F"
        }
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PrivateSubnet2/RouteTableAssociation"
      }
    },
    "testvpcPrivateSubnet2DefaultRouteC94968D3": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "testvpcPrivateSubnet2RouteTable26C5E053"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "NatGatewayId": {
          "Ref": "testvpcPublicSubnet2NATGateway8D7A9976"
        }
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/PrivateSubnet2/DefaultRoute"
      }
    },
    "testvpcIGW2C2BA83F": {
      "Type": "AWS::EC2::InternetGateway",
      "Properties": {
        "Tags": [
          {
            "Key": "Name",
            "Value": "Test-Infra/test-vpc"
          }
        ]
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/IGW"
      }
    },
    "testvpcVPCGW7060AA15": {
      "Type": "AWS::EC2::VPCGatewayAttachment",
      "Properties": {
        "VpcId": {
          "Ref": "testvpc8985080E"
        },
        "InternetGatewayId": {
          "Ref": "testvpcIGW2C2BA83F"
        }
      },
      "Metadata": {
        "aws:cdk:path": "Test-Infra/test-vpc/VPCGW"
      }
    }
  }
}

3. CloudFormationコードに含まれていたもの

以下のものができました。

Type できたもの 備考
AWS::EC2::VPC VPC 10.0.0.0/24,EnableDnsHostnamesとEnableDnsSupportはTrue,InstanceTenancyはDefault
AWS::EC2::Subnet PublicSubnet1Subnet 10.0.0.0/26,MapPublicIpOnLaunchはTrue
AWS::EC2::RouteTable PublicSubnet1RouteTable -
AWS::EC2::SubnetRouteTableAssociation PublicSubnet1RouteTableAssociation PublicSubnet1RouteTableをPublicSubnet1Subnetにアタッチ
AWS::EC2::Route PublicSubnet1DefaultRoute 0.0.0.0/0→IGW
AWS::EC2::EIP PublicSubnet1EIP PublicSubnet1NATGateway用EIP
AWS::EC2::NatGateway PublicSubnet1NATGateway -
AWS::EC2::Subnet PublicSubnet2Subnet 10.0.0.64/26
AWS::EC2::RouteTable PublicSubnet2RouteTable -
AWS::EC2::SubnetRouteTableAssociation PublicSubnet2RouteTableAssociation PublicSubnet2RouteTableをPublicSubnet2Subnetにアタッチ
AWS::EC2::Route PublicSubnet2DefaultRoute 0.0.0.0/0→IGW
AWS::EC2::EIP PublicSubnet2EIP PublicSubnet2NATGateway用EIP
AWS::EC2::NatGateway PublicSubnet2NATGateway -
AWS::EC2::Subnet PrivateSubnet1Subnet 10.0.0.128/26
AWS::EC2::RouteTable PrivateSubnet1RouteTable -
AWS::EC2::SubnetRouteTableAssociation PrivateSubnet1RouteTableAssociation PrivateSubnet1RouteTableをPrivateSubnet1Subnetにアタッチ
AWS::EC2::Route PrivateSubnet1DefaultRoute 0.0.0.0/0→PublicSubnet1NATGateway
AWS::EC2::Subnet PrivateSubnet2Subnet 10.0.0.192/26
AWS::EC2::RouteTable PrivateSubnet2RouteTable -
AWS::EC2::SubnetRouteTableAssociation PrivateSubnet2RouteTableAssociation PrivateSubnet2RouteTableをPrivateSubnet2Subnetにアタッチ
AWS::EC2::Route PrivateSubnet2DefaultRoute 0.0.0.0/0→PublicSubnet2NATGateway
AWS::EC2::InternetGateway IGW -
AWS::EC2::VPCGatewayAttachment VPCGW IGWをVPCにアタッチ

上記は、まさに以下の構成となります。
これは、AWSで良くベストプラクティスと言われているVPCの構成で、高可用性があるVPCのアーキテクチャとしても紹介されているものです。

NAT Gateway
引用: 「20180418 AWS Black Belt Online Seminar Amazon VPC」
https://www.slideshare.net/AmazonWebServicesJapan/20180418-aws-black-belt-online-seminar-amazon-vpc

ec2.Vpc(self, vpcName, cidr=vpcCidrBlock)と1行書いただけで、これだけのものができてしまうのはすごいですね!

4. 公式APIドキュメントの記述

VPCのクラス定義が書かれたAPIドキュメントには、以下の記述がありました。

VpcNetwork creates a VPC that spans a whole region. It will automatically divide the provided VPC CIDR range, and create public and private subnets per Availability Zone. Network routing for the public subnets will be configured to allow outbound access directly via an Internet Gateway. Network routing for the private subnets will be configured to allow outbound access via a set of resilient NAT Gateways (one per AZ).
https://docs.aws.amazon.com/cdk/api/latest/python/aws_cdk.aws_ec2/Vpc.html

Google翻訳すると、以下となります。

VpcNetworkは、リージョン全体にわたるVPCを作成します。提供されたVPC CIDR範囲を自動的に分割し、アベイラビリティーゾーンごとにパブリックおよびプライベートサブネットを作成します。パブリックサブネットのネットワークルーティングは、インターネットゲートウェイ経由でのアウトバウンドアクセスを直接許可するように設定されます。プライベートサブネットのネットワークルーティングは、1組の回復力のあるNATゲートウェイ(AZごとに1つ)を介したアウトバウンドアクセスを許可するように設定されます。

VPCのクラスからオブジェクトを1つ作るコードを書くだけで、高可用性のある構成が自動的にできることが分かりました。

5. まとめ

AWS CDK(Python3)でVPCを作ると、CloudFormationだと約440行のコードが約20行となり、さらに単なるVPC1個だけでなく、高可用性を持った構成を一撃で作ることが出来ました。
CloudFormationを書いていて面倒と感じる、Association周りのコードを書かなくても良いので楽に感じます。

元々CloudFormationに慣れていると、必要なリソースを全てコードに書きたくなりますが、AWS CDKでは、まずVPCのように範囲の広いサービスだけのコードを書いてみて、何がデフォルトでできるのかを観察してから、細かくカスタマイズしていくのが良さそうです。
慣れるまでに時間はかかりそうですが、デフォルトで何ができるのかを知れば、そのデフォルトで許容できる場合は、その部分のコードを書く必要はありませんから、開発スピードが早くなりそうです。

6. 参考文献

Docs » API Reference » aws_cdk.aws_ec2 » Vpc (AWS CDK for PythonのAPIドキュメント)
20180418 AWS Black Belt Online Seminar Amazon VPC

nasuvitz
AWS Japan APN Ambassador / AWS, DevOps, Infrastructure as Code and etc. ※記事、コメントは所属企業を代表した発言ではありません。
https://www.tis.jp/service_solution/aws/
tis
創業40年超のSIerです。
https://www.tis.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした