Connect lambda and RDS with RDS proxy using AWS CDK(ver.2) python
Tags: aws, lambda, rds, cdk
Published: 2023-06-13 06:56:21

Since I had the opportunity to develop with AWS CDK for a project, I would share the knowledge I gained from the development.
In this article, I will explain how to use CDK (ver. 2) Python to connect from an AWS Lambda to RDS in a private subnet via RDS Proxy.

See Github for full code.
[Github here](https://github.com/ShinyaT1994/cdk-lambda-rds-proxy.git)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m4irba8b80vvbcjvb23s.png)


## Prerequisites.

- AWS CDK v.2 must be installed
- Python 3.x must be installed
- AWS account and AWS CLI must be set up.

Cloud9 can be used to pass the above, so the content of this article was created using Cloud9.

## Build procedure
### Initialize CDK application
First, initialize the CDK application.

```Bash
$ mkdir cdk-lambda-rds-proxy
$ cd cdk-lambda-rds-proxy
$ cdk init --language python
```

### 2. install necessary packages
Here we activate the .venv created when we initialized the CDK app.

```Bash
$ source .venv/bin/activate
$ pip install -r requirements.txt
```

### 3. create the stack

```python:cdk_lambda_rds_proxy_stack.py
from constructs import Construct
from aws_cdk import (
    Stack,
    RemovalPolicy,
    aws_ec2 as ec2,
    aws_lambda as _lambda,
    aws_rds as rds,
    aws_secretsmanager as sm,
)


class CdkLambdaRdsProxyStack(Stack):
    def __init__(self, scope: Construct, id: str, **kwargs) -> None:
        super().__init__(scope, id, **kwargs)

        # Create VPC
        vpc = ec2.Vpc(self, "MyVPC",
            max_azs=2
        )

        # Create security group for RDS
        rds_security_group = ec2.SecurityGroup(
            self, 
            "RdsSecurityGroup",
            vpc=vpc,
            description="Allow Lambda access to RDS"
        )

        # Create RDS instance
        rds_instance = rds.DatabaseInstance(
            self, 
            "RDS",
            engine=rds.DatabaseInstanceEngine.postgres(
                version=rds.PostgresEngineVersion.VER_14_5
            ),
            vpc=vpc,
            security_groups=[rds_security_group],
            instance_type=ec2.InstanceType.of(
                ec2.InstanceClass.BURSTABLE3, ec2.InstanceSize.SMALL
            ),
            allocated_storage=20,
            removal_policy=RemovalPolicy.DESTROY,
            deletion_protection=False,
            database_name="MyDatabase",
            auto_minor_version_upgrade=True,
            multi_az=False
        )

        # Create security group for lambda
        lambda_security_group = ec2.SecurityGroup(
            self, 
            "LambdaSecurityGroup",
            vpc=vpc,
            description="Allow Lambda to access RDS Proxy"
        )

        # Create RDS Proxy
        rds_proxy = rds.DatabaseProxy(
            self, 
            "RDSProxy",
            proxy_target=rds.ProxyTarget.from_instance(rds_instance),
            vpc=vpc,
            security_groups=[lambda_security_group],
            db_proxy_name="MyRDSProxy",
            debug_logging=False,
            secrets=[rds_instance.secret],
            require_tls=True
        )
        
        # lambda layer of psycopg2
        psycopg2_layer = _lambda.LayerVersion.from_layer_version_arn(
            self, 
            "Psycoog2Layer",
            'arn:aws:lambda:ap-northeast-1:898466741470:layer:psycopg2-py38:1'
        )
        
        # Create lambda function
        lambda_function = _lambda.Function(
            self, 
            "LambdaFunction",
            runtime=_lambda.Runtime.PYTHON_3_8,
            handler="lambda_function.handler",
            code=_lambda.Code.from_asset("cdk_lambda_rds_proxy/lambda"),
            layers=[psycopg2_layer],
            vpc=vpc,
            security_groups=[lambda_security_group],
            vpc_subnets=ec2.SubnetSelection(subnets=vpc.private_subnets),
            environment={
                "DB_SECRET_ARN": rds_instance.secret.secret_arn,
                "DB_PROXY_ENDPOINT": rds_proxy.endpoint
            }
        )
    
        # Policies
        rds_instance.connections.allow_from(
            lambda_security_group,
            port_range=ec2.Port.tcp(5432)
        )
        rds_proxy.connections.allow_from(
            lambda_security_group,
            port_range=ec2.Port.tcp(5432)
        )
        
        rds_instance.secret.grant_read(lambda_function)
```

### 4. Create a Lambda function
Create a lambda folder, create a lambda_function.py file in it, and write the following

```python:lambda_function.py
import os
import json
import psycopg2
import boto3
from botocore.exceptions import ClientError

def get_db_credentials(secret_arn):.
    secrets_manager = boto3.client("secretsmanager")
    try:: secrets_manager = boto3.client("secretsmanager")
        response = secrets_manager.get_secret_value(SecretId=secret_arn)
        return json.loads(response["SecretString"])
    except ClientError as e: print(e)
        print(e)
        return None

def handler(event, context):
    secret_arn = os.environ["DB_SECRET_ARN"]]
    db_proxy_endpoint = os.environ["DB_PROXY_ENDPOINT"]]

    credentials = get_db_credentials(secret_arn)

    if not credentials: return { if not credentials
        return {
            "statusCode": 500, "body": "Error retrieving
            "body": "Error retrieving database credentials"
        }
    
    connection_string = f "dbname={credentials['dbname']} user={credentials['username']} password={credentials['password']} host={db_proxy_ endpoint} port={credentials['port']} sslmode=require"
    
    try:.
        conn = psycopg2.connect(connection_string)
        cursor = conn.cursor()
        cursor.execute("SELECT version();")
        result = cursor.fetchone()

        return {
            "statusCode": 200,.
            "body": f "Database version: {result[0]}"
        }

    except Exception as e:.
        print(e)
        return {
            "statusCode": 500, "body": "Error connecting to the database" }
            "body": "Error connecting to the database"
        }
```

### 5. Deploy the CDK app
Now that the Stack is ready, deploy it.

```bash
$ cdk deploy
```
Once deployed, the Lambda function will connect to the RDS database to retrieve version information.

↓↓ Actual connection confirmed.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/30obrffin6w1zzqiszmb.png)

## Summary

This article described how to use CDK(ver.2) Python to connect from a Lambda function to an RDS instance in a private subnet via RDS Proxy.
It seems to be an anti-pattern to connect Lambda and RDS directly, so please refer to this when configuring Lambda + RDS.


## Reference
[AWS CDK API Reference](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-construct-library.html)