I have a fairly simple task that I am unable to complete with AWS CDK: Create a subnet and launch an instance in it using an existing VPC.
The existing VPC is a CfnVpc that I obtained using the cdk migrate
command. Here it is:
class Lab(Stack):
@property
def vpc(self) -> ec2.CfnVPC:
return self._vpc
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(
scope, construct_id, description="Nat instance with two subnets", **kwargs
)
self._vpc = ec2.CfnVPC(
self,
"VPC",
enable_dns_support=True,
enable_dns_hostnames=True,
cidr_block=subnetConfig["VPC"]["CIDR"],
tags=[
{
"key": "Application",
"value": self.stack_name,
},
{
"key": "Network",
"value": "PublicA",
},
],
)
Here are my new resources:
class MyResources(Lab):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
vpc = ec2.Vpc.from_lookup(
self, "L2VPC", tags={"aws:cloudformation:logical-id": "VPC"}
)
publicSubnetB = ec2.Subnet(
self,
"PublicSubnetB",
availability_zone="us-east-1b",
cidr_block="10.0.2.0/24",
vpc_id=vpc.vpc_id,
)
cdk.Tags.of(publicSubnetB).add("Application", self.stack_name)
cdk.Tags.of(publicSubnetB).add("Network", "PublicB")
# These are also exposed from the cdk migrate command via properties.
ec2.CfnSubnetRouteTableAssociation(
self,
"PublicSubnetBRouteTableAssociation",
subnet_id=publicSubnetB.subnet_id,
route_table_id=self.publicRouteTable.ref,
)
ec2.CfnSubnetNetworkAclAssociation(
self,
"PublicSubnetBNetworkAclAssociation",
subnet_id=publicSubnetB.subnet_id,
network_acl_id=self.publicNetworkAcl.ref,
)
And finally, here's the failing instance:
instanceB = ec2.Instance(
self,
"InstanceB",
instance_type=ec2.InstanceType.of(
instance_class=ec2.InstanceClass.BURSTABLE3,
instance_size=ec2.InstanceSize.MICRO,
),
machine_image=ec2.AmazonLinuxImage(
generation=ec2.AmazonLinuxGeneration.AMAZON_LINUX_2
),
vpc=vpc,
vpc_subnets=ec2.SubnetSelection(
subnet_type=ec2.SubnetType.PUBLIC, availability_zones=["us-east-1b"]
),
user_data=ec2.UserData.custom(userDataB),
private_ip_address="10.0.2.119",
associate_public_ip_address=True,
security_group=sg,
user_data_causes_replacement=True,
)
AWS CDK says that there is no public subnet in az us-east-1b - even though I created it just prior. The only way this works is if I comment out the instance code, run it, clear the context, run it again, and comment back in the instance code. There has got to be a better way!
The solution was very tricky and not well documented in my opinion.
Since I was creating the subnet in the same stack as I used to look up the VPC, I had to use the ec2.Vpc.from_vpc_attributes()
class method instead of the ec2.Vpc.from_lookup()
class method.
# app.py
vpc = ec2.Vpc.from_lookup(self, "IVpc", region="us-east-1")
# ...[create public subnet ref CfnVpc]
# ...[create instance in public subnet]
jsii.errors.JavaScriptError:
Error: To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.PUBLIC })
at new Instance (/tmp/jsii-kernel-wHeWcm/node_modules/aws-cdk-lib/aws-ec2/lib/instance.js:1:5169)
at Kernel._Kernel_create (/tmp/tmpxiyze867/lib/program.js:10108:25)
at Kernel.create (/tmp/tmpxiyze867/lib/program.js:9779:93)
at KernelHost.processRequest (/tmp/tmpxiyze867/lib/program.js:11696:36)
at KernelHost.run (/tmp/tmpxiyze867/lib/program.js:11656:22)
at Immediate._onImmediate (/tmp/tmpxiyze867/lib/program.js:11657:46)
at process.processImmediate (node:internal/timers:478:21)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/ubuntu/acg_nlb_lab/infra/app.py", line 80, in <module>
MyInfra(app, STACK, env=env)
File "/home/ubuntu/.pyenv/versions/cdk-3.10/lib/python3.10/site-packages/jsii/_runtime.py", line 118, in __call__
inst = super(JSIIMeta, cast(JSIIMeta, cls)).__call__(*args, **kwargs)
File "/home/ubuntu/acg_nlb_lab/infra/app.py", line 55, in __init__
instanceB = ec2.Instance(
File "/home/ubuntu/.pyenv/versions/cdk-3.10/lib/python3.10/site-packages/jsii/_runtime.py", line 118, in __call__
inst = super(JSIIMeta, cast(JSIIMeta, cls)).__call__(*args, **kwargs)
File "/home/ubuntu/.pyenv/versions/cdk-3.10/lib/python3.10/site-packages/aws_cdk/aws_ec2/__init__.py", line 71951, in __init__
jsii.create(self.__class__, self, [scope, id, props])
File "/home/ubuntu/.pyenv/versions/cdk-3.10/lib/python3.10/site-packages/jsii/_kernel/__init__.py", line 334, in create
response = self.provider.create(
File "/home/ubuntu/.pyenv/versions/cdk-3.10/lib/python3.10/site-packages/jsii/_kernel/providers/process.py", line 365, in create
return self._process.send(request, CreateResponse)
File "/home/ubuntu/.pyenv/versions/cdk-3.10/lib/python3.10/site-packages/jsii/_kernel/providers/process.py", line 342, in send
raise RuntimeError(resp.error) from JavaScriptError(resp.stack)
RuntimeError: To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.PUBLIC })
# app.py
# ...[create public subnet ref CfnVpc]
# ...[create instance in public subnet]
vpc = ec2.Vpc.from_vpc_attributes(
self,
"IVpc",
vpc_id=self.vpc.ref,
availability_zones=["us-east-1a", "us-east-1b"],
public_subnet_ids=[self.publicSubnet.ref, publicSubnetB.subnet_id],
public_subnet_route_table_ids=[
self.publicRouteTable.ref,
self.publicRouteTable.ref,
],
)
# ...[able to use L2 IVpc and L2 instance]