AWS Lambda部署于VPC时如何启用出站公网访问能力
VPC内Lambda访问公网超时问题排查与解决
问题场景
我基于NodeJs技术栈,使用AWS Lambda搭配AWS API Gateway开发REST API,所有基础设施通过AWS SAM模板完成定义与编排。
Lambda内的业务逻辑非常简单:通过node-fetch库向公网公开示例接口发起POST请求,对应业务代码如下:
const mysql = require('mysql2'); const errorCodes = require('source/error-codes'); const PropertiesReader = require('properties-reader'); const fetch = require('node-fetch'); const prop = PropertiesReader('properties.properties'); const con = mysql.createConnection({ host: prop.get('server.host'), user: prop.get("server.username"), password: prop.get("server.password"), port: prop.get("server.port"), database: prop.get("server.dbname") }); exports.testApi = async (event, context) => { context.callbackWaitsForEmptyEventLoop = false; con.config.namedPlaceholders = true; if (event.body == null && event.body == undefined) { var response = errorCodes.missing_parameters; return response; } let body = JSON.parse(event.body) if (body.key == null ) { console.log("fire 1"); var response = errorCodes.not_null_parameters; return response; } try { let key = body.key; console.log("body", body); var notificationMessage = { "key": key }; const notificationResponse = await fetch("https://reqbin.com/sample/post/json", { method: 'post', body: JSON.stringify(notificationMessage), headers: { 'Content-Type': 'application/json' } }); const data = await notificationResponse.json(); // 返回响应 var response = { "statusCode": 200, "headers": { "Content-Type": "application/json" }, "body": JSON.stringify({ "message": data }), "isBase64Encoded": false }; return response; } catch (error) { console.log(error); // 返回错误响应 var response = { "statusCode": 500, "headers": { "Content-Type": "application/json" }, "body": JSON.stringify({ "error": error }), "isBase64Encoded": false }; return response; } };
项目根目录下的template.yaml配置了嵌套模板引用,根模板内容如下:
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > xxx-restapi xxx-restapi 项目SAM模板 # 全局函数配置 Globals: Function: Timeout: 5 VpcConfig: SecurityGroupIds: - sg-xxxxx SubnetIds: - subnet-xxxx - subnet-aaaa - subnet-bbbb - subnet-cccc - subnet-dddd - subnet-eeee Parameters: FirebaseProjectId: Type: String # 请勿在AWS控制台手动创建该域名,否则会导致部署失败 DomainName: Type: String Default: api2.someapp.com Resources: # 需要鉴权的HTTP API AuthGatewayHttpApi: Type: AWS::Serverless::HttpApi Properties: Domain: DomainName: !Ref DomainName EndpointConfiguration: REGIONAL CertificateArn: arn:aws:acm:us-east-1:xxxxxx:certificate/bac44716-xxxx-431b-xxxx-xxxx Route53: HostedZoneId: xxxxxxx IpV6: true Auth: Authorizers: FirebaseAuthorizer: IdentitySource: $request.header.Authorization JwtConfiguration: audience: - !Ref FirebaseProjectId issuer: !Sub https://securetoken.google.com/${FirebaseProjectId} DefaultAuthorizer: FirebaseAuthorizer # 无需鉴权的HTTP API NoAuthGatewayHttpApi: Type: AWS::Serverless::HttpApi Properties: Domain: BasePath: noauth DomainName: !Ref DomainName CertificateArn: arn:aws:acm:us-east-1:xxxx:certificate/xxx-420d-xxx-xxx-xxxx Route53: HostedZoneId: xxxxxx # Lambda执行角色配置 LambdaRole: Type: 'AWS::IAM::Role' Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - 'sts:AssumeRole' Path: / ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: root PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - ec2:DescribeNetworkInterfaces - ec2:CreateNetworkInterface - ec2:DeleteNetworkInterface - ec2:DescribeInstances - ec2:AttachNetworkInterface Resource: '*' Outputs: # 栈输出配置 SharedValueOutput: Value: !Ref FirebaseProjectId Description: 可引用模板内任意资源作为输出
对应的嵌套模板文件内容如下:
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > xxx-restapi xxx-restapi 项目嵌套SAM模板 Globals: Function: Timeout: 30 VpcConfig: SecurityGroupIds: - sg-xxxx SubnetIds: - subnet-xxx - subnet-aaa - subnet-ccc - subnet-sss - subnet-fff - subnet-eee Parameters: FirebaseProjectId: Type: String DomainName: Type: String Resources: NoAuthGatewayHttpApi2: Type: AWS::Serverless::HttpApi Properties: StageName: Prod MyApiMapping: DependsOn: NoAuthGatewayHttpApi2 Type: AWS::ApiGatewayV2::ApiMapping Properties: ApiMappingKey: no-auth DomainName: api2.xxx.com ApiId: !Ref NoAuthGatewayHttpApi2 Stage: !Ref NoAuthGatewayHttpApi2.Stage TestPostFunction: Type: AWS::Serverless::Function Properties: CodeUri: xxx-restapi/ Handler: source/fcm/test-api.testApi Runtime: nodejs14.x Events: GetRtcTokenAPIEvent: Type: HttpApi Properties: Path: /fcm/test-api Method: post ApiId: !Ref NoAuthGatewayHttpApi2
故障现象
- 代码在本地环境运行完全正常,部署到AWS后调用接口返回
503状态码,响应内容:
{ "message": "Service Unavailable" }
- CloudWatch日志显示函数执行30秒后强制超时,日志内容:
START RequestId: e84242ea-xxx-4aa0-xxx-xxx Version: $LATEST 2022-06-12T05:55:52.914Z e84242ea-xxx-4aa0-xxx-xxx INFO body { key: 'value' } END RequestId: e84242ea-xxx-xxx-dd37f2a005c0 REPORT RequestId: e84242ea-xxx-4aa0xxx993e-xxx Duration: 30032.56 ms Billed Duration: 30000 ms Memory Size: 128 MB Max Memory Used: 72 MB Init Duration: 315.65 ms 2022-06-12T05:56:22.936Z xxx-b568-xxx-993e-xxx Task timed out after 30.03 seconds
- 初步排查确认:所有不需要访问公网的Lambda函数均运行正常,定位问题根因为VPC内的Lambda未配置公网出站访问能力。当前所有VPC、安全组相关配置均在上述SAM模板中定义,未在AWS控制台额外手动配置VPC、安全组类资源。
解决步骤
问题核心原因是Lambda挂载的VPC子网默认没有公网出口路由,所有发往公网的请求都会被丢弃,直到触发函数超时。按以下步骤配置即可修复:
- 确认VPC已绑定互联网网关(IGW),如果没有就先新建并关联到目标VPC。
- 新建至少1个公有子网:给该子网关联的路由表添加路由规则,目标网段
0.0.0.0/0指向刚才绑定的互联网网关。 - 在公有子网内创建NAT网关,给NAT网关分配一个弹性公网EIP。
- 找到当前Lambda所在的私有子网关联的路由表,添加路由规则:目标网段
0.0.0.0/0指向刚才创建的NAT网关。 - 检查Lambda绑定的安全组出站规则,确认已放行
0.0.0.0/0的80、443端口出站流量,默认安全组会放行所有出站流量,如果之前做过严格限制需要补全规则。 - 如果不想承担托管NAT网关的费用,也可以选择在公有子网自建NAT实例做流量转发,生产环境优先推荐使用托管NAT网关,稳定性更高。
- 所有网络配置完成后,重新部署SAM栈,不需要修改业务代码,再次测试接口即可正常获取公网接口响应,不会再出现超时问题。
注意:不要直接把Lambda放在公有子网并开启公网IP分配来实现公网访问,这种方式会让Lambda的弹性网卡直接暴露在公网,存在安全风险,仅适合临时测试使用。
内容的提问来源于stack exchange,提问作者PeakGen
相关产品推荐
相关产品推荐

