亚马逊AWS官方博客

CloudFront 部署小指南(十六): 通过 CloudFront Functions 实现动态选择源站

动态选择源站(Origin Modification)是 CDN 的一个典型应用场景,特别在网页服务场景中,服务提供方希望可以根据访问者的地理位置或者设备信息将 HTTP 请求分配到合适的源站进行数据的处理和页面的渲染。

过去,Amazon CloudFront 使用 Lambda@Edge 或者使用重定向的功能来实现源站的动态选择;现在,Amazon CloudFront 支持在 CloudFront Functions 中对源站服务器进行动态的修改。您可以在 CloudFront Functions 中编写自定义逻辑,将请求转发到亚马逊云科技源站如 S3/API gateway/ELB,也可以将请求转发到任何公共 HTTP 端点,获得更加高的性能和性价比。

在接下来的博客中,我们将为大家介绍 CloudFront Functions 对源站服务器进行动态修改的功能架构和应用场景。

内容简介

我们将首先介绍这一功能的工作原理及其在 CloudFront 中的适用场景,分析这一功能在实际应用场景中的优势,展示它相比传统方式的独特价值。接着将详细说明如何部署和配置相关的 CloudFront Functions 代码。

通过这篇文章,希望读者可以理解 CloudFront Functions“修改源站服务器”功能的特点,并获得在具体项目中加以应用的实践指引。

架构描述

CloudFront Functions 是运行在 Amazon CloudFront 服务之上的边缘脚本,当来自客户端的 HTTP 请求进入 CloudFront(Viewer Request),或者 HTTP 响应从 CloudFront 发往客户端时(Viewer Response),我们都可以配置 CloudFront Function 来运行特定的代码,比如客户端鉴权 / HTTP header 改写 / URI 改写 / 自定义响应等。详细介绍请参考《Amazon CloudFront部署小指南(四)》。

发布 CloudFront Functions Origin Modification 这一功能后,我们可以在 Viewer Request 触发器下使用 CloudFront Functions,根据客户端传递的信息或 CloudFront 自带的请求头,动态选择不同的源站。下图展示了在 CloudFront Functions 中使用方法 updateRequestOrigin() 路由至不同的源站的架构。在此方法中,可以指定回源的特征,如源站域名、请求路径、请求协议、超时时间等。

相比于 Lambda@Edge 完成动态源站选择,CloudFront Functions 可以提供更低的延时和更高的扩展性。

  • 性能:Lambda@Edge – >10ms 每次执行时间;CloudFront Functions – <1ms 每次执行时间
  • 可扩展性:Lambda@Edge – up to 10K 每个区域每秒;CloudFront Functions – 10,000K 每秒并且可提升

在成本上,CloudFront Function 相比 Lambda Edge 的单价更低

  • Pricing:Lambda@Edge – $0.60/million invocation;CloudFront Functions – $0.10/million invocation

在执行效率上,Lambda@Edge 可以在CloudFront 到源站的转发路径上运行,所以当您拥有高度可缓存的内容时,Lambda@Edge 更具成本效益,因为它仅在缓存未命中时运行,而 CloudFront Functions在每个请求时运行。

在代码实现上,Lambda@Edge 可以执行更加复杂的逻辑,如从第三方数据源(如 Amazon DynamoDB 或 Amazon S3)获取数据。

所以我们建议在以下场景使用 CloudFront Functions 动态源站选择功能:

  • 当您的请求是动态的(无法被缓存)且始终回源时,CloudFront Functions 可提供更好的性能和更低的总体成本,例如需要动态回源的 A/B Testing 或需要根据客户端 IP 动态就近回源。
  • 当您已经有一个将在每个请求上运行的现有 Viewer Request CloudFront Functions 时,您可以将源站更新逻辑添加到现有函数中。

部署演示

演示场景:“根据客户端地址动态指定源站”

此演示将分别在三个 Region 的 EC2 上搭建源站服务器,分别是 us-east-1、ap-southeast-1、eu-central-1;CloudFront 根据访问客户端 IP 地址所在的地理位置,Cloudfront 会自动产生标头“CloudFront-Viewer-Country”,来动态就近选择源站服务器。

Step 1:创建 CloudFront Functions

CloudFront Functions Runtime 2.0 与 1.0 的区别:

  • 语言支持:
    • Runtime 1.0 支持 ES5.1,以及 ES6~9 的部分功能
    • Runtime 2.0 支持 ES5.1,以及 ES6~12 的部分功能
  • 模块/方法支持:
    • Runtime 2.0 支持更丰富的模块,如新增 Buffer 模块
    • Runtime 2.0 新增支持 atob() & btoa()全局函数
    • Runtime 2.0 移除了 1.0 中非标准的 string 方法

综上,新创建的 CloudFront Functions 均建议使用 Runtime2.0,它提供了更丰富的功能和更完善的开发体验。

Step 2:在三个区域分别启动 EC2 实例,部署 Nginx 服务,作为源站

在每台 EC2 上部署 NGINX 和带有亚马逊云科技 region 信息的页面

sudo yum install nginx
sudo systemctl start nginx
cd /usr/share/nginx/html/
vi index.html  --修改不同源站的内容,以区分地区
curl http://localhost/index.html --判断Nginx是否可以访问以及是否生效
PowerShell

在安全组放开 80 端口,此处因为是 Demo,所以未使用 HTTPS,生产环境建议使用 HTTPS 协议。

Step 3:CloudFront Functions 测试

当前 CloudFront Functions 未发布到线上,先完成测试。

CloudFront → 函数 → <函数名> → 测试 :

因为未发布到线上,所以“阶段”选择 Development,由于我们所需的 Header 头是 CloudFront 在 Viewer 收到请求后添加的,在测试阶段需要显示指定对应 Header。

由于 Functions 部署在 Viewer Request,所以执行结果输出无法提供具体的回源信息。可以在 CloudFront Functions 里打印最终的回源 domain,根据打印日志,确认代码逻辑是否正常:

根据 Viewer Country 为 CN,最终选择的源站为 ap-southeast-1,符合预期。

Step 4:为 CloudFront 分发创建缺省源站

CloudFront → 分配 → <分配名> → 源 → 创建源

需要在分发中配置一个缺省源站,才能进行 Step5 Behavior 里的相关配置。此处源站可随意选择上述三个区域的源站之一,也可以是任意其他地址。

Step 5:修改 CloudFront 分发行为

CloudFront → 分配 → <分配名> → 行为 → <行为名>

将第 4 步创建的缺省源站关联到行为中

为该行为配置缓存键和源请求策略

模拟的业务场景为动态请求,需要回源,所以在缓存策略处配置“CachingDisabled”;如果源站配置了和终端访问域名(CloudFront 备用域名)相同的 Servername,则源请求策略建议选择“AllViewer”;如果不是的话,选择“AllViewerExceptHostHeader”。

Step 6:关联并发布 CloudFront Functions

CloudFront → 分配 → <分配名> → 行为 → <行为名> → 函数关联:

CloudFront → 函数 → <函数名> → 发布 → 发布函数:

Step 7:验证功能是否生效

从中国大陆访问:模拟来自中国大陆的 HTTP 访问,Country 为 CN,根据 Functions 逻辑应访问 ap-southeast-1 的服务器。

查看 Functions Logs(CloudFront Functions logs 存储在 us-east-1 的 CloudWatch LogGroup 里):

从美国访问:

查看 Functions Logs:

附录

CloudFront Functions 代码:

import cf from 'cloudfront'; //启用对源站服务器进行修改需要引入的模块

function handler(event) {
    const request = event.request;
    const headers = request.headers;
    const country = headers['cloudfront-viewer-country'] &&
        headers['cloudfront-viewer-country'].value; 
    console.log(`Viewer Country: ${country}`); //打印CloudFront判断的country信息
    const clientIp = headers['cloudfront-viewer-address'] && headers['cloudfront-viewer-address'].value;
    console.log(`Client IP: ${clientIp}`); //打印CloudFront收到的客户端IP,确认和上述country是否匹配

    let domainName;
    if (country === 'US') { //当country为US时,配置源为US的地址
        domainName = 'ec2-54-236-28-210.compute-1.amazonaws.com';
    } else if (country === 'CN' || country === 'SG' || country === 'IN') { //当country为CN或SG或IN时,配置源为新加坡的地址
        domainName = 'ec2-47-129-137-148.ap-southeast-1.compute.amazonaws.com';
    } else if (country === 'DE' || country === 'IE' || country === 'GB' || country === 'FR') { //当country为DE/IE/GB等欧洲地区时,配置源为法兰克福的地址
        domainName = 'ec2-23-129-16-28.eu-central-1.compute.amazonaws.com';
    } else {
        domainName = 'default.compute-1.amazonaws.com';
    }

    console.log(`Domain Name: ${domainName}`); //打印对应的原服务器信息

    cf.updateRequestOrigin({
        "domainName": domainName
    });
    return request;
}
PowerShell

总结

本文介绍了 Amazon CloudFront 发布的 Functions 新功能——支持对源站服务器进行修改。这一重要更新为用户提供了更灵活的请求路由控制能力,使得在边缘位置实现智能请求分发成为可能。

主要优势包括:

  • 性能提升:相比 Lambda@Edge,CloudFront Functions 运行在边缘节点层面,可以处理更高的请求量,延迟更低。
  • 成本效益:对于需要动态路由的场景,CloudFront Functions 的成本是 L@E 的六分之一,是一个更经济的解决方案。

不过在选择使用 CloudFront Functions 还是 Lambda@Edge 时,需要根据具体使用场景权衡。高度可缓存内容可能更适合 Lambda@Edge,而对于需要动态路由且请求量大的场景,CloudFront Functions 则更具优势。

这一功能的发布进一步丰富了 Amazon CloudFront 的功能集,为构建全球化、高性能的应用提供了更多可能性。该功能目前在亚马逊云科技全球支持,中国区暂不支持。

本篇作者

李宛真

亚马逊云科技技术客户经理,主要负责企业级客户的安全合规、成本管理和技术支持等工作。目前专注于协助客户落地在亚马逊云的安全合规工作。在加入亚马逊云科技前拥有丰富的视频云行业(如 CDN、直播、对像存储等)的技术支持经验。

卢秋婷

亚马逊云科技技术客户经理,负责企业级客户出海业务的架构设计、成本优化和技术支持等工作,在加入亚马逊云科技前拥有丰富的视频云行业的技术支持经验。

叶明

亚马逊云科技边缘产品架构师,负责 CloudFront 和 Global Accelerator 服务在中国和全球的市场拓展,专注于互联网用户访问云上服务的感受的优化以及数据洞察。在互联网基础设施领域有丰富的实践经验。