## **摘要**
本文将介绍如何使用 Amazon Lambda\@edge 和 [Amazon CloudFront](https://aws.amazon.com/cn/cloudfront/?trk=cndc-detail) 构建多区域就近访问的应用,从而提高用户体验。与传统的多区域部署方案不同的是,该方案通过 Lambda\@edge 在 Origin Request 阶段修改请求的特性,动态地修改源站的域名,从而实现就近访问的目的。
## **方案场景介绍**
某个 Web 应用会部署在美西和新加坡,静态资源利用 S3 部署。美西和新加坡的用户通过 S3 读取图片/文本等静态信息。目前 S3 部署在新加坡,虽然 CF 可以缓存静态资源,但资源的首次加载或缓存过期、仍旧会增加美西用户的访问耗时。希望提供方案解决多个 Region 用户的访问体验。
## **解决方案**
可以在美西和新加坡 2 个区域均部署 S3 存储桶,当客户端请求访问 CloudFront 时未命中时后访问 Origin 源站时,利用 Lambda\@edge 在 Origin Request 阶段可以对 Request 请求进行 Domain 和 Host 的修改的特性,动态去修改源站的域名从而达到就近访问的目的,提高用户体验。
## **架构方案**
![image.png](https://dev-media.amazoncloud.cn/e33ae860828744568fe2f6e76684c515_image.png "image.png")
### **工作流程**
**前置工作**
1. 在 CloudFront 的 Behavior 上设置 Original Request 关联 Lambda\@edge,当请求经过 Original Request 时,通过 Lambda\@edge 进行修改。
![image.png](https://dev-media.amazoncloud.cn/2fefa3f85a2247118e232db657c2291c_image.png "image.png")
2. 在美西和新加坡上创建 S3 存储桶,并在 CloudFront 上以 S3 为目标创建源。
![image.png](https://dev-media.amazoncloud.cn/b58025abb2424ca3a2adc0f0c9f0ff87_image.png "image.png")
注意(**可选**):为了安全访问 S3 存储桶(不打开公开访问),可以通过 Cloudfront 的 OAC/OAI 身份验证方式来访问 S3,建议使用 OAC(Origin Access Control),因为它支持:
- Amazon Website Service 区域中的所有 [Amazon S3](https://aws.amazon.com/cn/s3/?trk=cndc-detail) 存储桶,包括 2022 年 12 月之后推出的选择加入区域。
- [Amazon S3](https://aws.amazon.com/cn/s3/?trk=cndc-detail) 使用 Amazon KMS 的服务器端加密(SSE-KMS)。
- 对 [Amazon S3](https://aws.amazon.com/cn/s3/?trk=cndc-detail) 的动态请求( PUT 和 DELETE )。
以下为 OAC 的配置(**可选**)
**在 S3 的 Policy 上配置只允许 OAC 访问**
```js
{
"Version": "2012-10-17",
"Statement": {
"Sid": "AllowCloudFrontServicePrincipalReadOnly",
"Effect": "Allow",
"Principal": {
"Service": "cloudfront.amazonaws.com"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::<S3 bucket name>/*",
"Condition": {
"StringEquals": {
"AWS:SourceArn": "arn:aws:cloudfront::<AWS 账户 ID>:distribution/<CloudFront distribution ID>"
}
}
}
}
```
**在 CloudFront 的 Origin 上开启 OAC**
![image.png](https://dev-media.amazoncloud.cn/75ee17d268cf4a91a8687d87f6a8aace_image.png "image.png")
![image.png](https://dev-media.amazoncloud.cn/a7fe397426f74f5997ae7dfa6bd7a147_image.png "image.png")
选择“签署请求”后,CloudFront 将使用 SigV4 签署每个发送到 S3 的 Request。它将包含身份验证信息、日期、动作、和参数等。以 Authorization head 的形式发送给 S3,S3 接受到信息后,通过密钥对信息进行签名然后进行比对。通过后处理请求。
最后,创建 Origin group,并将 CloudFront 的 behavior 上的源配置改为 Origin Group。
![image.png](https://dev-media.amazoncloud.cn/8a9bb4137e764494b0ca1b607a1f3d60_image.png "image.png")
![image.png](https://dev-media.amazoncloud.cn/1116574f38d241a994e258ed193786b2_image.png "image.png")
具体流程
1. 客户端发送请求至 CloudFront。
2. 请求未命中缓存时,在 Edge Location 上,CloudFront 在 Origin Request 调用 Lambda@edge。
3. Lambda@edge 读取当前 Region 的 Code,识别出是 us-west2 或者是 ap-southeast-1,并根据代码里预设的 map。找到该 region 对应的 S3 的地址(注意:也可以基于 route53 的延迟策略来找到最合适的 S3 存储桶位置,参考资料里会有此种方法的链接)。
4. Lambda@edge 修改 Request 的域名地址,Request 请求会路由到对应 S3 存储桶。
5. 返回从存储桶获取的对象到客户端。
Lambda@edge 代码具体如下:
```js
import json
us_bucket = "amazon-cloudfront-secure-static-site-s3bucketroot-21aorhazvsj1.s3.amazonaws.com"
ap_bucket = "amazon-cloudfront-sin-s3root.s3.ap-southeast-1.amazonaws.com"
default_bucket = "amazon-cloudfront-secure-static-site-s3bucketroot-21aorhazvsj1.s3.amazonaws.com"
s3Mapping = {
"us-east-1": us_bucket,
"us-east-2": us_bucket,
"eu-central-1": us_bucket,
"eu-west-2": us_bucket,
"ap-southeast-1": ap_bucket
}
def lambda_handler(event, context):
request = event['Records'][0]['cf']['request']
defaultRequest = event['Records'][0]['cf']['request']
print(request['origin'])
print('req='+json.dumps(request))
try:
originKey = list(request['origin'].keys())[0]
if originKey != 's3'
return request
currentRegion = context.invoked_function_arn.split(':')[3]
print("aaa="+currentRegion)
domainName = s3Mapping.get(currentRegion, default_bucket)
request['origin']['s3']['domainName'] = domainName
request['headers']['host'] = [{'key': 'host', 'value': domainName}]
return request
except TypeError:
print('modify s3 domain failure')
return defaultRequest
```
## **总结**
本文介绍了如何利用 Lambda\@edge 构建多区域就近访问应用,从而减少回源延迟,提升性能。通过在 CloudFront 中配置多个区域的源站,并利用 Lambda\@edge 在 Origin Request 阶段修改请求的特性,动态地修改源站的域名,可以实现就近访问的目的。此外,这个方案还具有以下几个优势:
* **高可用性**:通过在多个区域部署源站和 CloudFront,可以实现高可用性,确保即使某个区域出现问题,应用仍然可以正常访问。
* **高性能**:通过将静态资源分发到多个区域,并在边缘节点上缓存最新的资源,并在下一次请求时直接从缓存中获取,可以减少回源的次数和延迟,从而提高性能。
* **通用性**:除了对 S3 源站进行加速,在 API Gateway 等服务中也可以应用类似的方案,实现跨区域访问降低延迟的目的。
通过利用 Lambda\@edge 和 CloudFront 构建多区域就近访问应用,可以提高用户体验,减少回源延迟,提升性能,同时还具有高可用性和通用性,是一个非常值得尝试的解决方案。
## **参考资料**
OAC: [Amazon CloudFront](https://aws.amazon.com/cn/cloudfront/?trk=cndc-detail) introduces Origin Access Control (OAC) | Networking
https://aws.amazon.com/cn/blogs/networking-and-content-delivery/amazon-cloudfront-introduces-origin-access-control-oac/?trk=cndc-detail
SignV4:
https://docs.aws.amazon.com/IAM/latest/UserGuide/signing-elements.html?trk=cndc-detail
基于 Route53 的延迟策略动态修改 API gateway:
https://aws.amazon.com/cn/blogs/china/build-nft-offering-systems-with-cloud-native-components/?trk=cndc-detail
![开发者尾巴.gif](https://dev-media.amazoncloud.cn/0754daee74264fbbaa06c671402a12e2_%E5%BC%80%E5%8F%91%E8%80%85%E5%B0%BE%E5%B7%B4.gif "开发者尾巴.gif")