You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 19:15:33