cdk CI/CD Pipeline - destroy duplicate stacks

Viewed 350

I'm working on setting up a CI/CD Pipeline with cdk in typescript. I have a very modular stack structure so I'm having a Stage with 3 stacks: LambdasStack, EndpointsStack and TablesStack. As the name suggest I have all my Lambdas in LambdasStack and so on.

For the pipeline I want the following flow:

  1. Build
  2. Deploy Stacks for PreProd
  3. Integration Test
  4. Destroy Stacks of PreProd
  5. Manual approval before Prod
  6. Deploy Stacks for Prod

The PreProd stacks have to be destroyed before the deployment of the Prod stacks because of the unique names of the tables within the TablesStack. And that's what I'm struggling with. My code to destroy them is:

const deletePreProdStacks= new ShellStep('Delete deployed Stacks', {
            commands: [
                'npm install',
                'cdk destroy -f --all'
            ]
 }); 

With 'cdk destroy -f --all' the stacks of the stage are not found so they can't be deleted.

How can I solve this problem? Giving the tables autogenerated names can't be the right solution? Or is there an option to overwrite the PreProdStacks to ProdStacks?

I only have access to only one AWS Account. Because I read that having Testing/PreProd stage and Prod stage on different accounts.

Maybe someone has a similar best practice reference for me?

Thanks in advance :)

Edit1: tag update Edit2: added situation about deployment of PreProd & Prod in same account

3 Answers

I found an ok solution for giving the resources that can't be duplicated different names by adding the stagename in their properties like this:

In the Stack:

const bookingsTable = new dynamodb.Table(this, 'BookingsTable', {
        tableName: `${stageTag}-BookingsTable`,
        partitionKey: {
            name: 'bookingId',
            type: dynamodb.AttributeType.STRING
        },
        billingMode: dynamodb.BillingMode.PAY_PER_REQUEST
    });

In the Stage:

const tables = new TablesStack(this, `TablesStack`, {
        stageTag: this.stageName,
        env: {
            account: account,
            region: region
        }
    });

There by I create 2 tables one named PreProd-BookingsTable and the other Prod-BookingsTable.

So I don't need to destroy the already deployed stacks

Your PreProd stage's stacks are not being destroyed because cdk destroy isn't getting the stack names right. The cdk destroy command does the following:

  1. Synths the App using the command in the --app flag or in the app key of cdk.json.
  2. Gets the App's stack names. The names are in the cloud assembly output (specifically, in cdk.out/manifest.json) created in Step #1.
  3. Calls the CloudFormation DeleteStack API for each stack name.

You want to destroy stacks like PreProdLambdasStack, but the app.ts is producing other stack names. So nothing gets deleted. One way to fix this is to pass the stage name to your app file as an environment variable. Modify app.ts to concatenate the stage prefix to the base stack name to produce stacks with a stage name prefix like PreProd.

# ShellStep command
STAGE_PREFIX=PreProd && cdk destroy --app 'ts-node ./bin/app.ts' --all --force

Because stage.ts (stack creation) and app.ts (stack deletion) need the same stack code, keep your code DRY with a reusable stack-creation wrapper called in both contexts:

// call this in the stages and in app.ts

export const makeAppStacks = (
  scope: cdk.App | pipelines.Stage,
  account: string,
  region: string,
  stageName?: string
): void => {
  const prefix = (stageName && stageName !== 'Prod') ? stageName : ''
  new LambdasStack(scope, `${prefix}LambdasStack`, {...})
  new EndpointsStack(scope, `${prefix}EndpointsStack`, {...})
  new TablesStack(scope, `${prefix}TablesStack`, {...})
};

Notes

The AWS-recommended setup is a separate account for each environment. If, however, you are stuck with a single account for PreProd and Prod, the next cleanest option is to have separate stacks per environment. PreProd deploys and is tested, then is destroyed. To speed up pipeline execution, the Prod deploy and PreProd destroy can run in parallel.

Another best practice is to avoid assigning construct names at all, or AWS puts it, "Use generated resource names, not physical names". If pretty resource names are a requirement, consider assigning them only in Prod environments.

tableName: props.isProd ? 'MyGreatTableName' : undefined

Well you don't need to destroy your stacks but specify physical names for resources By “environment” (not preferred).
By environment I mean the stage name, e.g. “pre-prod”, “prod”, etc.

The best solution is to deploy your testing & PreProd & Prod stages in difference accounts.
And if you're using CDK popelines, deploying to multiple accounts shoudn't be too much work, the only thing you have to do is bootstrap all the accounts you deploy to to trust the deployment account (review the bootstrapping docs) and set the account IDs in the stages.

Related