これは何?
AWS CDKのL3コンストラクトであるaws_ecs_patternsを試してみて、RDSも必要だなーと思ってRDSを作成した際、サブネット周りのエラー内容になります。
稼働プロジェクト上でのサブネットの追加や参照の仕方についても考察したいと思います。
事前準備
CDKプロジェクト上のlib/test.ts
で、以下のようにApplicationLoadBalancedFargateServiceによってALB + ECS on Fargate環境を構築しました。
ECSクラスターをProtectedサブネット上に置き、Nat Gateway経由でインターネットにアウトバウンドします。
const vpc = new ec2.Vpc(this, `${props.resourceName}-vpc`, {
vpcName: `${props.resourceName}-vpc-01`,
ipAddresses: ec2.IpAddresses.cidr(props.vpcCidrValue),
natGateways: 1,
});
vpc.addInterfaceEndpoint(`${props.resourceName}-edpif-dkr`, { service: ec2.InterfaceVpcEndpointAwsService.ECR_DOCKER, });
vpc.addInterfaceEndpoint(`${props.resourceName}-edpif-ecr`, { service: ec2.InterfaceVpcEndpointAwsService.ECR, });
vpc.addGatewayEndpoint(`${props.resourceName}-edpgw-s3`, { service: ec2.GatewayVpcEndpointAwsService.S3, });
/* ---------- ECS Cluster ---------- */
const cluster = new ecs.Cluster(this, `${props.resourceName}-ecs-cluster`, {
vpc: vpc,
});
/* ---------- ECR ---------- */
const ecrNginxRepo = ecr.Repository.fromRepositoryName(this, `${props.resourceName}-ecr-nginx`, `${props.resourceName}/nginx`);
/* ---------- ECS Patterns ---------- */
const ecsServiceAlbFargate = new ecs_patterns.ApplicationLoadBalancedFargateService(this, `${props.resourceName}-ecspatterns`, {
cluster: cluster,
taskImageOptions: {
image: ecs.ContainerImage.fromEcrRepository(ecrNginxRepo),
},
taskSubnets: {
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
assignPublicIp: false,
deploymentController: {
type: ecs.DeploymentControllerType.ECS,
}
});
上記をcdk deploy
コマンドによって環境構築した後、RDSへの接続も検証するため、以下のようにRDSを追加しました。
サブネットタイプはPRIVATE_ISOLATED
と指定しました。
// 追加
/* ---------- RDS ---------- */
const rdsInstance = new rds.DatabaseInstance(this, `${props.resourceName}-rds-mysql`, {
vpc: vpc,
engine: rds.DatabaseInstanceEngine.MARIADB,
instanceType: new ec2.InstanceType("t3.micro"), // db.t3.micro
vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED },
storageType: rds.StorageType.GP3,
multiAz: true,
allocatedStorage: 20,
credentials: rds.Credentials.fromGeneratedSecret(
'mariadb',
{ secretName: `${props.resourceName}/mariadb/pw`, }
),
securityGroups: [sgRds],
});
しかし、cdk synth
にて、以下のようなエラーに遭遇しました。
Error: There are no 'Isolated' subnet groups in this VPC. Available types: Deprecated_Private_NAT,Private,Deprecated_Private,Public
:
:
エラーの原因
RDS上インスタンスの作成場所を SubnetType.PRIVATE_ISOLATED
として定義してPRIVATE_ISOLATEDの箇所にインスタンスを作成しようとしたが、そもそもSubnetType上でPRIVATE_ISOLATEDのサブネットが存在せず、エラーになったとのことです。
上記リンクの通り、NatGatewayを作成したことでサブネットタイプがPRIVATE_WITH_EGRESSと定義されていること & PRIVATE_ISOLATEDに該当するサブネットが環境上で存在しないことでこのようなエラーメッセージが発生しました。。。
確認方法
すでに作成してしまっている場合、VPCのPropertiesであるpublicSubnetsなどで、直接出力することもできます。
console.log(`publicSubnets = ${vpc.publicSubnets}`);
console.log(`privateSubnets = ${vpc.privateSubnets}`);
console.log(`isolatedSubnets = ${vpc.isolatedSubnets}`);
上記を追加してcdk synth
を実行することで, どのタイプのサブネットがあるのかを把握できます。
以下の実行結果は、IsolatedSubnetがない出力結果となります。
publicSubnets = TestStack/test-vpc/PublicSubnet1,TestStack/test-vpc/PublicSubnet2,TestStack/test-vpc/PublicSubnet3
privateSubnets = TestStack/test-vpc/PrivateSubnet1,TestStack/test-vpc/PrivateSubnet2,TestStack/test-vpc/PrivateSubnet3
isolatedSubnets =
また、新規でVPCを作成する場合は、subnetConfiguration を明示的に定義し、PRIVATE_WITH_EGRESS と PRIVATE_ISOLATEDの役割を持つサブネットを2つ作成するようにすればいいかと思います。
const vpc = new ec2.Vpc(this, `test-vpc`, {
vpcName: `test-vpc-01`,
ipAddresses: ec2.IpAddresses.cidr("10.0.0.0/16"),
subnetConfiguration: [
{
cidrMask: 24,
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: 'Protected',
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 24,
name: 'Private',
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
natGateways: 2,
});
既存VPC上でのサブネットの追加
上記のやり方での欠点としては、既存のVPCでsubnetConfigurationを定義しようとすると場合によってはReplaceの対象になるため、プロジェクトで稼働中のリソースでのケースでは避けたいアプローチです。
VpcコンストラクトでてっきりaddSubnetのようなメソッドがあるかと思ったらありませんでした。
仕方がないため、以下のようにPrivateSubnetコンストラクトを追記して、IsolatedSubnetを作成します。
:
const isolatedSubneta = new ec2.PrivateSubnet(this, `${props.resourceName}-subnet-isolated-a`, {
vpcId: vpc.vpcId,
availabilityZone: "ap-northeast-1a",
cidrBlock: "10.0.200.0/24",
});
const isolatedSubnetc = new ec2.PrivateSubnet(this, `${props.resourceName}-subnet-isolated-c`, {
vpcId: vpc.vpcId,
availabilityZone: "ap-northeast-1c",
cidrBlock: "10.0.210.0/24",
});
const isolatedSubnetd = new ec2.PrivateSubnet(this, `${props.resourceName}-subnet-isolated-d`, {
vpcId: vpc.vpcId,
availabilityZone: "ap-northeast-1d",
cidrBlock: "10.0.220.0/24",
});
:
:
console.log(`publicSubnets = ${vpc.publicSubnets}`);
console.log(`privateSubnets = ${vpc.privateSubnets}`);
console.log(`isolatedSubnets = ${vpc.isolatedSubnets}`);
上記をcdk deployを実行した後、console.log部分の出力結果を見ましたが以下のように、IsolatedSubnetに該当するSubnetTypeはみあたりませんでした。。(何でや。。)
publicSubnets = TestStack/test-vpc/PublicSubnet1,TestStack/test-vpc/PublicSubnet2,TestStack/test-vpc/PublicSubnet3
privateSubnets = TestStack/test-vpc/PrivateSubnet1,TestStack/test-vpc/PrivateSubnet2,TestStack/test-vpc/PrivateSubnet3
isolatedSubnets =
仕方がないため、subnetTypeで指定せず、subnetsで直接サブネットIDを参照するように変更するとうまくRDSの作成をすることができました。
// 追加
/* ---------- RDS ---------- */
const rdsInstance = new rds.DatabaseInstance(this, `${props.resourceName}-rds-mysql`, {
vpc: vpc,
engine: rds.DatabaseInstanceEngine.MARIADB,
instanceType: new ec2.InstanceType("t3.micro"), // db.t3.micro
// vpcSubnets: { subnetType: ec2.SubnetType.PRIVATE_ISOLATED }, // コメントアウト
vpcSubnets: { subnets: [isolatedSubneta, isolatedSubnetc, isolatedSubnetd] },
storageType: rds.StorageType.GP3,
multiAz: true,
allocatedStorage: 20,
credentials: rds.Credentials.fromGeneratedSecret(
'mariadb',
{ secretName: `${props.resourceName}/mariadb/pw`, }
),
securityGroups: [sgRds],
});
まとめ
列挙型便利だなーと思ったのですが今回のような追加/削除に伴ったケースだと、参照時には列挙型より直接リソースを指定した方が、今回のような事象は回避しやすいのかなとおもいました。
(時間があったら列挙型で途中で追加したサブネットに対しても、参照できるようにしてみたい。。)