使用Athena分析Cloudfront Standard Log查询CNAME流量和5xx错误

更新于2024年7月,加入5xx错误排查方法。

本文的分析实现两个目的:1)确认一个发布点上绑定多个CNAME时各自的流量;2)找到CloudFront控制台的监控面板上5xx报错的原因。

一、背景

1、关于CloudFront日志种类

Cloudfront日志分成Standard Log标准日志和Realtime Log实时日志。后者时效性好,但是需要Kinesis等日志处理方案,成本更高。对于排查一般的错误,使用标准日志保存到S3上,用Athena查询分析即可。

2、关于CNAME自定义域名和多CNAME的查询需求

在使用CloudFront发布点的时候,每个发布点都有一个唯一的CloudFront自动分配的域名,例如dxxxxxxxxx.cloudfront.net的格式,这个域名可以很直接发起HTTPS访问,可以被嵌入应用程序调用。为了使用自己的域名,可在CloudFront上绑定CNAME别名,这也被叫做自定义域名,例如web01.abc.com,这样就可以使用用户自己的域名对CDN发布点进行访问。

这里需要注意,即便绑定了自定义域名web01.abc.com,系统自动分配的dxxxxxxxxx.cloudfront.net域名还是可以被访问的,它是一直有效的。不过,只要不把这个自动分配的域名公开,它是不会产生流量的。因此,所有的流量都会来自于web01.abc.com

如果一个CDN发布点只绑定了一个自定义域名,并且系统自动分配的dxxxxxxxxx.cloudfront.net也没有对外发布,那么分析日志的时候,本CDN发布点的所有流量都会是自定义域名产生的。这时候可在CloudFront界面的Usage菜单下,可看到本发布点的日志流量总和。如下截图。

一个CDN发布点支持绑定多个CNAME自定义域名,例如一个CDN发布点绑定web01.abc.comimage01.abc.com两个域名。此时就有了日志分析的需求,如何根据不同的域名来统计流量,如何分别获取web01image01哪一个流量高哪一个低。这时候使用Athena查询CloudFront的日志来确定用量。

3、关于查询5xx错误的需求

进入CloudFront服务控制台,进入左侧的Telemetry遥测菜单,点击Monitoring监控个,在右侧选择对应的发布点,从报表中能看到诸如5xx Error rate breakdown (as a percentage of total requests)的监控数据,就是对应5xx错误的。那么如何排查这种错误呢,这里就需要打开日志,并通过日志确认发生错误的原始请求。

二、打开CloudFront Standard Log 标准日志并保存到S3

进入CloudFront服务控制点,找到要打开日志的发布点,点击进入发布点详情。在General标签页下方,可以看到在Alternate domain names备用域名下,有两个绑定上的自定义域名,下一步就是打开日志。在Settings位置点击Edit。如下截图。

将页面向下滚动,在Standard logging标准日志位置,把开关设置为On。在下方输入CloudFront日志要输出的S3存储桶。这里建议使用一个独立的存储桶(建议把存储桶创建在美东1、美西2、法兰克福、新加坡区域)。如果是配置了多个CDN发布点都使用同一个S3存储桶保存日志,那么可以设置Prefix前缀即子目录,通过子目录来区分。如下截图。

点击保存后,打开日志功能完成。

现在对本CDN发布点做一些访问请求。CloudFront的Standard Log一般需要等待5-10分钟后,才能在S3存储桶内看到日志。如下截图。

将日志下载到本地,使用文本编辑器打开,可以看到这个文件是CSV格式的,以空格作为隔离符号,其中的字段sc-bytes是传输的字节数,字段x-host-header是本次访问使用的域名。如果所有访问都是通过CloudFront自己分配的dxxxxxxxxx.cloudfront.net的格式来访问的,那么这个x-host-header就会显示CloudFront分配的域名。如果访问是使用了自定义域名CNAME来访问,那么这里就会显示本次访问使用的CNAME的名字。如下截图。

日志格式样本如下:

#Version: 1.0
#Fields: date time x-edge-location sc-bytes c-ip cs-method cs(Host) cs-uri-stem sc-status cs(Referer) cs(User-Agent) cs-uri-query cs(Cookie) x-edge-result-type x-edge-request-id x-host-header cs-protocol cs-bytes time-taken x-forwarded-for ssl-protocol ssl-cipher x-edge-response-result-type cs-protocol-version fle-status fle-encrypted-fields c-port time-to-first-byte x-edge-detailed-result-type sc-content-type sc-content-len sc-range-start sc-range-end
2024-03-18  05:37:43    HKG60-C1    757612  13.248.48.10    GET d51vuyprlknbq.cloudfront.net    /APIGW/log/apigw-log-01.png 200 -   Mozilla/5.0%20(Macintosh;%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit/605.1.15%20(KHTML,%20like%20Gecko)%20Version/17.3.1%20Safari/605.1.15 -   -   Miss    lepa9BqJcfUuxXihUsrvmqIqguWiGw7ec1SmYmaqj7X6StCe8vkoMg==    myworkshop.bitipcman.com    https   283 0.524   -   TLSv1.3 TLS_AES_128_GCM_SHA256  Miss    HTTP/2.0    -   -   9469    0.296   Miss    image/png   756274  -   -
2024-03-18  05:37:44    HKG60-C1    15510   13.248.48.10    GET d51vuyprlknbq.cloudfront.net    /favicon.ico    200 https://myworkshop.bitipcman.com/APIGW/log/apigw-log-01.png Mozilla/5.0%20(Macintosh;%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit/605.1.15%20(KHTML,%20like%20Gecko)%20Version/17.3.1%20Safari/605.1.15 -   -   Miss    6UUfUNdTZe0dvwv-zJ0WjGRGePnKA_c8JpOhHjBUnFg9vIOtWvy1Hg==    myworkshop.bitipcman.com    https   102 0.292   -   TLSv1.3 TLS_AES_128_GCM_SHA256  Miss    HTTP/2.0    -   -   9469    0.292   Miss    image/x-icon    15086   -   -

依据日志,就可以完成一系列分析。现在使用Athena进行下一步查询。

三、初始化Athena并配置数据表

1、本账号/本区域首次使用Athena用户的初始化配置

选择上一步的S3存储桶所在的Region,进入Athena服务控制台。如下截图。

进入Athena服务后,从左侧菜单找到Workgroups工作组设置。在2024年上半年,如果您在不同区域使用Athena,可能会遇到左侧菜单不一致的情况,这是由于各区域的Athena版本不一样。找到Workgroups后,点击进入,其中有一条是默认的Primary。点击进入。如下截图。

在工作组Primary的详情界面,可查看Query result location设置,这是Athena查询结果集所使用的S3存储桶,Athena查询结果会保存在这里。这个选项在第一次使用Athena时候必须设置好。点击Edit按钮进行配置。如下截图。

Query result location设置位置,输入S3存储桶用来保存Athena查询结果。这个存储桶要与之前的CloudFront日志的存储桶独立开,并且也是在Athena运行所在的区域。将事先创建好的存储桶填写进去。如下截图。

Athena首次设置设置完毕。

2、创建Athena表

在Athena界面上点击左侧的Query Editor按钮,在确认右侧的Database是选择了默认的default数据库,在查询Query部分输入如下的SQL脚本。

CREATE EXTERNAL TABLE IF NOT EXISTS default.cloudfront_logs (
  `date` DATE,
  time STRING,
  x_edge_location STRING,
  sc_bytes BIGINT,
  c_ip STRING,
  cs_method STRING,
  cs_host STRING,
  cs_uri_stem STRING,
  sc_status INT,
  cs_referrer STRING,
  cs_user_agent STRING,
  cs_uri_query STRING,
  cs_cookie STRING,
  x_edge_result_type STRING,
  x_edge_request_id STRING,
  x_host_header STRING,
  cs_protocol STRING,
  cs_bytes BIGINT,
  time_taken FLOAT,
  x_forwarded_for STRING,
  ssl_protocol STRING,
  ssl_cipher STRING,
  x_edge_response_result_type STRING,
  cs_protocol_version STRING,
  fle_status STRING,
  fle_encrypted_fields INT,
  c_port INT,
  time_to_first_byte FLOAT,
  x_edge_detailed_result_type STRING,
  sc_content_type STRING,
  sc_content_len BIGINT,
  sc_range_start BIGINT,
  sc_range_end BIGINT
)
ROW FORMAT DELIMITED 
FIELDS TERMINATED BY '\t'
LOCATION 's3://my-cloudfront-log-bucket/mydir/'
TBLPROPERTIES ( 'skip.header.line.count'='2' )

以上脚本中,需要替换S3存储桶的名称,然后点击Run按钮即可执行脚本。如下截图。

脚本执行完毕,可看到数据库创建成功。

注意以上建表是不包含分区键的,因此需要避免对全桶所有历史数据发起全量查询,以降低查询成本。

三、查询特定CNAME的流量总和

1、按发布点绑定的CNAME进行查询

编辑如下SQL代码,然后提交执行。

SELECT ROUND(SUM(sc_bytes)*1.0/1024/1024,2) AS total_DTO_MB
FROM default.cloudfront_logs
WHERE "x_host_header"='myworkshop.bitipcman.com';

在以上代码中,sc_byte字段是DTO传输的字节数,乘以1.0可以将其转换为浮点数,然后通过SUM函数求和,通过ROUND函数保留两位小数,这样就可以换算其单位MB了。如果不这样做,因为其默认是sc_byte是INT整数,在SQL运算中除法之后自动取整,结果就偏离很大,因此才使用这种表示方法做运算。如下截图。

2、一个发布点绑定多个CNAME时候分别求总和

如果一个发布点绑定了多个CNAME,希望针对所有CNAME求和,那么可以通过groupby函数来分别求和。

编辑如下SQL代码,然后提交执行。

SELECT "date" as LogDate,
    "x_host_header" AS Hostname,
    ROUND(SUM(sc_bytes) * 1.0 / 1024 / 1024, 2) AS total_DTO_MB
FROM default.cloudfront_logs
GROUP BY "date",
    "x_host_header";

这样即可分别求出多个CNAME各自的流量总和。如下截图。

四、查询5xx错误日志

1、CloudFront提示500错误的可能原因

一些常见的错误可能问题如下:

  • 504错误:CloudFront连接不上源站,检查CloudFront和源站直接的网络
  • 500/501/503错误:源站的应用错误,检查应用对外服务情况
  • 502错误:源站的SSL/TLS证书和加密等配置错误

2、模拟一个源站内部错误

现在在源站的web服务器上,部署一个错误的页面,这个页面包含拼写错误的代码,运行时候发生500服务器错误。

部署完毕后,通过CloudFront的加速点的完整网址,请求这个错误页面。注意不同的浏览器,包括Firefox、Chrome可能对错误的现实信息不一样,甚至显示白屏(白板页面)。

此时可通过curl -I https://xxxxx.xx/xxx的网址形式测试,通过使用-I参数,强制输出。例如测试结果如下:

HTTP/2 500 
content-type: text/html; charset=UTF-8
content-length: 0
date: Fri, 05 Jul 2024 07:23:18 GMT
server: Apache/2.4.58 ()
x-powered-by: PHP/7.4.33
x-cache: Error from cloudfront
via: 1.1 5cbbcc51eb95a2072bb8064803109254.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT20-C3
alt-svc: h3=":443"; ma=86400
x-amz-cf-id: F68tEajNyXnd1RwN-9aRluoe6otovlzxnO3Ioprv_e2tgxs8JOocQA==
age: 3

则表示成功触发了500错误。

3、搜索日志检索特定错误

在和上文相同的Athena的配置下,执行如下SQL:

SELECT * FROM "default"."cloudfront_logs" where status>499 limit 10;

查询结果如下:

由此即可看到错误日志被检索出来,根据日志里边的原始URL和请求信息,即可去调试。

五、最佳实践

使用标准日志时候,如果访问量大的话,也不建议长时间开启日志,而是手工开启日志一段时间,将日志保存到某个存储桶的特定目录中(加prefix),然后排查错误。排查结束后,关闭日志。

下次再排查其他事项,再次打开标准日志,在选择保存到存储桶步骤时候,选择保存到其他存储桶,或者保存到当前存储桶的其他目录中(更换prefix)。当然,这样也要求athena建表语句要有所调整,不过可以显著降低存储/查询成本。

六、参考文档

CloudFront标准日志 – 字段定义说明

https://docs.aws.amazon.com/zh_cn/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html#LogFileFormat

使用Athena查询CloudFront标准日志

https://docs.aws.amazon.com/zh_cn/athena/latest/ug/cloudfront-logs.html#create-cloudfront-table-standard-logs

Four Steps for Debugging your Content Delivery on AWS

https://aws.amazon.com/blogs/networking-and-content-delivery/four-steps-for-debugging-your-content-delivery-on-aws

Troubleshooting error responses from your origin

https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/troubleshooting-response-errors.html