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
CidrBlock
An IPv4 CIDR block to associate with the VPC.
Required: No
Type: String
Update requires: Replacement
"Update requires"が "Replacement"、つまり「置換」と記載されています。
「置換」の定義はこちらに記載があります。
置換
AWS CloudFormation は更新の際にリソースを再作成し、新しい物理 ID も生成されます。
「置換」…この言葉が気になりますね。もし、VPCが再作成される場合には紐づくリソースにも影響があり一大事なので念には念を入れて検証してみます。
開発・実行環境
- プログラミング言語: TypeScript (version: 4.7.4)
- CDK: version: 2.37.0
- OS: Ubuntu 20.04.4 LTS
環境準備: 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を混在できないことに注意が必要です。http://www.faqs.org/rfcs/rfc1918.html
再チャレンジ
そのため、同じクラス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などで構築経験が多数ございます。なにかございましたら、ぜひ、お声がけいただけますと幸いです。
執筆: 伊藤 好宏, 技官

