AWS CDKでSubnetを作る場合のPRIVATE_WITH_EGRESSとPRIVATE_ISOLATEDを比較してみた

AWS CDKでプライベートなサブネットを作る場合、PRIVATE_WITH_EGRESSかPRIVATE_ISOLATEDを選ぶ必要があります。Cfnのテンプレート上どう違うのか確認しました。
2024.06.10

はじめに

AWS CDKでVPCとSubnetを作る際にSubnetTypeを選択する必要があります。SubnetTypeはDeprecatedなものを除くとPUBLIC/PRIVATE_WITH_EGRESS/PRIVATE_ISOLATEDの3パターンから選択します。今回はこのSubnetTypeのPRIVATE_WITH_EGRESSとPRIVATE_ISOLATEDの内容を比較して、どんな内容が展開されるのかNATなども交えて確認してみます。

よく忘れてしまう内容なので記事にしました。ふと思い出したいときに参考になれば幸いです。単純なパターンのみ試したので、これも追記したほうがよいとお気づきの方いればXまでご連絡ください

環境

ライブラリ バージョン
aws-cdk 2.145.0

PRIVATE_WITH_EGRESSとは

CDKのソースコードのコメント部分から説明を引用します。

Subnet that routes to the internet, but not vice versa.

Instances in a private subnet can connect to the Internet, but will not allow connections to be initiated from the Internet. Egress to the internet will need to be provided. NAT Gateway(s) are the default solution to providing this subnet type the ability to route Internet traffic. If a NAT Gateway is not required or desired, set natGateways:0 or use SubnetType.PRIVATE_ISOLATED instead. (省略)

packages/aws-cdk-lib/aws-ec2/lib/vpc.ts

翻訳

インターネットへルーティングするサブネットですが、その逆はできません。

プライベートサブネット内のインスタンスはインターネットに接続できますが、インターネットから接続を開始することはできません。インターネットへの送信トラフィックを提供する必要があります。 NATゲートウェイは、このサブネットタイプにインターネットトラフィックをルーティングする機能を提供するためのデフォルトのソリューションです。 NATゲートウェイが不要な場合や望ましくない場合は、natGateways:0を設定するか、代わりにSubnetType.PRIVATE_ISOLATEDを使用します。 (省略)

上記の通り、インターネットへEgress通信する場合のみ許可。外部からの接続を許可しない場合のみ使用するSubnetTypeです。以下の公式ドキュメントにあるPrivate Subnetを指すタイプになります。

PRIVATE_ISOLATEDとは

こちらもCDKのソースコードのコメント部分から説明を引用します。

Isolated Subnets do not route traffic to the Internet (in this VPC), and as such, do not require NAT gateways.

Isolated subnets can only connect to or be connected to from other instances in the same VPC. A default VPC configuration will not include isolated subnets.

This can be good for subnets with RDS or Elasticache instances, or which route Internet traffic through a peer VPC.

packages/aws-cdk-lib/aws-ec2/lib/vpc.ts

翻訳

隔離されたサブネットは、(このVPC内で)インターネットへのトラフィックをルーティングしないため、NATゲートウェイを必要としません。

隔離されたサブネットは、同じVPC内の他のインスタンスとのみ接続できます。デフォルトのVPC構成には、隔離されたサブネットは含まれません。

これは、RDSやElasticacheインスタンスを持つサブネット、またはピアVPCを介してインターネットトラフィックをルーティングするサブネットに適しています。

上記ではVPC内の他のインスタンスとのみ接続するSubnetTypeと書かれています。以下の公式ドキュメントにあるIsolated Subnetを指すタイプになります。

ただ厳密にはVPC Endpointを設定することでAWSサービスと接続できたり、ルートテーブルとNAT Gatewayを後付できるので、あくまでVPCのConstruct単体の場合はEgress通信を提供しないよう設計されているものと思った方が良いです。

生成されるテンプレートを比較

本章では、実際にCDKのコードでCloudFormationのテンプレートを生成して、どのような記述になるのか比較してみます。

PRIVATE_WITH_EGRESSのコード

以下が試す際のコードです。今回はNAT Instnceを作成してルーティングがどうなるのか確認します。

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

    // NAT Instanceの設定
    const natProvider = NatProvider.instanceV2({
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.NANO),
      machineImage: ec2.MachineImage.latestAmazonLinux2023({
        cpuType: ec2.AmazonLinuxCpuType.ARM_64,
      }),
      defaultAllowedTraffic: ec2.NatTrafficDirection.OUTBOUND_ONLY,
    });

    const myVpc = new ec2.Vpc(this, `${id}-Vpc`, {
      ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
      maxAzs: 2,
      natGateways: 1,
      natGatewayProvider: natProvider,
      flowLogs: {},
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: "Public",
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: "Private",
          subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
        }
      ],
    });
    natProvider.securityGroup.addIngressRule(Peer.ipv4(myVpc.vpcCidrBlock), ec2.Port.allTraffic());
  }
}

cdk synthコマンドを実行してテンプレートを確認すると以下のようにルートテーブルが生成されます。しっかり、プライベートサブネットからNAT Instanceに向けてルートテーブルのルートが作成されます。PrivateSubnet1のみ記載していますが、PrivateSubnet2も同様のルートが設定されます。

  "CdkVpcTestStackVpcPrivateSubnet1DefaultRouteF0CFE160": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "InstanceId": {
     "Ref": "CdkVpcTestStackVpcPublicSubnet1NatInstance3FFB4401"
    },
    "RouteTableId": {
     "Ref": "CdkVpcTestStackVpcPrivateSubnet1RouteTable6AB66FC1"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkVpcTestStack/CdkVpcTestStack-Vpc/PrivateSubnet1/DefaultRoute"
   }
  },

NAT Gatewayを使う場合は、以下のようにルートテーブルにNAT Gatewayへの経路が追加されます。

  "CdkVpcTestStackVpcPrivateSubnet1DefaultRouteF0CFE160": {
   "Type": "AWS::EC2::Route",
   "Properties": {
    "DestinationCidrBlock": "0.0.0.0/0",
    "NatGatewayId": {
     "Ref": "CdkVpcTestStackVpcPublicSubnet1NATGateway9EA63F72"
    },
    "RouteTableId": {
     "Ref": "CdkVpcTestStackVpcPrivateSubnet1RouteTable6AB66FC1"
    }
   },
   "Metadata": {
    "aws:cdk:path": "CdkVpcTestStack/CdkVpcTestStack-Vpc/PrivateSubnet1/DefaultRoute"
   }
  },

上記から、以下の図のようなルーティングになっていることが確認できます。(インターネットゲートウェイなどは省略しています)

PRIVATE_ISOLATEDのコード

今度はPRIVATE_ISOLATEDのコードです。こちらもNAT Instnceを作成してルーティングがどうなるのか確認します。

※注意:差分をわかりやすくするため、どちらもサブネット名をPrivateとしていますが、併用する場合が多いと思うので本来は別名を付けたほうが良いです

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

    const natProvider = NatProvider.instanceV2({
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T4G, ec2.InstanceSize.NANO),
      machineImage: ec2.MachineImage.latestAmazonLinux2023({
        cpuType: ec2.AmazonLinuxCpuType.ARM_64,
      }),
      defaultAllowedTraffic: ec2.NatTrafficDirection.OUTBOUND_ONLY,
    });

    const myVpc = new ec2.Vpc(this, `${id}-Vpc`, {
      ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
      maxAzs: 2,
      natGateways: 1,
      natGatewayProvider: natProvider,
      flowLogs: {},
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: "Public",
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: "Private",
          subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
        },
      ],
    });
    natProvider.securityGroup.addIngressRule(Peer.ipv4(myVpc.vpcCidrBlock), ec2.Port.allTraffic());
  }
}

実行してPRIVATE_WITH_EGRESSとPRIVATE_ISOLATED、それぞれで生成されたCfnテンプレートを比較してみます。左がPRIVATE_WITH_EGRESSで右がPRIVATE_ISOLATEDです。

上記差分から分かるようにプライベートサブネットのルートテーブルの部分だけ違います。NAT Instanceを指定している場合でもルートテーブルにルートは作られません。図にすると以下のようになります。

結論

PRIVATE_WITH_EGRESSは、NAT GatewayやNAT Instance経由でインターネットに出るルートテーブルのルートが作成される。

PRIVATE_ISOLATEDは、インターネットに出るルートテーブルのルートが作成されない。

所感

CDKでVPCを構築する際に度々忘れて確認していたので図と記事にしました。どなたかの役に立てば幸いです。

製造ビジネステクノロジー部アーキテクトチームの佐藤智樹でした。