javascriptnode.jsaws-cloudformationaws-cdk

Refactor AWS cdk stack into multiple smaller stacks


I am running up against the resource limit in my current cdk stack and need to refactor into a smaller stack. I want to do so without decoupling existing resources. This is especially true of my databases.

My current stack deploys one vpc containing a bastion host, an api gateway and corresponding lambda, a few dynamodb tables, and a neptune database, and configures all the necessary permissions. The api gateway in particular is eating up my entire resource limit.

To solve this issue, I want to refactor out pieces of my stack into a smaller stack. The resources declared in the refactored stacks (currently LambdaStack and ApiGatewayStack) are declared identically to the resources in the existing mono-stack. The refactored top level stack is shown below:

export class MyStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: MyStackProps | any) {
    super(scope, id, props);

    dotenv.config();

    const { deploymentEnvironment } = props || { deploymentEnvironment: 'dev' };
    const isDev = deploymentEnvironment === 'dev';

    const vpcId = `NeptuneVPC${isDev ? 'Dev' : 'Prod'}`;
    const vpc = new ec2.Vpc(this, vpcId, {
      maxAzs: 2,
      // subnetConfiguration: [
      //   {
      //     cidrMask: 24,
      //     name: 'public',
      //     subnetType: ec2.SubnetType.PUBLIC,
      //   },
      //   {
      //     name: 'private',
      //     subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
      //   },
      // ],
    });

    const bastionName = `NeptuneBastionHost${isDev ? 'Dev' : 'Prod'}`;
    const bastionHost = this.createBastionHost(
      vpc,
      bastionName,
      `key-pair-neptune-bastion-${isDev ? 'dev' : 'prod'}`,
    );

    const lambdaStack = new LambdaStack(this, 'LambdaStack', {
      deploymentEnvironment,
      vpc,
    });

    const apiGatewayStack = new ApiGatewayStack(this, 'ApiGatewayStack', {
      deploymentEnvironment,
      vpc,
      lambdaStack,
    });

    const s3PublicBucket = new s3.Bucket(this, `my-${isDev ? 'dev' : 'prod'}-images`, {
      blockPublicAccess: new s3.BlockPublicAccess({
        blockPublicAcls: false,
        blockPublicPolicy: false,
        ignorePublicAcls: false,
        restrictPublicBuckets: false,
      }),
      objectOwnership: s3.ObjectOwnership.OBJECT_WRITER,
    });

    // S3
    s3PublicBucket.grantPublicAccess();
    s3PublicBucket.grantWrite(lambdaStack.mainLambda);
    s3PublicBucket.grantPutAcl(lambdaStack.mainLambda);

    // Dynamo, Neptune, etc...

I'm particularly worried about orphaning or deleting existing resources, so I was wondering:

  1. Will the resources I've refactored to their own stack be deleted and redeployed? My instinct is yes.
  2. Will the resources that I'd left in the top level stack be left alone? This piece is especially important because they represent all my core databases.
  3. If I wanted to move the stateful resources (databases and buckets) to their own stacks without deletion and recreation, how could I do so?

At the root of this is the question: how does cdk decide whether a resource refers to an existing resource or a new resource?


Solution

  • Will the resources I've refactored to their own stack be deleted and redeployed? My instinct is yes.

    Yes.

    Will the resources that I'd left in the top level stack be left alone? This piece is especially important because they represent all my core databases.

    If you didn't change them, they won't be changed.

    If I wanted to move the stateful resources (databases and buckets) to their own stacks without deletion and recreation, how could I do so?

    You would have to look into CDK import for importing them into your new stack after orphaning them from the old one (after setting the removalPolicy prop to RemovalPolicy.RETAIN).

    how does cdk decide whether a resource refers to an existing resource or a new resource?

    CDK does not decide this - you tell it whether you want to create a resource (e.g. new Bucket()) or refer to an existing one (e.g. Bucket.fromBucketArn()). Note that the latter does NOT import the resource into the stack - it simply allows your CDK code to refer to it in a read-only manner.

    As to how it knows whether it needs to create a new bucket when you have new Bucket in the code - that part happens outside of CDK and in CloudFormation, which is what keeps the state. It will check whether that resource has already been created by this stack, and not attempt to create it again if it has. It will NOT check if that resource exists or not, though - it only checks the previous operations of the stack in question