Node.js Serverless API本地正常,部署到AWS Lambda后无法启停EC2实例
问题:AWS Lambda部署后未执行EC2实例启停操作(本地运行正常)
场景描述
我用Node.js编写了一个Serverless API,通过POST接口传入以下JSON来启停EC2实例:
{"action" : "stop","instance_id" : "i-xxxxxxxxxxxxxxxx"}
本地使用sls offline运行时,能正常完成EC2实例的启停操作,但部署到AWS Lambda后,函数返回200成功响应,却实际没有执行EC2的启停动作,也没有任何报错日志。
问题代码
"use strict"; require("dotenv").config(); // load the SDK for JavaScript var AWS = require("aws-sdk"); // Set the region AWS.config.update({ region: "us-west-2" }); // Create EC2 service object var ec2 = new AWS.EC2({ apiVersion: "2016-11-15" }); module.exports.trigger = async (event) => { const body = JSON.parse(event.body); let id = body.instance_id; let action = body.action; var params = { InstanceIds: [id], DryRun: true, }; if (action.toUpperCase() === "START") { // Call EC2 to start the selected instances ec2.startInstances(params, function (err, data) { if (err && err.code === "DryRunOperation") { console.log("inside start"); params.DryRun = false; ec2.startInstances(params, function (err, data) { if (err) { console.log("Error", err); // issue = err; } else if (data) { console.log("Success", data.StartingInstances); // out = data.StartingInstances; } }); } else { console.log("You don't have permission to start instances."); } }); } else if (action.toUpperCase() === "STOP") { // Call EC2 to stop the selected instances ec2.stopInstances(params, function (err, data) { if (err && err.code === "DryRunOperation") { params.DryRun = false; ec2.stopInstances(params, function (err, data) { if (err) { console.log("Error", err); // issue = err; } else if (data) { console.log("Success", data.StoppingInstances); // out = data.StoppingInstances; } }); } else { console.log("You don't have permission to stop instances", err); } }); } return { statusCode: 200, body: JSON.stringify( { message: "Go Serverless v3.0! Your function executed successfully!", input: event, }, null, 2 ), }; };
问题原因
核心矛盾在于Lambda的执行模型与回调式异步代码的兼容性:
- 函数标记为
async,但内部使用了AWS SDK的回调式API(ec2.startInstances(params, callback)) - Lambda会在执行到
return语句后立即终止函数进程,不会等待回调函数中的代码执行 - 本地运行时,Node.js进程不会自动退出,所以回调代码能正常执行;但Lambda是事件驱动的无服务器环境,函数返回后会立刻销毁执行上下文,导致回调里的EC2操作根本没机会运行
解决方案
改用AWS SDK的Promise版本配合async/await,确保Lambda等待所有异步操作完成后再返回:
修改后的代码
"use strict"; require("dotenv").config(); const AWS = require("aws-sdk"); AWS.config.update({ region: "us-west-2" }); const ec2 = new AWS.EC2({ apiVersion: "2016-11-15" }); module.exports.trigger = async (event) => { try { const body = JSON.parse(event.body); const { instance_id: id, action } = body; const upperAction = action.toUpperCase(); // 校验action合法性 if (!["START", "STOP"].includes(upperAction)) { return { statusCode: 400, body: JSON.stringify({ message: "无效操作,仅支持START/STOP" }) }; } // 先执行DryRun验证权限 const dryRunParams = { InstanceIds: [id], DryRun: true }; try { if (upperAction === "START") { await ec2.startInstances(dryRunParams).promise(); } else { await ec2.stopInstances(dryRunParams).promise(); } } catch (dryRunErr) { // DryRunOperation错误表示权限验证通过,执行实际操作 if (dryRunErr.code === "DryRunOperation") { const actualParams = { InstanceIds: [id], DryRun: false }; let result; if (upperAction === "START") { result = await ec2.startInstances(actualParams).promise(); console.log(`实例${id}启动成功`, result.StartingInstances); } else { result = await ec2.stopInstances(actualParams).promise(); console.log(`实例${id}停止成功`, result.StoppingInstances); } return { statusCode: 200, body: JSON.stringify({ message: `EC2实例${upperAction}操作执行成功`, result: result, input: event }, null, 2) }; } // 捕获其他权限或操作错误 else if (dryRunErr.code === "UnauthorizedOperation") { console.error(`权限不足:无法${upperAction}实例${id}`, dryRunErr); return { statusCode: 403, body: JSON.stringify({ message: "无权限执行该操作", error: dryRunErr.message }) }; } else { throw dryRunErr; } } // 如果DryRun没有抛出错误(理论上DryRun应该会抛出DryRunOperation),说明异常情况 return { statusCode: 500, body: JSON.stringify({ message: "DryRun验证异常,请检查配置" }) }; } catch (err) { console.error("操作失败:", err); return { statusCode: 500, body: JSON.stringify({ message: "服务器内部错误", error: err.message }) }; } };
额外检查项
- Lambda IAM角色权限:确保Lambda的执行角色拥有
ec2:StartInstances和ec2:StopInstances权限,且资源范围包含目标EC2实例(例如添加权限策略:{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["ec2:StartInstances", "ec2:StopInstances"], "Resource": "arn:aws:ec2:us-west-2:你的账户ID:instance/i-xxxxxxxxxxxxxxxx" } ] } - 日志排查:部署后查看CloudWatch Logs,确认是否有隐藏的错误日志(之前的回调代码可能因为进程终止没来得及输出日志)
内容的提问来源于stack exchange,提问作者coderByChance
相关产品推荐
相关产品推荐

