/ #VPC #CDK 

【CDK/Python】CDKでSubnetのCidrBlockを指定する2つの方法

もくじ

CDKでVPCを作ってかつ、SubnetのCIDRは指定した値にする!ということをやりたかったのですが、 想像以上にハマったので記事にしました。

何がしたいの?

CDKでVPCを作って、さらにSubnetも作ってそのCIDRを任意の値に設定したい!

何も考えずにCDKでVPCを作ると勝手にSubnetが作られる、、、このときCidrBlockが自動で発番されてしまって任意の値に設定できない!

CIDRがどうでもいいよ!というユースケースもあるので、その場合はどうでもいいんですけれどもね(笑)

対策

まずCIDRを指定する方法ですが、2つありました。

1つ目がSubnetをVPCコンストラクトに自動発番させて、cfnの値を上書きする方法です。

もう1つがVPCとSubnetを個別で作る方法です。

方法1. SubnetはVPCコンストラクトに自動発番させて、cfnの値を上書きする

具体的なコードは下記の通りです。

import pathlib

from aws_cdk import Stack, aws_ec2, aws_lambda
from constructs import Construct

VPC_CIDR = "10.11.0.0/16"
PRIVATE_SUBNET_1_CIDR = "10.11.10.0/24"
PRIVATE_SUBNET_2_CIDR = "10.11.20.0/24"


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

        # VPC
        vpc = aws_ec2.Vpc(
            self,
            "vpc1",
            cidr=VPC_CIDR,
            max_azs=2,
            subnet_configuration=[
                aws_ec2.SubnetConfiguration(
                    name="Private",
                    cidr_mask=24,
                    subnet_type=aws_ec2.SubnetType.PRIVATE_ISOLATED,
                )
            ],
        )
        # SubnetのCidrBlockを上書きする
        selection = vpc.select_subnets(subnet_type=aws_ec2.SubnetType.PRIVATE_ISOLATED)
        for cider, subnet in zip(
            [PRIVATE_SUBNET_1_CIDR, PRIVATE_SUBNET_2_CIDR], selection.subnets
        ):
            cfn_subnet: aws_ec2.CfnSubnet = subnet.node.default_child
            cfn_subnet.cidr_block = cider

        # (おまけ)VPCにLambdaを乗せる場合の設定例

        aws_lambda.Function(
            self,
            "function1",
            code=aws_lambda.Code.from_asset(
                str(pathlib.Path(__file__).resolve().parent.joinpath("runtime"))
            ),
            runtime=aws_lambda.Runtime.PYTHON_3_9,
            handler="index.handler",
            vpc=vpc,
            vpc_subnets=aws_ec2.SubnetSelection(
                subnet_type=aws_ec2.SubnetType.PRIVATE_ISOLATED
            ),
            security_groups=[aws_ec2.SecurityGroup(self, "SG1", vpc=vpc)],
        )

ポイントはcfn側のCidrBlockを上書きしていることと、 max_azs=2でSubnetの数を制御してること。

不安なところは、CDKにSubnetをほぼ自動で作らせて、作られるSubnetの数を予想してCIDRを割り当てているので、 何かの拍子で意図しないSubnetが作られちゃったりした場、for分の箇所が成立しなくなってCIDRがおかしくなる、、、的な可能性が0ではないので若干不安です。。


(2022/04/1) 不安のもとが、つまりselect_subnetsで色々取れちゃう、、ということがコアであることに気が付きました。そしてその原因がsubnet_typeを指定して取得しているからだったので、任意でSubnetに名前をつけて、その名前で取得すれば不安にならない、、ということに気が付きました!

つまりこんな感じ↓

...
SUBNETS_NAME = "HogePrivate"

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

        # VPC
        vpc = aws_ec2.Vpc(
            ...
            subnet_configuration=[
                aws_ec2.SubnetConfiguration(
                    ...
                    name=SUBNETS_NAME,
                )
            ],
        )

        # SubnetのCidrBlockを上書きする
        selection = vpc.select_subnets(
            subnet_group_name=SUBNETS_NAME,
        )
        for cider, subnet in zip( [PRIVATE_SUBNET_1_CIDR, PRIVATE_SUBNET_2_CIDR], selection.subnets):
            ...

※ githubのコードに反映済みなので、詳しく見たい方はリポジトリ参照ください!


方法2. VPCとSubnetをバラで作る

こちらも体的なコードは下記の通りです。

import pathlib

from aws_cdk import Stack, aws_ec2, aws_lambda
from constructs import Construct

VPC_CIDR = "10.22.0.0/16"
PRIVATE_SUBNET_1_CIDR = "10.22.10.0/24"
PRIVATE_SUBNET_2_CIDR = "10.22.20.0/24"
PRIVATE_SUBNET_1_AVAILABILITY_ZONE = "ap-northeast-1a"
PRIVATE_SUBNET_2_AVAILABILITY_ZONE = "ap-northeast-1c"


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

        # VPC
        vpc = aws_ec2.Vpc(
            self,
            "vpc2",
            cidr=VPC_CIDR,
            subnet_configuration=[],  # 明示的に空にしないとSubnetが自動で作られる
        )

        # Subnet
        vpc_subnets = [
            aws_ec2.Subnet(
                self,
                "Private1",
                availability_zone=PRIVATE_SUBNET_1_AVAILABILITY_ZONE,
                cidr_block=PRIVATE_SUBNET_1_CIDR,
                vpc_id=vpc.vpc_id,
            ),
            aws_ec2.Subnet(
                self,
                "Private2",
                availability_zone=PRIVATE_SUBNET_2_AVAILABILITY_ZONE,
                cidr_block=PRIVATE_SUBNET_2_CIDR,
                vpc_id=vpc.vpc_id,
            ),
        ]

        # (おまけ)VPCにLambdaを乗せる場合の設定例

        aws_lambda.Function(
            self,
            "function2",
            code=aws_lambda.Code.from_asset(
                str(pathlib.Path(__file__).resolve().parent.joinpath("runtime"))
            ),
            runtime=aws_lambda.Runtime.PYTHON_3_9,
            handler="index.handler",
            vpc=vpc,
            vpc_subnets=aws_ec2.SubnetSelection(
                subnets=vpc_subnets,
            ),
            security_groups=[aws_ec2.SecurityGroup(self, "SG2", vpc=vpc)],
        )

ポイント、、、というか注意事項はAZがハードコーディングなので、別のリージョンにデプロイできません!(このコードは東京リージョンでしか使えない)

ただそれ以外は普通に1つ1つ作っているので、何ができるかわかりやすいです。

まとめ

CDKでCIDRを任意の値で作りたい場合は、2つの方法がありました。

cfnの値を上書きする方法はリージョンに関わらず動作するがSubnetがどう作られるか不透明で不安。

VPCとSubnetをバラで作る方法はSubnetを細かく設定できるので安心できるが、AZをリージョン毎に手動で変える必要がある。

感想

使い分けですが、個人的には方法2だけを使っていきたいなと思いました、、、方法1は実環境で使う勇気がないです。。。

(2022/04/01)どっちも若干気持ち悪いのですが、方法1の方が良いかなと思います。理由は方法1の方はSelectedSubnets型でSubnetを取得できるのですが、他のCDKコンストラクタで使い回す場合に、CfnSubnetで持っているよりもSelectedSubnetsで持っていた方が使い勝手よいことが多いからです。

余談

cdk-diaを使って今回の2つの方法を図にしてみました!

Stack1が方法1で、Stack2が方法2です!

結構違いますね。そしてやっぱり方法1はSubnetがいくつ作られるか分かりづらくて不安、、、

今回のリポジトリはこちら

https://github.com/sisi100/cdk_network

Author

Sisii

インフラが好きなエンジニアぶってるなにか