AWS Lambda RDS 连接超时

41

我正在尝试使用Node.js编写Lambda函数,连接到我的RDS数据库。数据库可以在我的Elastic Beanstalk环境中工作和访问。当我运行该函数时,它返回超时错误。

我试图将超时时间增加到5分钟,但结果完全相同。

我经过一些研究得出的结论是,这可能是一个安全性问题,但在Amazon的文档或答案(这是我在该主题上唯一找到的答案)中找不到解决方案。

以下是安全详细信息:

  • RDS和Lambda都在同一安全组中。
  • RDS具有所有流入和流出规则。
  • Lambda在其角色中具有AmazonVPCFullAccess策略。

我的代码如下:

'use strict';
console.log("Loading getContacts function");

var AWS = require('aws-sdk');
var mysql = require('mysql');

exports.handler = (event, context, callback) => {

   var connection = mysql.createConnection({
        host     : '...',
        user     : '...',
        password : '...',
        port     : 3306,
        database: 'ebdb',
        debug    :  false
    });

    connection.connect(function(err) {
      if (err) callback(null, 'error ' +err);
      else callback(null, 'Success');
    });

};

我得到的结果是:

"errorMessage": "2017-03-05T05:57:46.851Z 9ae64c49-0168-11e7-b49a-a1e77ae6f56c Task timed out after 10.00 seconds"

你是否为Lambda函数启用了VPC访问? - abdulbarik
是的,Lambda和RDS都在同一个(默认)VPC上。 - Sir Codesalot
https://notebookbft.wordpress.com/2018/01/09/querying-rds-mysql-db-with-nodejs-lambda-function/ - Rajind Ruparathna
10个回答

30

虽然使用上下文会起作用,但您只需向处理程序添加context.callbackWaitsForEmptyEventLoop = false;,然后像这样正常使用回调函数:

exports.handler = (event, context) => {
  context.callbackWaitsForEmptyEventLoop = false; 
  var connection = mysql.createConnection({
    //connection info
  });
  connection.connect(function(err) {
    if (err) callback(err); 
    else callback(null, 'Success');
  });
};

答案在文档中(我花了几个小时才找到):http://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-using-old-runtime.html

在“比较上下文和回调方法”一节中,有一个“重要”提示来解释事情。

提示的底部写道:

因此,如果您希望与上下文方法具有相同的行为,则必须将上下文对象属性callbackWaitsForEmptyEventLoop设置为false。

基本上,回调继续到事件循环的结尾,而不是像上下文一样结束事件循环。因此,设置callbackWaitsForEmptyEventLoop可以使回调像上下文一样工作。


2
传说!谢谢,这是正确的答案: context.callbackWaitsForEmptyEventLoop = false; - Gary Benade
不是所有的英雄都穿斗篷!谢谢。context.callbackWaitsForEmptyEventLoop = false; 是正确的。const response = { statusCode: 200, body: JSON.stringify({ headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Credentials': true, }, message: await mysqlConnector.create('table_name', {test: 50}), input: event, }), };callback(null, response); - RenanSS

19

RDS和Lambda都在同一个安全组中。

这就是关键。默认情况下,同一安全组内的通信是不允许的,您需要显式地允许它 (例如 sg-xxxxx ALL TCP)。只有当您的Lambda尝试通过私有IP访问数据库时,才能起作用。

如果它尝试通过公共IP访问它,则无法运行,并且您还需要为此打开必要的端口。

然而,有更好的方法:

  1. 为您的Lambda创建单独的安全组
  2. 为Lambda的sg在RDS中允许端口3306的入站流量。

1
为Lambda创建了一个单独的安全组,并在RDS上设置了所有入站流量,但问题仍然存在... - Sir Codesalot
6
生命救援者。谁能想到AWS默认情况下会阻止同一安全组内的通信? AWS的所有教程都没有提到这一点,它们明确指出您需要将Lambda和RDS放在同一组中,但未提及您需要启用它们进行通信。(我首选的方法是添加一个入站规则,允许来自同一安全组内的所有TCP流量,但当然也可以建议创建一个新的安全组并启用它以供Lambda使用。) - hawkip
只是为了补充一些背景信息。我遇到了一个比较模糊的特例:在初始失败后重试时,有时候连接会被建立并缓存。但是一旦我添加了安全组,它就开始稳定地工作了。 你有什么想法,为什么没有安全组的情况下它可能只能“有时候”工作,并且速度很慢? - Teodor Iuliu Radu

16

我想感谢所有帮忙的人,问题的根源和我之前想象的不同。代码中的 callback 由于某些原因无法正常工作,尽管它是 AMAZON 自己的默认示例。

正常工作的代码如下:

'use strict';
console.log("Loading getContacts function");

var AWS = require('aws-sdk');
var mysql = require('mysql');

exports.handler = (event, context) => {

   var connection = mysql.createConnection({
        host     : '...',
        user     : '...',
        password : '...',
        port     : 3306,
        database: 'ebdb',
        debug    :  false
    });

    connection.connect(function(err) {
      if (err) context.fail();
      else context.succeed('Success');
    });

};

1
我为此奋斗了一个多小时,接近两个小时。我以为我的防火墙规则出了问题。天哪,仅仅删除回调行就可以解决所有问题吗?不管怎样,这是个好提示,我也做了同样的事情。一定是某种回调死锁或者其他什么问题。 - Quad64Bit
1
在调用回调函数之前,您需要结束连接。由于连接仍然保持打开状态,Lambda 函数会超时。需要像这样将 connection.end(function (err) { callback(null, response);}); 添加到 .connect() 的回调函数中。 - fireant
2
看到这个答案 - 只想指出,根据 AWS 文档,回调参数取决于您的 NodeJS 版本是否可选:https://docs.aws.amazon.com/lambda/latest/dg/nodejs-prog-model-handler.html - jarodsmk
太棒了!!!非常感谢。顺便说一下,我移除了aws-sdk和zip文件大小从10MB降到了333k,但它仍然可以工作。 - Adrian Smith

2

当您最初设置数据库时,它将自动创建一个安全组,该安全组默认为您设置数据库的IP。当您从Lambda运行时,此规则会阻止流量。查看您的数据库错误日志,您可以确认它正在拒绝连接。

***** could not be resolved: Name or service not known

您需要在安全组中创建一个规则,以允许Lambda流量。转到RDS实例控制台,点击安全组,选择入站规则。您将在那里看到规则。然后发出命令开放给世界,查找AWS Lambda IP或创建VPC。


你的答案解决了我的问题,你知道我怎么找到我的AWS Lambda IP吗?@toonsend - AngelSalazar
我认为它不是那样工作的。你需要设置一个VPC。https://docs.aws.amazon.com/lambda/latest/dg/vpc.html - toonsend

1
我也遇到过类似的超时场景。问题在于在connection.connect()之后没有执行connection.end()。应该在callback之前执行connection.end()
有效代码:
  var mysql = require('mysql');

    var connection = mysql.createConnection({
        host     : 'host_name',
        user     : 'root',
        password : 'password'
    });


    module.exports.handler = (event, context, callback) => {

// **Connection to database**      
connection.connect(function(err) {
        if (err) {
          console.error('Database connection failed: ' + err.stack);
          return;
        }
        console.log('Connected to database.');
      });

    // **Hit DB Query**
      connection.query("Query", function(err, rows, fields) {
           console.log(rows);
        });


      //**Close Connection**

connection.end(); ***// Missing this section will result in timeout***

    //**Send API Response**
      callback(null, {
              statusCode: '200',
              body: "Success",
              headers: {
                  'Content-Type': 'application/json',
              },
      });

    };

1
我正在分享连接RDS的经验。

在此期间,您需要为Lambda函数启用VPC访问,并分配安全组

然后,在分配给RDS实例的安全组内,您将启用对分配给Lambda函数的安全组的访问。

您可以在此处获取更多信息。


请阅读问题。您提到的所有内容已经在问题中涵盖了。 - Mark B
1
Lambda和RDS都在同一个(默认)VPC上。 - Sir Codesalot

1

感谢上帝,我已经为此苦苦挣扎了一个星期。 - fedemengo

1

感谢@Sir Codesalot找到了解决此问题的方法。以下代码示例展示了如何在连接后运行SQL查询。我还删除了aws-sdk,将压缩文件大小从10MB降至300k。

'use strict';

var mysql = require('mysql');

exports.handler = (event, context) => {

var connection = mysql.createConnection({
    host: 'abc.eu-west-2.rds.amazonaws.com',
    user: 'dbuser',
    password: '1234',
    port: 3306,
    database: 'db1234',
    debug: false
});

connection.connect(function (err) {

    if (err) context.fail()

    let sql = "SELECT `id`,`tel`,`email` FROM `campaigns` WHERE `campaign` LIKE 'fish'"

    connection.query(sql, function (err, result) {
        if (err) throw err;
        context.succeed(JSON.stringify(result));
    });
});

};


0
问题并不是来自超时,而是来自你关闭连接的方式。如果你不想等待回调,可以使用.destroy(),或者在.end(function(err) { //现在调用你的回调函数 });中正确使用回调函数来关闭连接。
请参见此线程以获取更详细的解释。

0

连接的结束应该在回调函数之后:

因此,正确的代码:

    'use strict';
var mysql = require('mysql');

var connection = mysql.createConnection({
    host     : 'xxxxxx.amazonaws.com',
    user     : 'testuser',
    password : 'testPWD',
    port     : 3306,
    database: 'testDB',
    debug    : false        
});

module.exports.handler = (event, context, callback) => {
    // **Connection to database**      
    connection.connect(function(err) {
        if (err) {
            console.error('Database connection failed: ' + err.stack);
            context.fail();
            return;
        }
      else{ 
            console.log('Connected to database.');
        }
    });

    connection.query('show tables from testDB', function (error, results, fields) {
        if (error) {
            console.log("error: connection failed with db!");
            connection.destroy();
            throw error;
        } else {
            // connected!
            console.log("info: connection ok with db!");
            console.log(results);
            context.succeed("done");
            callback(error, results);
        }
    });

    //Send API Response
    callback(null, {
        statusCode: '200',
        body: 'succeed',
        headers: {
          'Content-Type': 'application/json',
        },
    });

    //Close Connection
    connection.end(); // Missing this section will result in timeout***

};

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接