Skip to content

sfernandezf/cdk-resources

Repository files navigation

CDK Resources

Motivation

Architecture

Regular aws-cdk has a stack-based architecture where resources are defined in each stack and then resources are shared between stacks (import from stacks). This project is proposing a resource-based architecture as this would enable a more natural organization of resources based on AWS services.

Typical CDK Structure

└── custom_construct/
|    └── constructs1.py
|    └── ...
|    └── constructs1.py
| 
└── stacks/
|    └── stack1.py
|    └── stack2.py
|    ...
|    └── stackN.py

└── app.py
└── cdk.json

CDK Resources Approach

└── custom_construct/
|    └── constructs1.py
|    └── ...
|    └── constructs1.py
| 
└── resources/
|    └── apigateway.py
|    └── ec2.py
|    └── ecs.py
|    └── eks.py
|    └── elasticsearch.py
|    └── ...
|    └── vpc.py
|
└── stacks/
|    └── stack1.py
|    └── stack2.py
|    ...
|    └── stackN.py

└── app.py
└── cdk.json

Environment Parameters

One of the most broadly used approach to parameterize a cdk stack based on environments also knows as stages (dev, ..., prod) is to pass them as configurations in thecdk.json file.

In a big multi stack project, this approach become a issue as cdk.json starts growing is difficult to manage

Parameterization based on context

{
  "app": "python3 app.py",
  "context": {
    "configurations": {
      "stack1": {
        "dev": {
          "aurora_cluster_instances": 1,
          ...
          "ecs_service_desired_container_count": 1
        },
        "prod": {
          "aurora_cluster_instances": 3,
          ...
          "ecs_service_desired_container_count": 5
        }
      },
      ...
    }
  }
}

CDK Resources Parameterization

class PostgreSqlRdsDatabase(Resource[aws_rds.DatabaseCluster]):
    construct_class = aws_rds.DatabaseCluster
    construct_props = dict(
        default=dict(
            ...
            instances=1,
            ...
        ),
        prod=dict(
            instances=2,
        )
    )

Installation

To install use pip

$ pip install cdk-resources

Components

Resource

Resources are the most important component as it contains mostly all the logic of the project. A resource is a natural representation of an AWS element, and in terms of cdk is the equivalent of a Construct Manager. Components must inherit from cdk_resources.Resource.

There are two types of resources: resource managed by the stack and imported resources.

Resource Attributes:

  • construct_class (Required): The aws_cdk.construct class this resource represent.

  • construct_props: Required only if it is a managed resource. The cdk construct class properties.

  • construct_lookup_method: Method of the aws_cdk.construct construct to be used to import the object.

  • construct_lookup_props: Required if the object is an imported resource. Kwargs used by construct_lookup_method to lookup for the object.

Resource Methods:

  • get(): Class method of the resource that returns the aws_cdk.construct. Either by lookup or because was previously created.

  • post_create(): Extra configurations to apply to the construct after construct was init.

Resource Examples:

As it can seen in the example below for the PostgreSqlRdsDatabase construct_class is aws_rds.DatabaseCluster, desired configurations for all the environments are being specified in the construct_props attr. And other resources are imported.

from aws_cdk import aws_rds, core, aws_ec2

from cdk_resources import Resource

from resources.ec2 import PostgreSqlRdsDatabaseSg
from resources.vpc import (
    DefaultVpc,
    DefaultPrivateDbASubnet,
    DefaultPrivateDbBSubnet,
    DefaultPrivateDbCSubnet,
)

class PostgreSqlRdsDatabase(Resource[aws_rds.DatabaseCluster]):
    construct_class = aws_rds.DatabaseCluster
    construct_props = dict(
        default=dict(
            engine=aws_rds.DatabaseClusterEngine.aurora_postgres(
                version=aws_rds.AuroraPostgresEngineVersion.VER_13_4
            ),
            backup=aws_rds.BackupProps(retention=core.Duration.days(3)),
            deletion_protection=True,
            instance_props=lambda: aws_rds.InstanceProps(
                instance_type=aws_ec2.InstanceType.of(
                    aws_ec2.InstanceClass.BURSTABLE3,
                    aws_ec2.InstanceSize.MEDIUM,
                ),
                security_groups=[PostgreSqlRdsDatabaseSg.get()],
                vpc=DefaultVpc.get(),
                vpc_subnets=aws_ec2.SubnetSelection(
                    subnets=[
                        DefaultPrivateDbASubnet.get(),
                        DefaultPrivateDbBSubnet.get(),
                        DefaultPrivateDbCSubnet.get(),
                    ]
                ),
                parameter_group=PostgreSqlParameterGroup().construct,
            ),
            instances=1,
            port=5432,
            removal_policy=core.RemovalPolicy.RETAIN,
            storage_encrypted=True,
        ),
        prod=dict(
            backup=aws_rds.BackupProps(retention=core.Duration.days(30)),
            instances=2,
            vpc_subnets=lambda: aws_ec2.SubnetSelection(
                subnets=[
                    DefaultPrivateDbASubnet.get(),
                    DefaultPrivateDbCSubnet.get(),
                    DefaultPrivateDbCSubnet.get()
                ]
            ),
        ),
    )

Stacks

A stack is the natural representation of a CFN Stack. All stacks must inherit from cdk_resources.ResourceStack.

Resource Attributes:

  • EXISTING_RESOURCES (list): The list of existing resources that must be inited in aws_cdk.scope. These are resources that are used by the Stack resources.

  • RESOURCES (list): The resources own for this stack.

Resource Examples:

As it can be seen in the example below for the SampleStack. The stack creates a DynamoTable, Security Group, RDS Aurora Parameter Group, and RDS Cluster.

Also, some resources must be imported. Those are specified in EXISTING_RESOURCE list as the VPC resources.

from cdk_resources import ResourceStack

from resources.dynamodb import DynamoTable
from resources.ec2 import PostgreSqlRdsDatabaseSg
from resources.rds import PostgreSqlRdsDatabase, PostgreSqlParameterGroup
from resources.sns import SnsTopic
from resources.vpc import (
    DefaultVpc,
    DefaultPrivateDbASubnet,
    DefaultPrivateDbBSubnet,
    DefaultPrivateDbCSubnet,
)


class SampleStack(ResourceStack):
    EXISTING_RESOURCES = [
        ("vpc", DefaultVpc),
        ("subnet_db_a", DefaultPrivateDbASubnet),
        ("subnet_db_b", DefaultPrivateDbBSubnet),
        ("subnet_db_c", DefaultPrivateDbCSubnet),
    ]
    RESOURCES = [
        # DynamoDB
        ("dynamodb", DynamoTable),
        # RDS
        ("postgresql-sg", PostgreSqlRdsDatabaseSg),
        ("postgresql-parameter-group", PostgreSqlParameterGroup),
        ("postgresqlDb", PostgreSqlRdsDatabase),
        # SNS
        ("sns-topic", SnsTopic),
    ]

Parameterization

to do

Examples

Here are some availables examples.