AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Parameters: RDSUserName: Type: String RDSPassword: Type: String RDSDBName: Type: String Resources: # create VPC RDSVPC: Type: AWS::EC2::VPC Properties: CidrBlock: 172.31.0.0/16 EnableDnsSupport: 'true' EnableDnsHostnames: 'true' Tags: - Key: Name Value: RDSVPC # create subnets privateDBSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref RDSVPC CidrBlock: 172.31.0.0/20 AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' Tags: - Key: Name Value: privateDBSubnet1 privateDBSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref RDSVPC CidrBlock: 172.31.16.0/20 AvailabilityZone: !Select - 1 - !GetAZs Ref: 'AWS::Region' Tags: - Key: Name Value: privateDBSubnet2 privateLambdaSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref RDSVPC CidrBlock: 172.31.32.0/20 AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' Tags: - Key: Name Value: privateLambdaSubnet1 privateLambdaSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref RDSVPC CidrBlock: 172.31.48.0/20 AvailabilityZone: !Select - 1 - !GetAZs Ref: 'AWS::Region' Tags: - Key: Name Value: privateLambdaSubnet2 publicSubnet1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref RDSVPC CidrBlock: 172.31.64.0/20 AvailabilityZone: !Select - 0 - !GetAZs Ref: 'AWS::Region' Tags: - Key: Name Value: publicSubnet1 publicSubnet2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref RDSVPC CidrBlock: 172.31.80.0/20 AvailabilityZone: !Select - 1 - !GetAZs Ref: 'AWS::Region' Tags: - Key: Name Value: publicSubnet2 # create and attach internet gateway # Internet gatewat and NAT are required so that Lambdas running inside VPC can invoke ManageConnections lambda function (running out of VPC) myInternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: myInternetGateway AttachInternetGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref RDSVPC InternetGatewayId: !Ref myInternetGateway # create route tables CustomRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref RDSVPC Tags: - Key: Name Value: CustomRouteTable myRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref RDSVPC Tags: - Key: Name Value: myRouteTable # attach route tables mySubnetRouteTableAssociation1: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref privateDBSubnet1 RouteTableId: !Ref myRouteTable mySubnetRouteTableAssociation2: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref privateDBSubnet2 RouteTableId: !Ref myRouteTable mySubnetRouteTableAssociation3: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref privateLambdaSubnet1 RouteTableId: !Ref myRouteTable mySubnetRouteTableAssociation4: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref privateLambdaSubnet2 RouteTableId: !Ref myRouteTable # public subnets get attached to CustomRouteTable mySubnetRouteTableAssociation5: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref publicSubnet1 RouteTableId: !Ref CustomRouteTable mySubnetRouteTableAssociation6: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref publicSubnet2 RouteTableId: !Ref CustomRouteTable # create and attach NAT gateway myNAT: DependsOn: RDSVPC Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt [myEIP,AllocationId] SubnetId: !Ref publicSubnet1 myEIP: DependsOn: AttachInternetGateway Type: AWS::EC2::EIP Properties: Domain: vpc # create routes RouteToNAT: Type: AWS::EC2::Route Properties: RouteTableId: !Ref myRouteTable DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref myNAT RouteToInternet: Type: AWS::EC2::Route Properties: RouteTableId: !Ref CustomRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref myInternetGateway # create security groups RDSSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Allow My SQL access from lambda subnets VpcId: Ref: RDSVPC SecurityGroupIngress: - IpProtocol: tcp FromPort: '3306' ToPort: '3306' SourceSecurityGroupId : !Ref LambdaSecurityGroup Tags: - Key: Name Value: RDSSecurityGroup LambdaSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Security group for Lambda ENIs VpcId: Ref: RDSVPC Tags: - Key: Name Value: LambdaSecurityGroup # Create Db subnet groups for RDS instance myDBSubnetGroup: Type: "AWS::RDS::DBSubnetGroup" Properties: DBSubnetGroupDescription: "description" SubnetIds: - !Ref privateDBSubnet1 - !Ref privateDBSubnet2 Tags: - Key: "Name" Value: "myDBSubnetGroup" # create IAM roles # Will be assumed by LambdaRDSTest and LambdaRDSTestHarness Lambda functions RDSLambdaTestRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - lambda.amazonaws.com Action: "sts:AssumeRole" Path: "/" Policies: - PolicyName: "AccessDDB" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "dynamodb:*" Resource: [!GetAtt ConnectionsCounter.Arn] - PolicyName: "AllowMetricAdd" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "cloudwatch:PutMetricData" Resource: "*" - PolicyName: "AllowInvoke" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "lambda:InvokeFunction" Resource: "*" #- !GetAtt LambdaRDSManageConnections.Arn #- !GetAtt LambdaRDSTest.Arn ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole # Will be assumed by RDSLambdaCFNInit Lambda function RDSLambdaCFNInitRole: Type: "AWS::IAM::Role" Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - lambda.amazonaws.com Action: "sts:AssumeRole" Path: "/" Policies: - PolicyName: "AccessDDB" PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: "dynamodb:*" Resource: [!GetAtt ConnectionsCounter.Arn] ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole ConnectionsCounter: Type: AWS::DynamoDB::Table Properties: ProvisionedThroughput: ReadCapacityUnits: 10 WriteCapacityUnits: 10 AttributeDefinitions: - AttributeName: "RDBMSName" AttributeType: "S" KeySchema: - AttributeName: "RDBMSName" KeyType: "HASH" LambdaRDSLayer: Type: AWS::Serverless::LayerVersion Properties: LayerName: MyLayer Description: Layer description ContentUri: '../lib' CompatibleRuntimes: - python3.6 LicenseInfo: 'Available under the Apache 2.0 license.' RetentionPolicy: Retain LambdaRDSTest: Type: AWS::Serverless::Function Properties: Handler: LambdaRDS_Test.lambda_handler Description: "Test Lambda function to access a RDS Database and read sample data" Runtime: python3.6 Role: !GetAtt RDSLambdaTestRole.Arn MemorySize: 128 Timeout: 60 Layers: - !Ref LambdaRDSLayer VpcConfig: # For accessing RDS instance SecurityGroupIds: - !Ref LambdaSecurityGroup SubnetIds: - !Ref privateLambdaSubnet1 - !Ref privateLambdaSubnet2 Environment: Variables: RDS_HOST: !GetAtt RDSMySQL.Endpoint.Address RDS_USERNAME: !Ref RDSUserName RDS_PASSWORD: !Ref RDSPassword RDS_DB_NAME: !Ref RDSDBName #HELPER_FUNCTION_ARN: !GetAtt LambdaRDSManageConnections.Arn DDB_TABLE_NAME: !Ref ConnectionsCounter LambdaRDSTestHarness: Type: AWS::Serverless::Function Properties: Handler: LambdaRDS_TestHarness.lambda_handler Description: "Provides a simple framework for conducting various tests of your Lambda functions" Runtime: python3.6 Role: !GetAtt RDSLambdaTestRole.Arn MemorySize: 1024 Timeout: 180 Environment: Variables: TEST_FUNCTION_ARN: !GetAtt LambdaRDSTest.Arn LambdaRDSCFNInit: DependsOn: - myEIP # for deletion, this lambda function requires access to S3 bucket, hence this dependency. (chained to NAT and internet gateway) - mySubnetRouteTableAssociation1 - mySubnetRouteTableAssociation2 - mySubnetRouteTableAssociation3 - mySubnetRouteTableAssociation4 - mySubnetRouteTableAssociation5 - mySubnetRouteTableAssociation6 - RouteToInternet - RouteToNAT - RDSSecurityGroup Type: AWS::Serverless::Function Properties: Handler: LambdaRDS_CFNInit.lambda_handler Description: "Lambda function which will execute when this CFN template is created, updated or deleted" Runtime: python3.6 Role: !GetAtt RDSLambdaCFNInitRole.Arn MemorySize: 128 Timeout: 60 Layers: - !Ref LambdaRDSLayer VpcConfig: # For accessing RDS instance SecurityGroupIds: - !Ref LambdaSecurityGroup SubnetIds: - !Ref privateLambdaSubnet1 - !Ref privateLambdaSubnet2 Environment: Variables: RDS_HOST: !GetAtt RDSMySQL.Endpoint.Address RDS_USERNAME: !Ref RDSUserName RDS_PASSWORD: !Ref RDSPassword RDS_DB_NAME: !Ref RDSDBName DDB_TABLE_NAME: !Ref ConnectionsCounter # Wire up the lambda function LambdaRDSCFNInit to execute on stack create, update or delete LambdaRDSCFnTrigger: Type: Custom::LambdaRDS Version: 1.0 Properties: ServiceToken: !GetAtt [ LambdaRDSCFNInit, Arn ] RDSMySQL: Type: AWS::RDS::DBInstance Properties: AllocatedStorage: 5 DBInstanceClass: db.m4.xlarge DBName: !Ref RDSDBName Engine: mysql MasterUsername: !Ref RDSUserName MasterUserPassword: !Ref RDSPassword MultiAZ: False PubliclyAccessible: False StorageType: gp2 DBSubnetGroupName: !Ref myDBSubnetGroup VPCSecurityGroups: - !Ref RDSSecurityGroup DeletionPolicy: Delete RDSLambdaDashboard: Type: AWS::CloudWatch::Dashboard Properties: # DashboardName: 'RDSLambda' DashboardBody: !Sub '{ "widgets": [ {"type":"metric", "x":1, "y":0, "width":24, "height":12, "properties": { "metrics": [ [ "RDSLambda", "Remaining Connections", "DBName", "Prod_MySQL", { "stat": "Maximum" } ], [ ".", "NoConnLeftError", ".", ".", { "yAxis": "right" } ] ], "view": "timeSeries", "stacked": false, "region": "${AWS::Region}", "stat": "Sum", "period": 1, "annotations": { "horizontal": [ { "label": "Max Connections Available", "value": 50, "fill": "below" } ] }, "title": "RDS Lambda", "yAxis": { "left": { "label": "Remaining Connections" }, "right": { "label": "No Connections Error" } }, "start": "-PT1M", "end": "P0D" } }] }' Outputs: RDSMySQLEndPoint: Description: The endpoint of the RDS MySQL instance Value: !GetAtt RDSMySQL.Endpoint.Address