浅谈 AWS CloudFront 在特殊场景下的配置和错误处理

阅读数:180 2019 年 9 月 18 日 15:05

浅谈AWS CloudFront在特殊场景下的配置和错误处理

近期笔者参与了一个视频加速传输的项目,他们产品的用户遍及全球各地。为了将视频内容快速稳定提供给他们的客户,他们原先开发和部署了自己的 CDN 网络,但随着业务的发展,自建的 CDN 网络已经很难支撑不断增长的用户规模,我们与客户一起构建基于 AWS Cloudfront 与自建 CDN 的混合部署满足更大的业务需求。本文适用于 AWS Global Region 环境,分为以下两部分内容:

  1. 第一部分介绍了如何通过 Lambda@Edge 替待 presigned url 来防止 Cloudfront 内容被非法访问。
  2. 第二部分介绍了在使用 Cloudfront 过程中针对发生的问题如何进行分析和排除。

1. 使用 Lambda@Edge 替代 presigned url 实现防盗链

AWS 官方提供了 Pre-signed URL 功能防止对通过 CloudFront 内容的非法访问,但是在测试过程中,我们发现部分安卓、低端设备对于可以接受的 URL 的长度有限制,进而导致 Pre-signed URL 不被接受。为了解决此问题,需要将客户自研 CDN 的防盗链机制和 Cloudfront 通过 Lambda@edge 结合在一起。在此过程中有以下几点注意事项:

a. 针对 HLS 视频配置 m3u8 文件的缓存行为时,需要通过下图的设置将其设置成为不缓存:
浅谈AWS CloudFront在特殊场景下的配置和错误处理

提示:这里需要说明的是,虽然大多数情况下,只要缓存 TTL 小于 TS 分片产生的时间间隔就可以进行缓存(参考文档)。但在本场景中,由于每个客户机顶盒设备获取的 m3u8 文件内容都不相同,无需缓存,因此我们将 TTL 全部设置为 0。

同时通过下列设置将客户端设备访问内容时所带的用于识别身份的查询串 token 除了在 lambda@edge 中进行处理外,还通过 CloudFront 转发给作为源的客户自研 CDN,这样客户自研 CDN 在接收到 token 后可以将将其附加到其产生的 m3u8 文件中的 TS 分片连接(这里需要说明的是用户自研 CDN 并不是像 CloudFront 一样是一个纯粹的内容分发网络,它还具有视频转码和重新分片的功能):

浅谈AWS CloudFront在特殊场景下的配置和错误处理

b. 针对 HLS 视频配置 TS 文件的缓存行为时,由于是实时视频(作为源的客户自研 CDN 每 3 秒产生一个 m3u8 文件,里面包含 3 个播放时长是 3 秒的 TS 分片),所以无需缓存太长时间:
浅谈AWS CloudFront在特殊场景下的配置和错误处理

另外对于终端设备访问 TS 文件时所带的 token 查询串,则交由 Lambda@edge 处理,无需再传递给作为源的客户自研 CDN:
浅谈AWS CloudFront在特殊场景下的配置和错误处理

c. 在 AWS 控制台创建下列 Lambda 函数对终端设备访问 m3u8 和 ts 文件是所带的查询串中的 token 进行验证:

复制代码
var crypto = require('crypto');
global.key = '**********************';
global.response = {
status: '401',
statusDescription: 'Unauthorized',
};
exports.des = {
algorithm:{ des3_ecb:'des-ede3'},
encrypt:function(plaintext, iv)
{
try {
var biv = new Buffer(iv ? iv : 0);
var cipher = crypto.createCipheriv(this.algorithm.des3_ecb, global.key, biv);
cipher.setAutoPadding(true);
var ciph = cipher.update(plaintext, 'utf8', 'hex');
ciph += cipher.final('hex');
return ciph;
} catch (e) {
return '';
}
},
decrypt:function(encrypt_text, iv)
{
try {
var bkey = new Buffer(global.key);
var biv = new Buffer(iv ? iv : 0);
var decipher = crypto.createDecipheriv(this.algorithm.des3_ecb, bkey, biv);
decipher.setAutoPadding(true);
var txt = decipher.update(encrypt_text, 'hex', 'utf8');
txt += decipher.final('utf8');
return txt;
} catch (e) {
return '';
}
}
};
exports.handler = async (event, context, callback) => {
const request = event.Records[0].cf.request;
const qs = request.querystring;
const clientIp = request.clientIp;
//console.log(qs);
//console.log(request);
var querystring = require("querystring");
var qobject = querystring.parse(qs);
//console.log(qobject);
var authinfo;
for (let key in qobject)
{
//console.log('key:' + key + ', value:' + qobject[key]);
if(key == 'AuthInfo')
{
authinfo = qobject[key];
//console.log('found key[authinfo]:' + authinfo);
}
}
var cryputil = require('.');
//var encrypt_text = cryputil.des.encrypt('*****', 0);
//console.log('test enc:' + encrypt_text);
var decrypt_text = cryputil.des.decrypt(authinfo, 0);
//console.log(decrypt_text);
var params = decrypt_text.split('$$');
//console.log(params);
//console.log('array size:' + params.length);
if(params.length != 6)
{
callback(null, global.response);
return;
}
var now = Date.now();
var timestamp = 1000 * (parseInt(params[0]) + 7 * 60 * 60);
//console.log(timestamp + ':' + now);
if(timestamp < now)
{
callback(null, global.response);
return;
}
if(params[1] != clientIp)
{
callback(null, global.response);
return;
}
//console.log(request);
callback(null, request);
};

提示:(1)Lambda 函数必须在 N. Virginia 区域 (US East) 创建 。

(2)创建函数时可以选择使用 cloudfront-modify-reponse-header 作为蓝图然后再进行修改。
浅谈AWS CloudFront在特殊场景下的配置和错误处理

Lambda 函数创建完后,可以如下图所示在控制台选择 Cloudfront 作为触发器,将其与对应的 m3u8 和 ts 缓存行为的 viewer request 关联起来:
浅谈AWS CloudFront在特殊场景下的配置和错误处理

浅谈AWS CloudFront在特殊场景下的配置和错误处理

浅谈AWS CloudFront在特殊场景下的配置和错误处理

2. Cloudfront 的错误分析和调试步骤

下面把在环境构建过程中遇到的问题以及排错建议罗列如下:

a. 可能发生在源站上:源站返回 HTTP 5xx 错误

b. 也可能发生在 CloudFront 上:CloudFront 无法连接到源站

c. 还可能发生在 Lambda @ Edge 上:Lambda @ Edge 代码抛出未处理的异常

为了确保内容可以以最小的中断时间持续交付给用户,当 Cloudfront 内容交付失败时,我们需要快速定位问题并迅速采取措施解决问题。 在下文中我们描述了如何通过下列四个步骤来对 AWS 内容交付中可能遇到的问题进行分析和调试:

2.1 启用 CloudFront 日志记录

要分析问题发生的原因,首先要有可供分析的日志数据,因此我们需要配置 CloudFront 输出包含有关其所接收的每个用户请求的详细信息的日志文件。 具体操作步骤如下图所示,登录 AWS 管理控制台然后转到 CloudFront 控制台,在 CloudFront 分配的常规配置选项中启用日志记录(Logging)。

浅谈AWS CloudFront在特殊场景下的配置和错误处理

启用日志记录后,CloudFront 会开始将访问日志以 W3C 扩展日志文件格式存放到配置中指定的 Amazon S3 存储桶(日志输出会有一定时间的延迟)。

提示:如果需要减少日志文件的存储成本,你可以利用 S3 对象生命周期管理功能(参见 S3 Object Lifecycle management)在一段时间后删除日志或将日志移动到更便宜的 Glacier 存储层。

接下来你可以设置 Amazon Athena 以便于查询 CloudFront 访问日志。 Athena 是一种交互式查询服务,可以使用标准 SQL 查询轻松分析 Amazon S3 日志中的数据。 Athena 是无服务器的,因此没有需要管理的基础架构,你只需为运行的查询付费。 您可以按照以下步骤完成 Athena 的设置:

在 Athena 中创建 CloudFront 日志表

将以下 DDL 语句复制并粘贴到 Athena 控制台中。修改语句中用于存储日志的 Amazon S3 存储桶的名字。

复制代码
CREATE EXTERNAL TABLE IF NOT EXISTS default.cloudfront_logs (
`date` DATE,
time STRING,
location STRING,
bytes BIGINT,
request_ip STRING,
method STRING,
host STRING,
uri STRING,
status INT,
referrer STRING,
user_agent STRING,
query_string STRING,
cookie STRING,
result_type STRING,
request_id STRING,
host_header STRING,
request_protocol STRING,
request_bytes BIGINT,
time_taken FLOAT,
xforwarded_for STRING,
ssl_protocol STRING,
ssl_cipher STRING,
response_result_type STRING,
http_version STRING,
fle_status STRING,
fle_encrypted_fields INT
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
LOCATION 's3://CloudFront_bucket_name/AWSLogs/ACCOUNT_ID/'
TBLPROPERTIES ( 'skip.header.line.count'='2' )

在 Athena 控制台中运行上述查询后 Athena 会注册 cloudfront_logs 表,后面我们就可以通过 SQL 语句来查询 Cloudfront 日志数据了。

提示:为了减少查询 CloudFront 日志所需的成本和时间,你可以按照此博客文章中的指导对日志文件进行分区。

2.2 设置 CloudWatch 告警

日志文件为我们提供了 Cloudfront 出现问题时可以用于分析的数据,但我们还需要一种在问题出现时可以及时通知运维管理人员的机制。 为了发送警报通知,我们建议使用 Amazon SNS 这种完全托管的发布 / 订阅消息服务。首先我们打开 Amazon SNS 控制台,创建一个针对特定主题的邮件消息订阅:

浅谈AWS CloudFront在特殊场景下的配置和错误处理

接着我们可以在 Amazon CloudWatch 里面配置告警。CloudFront 与 CloudWatch 是完全集成的,它可以以 1 分钟的粒度自动发布针对每个 Cloudfront 分配的六个监控指标 (请求,下载字节数,上载字节数,4xx 错误率,5xx 错误率,总错误率),如果有与 Cloudfront 分配相关联的 lambda 函数,CloudFront 还会为每个分配发布额外的四个监控指标(Lambda@Edge 的 5xx 错误率, Lambda 执行错误 , 无效函数响应错误和 Lambda Throttles )。 我们可以在 CloudFront 控制台或 CloudWatch 控制台中根据这些指标设置监控告警。 例如要在 CloudWatch 控制台中根据 5xxErrorRate 指标设置警报,我们可以执行以下操作:

a. 在 AWS 控制台中,打开相应区域(例子中是 us-east-1)中的 CloudWatch 控制台。

b. 在“ Alarms”选项卡上,为需要监控的 CloudFront 分配 ID 选择 Create Alarm > Select metric > CloudFront > Per-Distribution Metrics > 5xxErrorRate,然后在“Period”输入 1 分钟。

c. 根据应用程序监控要求设置 5xxErrorRate 的触发警报阀值。

d. 在“Send notification to:”设置中 ,选择之前创建的 SNS 主题。

e. 选择 Create Alarm 。

浅谈AWS CloudFront在特殊场景下的配置和错误处理

配置完成后起初告警的状态是 INSUFFICIENT_DATA,但很快就会变为 OK 状态。
浅谈AWS CloudFront在特殊场景下的配置和错误处理

2.3 分析和解决 CloudFront 上发生的内容交付问题

当接收到告警时,我们首先需要区分问题是由 CloudFront 还是由 Lambda @ Edge 函数引起,以便后续可以使用正确的工具进行故障排除。 要帮助确定错误背后的原因,我们可以打开 CloudFront 控制台上的监控仪表板(Monitoring dashboard ) ,选择并查看 Cloudfront 分配的指标和关联的 Lambda 函数指标。例如假设我们收到了上文中根据 5xxErrorRate 指标设置的警报触发产生的通知,我们可以按照以下步骤跟踪发生的情况:

a. 在 CloudFront 控制台中,选择“ Monitor” 。

b. 搜索并选择您的 CloudFront 分配,然后选择 View distribution metrics 。
浅谈AWS CloudFront在特殊场景下的配置和错误处理

c. 首先看一下 Error rate 仪表板。 如果发现是 Lambda @ Edge 导致 5xx 错误,那么我们可以跳到下一章节描述的步骤:调试 Lambda @ Edge 函数来处理这个问题。 否则的话我们可以确认错误是由 CloudFront 引起的,我们可以按照下列步骤进行进一步的分析以查看导致它们的原因。
浅谈AWS CloudFront在特殊场景下的配置和错误处理

d. 在 Athena 中对 CloudFront 日志文件执行以下查询语句来统计分析错误。下面的第一个查询按错误代码统计出发生 5xx 错误的请求数。当我们查看查询结果时,假设发现 500 是发生数量最多错误代码,接下来我们可以使用第二个查询过滤错误代码,以确定返回 500 错误数量最高的 URI。

复制代码
SELECT status, count(status) AS count FROM cloudfront_logs WHERE status >= 500 GROUP BY status ORDER BY count DESC
SELECT uri, count(uri) AS count FROM cloudfront_logs WHERE status = 500 GROUP BY uri ORDER BY count DESC

根据上述统计分析,我们可以识别出现最多问题和跟它相关联的 URI,然后我们就可以采取相应的措施来解决问题。 例如在分析过程中发现日志中存在以下错误代码之一都可能是问题:

  • 当源站无法访问或超时未响应时,CloudFront 会返回 504 网关超时错误。 我们可以检查这些错误的时间戳,并将它们与源站的日志进行比较。 如果我们需要更多帮助,可以使用 AWS 控制台上的 Support 菜单来开故障工单,并提供从 Cloudfront 分配日志文件中获取的相关的请求 ID( x-edge-request-id )。
  • 您的源站返回 500,501 或 503 内部错误,这些响应会缓存在 CloudFront 上。我们通过查询 Athena 日志文件可以过滤出导致故障的服务器端 URI,然后进一步分析和调试这个 URI 对应的应用程序代码。
  • 在源站上由于错误的 TLS 配置,导致客户端访问出现 502 Bad Gateway 错误 。日志中出现这种信息时我们可以根据这个信息检查源站上的 TLS 证书和配置 。
    有关不同 HTTP 错误以及如何防止它们发生的详细帮助信息,请参阅 CloudFront 文档 。

2. 4 调试 Lambda @ Edge 函数

如果我们在 3.2 节的查询分析过程中,从 Error rate 图表中看到是 Lambda @ Edge 出现 HTTP 5XX 错误峰值,则下一步我们需要了解导致错误出现的原因。 例如出现 HTTP 5XX 的错误峰值可能是由 Lambda 函数代码中的异常引起的,也可能是函数返回了对 CloudFront 的无效响应,或者函数可能已被限流(throttled)。 要了解有关 Lambda @ Edge 错误类型的更多信息,请参阅测试和调试 Lambda @ Edge 函数 。

为了发现在特定情况下导致错误峰值的原因,我们可以深入分析 Cloudfront 控制台上的监控图表。 首先选择监控仪表板中的 Lambda @ Edge Errors 选项卡,我们可以看到如下内容:
浅谈AWS CloudFront在特殊场景下的配置和错误处理

浅谈AWS CloudFront在特殊场景下的配置和错误处理

浅谈AWS CloudFront在特殊场景下的配置和错误处理
浅谈AWS CloudFront在特殊场景下的配置和错误处理

根据日志和监控图标中出现的错误类型,我们可以根据以下决策图中的步骤来调试 Lambda @ Edge 函数。 下文介绍了调试不同错误类型的详细步骤。

浅谈AWS CloudFront在特殊场景下的配置和错误处理

a. 执行错误

执行错误是使用 Lambda @ Edge 时最常见的错误。因为 Lambda @ Edge 函数中存在未处理的异常或代码中存在错误导致 CloudFront 没有从 Lambda @ Edge 获得响应时会发生执行错误这种情况。要解决此问题,我们需要通过分析 CloudWatch 中的 Lambda @ Edge 日志来调试代码。 如果 Lambda @ Edge 函数未正确执行 ,它会尝试将错误对象转换为具有以下格式的字符串,并将该字符串发送到 CloudWatch 日志文件。

复制代码
{
"errorMessage": "something is wrong",
"errorType": "Error",
"stackTrace": [ "exports.handler (/var/task/index.js:10:17)" ]
}

要在查看执行错误,请在控制台的 Monitoring Dashboard 中选择正在分析的 Lambda @ Edge 函数,然后选择 View logs ,我们将被重定向到 CloudWatch 控制台,在这里可以查看与 Lambda 函数关联的日志组的详细信息。

浅谈AWS CloudFront在特殊场景下的配置和错误处理

要在 CloudWatch 中更快地分析日志,我们可以使用 CloudWatch Logs Insights 工具。 Insights 能够以交互方式搜索和分析日志数据。 我们可以运行查询以便快速了解操作问题和解决它们。 例如我们可以通过 Insights 中的以下查询过滤错误消息,仅查看与特定错误相关的消息。

浅谈AWS CloudFront在特殊场景下的配置和错误处理

我们还可以使用 Insights 查询代码用 Console.log()接口输出的日志文件。 例如一个 Lambda @ Edge 函数,该函数用于单页面应用程序的 A / B 测试。 在代码中我们可以通过执行以下操作来记录在每个 Lambda @ Edge 执行中选择的页面版本:

复制代码
console.log('INFO { spaVersion:', spaVersion, "}");

然后我们可以在 Insights 中使用以下查询来过滤 INFO 消息,提取所选页面版本并计算每次出现的次数:

fields @timestamp, ddbRegion

| filter @message like /INFO/

| sort @timestamp desc

| stats count() by ddbRegion

b. CloudFront 验证错误

有时 Lambda 函数会向 CloudFront 返回无效的响应。 例如如果响应的对象结构不符合 Lambda @ Edge 事件结构 ,或者响应包含无效标头或其他无效字段,就会返回验证错误。 请注意在 Lambda 控制台中测试 Lambda 函数时,CloudFront 不会验证您的 Lambda 函数,这意味着 Lambda 函数可以在控制台中正确运行,但在将其添加到分配并在 CloudFront 中部署时仍然可能会失败。

CloudFront 将验证日志发送到用户附近的 AWS 区域中的 CloudWatch 日志。 在“监控仪表板”中,选择要查看日志的区域,然后选择“查看日志”,将被重定向到 CloudWatch 控制台以查看与 Lambda 函数关联的日志组。
浅谈AWS CloudFront在特殊场景下的配置和错误处理

在日志中,您可以看到 CloudFront 验证错误的原因,如下图所示:

浅谈AWS CloudFront在特殊场景下的配置和错误处理

c. 限流

并发性是指特定 AWS 区域中 Lambda @ Edge 同时执行的数量。如果达到区域并发限制 ,Lambda @ Edge 服务会按每个区域的限制限制 Lambda 函数调用。要更好地了解达到并发限制的位置和原因,请在“监控仪表板”上选择“Lambda @ Edge”功能,然后选择“ View function metrics” 。
浅谈AWS CloudFront在特殊场景下的配置和错误处理

然后检查 Invocations 和 Duration 仪表板,它按区域显示按区域调用函数的频率,以及函数执行的时间:

浅谈AWS CloudFront在特殊场景下的配置和错误处理

浅谈AWS CloudFront在特殊场景下的配置和错误处理

在三种情况下可能会导致 Lambda 函数的并发运行受到限制:

a. 第一种会受到限制情况发生当请求数量很高导致频繁地调用函数时。如果发生这种情况,我们可以向 AWS 支持部门申请提升 Lambda 函数受到限制的区域中的并发执行的限制。 另外一个选项是考虑重新调整应用程序架构以减少并发调用 Lambda @ Edge 的数量,例如在 Lambda @ Edge 触发器设置中指定更具体的 CloudFront 行为。

b. 第二种受到限制情况是当 Lambda 函数代码执行时间较长时。 要解决此问题可以考虑检查 Lambda 函数是否有依赖的外部组件在运行时没有响应。 如果是这样的话可以在 Lambda 函数代码中设置超时。

c. 第三种受到限制情况发生当应用程序在极短时间内(秒级)接收到突然的流量高峰时。 在这种情况下,Lambda @ Edge 需要为 Lambda 函数的运行启动新的运行时(runtime),这会给 Lambda 函数的执行持续时间增加额外的延迟开销。 此延迟开销会更快地消耗可用的执行并发级别。 减少此开销的方法之一是压缩 Lambda 函数部署包的大小。

要了解有关优化并发性的更多信息,请阅读关于 Lambda @ Edge 设计最佳实践的博客文章。

作者介绍:
黄诚智
AWS 解决方案架构师,负责基于 AWS 云计算方案架构的咨询和设计,在国内推广 AWS 云平台技术和各种解决方案。在加入 AWS 之前曾任职于 CA,Sun 和 Citrix 等多家大型跨国 IT 企业,超过 24 年的通信,金融企业应用系统开发和 IT 架构经验。

刘天龙
AWS 解决方案架构师,负责支持客户完成各种 Workload 在 AWS 上的架构设计,加入 AWS 之前先后服务于运营商、电力等大型企业,以及 Microsoft 和 Citrix 等外企,熟悉大型网络构建及优化,迁移上云及容灾等解决方案。

本文转载自 AWS 博客。

原文链接:
https://amazonaws-china.com/cn/blogs/china/aws-cloudfront-in-special-occasions-configuration-and-mistake-handling/

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

评论

发布