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 回答
0
已接受的回答

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
已回答 1 个月前
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
专家
已回答 1 个月前
profile pictureAWS
专家
已审核 1 个月前
  • 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.

您未登录。 登录 发布回答。

一个好的回答可以清楚地解答问题和提供建设性反馈,并能促进提问者的职业发展。

回答问题的准则