Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to manage multiple environments using aws cdk?

I am moving our infra code from Terraform to AWS cdk. And I am trying to find an optimal way to manage multiple environments that have multiple stacks to be deployed. If I go by the recommendation in the documentation, I would have to define multiple stacks for multiple environments and it can get confusing. E.g.

const app = new cdk.App();

new DevStack1(app, 'dev-stack-1', settings.dev)
new DevStack2(app, 'dev-stack-2', settings.dev)
.
.


new ProdStack1(app, 'prod-stack-1', settings.prod)
new ProdStack2(app, 'prod-stack-2', settings.prod)
.
.

Where settings are shared between different stacks in the same environments. And Then I would have to deploy each stack one after the other. Is there a better way of doing this?

like image 210
Keshav Potluri Avatar asked Apr 30 '20 04:04

Keshav Potluri


People also ask

What is AWS CDK stack?

The unit of deployment in the AWS CDK is called a stack. All AWS resources defined within the scope of a stack, either directly or indirectly, are provisioned as a single unit. Because AWS CDK stacks are implemented through AWS CloudFormation stacks, they have the same limitations as in AWS CloudFormation.

Is terraform better than CDK?

Terraform deploys resources using the AWS SDK, whereas the CDK code is first converted to CloudFormation templates and then applied. Terraform would work slightly faster than AWS CDK, particularly because of the time CDK takes to convert code to CloudFormation Template.

Does AWS CDK use CloudFormation?

AWS CDK is available to define and deploy AWS resources in all public regions. Since AWS CDK leverages the CloudFormation service, refer to Regional Products and Services for details about specific resource availability per AWS Region.


1 Answers

Edit: since this answer has reasonable amount of views, I want to update it to reflect a better, more CDK native approach. I will keep original answer below as it might work better for someone.

CDK parameters can be stored directly in cdk.json under context key and retrieved inside the app by using ConstructNode.try_get_context(param_name).

See official doc: https://docs.aws.amazon.com/cdk/latest/guide/get_context_var.html

So this is an example of cdk.json with different params for different envs:

{
  "app": "python3 app.py",
  "context": {
    "dev": {
      "vpc": {
        "vpc_id": "blabla"
      }
    },
    "stage": {
      "vpc": {
        "vpc_id": "bleble"
      }
    }
}

Now you can also supply context param via CLI key --context env_name=dev and use its value in code to get relevant settings.

Practically most if not all common use constructs like Stack, App, NestedStack all have node attribute, so you can access context from anywhere in your app.

There are 2 caveats to using it:

  1. It doesn't allow access to lower level keys (or at least it's not documented). This means you cannot use self.try_get_context("dev.vpc.vpc_id"), you need to retrieve top level key by self.try_get_context("dev") and make your way down on your own.

  2. If you have bool params in your context and try to override them by using CLI key --context key=False, these params will be turned into str, and it's very easy to fall into trap if you're using common sense syntax of:

if self.try_get_context("deploy_vpc"):
    MyVPC(app, "my new vpc")

Since "False" is evaluated to be True as a non-empty string, you will not get what you expect.


OLD ANSWER

I'm not sure if consensus on this exists, since CDK is still a new thing and also supports multiple languages. But what I've personally done is storing settings in YAML file for different environments, and then supplying that file via environment variable.

Python example:

Config is a YAML file which holds items like required tags for resources, some settings on app level (like account ID and region) and some stack level settings (like names of resources etc).

Assuming standard project layout, where you have app.py main file and cdk/cdk_stack.py file with stack description. In app.py:

from ruamel.yaml import YAML
...
from cdk.cdk_stack import MyStack


def load_config() -> dict:
    """
    Import settings from YAML config
    :return: dict with configuration items
    """
    # This variable should always be set
    # CDK doesn't work well with argparse
    config_path = os.getenv("CONFIG_PATH")
    if not config_path:
        raise RuntimeError("You need to supply config file path with CONFIG_PATH env variable")
    # We don't verify config content, let fail in case something is missing
    with open(config_path) as config_file:
        config = YAML().load(config_file.read())
    return config

def init_app() -> core.App:
    """
    Initiates CDK main_app for deployment
    :return: main_app
    """
    main_app = core.App()
    config = load_config()
    # Account ID and region have to be explicitly set in order to import existing resources
    MyStack(
        main_app,
        "my stack",
        env={
            'account': config["account_id"],
            'region': config["region"]
        },
        config=config
    )
    # Tags are applied to all tagable resources in the stack
    for key, value in config["tags"].items():
        core.Tag.add(scope=main_app, key=key, value=value)
    return main_app


if __name__ == '__main__':
    app = init_app()
    app.synth()

Then in cdk/cdk_stack.py:

class MyStack(core.Stack):
    """
    Describes CF resources
    """
    def __init__(self, scope: core.Construct, id: str, **kwargs) -> None:
        # Pop the config object first, since parent object doesn't expect it and will crash
        config = kwargs.pop("config")
        super().__init__(scope, id, **kwargs)
...
like image 62
Oleksii Donoha Avatar answered Oct 10 '22 10:10

Oleksii Donoha