Patrick
Patrick

Reputation: 303

AWS CDK Python - SubnetSelection and ISubnet objects

Background

I am attempting to create an EKS Cluster with the Cluster object in Python using the AWS CDK.

I have a Stack that constructs networking objects such as VPCs and Subnets. That Stack is defining three "types" of subnets:

  1. A control subnet group - contains EKS ENIs
  2. A worker subnet group - contains Worker node groups
  3. A public subnet group - uses public route tables and will be responsible for ALBs, etc.

The code defining that information is below. This is coming from my Networking Stack:

# Define the number of subnets to create in a for loop later on. This will be a shared value between the worker, control, and public subnets. 
       subnet_count = range(1,3)

       # Create empty lists for each of our subnet types. These will hold our SubnetConfigurations that are passed to VPC creation
       self.public_subnets = []
       self.worker_subnets = []
       self.control_subnets = []


       # Loop through our defined range above, creating the appropriate control, worker, and public subnets, aligning to CIDRs above
       for x in subnet_count:
           x = str(x)
           self.control_subnets.append(ec2.SubnetConfiguration(
               name = 'Control-0{}'.format(x),
               cidr_mask=28,
               subnet_type = ec2.SubnetType.PRIVATE_WITH_NAT,
               reserved = False
           ))
           self.worker_subnets.append(ec2.SubnetConfiguration(
               name = 'Worker-0{}'.format(x),
               cidr_mask=24,
               subnet_type = ec2.SubnetType.PRIVATE_WITH_NAT,
               reserved = False
           ))
           self.public_subnets.append(ec2.SubnetConfiguration(
               name = 'Public-0{}'.format(x),
               cidr_mask=27,
               map_public_ip_on_launch=True,
               subnet_type = ec2.SubnetType.PUBLIC,
               reserved = False
           ))

and then I create a VPC for use with EKS, by unpacking those SubnetConfiguration lists:

       self.kubernetes_vpc = ec2.Vpc(self, 
       "Kubernetes", 
       cidr=my_cidr,
       default_instance_tenancy=ec2.DefaultInstanceTenancy.DEFAULT,
       enable_dns_hostnames=True,
       enable_dns_support=True,
       flow_logs=None,
       gateway_endpoints=None,
       max_azs=2,
       nat_gateway_provider=ec2.NatProvider.gateway(),
       nat_gateways=1, # this is 1 PER AZ
       subnet_configuration=[*self.public_subnets,*self.control_subnets,*self.worker_subnets],
       vpc_name="Kubernetes",
       vpn_connections=None
       )

I pass this stack to the EKS Cluster Stack, referenced as my_network_stack

So what I'm trying to do now is specifically call out, using subnet_group_name parameter of ec2.SubnetSelection the names of the Control Subnets that are created in the other Stack, and hand those over to the Cluster's vpc_subnets parameter in the EKS Stack.

        self.control_subnets = []

        for subnet_config in my_network_stack.control_subnets:
            self.selected_subnets = my_network_stack.kubernetes_vpc.select_subnets(subnet_group_name=subnet_config.name).subnets

The my_network_stack.control_subnets is the self.control_subnets defined earlier in the Networking Stack.

This should be giving me a list of ISubnet objects that were selected properly based on the Control Subnet group names, I set up a simple test here:

for item in self.selected_subnets:
    logging.debug(type(item))

which returns

DEBUG:root:<class 'aws_cdk.aws_ec2.PrivateSubnet'>
DEBUG:root:<class 'aws_cdk.aws_ec2.PrivateSubnet'>

Those are ISubnet objects, correct?

Method 1

My first attempt to try to get this to work is to provide an unpacker for the specific list, which should be a group of ISubnet objects (truncated Cluster parameters):

self.Cluster = eks.Cluster(
    vpc_subnets = [
        ec2.SubnetSelection(subnets=[*self.selected_subnets])
    ]

which gives me the error:

jsii.errors.JSIIError: Expected object reference, got "latest"

Not entirely sure what I'm doing wrong. I've tried some variations on passing in the correct list of ISubnet objects, even when I specifically call out the array index:

vpc_subnets = [
    ec2.SubnetSelection(subnets=[self.selected_subnets[0], self.selected_subnets[1]])
]

but the same error occurs.

Method 2

Use the actual SubnetSelection function to get a list of ISubnet Objects:

vpc_subnets = [

    ec2.SubnetSelection(subnets= 
[my_network_stack.kubernetes_vpc.select_subnets(subnet_group_name=self.control_subnet_names[0]).subnets, my_network_stack.kubernetes_vpc.select_subnets(subnet_group_name=self.control_subnet_names[1])])

]

which gives me the error:

jsii.errors.JSIIError: Expected object reference, got [{"$jsii.byref":"aws-cdk-lib.aws_ec2.PrivateSubnet@10011"},{"$jsii.byref":"aws-cdk-lib.aws_ec2.PrivateSubnet@10012"}]

This looks like it could potentially be list of dictionary references with the actual ISubnet Object, in that case, not sure how it's better than Method 1, where the actual object is referenced.

Output of pip freeze:

$ pip freeze
attrs==21.4.0
aws-cdk-lib==2.8.0
cattrs==1.10.0
constructs==10.0.37
jsii==1.52.1
publication==0.0.3
python-dateutil==2.8.2
six==1.16.0
typing-extensions==4.0.1

Update: Solution

As the answerer pointed out, this error expected object, got 'latest' was related to the ALB version in the cluster creation statement, not the subnets being passed in. That was the problem all along. I've included that (broken) code below:

        self.cluster = eks.Cluster(
            self,
            "InfrastructureCluster",
            default_capacity_type=eks.DefaultCapacityType.NODEGROUP,
            alb_controller=eks.AlbControllerOptions(version='latest'),
            endpoint_access=eks.EndpointAccess.PUBLIC_AND_PRIVATE,
            version=eks.KubernetesVersion.V1_21,
            cluster_name="InfrastructureCluster",
            security_group=my_network_stack.controlplane_security_group,
            vpc=my_network_stack.kubernetes_vpc,
            vpc_subnets=
            [
                 ec2.SubnetSelection(subnets=self.selected_subnets)
            ],
        )

Additionally, this doesn't fix the problem with passing in the subnets, but I was able to finally get this code to work. The key here is that select_subnets(subnet_group_name=subnet_config returns a list of ISubnet objects, so you have separate that into objects, then unpack that into the cluster's vpc_subnets:

for subnet_config in my_network_stack.control_subnets:
    for item in my_network_stack.kubernetes_vpc.select_subnets(subnet_group_name=subnet_config.name).subnets:
        self.selected_subnets.append(item)


# Later, during cluster creation:

vpc_subnets=
[
    ec2.SubnetSelection(subnets=[*self.selected_subnets])
]

I was only able to get the above to work, combinations of passing in the list object (SubnetSelection(subnets=[]) requires a list) would lead to syntax errors.

Upvotes: 1

Views: 2344

Answers (1)

gshpychka
gshpychka

Reputation: 11588

The following woks fine and is not the cause of the issue:

self.Cluster = eks.Cluster(
    vpc_subnets = [SubnetSelection(subnets=selected_subnets)]

The cause of the issue is the following line in the initialization of the Cluster, which you provided in chat:

 alb_controller=eks.AlbControllerOptions(version="latest")

The version parameter of the AlbControllerOptions expects an instance of AlbControllerVersion, not a string.

The correct code would be:

 alb_controller=eks.AlbControllerOptions(version=eks.AlbControllerVersion.V2_3_1),

Upvotes: 2

Related Questions