从 VPC 中的 Lambda 访问 AWS S3

Access AWS S3 from Lambda within VPC

总的来说,我对在 VPC 中使用 AWS Lambda 感到很困惑。问题是 Lambda 在尝试访问 S3 存储桶时超时。解决方案似乎是 VPC 端点。

我已将 Lambda 函数添​​加到 VPC,以便它可以访问 RDS 托管数据库(下面的代码中未显示,但功能正常)。但是,现在我无法访问 S3,并且任何尝试都会超时。

我尝试创建一个 VPC S3 端点,但没有任何改变。

VPC 配置

每当我第一次创建 EC2 实例时,我都会使用默认创建的简单 VPC。它有四个子网,都是默认创建的。

VPC 路由 Table

_Destination - Target - Status - Propagated_

172.31.0.0/16 - local - Active - No

pl-63a5400a (com.amazonaws.us-east-1.s3) - vpce-b44c8bdd - Active - No

0.0.0.0/0 - igw-325e6a56 - Active - No

简单的 S3 下载 Lambda:

import boto3
import pymysql
from StringIO import StringIO

def lambda_handler(event, context):
    s3Obj = StringIO()

    return boto3.resource('s3').Bucket('marineharvester').download_fileobj('Holding - Midsummer/sample', s3Obj)

我的问题的原因是没有正确配置我的安全组的出站规则。具体来说,我需要添加目的地为 pl-XXXXXXXX(S3 服务。实际值由 AWS 控制台提供)的自定义协议出站规则。

使用 boto3,S3 url 默认是 virtual,然后需要将互联网访问解析为区域特定的 url。这会导致 Lambda 函数挂起直到超时。

要解决此问题,需要在创建客户端时使用 Config 对象,它告诉 boto3 创建基于 path 的 S3 url:

import boto3 
import botocore

client = boto3.client('s3', 'ap-southeast-2', config=botocore.config.Config(s3={'addressing_style':'path'}))

请注意,调用中的区域必须是您要将 lambda 和 VPC 端点部署到的区域。

然后您将能够在 Lambda 的安全组中为 VPC 端点使用 pl-xxxxxx 前缀列表,并且仍然可以访问 S3。

这里有一个工作 CloudFormation script 可以证明这一点。它创建一个 S3 存储桶、一个与仅包含私有子网和 VPC 端点的 VPC 关联的 lambda(将记录放入存储桶)以及必要的 IAM 角色。

还有一个与其他答案中未解决的子网和路由有关的问题,因此我创建了一个单独的答案,但前提是上述所有答案都适用。您必须让它们都正确,lambda 函数才能访问 S3。

当您创建我去年秋天创建的新 AWS 帐户时,没有路由 table 自动与您的默认 VPC 关联(请参阅控制台中的路由表 -> 子网关联)。

因此,如果您按照 instructions 创建端点并为该端点创建路由,则不会添加任何路由,因为没有子网可以放置它。与 AWS 一样,您不会收到错误消息...

你应该做的是为你的 lambda 函数创建一个子网,将该子网与路由 table 和 lambda 函数相关联,然后重新运行端点指令,如果成功,你将找到一条路由table 包含三个这样的条目:

Destination     Target
10.0.0.0/16     Local
0.0.0.0/0       igw-1a2b3c4d
pl-1a2b3c4d     vpce-11bb22cc

如果您只有两个条目(没有'pl-xxxxx'条目),那么您还没有成功。

最后,我想 lambda 函数需要一个子网才能生存就不足为奇了,就像网络中的任何其他实体一样。最好不要将它与您的 EC2 实例放在同一个子网上,因为 lambda 可能需要不同的路由或安全权限。请注意,lambda 中的 GUI 确实希望您在两个不同的 AZ 中拥有两个子网,这也是一个好主意。

还有一个与 VPC 端点相关的解决方案。

在 AWS 控制台上,选择 VPC 服务,然后选择端点。创建一个新端点,将其关联到 s3 服务

然后 select VPC 和路由 Table。

然后 select 访问级别(完全或自定义),它将起作用。

我只想在其他答案中添加一个答案,这可能会影响冷启动时间较慢的 运行 函数。

我已按照有关为 S3 设置网关的所有说明进行操作,但仍然无法正常工作。我创建了一个测试 Node.js 函数,它只列出了存储桶 - 我验证了在没有 S3 网关的情况下这不起作用,但是一旦建立了网关就可以。所以我知道部分工作正常。

在调试时,我正在更改函数的超时以确保函数已更新,并且在调用和测试时我使用的是最新版本的代码。

我已将超时时间减少到 10 秒,结果我的函数在冷启动时需要更多的 15 秒。 一旦我再次增加超时,它就起作用了。

要从 VPC 内的 Lambda 函数内访问 S3,您可以使用 Natgateway(与 VPC 端点相比是一种昂贵得多的解决方案)。如果您在 VPC 中有两个私有子网(其中子网具有到 NAT 网关的路由)并将它们与 Lambda 关联,它可以像 VPC 外部的任何 Lambda 一样访问 S3 存储桶。 陷阱

  1. 如果您将 public 子网与 Lambda 相关联并期望它可以工作,它不会。
  2. 确保您的安全组已就位以接受进入。

这种方法将使 Lambda 函数可以访问 Internet 上的任何可用服务。详细步骤可以关注这篇博文https://blog.theodo.com/2020/01/internet-access-to-lambda-in-vpc/

添加到 的答案中,这是一个可以在 CDK 中使用的结构:

 const vpcEndpoint = new ec2.GatewayVpcEndpoint(this, 'S3GatewayVpcEndpoint', {
      vpc: myVpc,
      service: { name: 'com.amazonaws.us-west-1.s3' },
    })

    const rolePolicies = [
      {
        Sid: 'AccessToSpecificBucket',
        Effect: 'Allow',
        Action: [
          's3:ListBucket',
          's3:GetObject',
          's3:PutObject',
          's3:DeleteObject',
          's3:GetObjectVersion',
        ],
        Resource: ['arn:aws:s3:::myBucket', arn:aws:s3:::myBucket/*'],
        Principal: '*',
      },
    ]
    rolePolicies.forEach((policy) => {
      vpcEndpoint.addToPolicy(iam.PolicyStatement.fromJson(policy))
    })