Deploy multiple AWS environments using cdk (python).
Published: 2023-10-15 20:29:35

This is how I deploy multiple AWS environments using python cdk.
If you decided to use cdk and haven't implemented it already, you can start here: (https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)
* making text bold is not working as expected. If you know how to fix it, please let me know.

I assume you know the basics, explained in these aws docs.
First, let's see how we implement a single stack (single deployment environment).
The files structure should look similar to this:
```
    MyProject
    │    
    └───cdk_folder   
        └───cdk_folder
        │     |  __init__.py
        │     | my_cdk_stack.py   
        └───tests ... (folder)
        └───.venv ... (folder)
        └───cdk.out ... (folder)
        |   cdk.json
        |   app.py
```
We will look at the files `MyProject/cdk_folder/cdk_folder/my_cdk_stack.py` and `app.py`

The my_cdk_stack.py will define the stack contents. In this example will create an SQS queue:
```
import aws_cdk
from aws_cdk import (
    Stack,
    aws_sqs as _sqs,
)
from constructs import Construct

class MyCdkStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
        my_queue = _sqs.Queue(self, f'SQSMyQueue', queue_name='my_queue')
```
app.py:
```
import aws_cdk as cdk
from cdk_folder.my_cdk_stack import MyCdkStack
app = cdk.App()
MyCdkStack(app, 'MyCdkStack' env=cdk.Environment(account=<account id>, region=<region>))
app.synth()
```

now you can run the commands as explained in the docs (cdk synth and/or cdk deploy) after you have run cdk bootstrap.

Now let's say you want a production environment and a test environment, each accessing a different database and have its own api gateway and labmda function for example.
We can create another stack in app.py:
```
import aws_cdk as cdk
from cdk_folder.my_cdk_stack import MyCdkStack
app = cdk.App()
**MyCdkStack(app, 'MyCdkStackProd' env=cdk.Environment(account=<account id>, region=<region>))
MyCdkStack(app, 'MyCdkStackTest' env=cdk.Environment(account=<account id>, region=<region>))**
app.synth()
```

You'd probably want each environment to be different in some ways - the lambda functions to access a different database, have a different domain for cloud front distribution and so on.
For this, you can add parameters to your stack (or wrap all of them in one object):
from my_cdk_stack.py:
```
from aws_cdk.aws_lambda_python_alpha import PythonFunction
from aws_cdk import (
    Stack,
    aws_cloudfront as _cloud_front,
    aws_secretsmanager as _secretmanager,
)

class MyStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **env_name: str, rds_password_secret_arn: str, db_endpoint: str**, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)
      
        rds_secret = _secretmanager.Secret.from_secret_attributes(self, f'{construct_id}-
        RdsCredentialsSecret', secret_complete_arn=rds_password_secret_arn)
       
        common_environment_vars = {
            'rds_credentials': rds_password_secret_arn,
            'db_endpoint': db_endpoint,
            'region': self.region
        }

        lambda_login = PythonFunction(
            self,
            f'{construct_id}-LoginHandler',
            function_name=f'MyCdkStack{env_name}-LoginHandler',
            entry='../server',
            runtime=_lambda.Runtime.PYTHON_3_8,
            handler='login',
            index='auth.py',
            environment=common_environment_vars
        )

        _cloud_front.Distribution(self, f'{construct_id}-AppDistribution', default_behavior=_cloud_front.BehaviorOptions(
                                      origin=... viewer_protocol_policy=_cloud_front.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,),
                                  domain_names=[
                                      f'app.{env_name}.your_domain.com' # or whatever you need based on {env_name}
],
                                  default_root_object='index.html')
```
and the app.py file:
```
import aws_cdk as cdk
from cdk_folder.my_cdk_stack import MyCdkStack
app = cdk.App()
MyCdkStack(app, '**MyCdkStackProd**' env=cdk.Environment(account=<account id>, region=<**region1**>),
**db_endpoint=<db_endpoint1>, rds_password_secret_arn=<secret_arn1>**
)
MyCdkStack(app, '**MyCdkStackTest**' env=cdk.Environment(account=<account id>, region=<**region2**>),
**db_endpoint=<db_endpoint2>, rds_password_secret_arn=<secret_arn2>**
)
app.synth()
```


I use different regions, so for the test environment I can choose the cheapest region for my use case, and also I can easily switch between environments in the AWS console by switching the regions in the console. You will need to bootstrap each region:
"`cdk bootstrap aws://ACCOUNT-NUMBER-1/REGION-1 aws://ACCOUNT-NUMBER-2/REGION-2 ...`"
(from [](https://docs.aws.amazon.com/cdk/v2/guide/bootstrapping.html)) (`ACCOUNT-NUMBER-1` can be the same as `ACCOUNT-NUMBER-2`).