Search code examples
pythonpython-3.7aws-cdk

AWS CDK Python - Passing multiple variables between Stacks


Background

I have two Stacks within my CDK App. One is a Stack that defines Network Constructs (such as a VPC, Security Groups, etc), and one is a Stack responsible for creating an EKS cluster. The EKS Cluster needs to be able to consume variables and output from the Networking Stack as part of the cluster provisioning process. Examples below:

NetworkStack:

class NetworkingStack(Stack):
   def __init__(self, scope: Construct, id: str, MyCidr,**kwargs) -> None:
       super().__init__(scope, id)

       subnet_count = range(1,3)
       public_subnets = []
       worker_subnets = []
       control_subnets = []

       for x in subnet_count:
           x = str(x)
           control_subnets.append(ec2.SubnetConfiguration(
               name = 'Control-0{}'.format(x),
               cidr_mask=28,
               subnet_type = ec2.SubnetType.PRIVATE_WITH_NAT,
               reserved = False
           ))
           worker_subnets.append(ec2.SubnetConfiguration(
               name = 'Worker-0{}'.format(x),
               cidr_mask=24,
               subnet_type = ec2.SubnetType.PRIVATE_WITH_NAT,
               reserved = False
           ))
           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
           ))

       kubernetes_vpc = ec2.Vpc(self, 
       "Kubernetes", 
       cidr=MyCidr,
       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=[*public_subnets,*control_subnets,*worker_subnets],
       vpc_name="Kubernetes",
       vpn_connections=None
       )
       

       controlplane_security_group = ec2.SecurityGroup(self, "ControlPlaneSecurityGroup", vpc=kubernetes_vpc)
       workerplane_security_group = ec2.SecurityGroup(self, "WorkerPlaneSecurityGroup", vpc=kubernetes_vpc) 
       public_security_group = ec2.SecurityGroup(self, "PublicPlaneSecurityGroup", vpc=kubernetes_vpc)

Now, the Cluster Stack needs to consume several of the variables created here. Specifically, it needs to be able to access the kubernetes_vpc, public/control/worker subnets, and the public/control/workerplane security groups

In the same region, same account, same CDK environment I have another Stack, the Cluster Stack:

ClusterStack:

        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=MyNetworkStack.controlplane_security_group,
            vpc=MyNetworkStack.kubernetes_vpc,
            vpc_subnets=ec2.SubnetSelection(
                subnets=[*MyNetworkStack.control_subnets, *MyNetworkStack.worker_subnets, *MyNetworkStack.public_subnets],
            )
        )

The official documentation uses the example of sharing a bucket between Stacks in order to access the StackA bucket from StackB. They do this by passing the object between Stacks. This would mean I need to create an argument for each variable that I'm looking for a pass it between Stacks. Is there an easier way?

I tried passing the actual NetworkingStack to the ClusterStack as such:

class ClusterStack(Stack):
    def __init__(self, scope: Construct, id: str, MyNetworkStack, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

and then in app.py:

NetworkingStack(app, "NetworkingStack", MyCidr="10.40.0.0/16")
ClusterStack(app, "ClusterStack", MyNetworkStack=NetworkingStack)

but that doesn't seem to work when I reference the variables in NetworkingStack as attributes e.g. security_group=MyNetworkStack.controlplane_security_group. The error I get usually relates to this variables specifically AttributeError: type object 'NetworkingStack' has no attribute 'controlplane_security_group'

Question:

What is the best way to pass in multiple variables between Stacks, without having to define each variable as an argument?


Solution

  • Step 1

    Change any variable you want to reference to an attribute of the NetworkStack class.

    So, instead of:

    controlplane_security_group = ec2.SecurityGroup(self, "ControlPlaneSecurityGroup", vpc=kubernetes_vpc)
    

    You should do:

    self.controlplane_security_group = ec2.SecurityGroup(self, "ControlPlaneSecurityGroup", vpc=kubernetes_vpc)
    

    Step 2

    Instead of passing the class, you need to pass the object.

    So, instead of:

    NetworkingStack(app, "NetworkingStack", MyCidr="10.40.0.0/16")
    ClusterStack(app, "ClusterStack", MyNetworkStack=NetworkingStack)
    

    You should do:

    networking_stack = NetworkingStack(app, "NetworkingStack", MyCidr="10.40.0.0/16")
    ClusterStack(app, "ClusterStack", MyNetworkStack=networking_stack)
    

    Now MyNetworkStack.controlplane_security_group would work correctly.

    I would also suggest one more change which is to change the name MyNetworkStack to my_network_stack since that's the general Python convention for method argument names according to PEP8.