亚马逊AWS官方博客

通过 Amazon CloudFront 实时日志快速构建自定义的 CDN 监控

背景

Amazon CloudFront 是一个全球性的内容分发网络 (CDN),您可以借助 CloudFront 以低延迟和高可用性向查看者或者最终用户分发内容。

通常来讲,Amazon CloudFront 的客户都希望能更快更及时地获取并监控到当前配置的 CloudFront Distribution 的使用情况,以便可以做出相应的网络策略调整和优化。Amazon CloudFront 提供了 Amazon CloudWatch 的内置集成,可以提供监控(例如请求数、4xx 错误率、5xx 错误率等)和标准日志,这些内容都有助于客户理解 CloudFront Distribution 的使用率和活动。另外,CloudFront 也在控制台上提供了有关 CloudFront 活动的报告,例如 Cache Statistics、Popular Objects 以及 Top Referrers 等,多数报告都是基于 CloudFront 访问日志中的数据。

然而,这些监控指标和标准日志并不具备很高的实时性,无法满足一些时间敏感型的使用案例。因此,亚马逊云科技于2020年8月正式推出了 CloudFront 实时日志功能,这些实时日志的内容涵盖了 CloudFront 接收的有关查看者 viewer 请求的详细信息。利用 CloudFront 实时日志,您可以实时获取有关向分配发出的请求的信息(日志在收到请求后的几秒钟内开始传输)。您可以使用实时日志来进行监控和分析,并根据内容交付性能采取相应措施。此外,有些应用场景会有自定义的监控指标,这些指标可能没有原生提供在 CloudWatch 监控中,您可以通过实时日志获得额外的可配置性,在实时日志中选择提取需要的字段,按照自定义的逻辑来处理日志,使您可以根据业务实际情况进行配置。

CloudFront 实时日志会分发至 Amazon Kinesis Data Streams 数据流中,使您可以轻松监控您的内容分发性能并快速响应操作事件。您还可以使用 Amazon Kinesis Data Firehose 服务对接Kinesis Data Streams 数据流,快速将这些日志轻松交付至通用的数据存储中,例如 Amazon S3,或者是 Amazon Redshift、Amazon OpenSearch Service 以及 Datadog 等其他服务提供商,进而轻松构建实时控制面板、设置监控警报,并快速调查异常情况,及时地响应操作事件,以满足不同的需求。在本文的解决方案示例中,我们选择将这些实时日志传输到 Amazon S3,之后您可以使用无服务器架构的 Amazon Athena 服务,即时使用标准 SQL 分析 Amazon S3 中的数据生成自定义的监控指标,存储到高性能的 DynamoDB 数据库中,通过 API Gateway 提供用户接口。

解决方案概览

本解决方案实现涉及的 Amazon 服务以及整体架构如下所示:

本解决方案中 CloudFront 监控的数据流如下所示:

  1. 首先,用户需要开启 CloudFront 实时日志;

开启的过程中需要配置对应的 Kinesis Data Stream 数据流。

  1. 设置 Kinesis Data Firehose 快速完成数据传输,使得 CloudFront 的实时日志会依次通过 Kinesis Data Stream 和 Kinesis Data Firehose,最终存储到 S3 桶中;

在此过程中,如果您想监控在全球范围内每个国家以及每个运营商的加速情况,您可以参考示例中 Kinesis Data Firehose 配置调用  Amazon Lambda 函数 Log Transformer 实现通过客户端 IP 找到 ISP 和国家代码,并配合 Kinesis Data Firehose 实现动态分区,即将实时日志通过分区键进行分区,并存储到 S3 桶中,如 year=2021/month=12/day=10。

  1. 通过 Athena 查询 S3 中的实时日志;

为了让 Athena 通过数据分区加速数据查询,通过 Amazon EventBridge 每天会新建第二天的所有分区,并删除前一天的分区。Lambda 函数 MetricCollector 用来分析实时日志和收集监控指标,它会每5分钟执行一次。

  1. 将查询数据的结果保存在 DynamoDB 中方便后续获取;

通过 Athena 查询出相应的监控指标数据,例如通过带宽计算 CHR(缓存命中率)、下载速率,最终将监控指标数据存储在 DynamoDB 表格中。

  1. 最终用户接口使用 API Gateway;

用户接口通过 API Gateway 和 Lambda 函数 MetricManager 生成一个 Restful API,MetricManager 会读取 DynamoDB 表格中的监控指标并返回相应结果。为了进一步加强安全管理限制 API 的访问,示例中 API Gateway 中开启 Cognito 授权,访问接口的用户需携带 Cognito 令牌才能正常请求 API。

在监控指标部分,本解决方案可以满足灵活的定制化需求,根据您的特定需求从实时日志中自定义指标逻辑,只需要在 Athena 中通过方便的 SQL 语句就可以实现。本示例中提供了以下10个参考指标:

  1. request: 从客户端到 CloudFront 的请求数量
  2. requestOrigin: 回源的请求数量
  3. statusCode: 从客户端到 CloudFront 的状态码
  4. statusCodeOrigin: 回源的状态码
  5. bandwidth: 从客户端到CloudFront的带宽
  6. bandwidthOrigin: 回源的带宽
  7. chr (cache hit rate): 通过请求数量计算的缓存命中率
  8. chrBandWith: 通过带宽计算的缓存命中率
  9. downloadSpeed: 从客户端到CloudFront的下载速率
  10. downloadSpeedOrigin: 回源的下载速率

上述参考指标有效地补充了目前 CloudWatch 对 CloudFront 的监控指标,可以帮助您从多个维度了解 CloudFront 的运行情况。有关 CloudFront 默认内置的监控指标,可以参考使用 Amazon CloudWatch 监控 CloudFront

方案部署

解决方案采用 CloudFormation 一键部署的方式,您可以点击下方链接完成部署。

https://console.thinkwithwp.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=cloudFrontExtensionMonitoring&templateURL=https://aws-gcr-solutions.s3.amazonaws.com/aws-cloudfront-extensions/latest/CloudFrontMonitoringStack.template

在创建 CloudFormation 堆栈界面,输入如下参数,其中CloudFrontDomainList是需要获取监控指标的CloudFront分配的域名

参数定义如下:

名称 详细信息 示例
CloudFrontDomainList 待采集日志的CloudFront分配 example1.cloudfront.net
deployStage API Gateway部署的阶段,默认是prod dev,qa,beta,gamma,prod
CloudFrontLogKeepDays CloudFront实时日志在S3桶中最大存放时间,默认是120天 120

CloudFormation 部署完成后可在输出标签页中看到如下信息,最下方是监控API的链接,在全部配置完成后您可调用此链接获取监控指标。

进入CloudFront控制台,在实时配置标签页中点击创建配置按钮

采样率输入100并选择如下日志字段(按照下图字段顺序进行选择)

CloudFormation 会自动部署一个Kinesis数据流,选择此数据流。在分配栏中,选择需要获取监控指标的CloudFront分配ID(即部署CloudFormation时填入域名的CloudFront分配)后,点击创建配置按钮

至此,此解决方案配置完成,您可通过CloudFormation部署时生成的API链接访问监控指标。

方案使用说明

此解决方案会通过CloudFormation一键部署如下服务:

  1. 流数据处理服务 Amazon Kinesis

此解决方案分别创建了一个 Kinesis 数据流和一个传输流。


传输流中通过 Lambda 函数 cf-real-time-logs-transformer 来转换记录数据,实现 IP 转 ISP/Country Code 和通过日志中的请求时间添加分区键,您可通过修改此 Lambda 函数自定义更多的转换规则。

实时日志经过传输流后会存储在 S3 桶中,并会进行动态分区,即自动按照时间在S3桶中新建文件夹,例如 year=2021/month=12/day=10/hour=08/minute=09,并将实时日志文件存储到相应文件夹中。由于在 cf-real-time-logs-transformer 中已通过日志中的请求时间添加了分区键,因为最终是按照请求时间而不是日志生成时间进行分区的。

S3 桶中的日志文件即为 Athena 查询的数据源,在查询过程中,此解决方案会根据时间找到相应的分区,并在其中进行查询。

  1. 数据 ETL 服务 Amazon Glue

此解决方案会创建一个数据库和表,表的架构已自动定义好,表中的数据即为S3桶中的实时日志,Lambda 函数会调用 Athena 在此表中通过 SQL 语句进行查询,从而得到相应的监控指标。


  1. NoSQL 数据库 Amazon DynamoDB

此解决方案会创建一个 DynamoDB 表,表定义如下:

名称 详细信息 示例
metricId 分区键。命名格式为<metric>-<domain_name> chr-example.cloudfront.net
timestamp 日志生成的时间戳 1637214900
metricData 监控指标数据。根据指标不同,监控指标数据也不尽相同

request、bandwidth、chr 为数值

statusCode、downloadSpeed为JSON,如:

[
    {
        "StatusCode": "200",
        "Count": "299"
    },
    {
        "StatusCode": "403",
        "Count": "61"
    }
]
  1. 无服务器函数 Amazon Lambda

此解决方案部署后会自动生成如下 Lambda 函数

名称 详细信息
metricsCollector 开头的函数 通过Athena查询日志得到监控指标,并将其插入到DynamoDB表中
metricsManager 读取DynamoDB表中的监控指标,并封装成统一格式,用于为API返回结果
addPartition 生成第二天的分区
deletePartition 删除前一天的分区
cf-real-time-logs-transformer 通过client IP找到ISP和Country Code,并在Kinesis流中添加分区键

公共的操作(如 Athena 查询)已被封装成 Lambda Layer。metricsCollector 函数默认每5分钟触发一次收集监控指标,您可修改 EventBridge 将其调整成需要的时间间隔。

其他配置参数可在环境变量中修改:

addPartition 函数会在每天 metricCollector 函数收集日志前将分区建好,deletePartition 函数会清除不需要的分区从而提高 Athena 查询速度。目前是分区到分钟,若您的日志量不大,可只分区到小时。

  1. 网关托管服务 Amazon API Gateway

查询字符串说明:

名称 详细信息 必需 示例
Domain 待分析监控指标的域名 example.cloudfront.net
Metric

监控指标,若设置为all则会返回全部指标,有效指标为

request: 从客户端到CloudFront的请求数量

requestOrigin: 回源的请求数量

statusCode: 从客户端到CloudFront的状态码

statusCodeOrigin: 回源的状态码

bandwidth: 从客户端到CloudFront的带宽

bandwidthOrigin: 回源的带宽

chr: 通过请求数量计算的缓存命中率

chrBandWith: 通过带宽计算的缓存命中率

downloadSpeed: 从客户端到CloudFront的下载速率

downloadSpeedOrigin: 回源的下载速率

all
StartTime 开始时间,为UTC时间 2021-12-10 09:00:00
EndTime 结束时间,为UTC时间 2021-12-10 09:30:00

API返回格式如下:

a. 请求数量(request 或 requestOrigin)

监控指标以每五分钟记录一次,输出到JSON中

{
    "Response": {
        "Data": [
            {
                "CdnData": [
                    {
                        "Metric": "request",
                        "DetailData": [
                            {
                                "Time": "2021-10-21 11:05:00",
                                "Value": "430"
                            },
                            {
                                "Time": "2021-10-21 11:10:00",
                                "Value": "124"
                            },
                            {
                                "Time": "2021-10-21 11:15:00",
                                "Value": "355"
                            },
                            {
                                "Time": "2021-10-21 11:20:00",
                                "Value": "65"
                            },
                            {
                                "Time": "2021-10-21 11:25:00",
                                "Value": "361"
                            },
                            {
                                "Time": "2021-10-21 11:30:00",
                                "Value": "83"
                            }
                        ]
                    }
                ]
            }
        ],
        "RequestId": "7569bbc3-a3ec-4ff3-94ec-f81a63ff5587",
        "Interval": "5min"
    }
}

b. 带宽(bandwidth 或 bandwidthOrigin)

{
    "Response": {
        "Data": [
            {
                "CdnData": [
                    {
                        "Metric": "bandwidth",
                        "DetailData": [
                            {
                                "Time": "2021-10-21 11:05:00",
                                "Value": "3434275"
                            },
                            {
                                "Time": "2021-10-21 11:10:00",
                                "Value": "1385221"
                            },
                            {
                                "Time": "2021-10-21 11:15:00",
                                "Value": "2248372"
                            },
                            {
                                "Time": "2021-10-21 11:20:00",
                                "Value": "410043"
                            },
                            {
                                "Time": "2021-10-21 11:25:00",
                                "Value": "2261034"
                            },
                            {
                                "Time": "2021-10-21 11:30:00",
                                "Value": "522360"
                            }
                        ]
                    }
                ]
            }
        ],
        "RequestId": "7569bbc3-a3ec-4ff3-94ec-f81a63ff5587",
        "Interval": "5min"
    }
}

c. 下载速率(downloadSpeed 或 downloadSpeedOrigin)

下载速率依次按照国家和运营商进行聚类,并计算下载速率分布。如下图所示,下载速率分布在两个国家(CN和IT),其中CN有一个ISP(Beijing Guanghuan Xinwang Digital),IT有三个ISP。在所有来自CN的Beijing Guanghuan Xinwang Digital的请求中,有25%的请求的下载速率在0KB/s~250KB/s,有75%的请求的下载速率在250KB/s~500KB/s。

{
    "Response": {
        "Data": [
            {
                "CdnData": [
                    {
                        "Metric": "downloadSpeed",
                        "DetailData": [
                            {
                                "Time": "2021-10-21 11:05:00",
                                "Value": {
                                    "CN": {
                                        "Beijing Guanghuan Xinwang Digital": {
                                            "750K": "0",
                                            "250K": "0.25",
                                            "4M": "0",
                                            "3M": "0",
                                            "2M": "0",
                                            "1M": "0",
                                            "500K": "0.75",
                                            "Other": "0"
                                        }
                                    },
                                    "IT": {
                                        "G-Core Labs S.A.": {
                                            "750K": "0",
                                            "250K": "0",
                                            "4M": "1",
                                            "3M": "0",
                                            "2M": "0",
                                            "1M": "0",
                                            "500K": "0",
                                            "Other": "0"
                                        },
                                        "Aruba S.p.A.": {
                                            "750K": "0",
                                            "250K": "1",
                                            "4M": "0",
                                            "3M": "0",
                                            "2M": "0",
                                            "1M": "0",
                                            "500K": "0",
                                            "Other": "0"
                                        },
                                        "Seflow S.N.C. Di Marco Brame' & C.": {
                                            "750K": "0",
                                            "250K": "1",
                                            "4M": "0",
                                            "3M": "0",
                                            "2M": "0",
                                            "1M": "0",
                                            "500K": "0",
                                            "Other": "0"
                                        }
                                    },
                                    "timestamp": "1634814300",
                                    "domain": "d2yengim8ao3gc.cloudfront.net"
                                }
                            }
                        ]
                    }
                ]
            }
        ],
        "RequestId": "7569bbc3-a3ec-4ff3-94ec-f81a63ff5587",
        "Interval": "5min"
    }
}

d. 缓存命中率(chr 或 chrBandWidth)

缓存命中率会分别按照请求数量和带宽进行计算,计算公式为

chr = 缓存命中的请求数量/总请求数量

chrBandWidth = 缓存命中的请求带宽/总请求带宽

{
    "Response": {
        "Data": [
            {
                "CdnData": [
                    {
                        "Metric": "chr",
                        "DetailData": [
                            {
                                "Time": "2021-10-21 11:05:00",
                                "Value": "94.83"
                            },
                            {
                                "Time": "2021-10-21 11:10:00",
                                "Value": "98.98"
                            },
                            {
                                "Time": "2021-10-21 11:15:00",
                                "Value": "95.35"
                            },
                            {
                                "Time": "2021-10-21 11:20:00",
                                "Value": "100.00"
                            },
                            {
                                "Time": "2021-10-21 11:25:00",
                                "Value": "99.02"
                            },
                            {
                                "Time": "2021-10-21 11:30:00",
                                "Value": "92.65"
                            }
                        ]
                    }
                ]
            }
        ],
        "RequestId": "7569bbc3-a3ec-4ff3-94ec-f81a63ff5587",
        "Interval": "5min"
    }
}

e. 状态码(statusCode 或 statusCodeOrigin)

{
    "Response": {
        "Data": [
            {
                "CdnData": [
                    {
                        "Metric": "statusCode",
                        "DetailData": [
                            {
                                "Time": "2021-10-21 11:05:00",
                                "Value": [
                                    {
                                        "StatusCode": "403",
                                        "Count": "80"
                                    },
                                    {
                                        "StatusCode": "200",
                                        "Count": "348"
                                    },
                                    {
                                        "StatusCode": "499",
                                        "Count": "2"
                                    }
                                ]
                            },
                            {
                                "Time": "2021-10-21 11:10:00",
                                "Value": [
                                    {
                                        "StatusCode": "200",
                                        "Count": "98"
                                    },
                                    {
                                        "StatusCode": "403",
                                        "Count": "26"
                                    }
                                ]
                            },
                            {
                                "Time": "2021-10-21 11:15:00",
                                "Value": [
                                    {
                                        "StatusCode": "200",
                                        "Count": "299"
                                    },
                                    {
                                        "StatusCode": "403",
                                        "Count": "56"
                                    }
                                ]
                            },
                            {
                                "Time": "2021-10-21 11:20:00",
                                "Value": [
                                    {
                                        "StatusCode": "200",
                                        "Count": "54"
                                    },
                                    {
                                        "StatusCode": "403",
                                        "Count": "11"
                                    }
                                ]
                            },
                            {
                                "Time": "2021-10-21 11:25:00",
                                "Value": [
                                    {
                                        "StatusCode": "403",
                                        "Count": "56"
                                    },
                                    {
                                        "StatusCode": "200",
                                        "Count": "304"
                                    },
                                    {
                                        "StatusCode": "499",
                                        "Count": "1"
                                    }
                                ]
                            },
                            {
                                "Time": "2021-10-21 11:30:00",
                                "Value": [
                                    {
                                        "StatusCode": "403",
                                        "Count": "15"
                                    },
                                    {
                                        "StatusCode": "200",
                                        "Count": "68"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            }
        ],
        "RequestId": "7569bbc3-a3ec-4ff3-94ec-f81a63ff5587",
        "Interval": "5min"
    }
}
  1. 身份认证服务 Amazon Cognito

默认 API Gateway 创建的API是公开的,所有人都可以访问。此解决方案会创建一个 Cognito 用户池、域名、资源服务器和应用程序客户端用来实现鉴权,即只有鉴权通过后才能访问 API。

并且在 API Gateway 中会自动创建一个 Cognito 授权方,将其配置到相应 API 资源中, Cognito 的令牌需要配置在 Authorization 标头中。


 

方案测试

此解决方案部署完后,在 API Gateway 控制台中会看到以 CloudFrontPerformanceMetrics开头的 API。

首先通过下方命令获取 Cognito 令牌(user_id、secret_key、domain_name 可在 Cognito 控制台中找到,将其替换成实际值)。

curl -X POST –user 2furjuugqiqgk1n3f5j4ussenh: rf2qa6kk38ffpcpql2np4o01vo97c3rv2fkkq897ci8s7kcbd1h ‘https://<domain_name>.auth.us-east-1.amazoncognito.com/oauth2/token?grant_type=client_credentials’ -H ‘Content-Type: application/x-www-form-urlencoded’

执行完命令后会得到访问令牌,如下图所示:

API 测试工具选择 Postman,打开工具后添加 header(key 为 Authorization,value 为上图中的 access_token),输入CloudFormation部署后生成的API链接,并加上查询字符串后发送请求,即可返回监控指标。

链接示例:https://example-metric-api.us-east-1.amazonaws.com/prod/metric?StartTime=2021-10-21%2011:00:00&EndTime=2021-10-21%2011:30:00&Metric=all&Domain=example.cloudfront.net

(将域名替换成CloudFormation堆栈输出标签页的链接,Domain替换成需要获取监控指标的CloudFront分配的域名,StartTime和EndTime即为监控指标的起始时间)

 

小结

本文介绍了一种通过 Amazon CloudFront 实时日志构建 CDN 监控的解决方案,在已有的 CloudWatch 监控基础上,作为自定义实时监控的补充以满足不同的应用场景需求,帮助您实现快速高效的 CDN 管理。在开启实时日志后,数据通过 Kinesis Data Streams 和 Kinesis Data Firehose 传输到 S3 存储桶中,然后通过 Athena 查询处理数据后存储到 DynamoDB 中,最后通过 API Gateway + Cognito 提供安全的访问接口。您也不用担心上述服务的部署和配置,解决方案提供了 CloudFormation 一键部署,可以方便快捷地将整个环境准备完成。

上述解决方案已经发布到 开源解决方案Amazon CloudFront Extensions中 https://github.com/awslabs/aws-cloudfront-extensions/tree/main/templates/aws-cloudfront-monitoring,欢迎大家一起来参与贡献!

本篇作者

吕宁

亚马逊云科技解决方案架构师,负责开发和推广亚马逊云科技解决方案,在DevOps,Edge,Serverless 等方向有丰富的实践经验。

史天

亚马逊云科技解决方案架构师。拥有丰富的云计算、大数据和机器学习经验,目前致力于数据科学、机器学习、无服务器等领域的研究和实践。译有《机器学习即服务》《基于Kubernetes的DevOps实践》《Prometheus监控实战》等。

顾明

亚马逊云科技资深解决方案架构师,负责设计和研发亚马逊云科技解决方案,在通信行业和电商行业具有超过十五年的研发经验,的在Serverless, DevOps,Data Analysis等方向有丰富的实践经验。

叶明

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