RDS timeout when connecting from Lambda function

Hi there, I’m new to serverless and not too hot on AWS VPCs either so forgive me!!!

I’m trying to create a system that is triggered by an image landing in an S3 bucket, this fires a Lambda function that requests image labels from Rekognition and sends those labels in an SNS message. The SNS message then triggers a separate Lambda function that will insert the image details into a Postgres DB. However, my db function is just timing out - perhaps due to security groups? Here’s my code:

serverless.yml

service: client-image

frameworkVersion: ">=1.10.0"

provider:
  name: aws
  runtime: nodejs6.10
  memorySize: 512
  timeout: 10
  stage: dev
  region: eu-west-1

  iamRoleStatements:
    - Effect: Allow
      Action:
        - s3:*
      Resource: "*"
    - Effect: "Allow"
      Action:
        - "rekognition:*"
      Resource: "*"   
    - Effect: "Allow"
      Resource: "*"
      Action:
        - "sns:*"

functions:
  imageAnalysis:
    handler: handler.imageAnalysis
    events:
      - s3:
          bucket: client
  dbInsert:
    handler: handler.dbInsertImage
    events:
      - sns: imageDispatcher
    vpc:
      securityGroupIds:
        - "sg-********"
      subnetIds:
        - "subnet-********"
        - "subnet-********"
        - "subnet-********"

I set the securityGroupIds and subnetIds to match those of my RDS instance.

handler.json

'use strict';

const AWS = require('aws-sdk');
const ImageAnalyser = require('./lib/imageAnalyser');
const Pool = require('pg-pool')
const pool = new Pool({
    host: '********',
    database: '*****',
    user: '*****',
    password: '********',
    port: 5432,
    max: 1,
    min: 0,
    idleTimeoutMillis: 300000,
    connectionTimeoutMillis: 1000
});

/**
 Analyse an image on S3 using bucket and image name
 */
module.exports.imageAnalysis = (event, context, callback) => {

    console.log(JSON.stringify(event));
    console.log("Region:" + event.Records[0].awsRegion);
    console.log("Bucket name:" + event.Records[0].s3.bucket.name);
    console.log("Image name:" + event.Records[0].s3.object.key);
    console.log(event.Records[0].s3.object);

    const region = event.Records[0].awsRegion;
    const bucket = event.Records[0].s3.bucket.name;
    const image = event.Records[0].s3.object.key;

    const s3Config = {
        bucket: event.Records[0].s3.bucket.name,
        imageName: event.Records[0].s3.object.key,
    };

    const sns = new AWS.SNS();

    const accountId = '248221388880';

    return ImageAnalyser
        .getImageLabels(s3Config)
        .then((labels) => {

            const labelsJson = JSON.stringify(labels);

            const params = {
                Subject: 'image|new',
                Message: '{' +
                    '"region" : "'+ region     +'", ' +
                    '"bucket" : "'+ bucket     +'", ' +
                    '"image"  : "'+ image      +'", ' +
                    '"labels" : ' + labelsJson +
                '}',
                TopicArn: `arn:aws:sns:eu-west-1:${accountId}:imageDispatcher`
            };

            sns.publish(params, (error, data) => {
                if (error) {
                    callback(error);
                }
                callback(null, { message: 'Message successfully published to SNS topic "dispatcher"', event });
            });

            const response = {
                statusCode: 200,
                body: JSON.stringify({ Labels: labels }),
            };

            callback(null, response);
        })
        .catch((error) => {
            callback(null, {
                statusCode: error.statusCode || 501,
                headers: { 'Content-Type': 'text/plain' },
                body: error.message || 'Internal server error',
            });
        });
};

module.exports.dbInsertImage = (event, context, callback) => {
    console.log(JSON.stringify(event, null, 2));
    console.log(event.Records[0].Sns.Subject);
    console.log(event.Records[0].Sns.Message);

    context.callbackWaitsForEmptyEventLoop = false;
    let client;
    pool.connect().then(c => {
        client = c;
        return client.query("select 'stuff'");
    }).then(res => {
        client.release();
        const response =  {
            "isBase64Encoded": false,
            "statusCode": 200,
            "body": JSON.stringify(res.rows)
        }
        callback(null, response);
    }).catch(error => {
        console.log("ERROR", error);
        const response =  {
            "isBase64Encoded": false,
            "statusCode": 500,
            "body": JSON.stringify(error)
        }

        callback(null, response);
    });

};

Cloudwatch gives this error:
2017-10-25T16:56:54.104Z 7b626e7e-b9a5-11e7-b485-5d8728ce0c70 ERROR Error: Connection terminiated due to connection timeout
at Connection.con.once (/var/task/node_modules/pg/lib/client.js:179:21)
at Connection.g (events.js:292:16)
at emitNone (events.js:86:13)
at Connection.emit (events.js:185:7)
at Socket. (/var/task/node_modules/pg/lib/connection.js:75:10)
at emitOne (events.js:96:13)
at Socket.emit (events.js:188:7)
at TCP._handle.close [as _onclose] (net.js:497:12)

Any ideas? Thank you

I found the answer - in case anyone else has the same issue - I needed to add an Inbound rule on the RDS security group allowing communication to the instance from anything in the VPC i.e.

  • Type: PostgreSQL
  • Protocol: TCP
  • Port: 5432
  • Source: 0.0.0.0/0

Ideally you wouldn’t use a source of “0.0.0.0/0” - it means anyone can communicate with your RDS i.e. the whole Internet. You should be OK, assuming you haven’t put your RDS instance in a public subnet… :crossed_fingers:

At least use the security group that you already have as the source - that way things inside the SG can talk to other things in the SG, but nothing outside of it can.

Ideally you’d have separate SGs for your RDS and Lambda functions, but it’s a lot to do when you just get started.

Also make sure the execution role have permissions:

iamRoleStatements:
- Effect: Allow
Action:
- “ec2:CreateNetworkInterface”
- “ec2:DescribeNetworkInterfaces”
- “ec2:DeleteNetworkInterface”
Resource: “*”

https://docs.aws.amazon.com/lambda/latest/dg/vpc.html