亚马逊AWS官方博客

从托管服务 Amazon Lambda 访问客户 Amazon VPC 内部私有资源

专题摘要

亚马逊云科技托管服务访问用户 Amazon VPC 内部私有资源,通常发生在计算或接口类的托管服务访问用户 VPC 内部资源的情形下。我们会选择性的针对 Amazon Lambda,Amazon Glue 以及 Amazon API Gateway 访问不同的资源进行详细的探讨。这些托管服务访问 VPC 内部资源,和前面章节中讲到的 VPC 内部实例通过 VPC 接口终端节点访问外部托管服务,虽然看似相反的逻辑,但本质上非常接近,都是通过在 VPC 内生成弹性网络接口进行交互的。

本文是“在亚马逊云科技上围绕 Amazon VPC 打造内外兼修的合适架构”系列主题的第三部分,主要讲述在亚马逊云科技中的托管服务 Amazon Lambda 中,如何访问 VPC 中的内部诸如 Amazon RDS 数据库或缓存数据库 Amazon ElastiCache 的服务。

本系列专题由如下几部分组成:

场景介绍

由于 Amazon Lambda 是在客户 VPC 之外的亚马逊云科技托管服务,而 Amazon RDS 服务在客户的 VPC 内部。因此,Lambda 访问 VPC 中的 Amazon RDS 实例,一般通过在 Lambda 中设置 VPC 以及子网等信息,这样 Lambda 在运行的时候会在指定的子网中生成弹性网络接口 ENI,以便能够和 VPC 内部的资源例如 RDS 进行交互。

为 Lambda 配置 VPC 和子网信息后,在 VPC 内部生成的弹性网络接口 ENI 会分配到子网内部的 IP 地址。同时,也意味着,Lambda 在访问该 VPC 外部的托管服务时,也变成了从客户 VPC 内部访问外部托管服务的交互方式。这样,如果 Lambda 还需要访问 VPC 外部的托管服务诸如 Amazon S3,Amazon SNS,Amazon DynamoDB 的时候,如果其所在的子网不能通过 NAT 网关连接外网,就需要建立 VPC 终端节点来访问这些托管服务。

实验环境配置

在本实验中,我们通过设置 Lambda 的 VPC、子网以及安全组等属性,来访问已经创建好的 RDS 数据库,同时,通过在该 VPC 中创建接口终端节点,Lambda 还可以发送消息给 SNS。实验的架构图如下图所示:

在实验之前,我们根据架构图创建或修改必要的现有安全组。由于 Lambda 需要配置并访问 Amazon RDS,同时还要通过 VPC 终端节点来访问其他的托管服务例如 Amazon SNS,因此我们需要新建或者修改如下安全组,以确保网络连接畅通。

实验中,Lambda 关联的安全组 “lambda-sg” 允许在端口 3306 访问 RDS 数据库,同时也允许在端口 443 访问接口终端节点关联的安全组 “endpoint-sg”,如下表所示:

安全组 “lambda-sg” 的出站规则:

Security Group “lambda-sg” Outbound Rule
Protocol Port range Destination Description
TCP 3306 rds-sg 允许 lambda 发起在端口 3306 访问 RDS 所在的安全组 rds-sg 的出站流量。
TCP 443 endpoint-sg 允许 lambda 发起在端口 443 访问 VPC 终端节点所在的安全组 endpoint-sg 的出站流量。

安全组 “rds-sg” 的入站规则:

Security Group “rds-sg” Inbound Rule
Protocol Port range Source Description
TCP 3306 lambda-sg 允许来自 lambda 所在的安全组中发起的在端口 3306 访问 RDS 数据库的入站流量。

安全组 “lambda-sg” 的入站规则:

Security Group “endpoint-sg” Inbound Rule
Protocol Port range Source Description
TCP 443 lambda-sg 允许来自 lambda 所在的安全组中发起的在端口 443 访问网络终端节点的入站流量。

我们按照如下的步骤进行实验操作:

  1. 在中国宁夏区,创建新的 SNS Topic,名为 lambda-test-topic, arn 为 “arn:aws-cn:sns:cn-northwest-1:XXXXXXXXXXXX:lambda-test-topic”。

  1. 创建 Lambda 执行的角色 LambdaExecRole,其策略中包括访问 SNS Topic 的权限和 Lambda 执行日志输出访问 CloudWatch Logs 的权限。另外,由于 Lambda 在配置 VPC 属性后,会在运行时动态创建弹性网络接口,所以 Lambda 的权限需还要包含策略 “AWSLambdaVPCAccessExecutionRole”。

  1. 将如下的 python 编写的 Lambda 代码部署到中国宁夏区域,选择执行角色为 LambdaExecRole,不配置 VPC 网络设置。
import sys
import logging
import pymysql
import json
import boto3

#Those can be configured as Enironment Variables in Lambda.
rds_host = "XXXXXXXXXX.csz5pwpxpwqe.rds.cn-northwest-1.amazonaws.com.cn"
name = "dbuser"

#Password can be saved in Amazon Secret Manager for security purpose.
#It can be configured as ARN of the Amazon Secretes Manager and 
#the corresponding APIs can be called to get the decypted password. 
password = "dbpassword" 
db_name = "myDatabase"
sns_topic_arn = "arn:aws-cn:sns:cn-northwest-1:XXXXXXXXXXXX:lambda-test-topic"
notification = "Here is the SNS notification for Lambda function ."

logger = logging.getLogger()
logger.setLevel(logging.INFO)

try:
    conn = pymysql.connect(host=rds_host, user=name, passwd=password, db=db_name, connect_timeout=5)
except pymysql.MySQLError as e:
    logger.error("ERROR: Unexpected error: Could not connect to MySQL instance.")
    logger.error(e)
    sys.exit()

logger.info("SUCCESS: Connection to RDS MySQL instance succeeded")
def lambda_handler(event, context):

    item_count = 0

    with conn.cursor() as cur:
        cur.execute("select * from employees")
        for row in cur:
            item_count += 1
            logger.info(row)

    client = boto3.client('sns')
    response = ""

    try: 
        response = client.publish (
            TargetArn = sns_topic_arn,
            Message = json.dumps({'default': notification}),
            MessageStructure = 'json'
        )
        
    except:
        logger.error("ERROR: Unexpected error: Could not connect to SNS Topic.")
        sys.exit()

    logger.info("SUCCESS: put message to SNS Topic.")

    return {
      'statusCode': 200,
      'body': json.dumps(response)
    }
  1. 通过控制台测试该 Lambda 函数,会出现访问 RDS 数据库超时(timed out)的错误。这是由于 Lambda 是运行在亚马逊云科技托管的 VPC 中,因而网络上无法访问客户 VPC 中的资源。

  1. 在该 Lambda 的【配置】选项卡中的【VPC】属性,点击【编辑】配置如下 VPC 属性,选择私有子网 TEST Private Subet(AZ1)TEST Private Subet(AZ2),并选择安全组 lambda-sg 后,点击<保存>。

  1. 再次通过控制台,对此 Lambda 函数进行测试,则可以看到成功访问 RDS 中的日志数据,但是依然无法访问 SNS Topic,这是因为 Lambda 配置了 VPC 信息后,没有路由通过 NAT 网关访问互联网,也没有对应的 VPC 终端节点。

  1. 我们创建名为 endpoint-SNS 的终端节点,服务名称为 “com.amazonaws.cn-northwest-1.sns”,安全组选择 endpoint-sg



  1. 再次通过控制台,测试并调用此 Lambda 函数,则可以成功访问 RDS 中的数据,同时可以访问 SNS Topic。

结论和总结

综上,我们为 Lambda 配置了 VPC 信息,包括子网和安全组后,Lambda 在运行时,会在配置的子网里生成弹性网络接口 ENI,该网络接口需要按照 VPC 内部网络访问规则来访问 Amazon RDS 等服务。因此,需要保证 Lambda 所配置的子网以及安全组中的规则以及被访问的 RDS 所在的子网以及安全组中的规则,在指定的端口可以通行。

同时这也意味着,如果 Lambda 所配置的子网如果无法通过 NAT 网关访问互联网,那么 Lambda 本身也无法通过互联网访问 VPC 外的其他托管服务例如 Amazon SNS,Amazon S3 等,因而在这种情况下,还需要建立相应的接口终端节点来通过 PrivateLink 访问这些服务的终端节点服务(Endpoint Service)。

特别提示:

Lambda 配置 VPC 信息,并不代表 Lambda 本身在客户 VPC 内部运行,只是代表 Lambda 执行的上下文中,拥有了访问 VPC 内部资源的能力。Lambda 本身还是在亚马逊托管的 VPC 内部运行,因而还是属于 VPC 外部托管的服务,因而可以被其他的托管服务直接访问或者直接集成,例如通过各种触发器比如 Event Bridge,API Gateway 来触发 Lambda。另外,Lambda 本身在这种情况下,如果需要访问其他的客户 VPC 之外的服务,就需要通过 NAT 网关或者 VPC 终端节点进行访问。

Lambda 配置的子网,即使是公有子网,由于无法获得公有 IP,因此也无法和互联网直接通信。也就是说,在这种情况下,要访问 VPC 外部的其他托管服务,就必须用 VPC 终端节点。所以,一般来说,我们并不推荐为 Lambda 配置公有子网,而是配置私有子网,在获得访问 VPC 内部资源的同时,如果需要访问 VPC 外部托管服务,可以通过公有子网中的 NAT 网关,或者创建 VPC 终端节点访问。但是此规则并不限制 Amazon CloudWatch Log 服务,这是为了方便帮助用户在排查函数中的故障,Lambda 内置了自动和 CloudWatch Logs 集成的机制。在设置相应的权限后(Lambda 的 Execution Role),就可以将代码中的日志推送到与 Lambda 函数关联的 CloudWatch Logs 组中,该日志组名为 /aws/lambda/<function name>。

同样,Lambda 配置 VPC 信息后生成的弹性网络接口 ENI,会占用相应子网中的可用 IP,因此需要预留 IP。另外,Lambda 会根据所配置的安全组和子网的组合的唯一性来生成弹性网络接口,也就是说,两个不同的 Lambda 函数,如果配置同样的参数,他们会复用已有的弹性网络接口,而不会创建新的,这样会节省子网内部 IP地址。在实际应用中,不仅仅需要考虑 Lambda 本身生成的弹性网络接口 ENI 的个数,还需要考虑因此而附加需要的访问其他外部托管服务的 VPC 接口终端节点生成的 ENI 占用的 IP 地址。

另外,Lambda 配置 VPC 信息需要 Lambda 执行角色 IAM Role 具有特殊权限策略 “AWSLambdaVPCAccessExecutionRole”,以保证在服务运行的时候可以创建弹性网络接口。

参考链接

VPC networking for Lambda

https://docs.aws.amazon.com/lambda/latest/dg/foundation-networking.html

New – Access Resources in a VPC from Your Lambda Functions

https://aws.amazon.com/blogs/aws/new-access-resources-in-a-vpc-from-your-lambda-functions/

Three ways to use Amazon services from a Lambda in a VPC

https://www.alexdebrie.com/posts/aws-lambda-vpc/

Accessing Amazon CloudWatch logs for Amazon Lambda

https://docs.aws.amazon.com/lambda/latest/dg/monitoring-cloudwatchlogs.html

Announcing improved VPC networking for Amazon Lambda functions

https://aws.amazon.com/blogs/compute/announcing-improved-vpc-networking-for-aws-lambda-functions/

How to securely provide database credentials to Lambda functions by using AWS Secrets Manager

https://aws.amazon.com/blogs/security/how-to-securely-provide-database-credentials-to-lambda-functions-by-using-aws-secrets-manager/x

本篇作者

张亮

亚马逊云科技解决方案架构师,有近 17 年的 IT 从业经验,曾就职于 DXC,Misys 等公司。在多个行业的企业应用开发、架构设计及建设方面有丰富的实践经验。目前主要负责合作伙伴的架构咨询和方案设计,致力于亚马逊云科技云服务在国内的应用及推广。