使用VPC Endpoint从VPC或IDC内访问S3

一、背景

AWS PrivateLink for S3 于2020年 re:Invent 上被宣布并在2021年2月正式发布。AWS Global区域和AWS中国区域均支持本功能。

通常情况下应用程序和客户端对S3的访问是通过互联网进行,被访问的地址是S3在本区域的Endpoint,例如北京区域是 https://s3.cn-north-1.amazonaws.com.cn。要访问S3应用程序所在网段必须具有能通达到互联网的路由条目,同时需要具备公网IP、弹性IP或者借助NAT网关以访问到S3终端节点。

在2021年2月之前,如果客户对访问S3有合规要求要求必须从内部网络而不是互联网访问,一般会考虑采用如下方案:

  • Direct Connect Public VIF:从私有IDC架设专线并分配Public VIF。从IDC发送过来的流量通过Public VIF访问到S3的终端地址;
  • VPC Endpoint Gateway Endpoint:这一功能在2015年发布。在VPC内配置一个网关类型的终端节点并自动在路由表中增加一条路由,内部子网可通过本路由条目访问到S3的终端节点,并且还可保持本子网没有其他外网访问策略。这种方式的局限性是本方式只能对VPC内的服务有效,如果要访问S3的客户端是位于另外一个AWS Region的VPC内,或者位于私有IDC,那么无法实现直接访问,需要在配置了VPC Endpoint Gateway Endpoint的VPC内部署代理,并通过此代理访问S3。由此带来了架构的复杂性。

2021年2月,AWS海外区域和中国区域同时推出了S3 VPC Endpoint Interface模式的支持,在此模式下可将S3的访问节点映射为一个VPC内的ENI入口,用户可在其他AWS区域的VPC内或者IDC内直接调用本地址即可访问到S3。此方式既不需要Public VIF又无须在VPC内部署S3代理,大大简化了架构设计。

本文实验环境如下:

系统架构

实验架构如上所示,接下来将分别展示 Gateway Endpoint 模式和 Interface Endpoint 模式,并使用测试脚本模拟访问。此外,Interface Endpoint模式还允许从其他区域的VPC和私有IDC中通过Direct Connect专线等方式访问,在上图中也有对应接入架构,其访问方式和原理与Private subnet访问方式相同,本文将不再展示这一部分配置。

二、模拟内部子网环境

1、创建内部子网

构建如下测试环境:

  • 使用AWS本区域的默认VPC;分别构建外部子网和内部子网共2种类型的子网,可用区不限,两个子网使用独立的路由表;
  • 外部子网允许EC2自动获得Public IP,路由表的默认网关0.0.0.0/0指向IGW,一跳即可到达互联网;外部子网中创建一个NAT网关,分配一个EIP作为出口IP;
  • 内部子网不自动分配Public IP,路由表默认允许VPC内路由,0.0.0.0/0指向NAT网关,通过NAT网关可对外访问;
  • 两个子网重分别创建一个EC2,选择Amazon Linux 2操作系统,其余参数默认;
  • 使用外部子网的EC2作为跳板机,然后ssh登录到内部子网的EC2。

在实验开始之前,确认内部子网的路由表中包含 0.0.0.0/0 的路由条目,这将允许内部子网中的EC2向外访问安装软件和补丁升级。在此时默认路由应指向NAT网关。如下截图:

2、测试AWS CLI访问

首先配置好CLI所需要的 Access Key,或者使用IAM Role更加安全。然后在本VPC内,执行 aws s3 ls s3://mybucket/ 可以正常列出本存储桶内的文件。由于内部子网配置了默认网关出口是NAT Gateway,因此这个访问可以直接访问成功。返回信息如下:

[ec2-user@ip-172-31-200-161 ~]$ aws s3 ls s3://mybucket/
                           PRE demo1/
                           PRE demo2/
                           PRE demo3-zips/
                           PRE spectrum/
                           PRE spectrum2/
2021-02-10 03:44:46          0 demo3-zips_$folder$
[ec2-user@ip-172-31-200-161 ~]$

3、测试Python程序API调用

为了测试应用客户端访问,我们使用Python构建一个简单的程序,通过boto3库调用S3的API。执行如下命令安装Python3和Boto3库:

yum install python3 -y
pip3 install -i https://opentuna.cn/pypi/web/simple boto3

安装完成后,新建 ListBucket.py 文件,内容如下:

import boto3

# Retrieve the list of existing buckets
s3 = boto3.client('s3')
response = s3.list_buckets()

# Output the bucket names
print('Existing buckets:')
for bucket in response['Buckets']:
    print(f'  {bucket["Name"]}')

保存后,运行 python3 ListBucket.py 这个测试程序,将返回所有的桶名称。由此表示S3访问正常。

三、使用Gateway Endpoint实现从VPC内访问S3

1、断开内部子网的对外访问路由

找到上文配置的路由表,将其中 0.0.0.0/0 这一条指向NAT Gateway的默认路由条目删除。在本子网内的EC2上执行ping、cli等操作验证本子网不能访问S3。

2、配置Gateway Endpoint

进入VPC控制台,在左侧找到Endpoints终端节点,点击创建。如下截图:

在创建终端节点界面,选择服务是AWS服务,在搜索框中输入关键字 s3,找到类型是Gateway,并在下方选择VPC。然后向下滚动页面。如下截图:

在页面下方找到路由表配置,选中内部子网所对应的路由表。在策略位置,选择Full Access给予完全访问权限。接下来继续向下滚动屏幕。如下截图:

在最后一步点击创建。如下截图:

看到绿色的Available表示Gateway模式的Endpoint创建完成。如下截图:

为了验证创建成功,返回VPC所在的内部子网对应的路由表。点击查看路由表,点击第二个标签页路由条目,可看到VPC Endpoint Gateway Endpoint会在其中加入了路由条目。它包含本区域的S3终端节点的IP地址,并且下一跳为VPC Endpoint。这表示配置成功。如下截图:

接下来验证访问。

3、测试AWS CLI访问

在内部子网所在的EC2上,执行如前文测试过的 aws s3 ls s3://mybucket/ ,可看到访问正常。

4、测试Python程序API调用

运行 python3 ListBucket.py 测试程序,将返回所有的桶名称。

由此表示S3访问正常。

4、删除Gateway Endpoint

测试成功,从VPC界面删除Gateway Endpoint。在VPC的Endpoint界面删除掉终端服务后,路由表中的条目也会自动消失,不需要手工删除。

四、使用Interface Endpoint实现从其他VPC、私有IDC等环境访问S3

1、断开内部子网的对外访问路由

找到上文配置的路由表,将其中 0.0.0.0/0 这一条指向NAT Gateway的默认路由条目删除。在本子网内的EC2上执行ping、cli等操作验证本子网不能访问S3。

2、配置Interface模式下所需要的安全规则组

进入VPC控制台,创建一个新的安全规则组,规则如下:

  • 入栈:只允许443端口来源是本VPC的流量,例如 172.31.0.0/16 。如果是生产环境则可以进一步提升安全设置为仅允许EC2的IP地址访问;
  • 出栈:允许去往 0.0.0.0/0 的所有出栈流量。

保存安全规则组名称叫做 VPCEndpoint,后文配置中将要使用。

3、配置Interface Endpoint

进入VPC控制台,在左侧找到Endpoints终端节点,点击创建。在创建Endpoint界面,选择服务是 AWS Service,在搜索框中输入s3过滤显示结果,然后选择S3的Interface类型。如下截图:

选择Endpoint所在的子网。本实验使用的是可用区 northwest-1c 的子网,因此仅选中本可用区,其他可用区不选中。如果是生产环境,则可以将应用所使用的多个子网都选中。如下截图:

在选择安全组界面,首先点击默认选中的安全组边上的叉子符号,取消其选中。然后从下拉框中,选择上一步创建好的安全规则组 VPCEndpoint 。如下截图:

最后一步选择访问授权是完全访问。如下截图:

点击创建完成Interface Endpoint的创建。

进入VPC Endpoint界面,在其中找到刚才创建的类型是Interface的节点,且要等待其 Status 状态变为绿色即可开始使用。在右下角的DNS名称中,格式为 *.vpce-0f56080ae4146381b-qeitm5wo.s3.cn-northwest-1.vpce.amazonaws.com.cn 的这一段请复制下来,后续代码中需要调用。如下截图:

4、测试AWS CLI访问

请注意,需要AWSCLI版本在2.1以上才可以通过VPC Endpoint访问到S3。

执行以下命令,在AWSCLI命令后额外指定Endpoint。注意bucket是保留名称不要替换,从vpce起更换为上一步创建Endpoint后获得的域名。命令如下:

aws s3 ls s3://mybucket/ --endpoint-url https://bucket.vpce-0f56080ae4146381b-qeitm5wo.s3.cn-northwest-1.vpce.amazonaws.com.cn

命令执行后可以看到访问正常。

请注意,如果AWSCLI是1.x版本,那么在使用Endpoint访问时候可能提示如下错误:

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

解决这个问题需要升级到2.x版本才可以支持。执行 aws --version 命令可查询AWSCLI的版本。例如以下是的例子是1.18版本需要到升级2.x版本才可以正常访问。

aws-cli/1.18.147 Python/2.7.18 Linux/4.14.214-160.339.amzn2.x86_64 botocore/1.18.6

升级方法请参考相关文档。

5、测试Python程序API调用

构建一个 ListBucket_endpoint.py 文件,内容如下。请替换其中的endpoint_url为上一步在CLI测试中使用的url。注意以bucket开头。代码如下:

import boto3

# Retrieve the list of existing buckets
s3 = boto3.client(
        service_name='s3',
        endpoint_url='https://bucket.vpce-0f56080ae4146381b-qeitm5wo.s3.cn-northwest-1.vpce.amazonaws.com.cn'
)
response = s3.list_buckets()

# Output the bucket names
print('Existing buckets:')
for bucket in response['Buckets']:
    print(f'  {bucket["Name"]}')

保存后,运行 python3 ListBucket_endpoint.py 测试程序,将返回所有的桶名称。表示通过Endpoint访问S3正常。

五、Interface Endpoint 解析配置和性能测试

本节在前一环节实验成功的情况下,继续探讨如何免修改代码实现 Interface Endpoint 的调用,并将对 Interface Endpoint 在VPC内生成的访问入口进行吞吐量测试。

1、使用 Route 53 Private Zone 设置VPC内专有解析

前文在 Interface Endpoint 的实验中可以看到,无论是使用CLI方式还是使用SDK编写代码的方式访问S3,都需要修改代码显式声明新的S3 Endpoint,也就是 Interface Endpoint 生成的新的访问地址。由此对于不方便调整代码的应用程序将产生不便。如何才能实现无需修改代码的即可使用Interface Endpoint?下面通过使用 Route 53 Private Zone 设置专门的 Endpoint 解析,将原有的S3访问地址以别名方式解析到 Interface Endpoint 新生成的访问地址,即可解决这一问题。

首先进入Route53服务。点击创建新的Hosted Zone。如下截图:

在域名位置输入 s3.cn-northwest-1.amazonaws.com.cn ,也就是要访问的S3桶所在的区域对应的Endpoint。在描述位置输入 PrivateLink for S3 ,在类型位置选择 Private hosted zone 。如下截图:

在要关联的VPC位置,选择EC2所在的区域和VPC,并点击右下角按钮创建。如下截图:

创建Zone成功后,点击右侧Create record按钮创建新的解析记录。如下截图:

在创建新的解析记录界面,记录名称的位置留空,此时页面会自动显示一个灰色的blog,不用输入信息则表示是留空没有填写的状态。在记录类型的位置选择 A 记录,在 Route traffic to 选项右侧有一个 Alias 别名的开关,打开这个开关。最后从下拉框中找到最后一项 Alias to VPC endpoint 。如下截图:

此时页面将自动加载显示VPC Endpoint的区域,从下拉框中选择所在的区域,再从页面中选择VPC Endpoint的第一条记录。最后点击创建记录按钮创建解析。如下截图:

解析创建成功过后,可以看到有如下一条记录,其中Value部分值的位置,显示的是 \052 开头的一条记录。052在ASCII字符中表示通配符。至此表示解析创建成功。如下截图:

上一条zone的根记录是提供ListBucket的终端节点访问。对单个桶的文件访问还需要在这个终端节点下进一步添加通配符为子域名提供解析。添加如下一条解析记录,记录名称位置输入星号,解析目标的填写方法同上一步。如下截图:

配置解析完成。

现在回到前文实验中的EC2上,执行 aws s3 lspython ListBucket.py,不需要为命令添加 Interface Endpoint,也不需要修改代码,即可看到二者皆正常工作。其原理就是通过Route 53 Private Zone将正常的对S3 Endpoint的访问通过别名方式解析到了Interface Endpoint上,即可不用实现正常访问。

如果访问S3的客户端不在本VPC而是在远端,例如Peering的另一个Region的VPC,或者是私有IDC,那么在远程网络上也配置如上类似的解析服务即可实现不修改代码访问S3。例如当私有IDC内的应用需要通过终端节点访问时,可以在IDC所使用的企业自有DNS服务器(如微软DNS或Bind)上创建如上域名解析,将普通S3访问的Endpoint以别名方式指向到Interface Endpoint,即可完成平滑割接。

2、测试 Interface Endpoint 吞吐量

在Interface Endpoint功能尚未推出时,VPC之外的环境要通过内网访问S3,需要在VPC内架设一个EC2作为代理。而此台EC2的网络出口性能是制约S3吞吐的一个关键,且EC2还存在单点故障问题。使用Interface Endpoint后,Interface实际由VPC内的ENI虚拟网卡提供服务,可以获得更高兄能和可靠性。

吞吐量测试方法是在同一个VPC内配置数个EC2作为客户端并发访问S3的Interface Endpoint。EC2配置如下:

  • 规格 c5.xlarge,4vCPU,8GB,Up to 10Gbps的网络性能;
  • 磁盘 200GB gp3,3000 IOPS,吞吐250MB(在gp3原125MB吞吐基础上配置额外的预置吞吐);
  • 要传输的文件尺寸在3.4GB左右,格式为ISO,位于S3存储桶根目录。

测试过程使用CLI从单个节点发起访问,记录传输时间和吞吐速率;再从4个节点和8个节点并行方式同时发起访问,观察多节点访问速度是否会存在瓶颈。

单节点访问传输时间约16秒传输完成。如下截图:

4节点并发传输发起操作时可看到瞬时速度如下截图:

4节点并发传输完毕后,传输时间如下截图:

随着访问节点的数量增多,Interface Endpoint 的吞吐依然能够保证每个节点都达到和单节点一样的速度。4个节点同时传输时候速度在210MB/s左右,折算下来总吞吐在6.72Gbps以上。

接下来我们将节点增加到8个,同时发起操作,可看到8个节点都在以210MB/s以上的速度传输,速度不会因为节点的增多而衰减。

8个节点以210MB/s的速度传输折算为 210MB/s * 8 * 8 = 13.44Gbps ,可看到 Interface Gateway 的ENI网卡可满足10Gbps以上的传输需求。换句话说,当通过 Direct Connect 架设了10Gbps专线, Interface Gateway 也可完全满足对S3的访问需求。针对大于10Gbps的吞吐场景本文并未进一步发起测试,当前结果仅供参考。

通过以上测试,可以看到使用Interface Endpoint可以获得比使用EC2作为S3 Proxy更好的性能。

六、小结

Gatewa Endpoint和Interface Endpoint两种方式提供S3访问。二者可同时使用不冲突。同时使用二者时候网络流量的区分是:

  • CLI和程序如果不指定Endpoint时候,流量从默认路由条目自动寻找Gateway Endpoint,然后流向S3入口;
  • CLI和程序指定Interface Endpoint的时候,流量会送往本VPC的一个ENI网卡对应的内网IP,然后流量去往S3;
  • CLI和程序不指定Endpoint时候,如果通过 Route 53 Private Zone 配置了私有解析将S3访问的域名做别名指向到Interface Endpoint上,那么CLI和程序将通过Interface Endpoint访问S3。

二者差异:

  • Gateway Endpoint方式可以无需修改代码;Interface Endpoint方式需要在程序中显式声明接口地址,或者通过Route53 Private Zone增加别名解析;
  • Gateway Endpoint方式只能在本VPC内访问,如果从VPC以外的地方发起访问,则需要搭建代理机制;Interface Endpoint 方式则体现为本VPC的ENI网卡具有内网地址,可以从三层网络能通达的位置(包括其他Region的VPC Peering以及IDC等地点)发起调用;
  • Gateway Endpoint方式不限制网络性能,每个EC2访问S3的吞吐由本EC2的网络吞吐能力决定;Interface Endpoint 方式是在VPC内模拟了ENI网卡作为S3流量入口,测试结果显示ENI可承担超过10Gbps的流量。此外还可以通过在每个AZ分别部署 Interface Endpoint 并且一个子网内可以部署多个Interface Endpoint来提升吞吐能力。

综上所述,Interface Endpoint方式赋予整个架构更大的灵活性,更加简单方便的实现私密性的合规接入,建议与Gateway Endpoint搭配使用可获得最佳效果。

七、参考资料

英文博客新功能发布:

https://aws.amazon.com/blogs/aws/aws-privatelink-for-amazon-s3-now-available/

官网文档:

https://docs.aws.amazon.com/AmazonS3/latest/userguide/privatelink-interface-endpoints.html#accessing-bucket-and-aps-from-interface-endpoints

Python Boto3 SDK参考文档:

https://boto3.amazonaws.com/v1/documentation/api/latest/guide/s3-example-creating-buckets.html