AWS CDKでVPCにCIDRブロックを追加してみた

はじめに

とあるAWS環境にてCDKで管理しているAWS VPC内でIPアドレスの枯渇が懸念されたため、VPCにCIDRブロックを追加することになりました。

マネージメントコンソールで実施した経験はあるものの、CDKでの追加はなかなか機会はないものですね。特にCIDRを追加する際にVPCへの影響が非常に気になりましたので、検証することにしました。

せっかくですので検証した結果を当該ブログ記事で共有いたします。

VPCへの影響?

もし、CIDRが追加された場合にVPC IDも変わってしまうとそれに依存する数々のサービスも変更する必要があり非常に面倒なことになります。マネージメントコンソール上で手動で操作する限りは問題ないのは分かっていたのですが、CDKの挙動も念のため確認しておきます。

CDKは最終的にはCloudFormationテンプレートを生成し、最終的にはCloudFormationに依存するためCfnのドキュメントを読んでみました。さて、今回のデプロイでリソースが中断されるかを確認したところ以下のようにCIDRに対して"Replacement"の記述がありました。

Replacement

docs.aws.amazon.com

"Update requires"が "Replacement"、つまり「置換」と記載されています。 「置換」の定義はこちらに記載があります。

置換 AWS CloudFormation は更新の際にリソースを再作成し、新しい物理 ID も生成されます。

docs.aws.amazon.com

「置換」…この言葉が気になりますね。もし、VPCが再作成される場合には紐づくリソースにも影響があり一大事なので念には念を入れて検証してみます。

開発・実行環境

環境準備: VPCにCIDRを1つだけ指定してデプロイ

まずは、普通にVPCを作成し、CIDR 10.0.0.0/16を割り当てます。 その中から10.0.1.0/24と10.0.2.0/24を切り出して、2つのサブネットに割り当てます。(だいぶネットワークアドレスを余らせてもったいないですね)

CDKで記述するとこのような記述になります。個人的な意見ですがCDKのL2コンストラクトを活用したサブネットの生成方法がありますが、ほどよくカスタマイズするためにはこの方法がよいと思います。

import { aws_ec2 as ec2, Stack, StackProps } from "aws-cdk-lib";
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';

export class CidrStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    //
    // VPCの作成
    //

    const vpcCidrTest = new ec2.Vpc(this, "vpcCidrTest", {
      cidr: "10.0.0.0/16",
      maxAzs: 2,
      vpcName: "vpcCidrTest",
      subnetConfiguration: [], 
    });

    //
    // Private Subnetを各AZに作成
    //

    const privateSubnet1a = new ec2.PrivateSubnet(this, "privateSubnet1a", {
      vpcId: vpcCidrTest.vpcId,
      availabilityZone: "ap-northeast-1a",
      cidrBlock: "10.0.1.0/24",
    });

    const privateSubnet1c = new ec2.PrivateSubnet(this, "privateSubnet1c", {
      vpcId: vpcCidrTest.vpcId,
      availabilityZone: "ap-northeast-1c",
      cidrBlock: "10.0.2.0/24",
    });
  }
}

cdk diff してみると追加・変更されるリソース一覧が表示されます。見てみると・・・

Resources
[+] AWS::EC2::VPC vpcCidrTest vpcCidrTest05F8E38C
[+] AWS::EC2::Subnet privateSubnet1a/Subnet privateSubnet1aSubnet37CE2989
[+] AWS::EC2::RouteTable privateSubnet1a/RouteTable privateSubnet1aRouteTable8F0BB848 ★
[+] AWS::EC2::SubnetRouteTableAssociation privateSubnet1a/RouteTableAssociation  privateSubnet1aRouteTableAssociation7D8BF919 ★
[+] AWS::EC2::Subnet privateSubnet1c/Subnet privateSubnet1cSubnetB2D972E2
[+] AWS::EC2::RouteTable privateSubnet1c/RouteTable privateSubnet1cRouteTableA8A10559 ★
[+] AWS::EC2::SubnetRouteTableAssociation privateSubnet1c/RouteTableAssociation privateSubnet1cRouteTableAssociationEFFFAA5F ★

VPC, Subnetなどが並んでいますね。★の部分はCDK内のコードには直接記載していないものですが、CDKから生成されるCfnテンプレートをみてみたら、VPC内のサブネット間でアクセスするのに必要なルートテーブルも自動的に作成され、サブネットにアタッチされるコードになっていました。自動的に補完してくれるなんてCDKは便利ですね。

CIDR-result

cidr-subnet-result

いよいよCIDRを追加してみる

さて、それでは既存のCIDR(10.0.0.0/16)に192.168.0.0/16のCIDRを追加してみましょう。以下のコードを先ほどのコードの後方部分に追記しました。

    //
    // CIDRを作成したVPCに追加
    // 

    const AdditionalVpcCidrBlock = new ec2.CfnVPCCidrBlock(this, 'AdditionalVpcCidrBlock', {
      vpcId: vpcCidrTest.vpcId,
      cidrBlock: "192.168.0.0/16"
    })

    const privateNewCidrSubnet1a = new ec2.PrivateSubnet(this, "privateNewCidrSubnet1a", {
      vpcId: vpcCidrTest.vpcId,
      availabilityZone: "ap-northeast-1a",
      cidrBlock: "192.168.1.0/24",
    });

    const privateNewCidrSubnet1c = new ec2.PrivateSubnet(this, "privateNewCidrSubnet1c", {
      vpcId: vpcCidrTest.vpcId,
      availabilityZone: "ap-northeast-1c",
      cidrBlock: "192.168.2.0/24",
    });
  }

あれ? エラー発生!!

そして、いよいよcdk deploy です!! が・・・!?

✨  Synthesis time: 5.11s

CidrStack: deploying...
[0%] start: Publishing dcb089307c9305a7fb8488527a0eb84693220088944997f6e60948cdfaa44e58:current_account-current_region
[100%] success: Published dcb089307c9305a7fb8488527a0eb84693220088944997f6e60948cdfaa44e58:current_account-current_region
CidrStack: creating CloudFormation changeset...
[███████████████████████▏··································] (4/10)

7:28:56 PM | CREATE_FAILED        | AWS::EC2::VPCCidrBlock                | AdditionalVpcCidrBlock
The CIDR '192.168.0.0/16' is restricted. Use a CIDR from the same private address range as the current VPC CIDR, or use a publicly-routable CIDR. For add
itional restrictions, see https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html#VPC_Sizing (Service: AmazonEC2; Status Code: 400; Error
Code: InvalidVpc.Range; Request ID: 3e46512c-a4a6-4a71-af11-f63e5a553c02; Proxy: null)


 ❌  CidrStack failed: Error: The stack named CidrStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: The CIDR '192.168.0.0/16' is restricted. Use a CIDR from the same private address range as the current VPC CIDR, or use a publicly-routable CIDR. For additional restrictions, see https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html#VPC_Sizing (Service: AmazonEC2; Status Code: 400; Error Code: InvalidVpc.Range; Request ID: 3e46512c-a4a6-4a71-af11-f63e5a553c02; Proxy: null)
    at prepareAndExecuteChangeSet (/usr/local/lib/node_modules/aws-cdk/lib/api/deploy-stack.ts:386:13)
    at runMicrotasks (<anonymous>)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)
    at CdkToolkit.deploy (/usr/local/lib/node_modules/aws-cdk/lib/cdk-toolkit.ts:219:24)
    at initCommandLine (/usr/local/lib/node_modules/aws-cdk/lib/cli.ts:347:12)

The stack named CidrStack failed to deploy: UPDATE_ROLLBACK_COMPLETE: The CIDR '192.168.0.0/16' is restricted. Use a CIDR from the same private address range as the current VPC CIDR, or use a publicly-routable CIDR. For additional restrictions, see https://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Subnets.html#VPC_Sizing (Service: AmazonEC2; Status Code: 400; Error Code: InvalidVpc.Range; Request ID: 3e46512c-a4a6-4a71-af11-f63e5a553c02; Proxy: null)

デプロイしたところ、画面キャプチャのようにかなり真っ赤な文字でエラーが出力されました。画面キャプチャはこちら↓

cdk deploy error

今回の問題は、元々の10.x.x.xのクラスAのCIDRに192.168.x.xのクラスCを追加したために発生しています。

VPCのCIDRとして指定可能なアドレスであっても既存のCIDRとは異なるクラスのCIDRを混在できないことに注意が必要です。www.faqs.org

再チャレンジ

そのため、同じクラスAのCIDR"10.1.0.0/16"を追加することで、問題なくデプロイが可能です。 コードは以下のようになります。

    //
    // CIDRを作成したVPCに追加
    // 

    const AdditionalVpcCidrBlock = new ec2.CfnVPCCidrBlock(this, 'AdditionalVpcCidrBlock', {
      vpcId: vpcCidrTest.vpcId,
      cidrBlock: "10.1.0.0/16"
    })

    //
    // Private Subnetを各AZに作成し、新規追加したCIDRを割り当てる
    //今回はおとなしく、同じネットワーククラス内からを指定

    const privateNewCidrSubnet1a = new ec2.PrivateSubnet(this, "privateNewCidrSubnet1a", {
      vpcId: vpcCidrTest.vpcId,
      availabilityZone: "ap-northeast-1a",
      cidrBlock: "10.1.1.0/24",
    });

    const privateNewCidrSubnet1c = new ec2.PrivateSubnet(this, "privateNewCidrSubnet1c", {
      vpcId: vpcCidrTest.vpcId,
      availabilityZone: "ap-northeast-1c",
      cidrBlock: "10.1.2.0/24",
    });
  }

デプロイ結果

デプロイした結果、すんなりと新規CIDRとサブネットが追加されました。

NewCIDR

Subnet

なお、結果としてCIDRを追加する際にはVPC IDは変更されませんでした。その他にも置き換えられるようものはありませんでした。CIDR追加時には、特に別のリソースを気にしなくてよさそうです。

追加したCIDRを変更するしてみると!?

追加したCIDRのIP(10.1.0.0/16)を10.2.0.0/16に変更してみました。

    //
    // CIDRを作成したVPCに追加
    // 

    const AdditionalVpcCidrBlock = new ec2.CfnVPCCidrBlock(this, 'AdditionalVpcCidrBlock', {
      vpcId: vpcCidrTest.vpcId,
      cidrBlock: "10.2.0.0/16"
    })


    //
    // Private Subnetを各AZに作成し、新規追加したCIDRを割り当てる
    //

    const privateNewCidrSubnet1a = new ec2.PrivateSubnet(this, "privateNewCidrSubnet1a", {
      vpcId: vpcCidrTest.vpcId,
      availabilityZone: "ap-northeast-1a",
      cidrBlock: "10.2.1.0/24",
    });

    const privateNewCidrSubnet1c = new ec2.PrivateSubnet(this, "privateNewCidrSubnet1c", {
      vpcId: vpcCidrTest.vpcId,
      availabilityZone: "ap-northeast-1c",
      cidrBlock: "10.2.2.0/24",
    });
  }

cdk diffは、以下の通りです。"Replacement"表記が出ていますね。こちらが置き換えられるリソースです。

デプロイ後に結果を確認したところ、以下のことが分かりました。

  • VPC自体は変更されないので、VPC IDの変更もなし
  • VPCのCIDRが記載されている「リストの中身が置換」される。挙動として10.2.0.0がCIDRのリストに先に追加され、CIDRが3つになる。最後に10.1.0.0が削除され、最終的に想定通り2つのCIDRになる
  • Subnetも新しいものが追加された後に、古いものが削除される(置換)

上記のようにCDKで追加したCIDRの変更は可能ですが、いずれにしても紐づくSubnetも内部的に再作成されるため、動作しているEC2, エンドポイント含め様々な考慮が必要です。また、CIDRを変更する場合に先に新しいCIDRがリストに追加されるため、CloudFormationの置き換え時にCIDR数の上限を超えて失敗する場合がありますので注意が必要です。

まとめ

今回は、CDKでCIDRブロックを追加してみました。CDKで操作する時には、Cfnのドキュメントも読み込んでおいた方が安全です。今回のようにドキュメント上では "Replacement" とありましたが、具体的に「何が」置き換えられるのかイメージしにくいものもありますね。

その場合は、百聞は一見に如かずで、CDKの再利用性を生かしてコードでさらっと検証してみましょう。

JTPでは、AWS環境をCDK, Terraform, CloudFormationなどで構築経験が多数ございます。なにかございましたら、ぜひ、お声がけいただけますと幸いです。

  執筆: 伊藤 好宏, 技官