AWS CDK: Create a Subnet and Launch Instance in it With Existing VPC

0

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!

2 Answers
0
Accepted Answer

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.

Does not work:

# app.py

vpc = ec2.Vpc.from_lookup(self, "IVpc", region="us-east-1")

# ...[create public subnet ref CfnVpc]

# ...[create instance in public subnet] 

Error Logs

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 })

Works:

# 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]
jcaws
answered 19 days ago
0

It's all about dependencies... You have an issue with the timing of resource creation when using AWS CDK. AWS CDK provisions resources in the order they are defined within the construct. However, when you use ec2.Vpc.from_lookup() to obtain a VPC, CDK might not wait for the lookup to complete before moving on to create other resources, resulting in the failure to find the public subnet in availability zone us-east-1b.

Try to use core.Construct to encapsulate the lookup logic and create a custom resource that waits for the lookup to finish.

profile picture
EXPERT
answered 21 days ago
profile pictureAWS
EXPERT
reviewed 21 days ago
  • Hmm... I'm trying to understand this. So if I had something like this?

    app = cdk.App()
    env = cdk.Environment(region="us-east-1", account="289298103482")
    subnet = MySubnet(app, "cfst-1180-subnet-9a638f661a5137e356f02ee9c2db058c", env=env)
    resources = MyResources(app, "cfst-1180-9a638f661a5137e356f02ee9c2db058c", env=env)
    subnet.node.add_dependency(resources)

    In my mind this should work - create the subnet first, then launch the instance into it. But I'm still getting the same RuntimeError: To set 'associatePublicIpAddress: true' you must select Public subnets (vpcSubnets: { subnetType: SubnetType.PUBLIC }) error.

You are not logged in. Log in to post an answer.

A good answer clearly answers the question and provides constructive feedback and encourages professional growth in the question asker.

Guidelines for Answering Questions