Original URL:https://aws.amazon.com/blogs/big-data/analyze-your-amazon-s3-spend-using-aws-glue-and-amazon-redshift/
AWS 成本和使用情况报告 (CUR) 会追踪您的 AWS 使用情况,并提供与之相关联的预计费用。您可以配置此报告为以小时或日为间隔显示数据;在账单收费期最后结束前,它至少每天会更新一次。成本和使用情况报告会自动发送到由您指定的 Amazon S3 存储桶,可以在那里直接下载。您还可以将该报告整合到 Amazon Redshift,也可以通过 Amazon Athena 进行查询,或者把它上传到 Amazon QuickSight。如需更多信息,见使用 Amazon Athena 和 Amazon QuickSight 对 AWS 成本和使用情况数据进行查询与可视化。
本文将介绍采用 AWS Glue 数据目录和 Amazon Redshift,整合 AWS CUR、S3 清单报告和 S3 服务器访问日志,从而对 S3 使用情况和支出进行分析的解决方案。
先决条件
在开始前,满足以下先决条件:
- 您需要一个 S3 存储桶,以便用于 S3 清单和服务器访问日志数据文件。如需更多信息,见创建存储桶和什么是 Amazon S3?
- 您必须为Amazon Redshift和AWS Glue创建或者选择合适的具有读取S3上数据权限的IAM角色针对本文,选择两个非限制性的 IAM 角色(AmazonS3FullAccess 和 AWSGlueConsoleFullAccess),建议您根据您的实际情况对访问加以限制。
Amazon S3 清单
Amazon S3 清单是 S3 提供帮助对您的存储进行管理的工具之一。您可以用它对您的业务、合规和监管需求对象的复制与加密状态进行审计和报告。Amazon S3 清单每天或每周会为特定 S3 存储桶提供列出您的对象及其对应元数据列表, 以CSV、ORC,或Parquet 格式输出。
Amazon S3 服务器访问文件
服务器访问日志详细记录了您向存储桶发起的请求。服务器访问日志对于众多应用程序来说都相当有用,如在安全方面和访问审计时。它还有助于理解您的客户群与您的 S3 账单。
AWS Glue
AWS Glue 是一项完全托管的提取、转换和加载 (ETL) 服务,可简化并使对您的数据进行分类、清理、丰富,以及在各种数据存储之间进行可靠移动变得更具成本效益。AWS Glue 由一个被称为“数据目录”的中央元数据存储库、一个使用表格填充“数据目录”的爬网程序、一个自动生成 Python 或 Scala 代码的 ETL 引擎,以及一个可处理依赖关系解析、作业监控和重试的灵活计划程序组成。AWS Glue 采用无服务器技术,无需对基础设施进行设置或管理。本文使用 AWS Glue 对 S3 清单数据和服务器访问日志进行目录编列,让您可以通过 Amazon Redshift Spectrum 进行查询。
Amazon Redshift
Amazon Redshift 是一项完全托管的云中 PB 级数据仓库服务。您可以使用Amazon Redshift 对存储在 S3 上结构化数据进行高效查询与检索,而不用将数据加载到 Amazon Redshift 原生表当中。您可以在 AWS Glue 数据目录中定义文件的结构,从而创建 Amazon Redshift 外表。
设置 S3 清单报告以用于分析
本文将 Parquet 做为清单报告输出文件格式,并且每天将文件传输到 S3 存储桶。您可以在高级设置下方选择传输的频率与输出文件格式,如下方截图所示:
如需关于配置您的 S3 清单的更多信息,见如何配置 Amazon S3 清单?
下图显示的是此解决方案的数据流:
以下步骤对上述数据流图表进行了总结:
- S3 清单报告被传输到您配置的 S3 存储桶。
- AWS Glue 爬网程序会爬取此 S3 存储桶,并在 AWS Glue 数据目录中填充元数据。
- 然后,即可通过 Redshift 中的外部scheme对 AWS Glue 数据目录进行访问。
- 现在,您可以合并与查询 S3 清单报告(在 AWS Glue 数据目录中提供)以及成本和使用情况报告(在其他 S3 存储桶中提供)以用于分析。
清单报告被传输至 S3 存储桶。以下截图显示的是 S3 清单报告的 S3 存储桶结构:
此存储桶中有一个数据文件夹。此文件夹含有您想要分析的 Parquet 格式数据。以下截图显示的是该文件夹的内容。
这些都是日常文件,每天都有一个。
配置 AWS Glue 爬网程序
您可以使用 AWS Glue 爬网程序来发现您的 S3 存储桶中的此数据集,并且在数据目录中创建Schema 。在创建这些表以后,您可以直接从 Amazon Redshift 对其进行查询。
要配置您的爬网程序以便从您的 S3 存储桶读取 S3 清单文件,执行以下步骤:
- 选择爬网程序名称。
- 选择 S3 作为数据存储,并指定 S3 到数据的路径
- 选择从 S3 读取数据的 IAM 角色—
AmazonS3FullAccess
和 AWSGlueConsoleFullAccess
。
- 设置运行爬网程序的频率计划。
- 通过选择数据库与添加前缀(若有)配置爬网程序的输出。
本文使用数据库 s3spendanalysis。
以下截图显示的是已完成的爬网程序配置。
运行此爬网程序,以添加表到您的 Glue 数据目录。在成功完成对爬网程序的设置以后,转至您的 AWS Glue 控制台的表部分,以验证表的详细信息和表的元数据。以下截图显示的是在成功设置 AWS Glue 爬网程序以后的表的详细信息和表的元数据:
创建外部 schema
在可以对 S3 清单报告进行查询前,您需要在 Amazon Redshift 中创建外部schema(并随后创建外部表)。Amazon Redshift 外部schema会引用外部数据目录中的外部数据库。由于使用 AWS Glue 数据目录作为您的外部目录,在 Amazon Redshift 内创建外部schema以后,您可以在 Amazon Redshift 内看到您的数据目录中的全部外部表。要创建外部schema,输入以下代码:
create external schema spectrum_schema from data catalog
database 's3spendanalysis'
iam_role 'arn:aws:iam::<AWS_IAM_ROLE>';
查询表
在 Amazon Redshift 控制面板的查询编辑器的下方,您可以看到数据表。您还可以查询 svv_external_schemas 数据表,以验证您的外部schema创建成功。见以下截图。
您现在可以从 Amazon Redshift 直接查询 S3 清单报告,而无需首先将数据移动到 Amazon Redshift。以下截图显示的是如何使用 Amazon Redshift 控制台的“查询编辑器”执行此操作:
设置 S3 服务器访问日志以用于分析
下图显示的是此解决方案的数据流。
以下步骤对上述数据流图表进行了总结:
- S3 服务器访问日志会被传输到您配置的 S3 存储桶。
- 然后可以从 Amazon Redshift 直接对这些服务器访问日志进行访问查询(注,我们将选择创建外部表用于此目的,如下说明)。
- 现在,您可以合并与查询 S3 服务器访问日志和成本和使用情况报告(在其他 S3 存储桶中提供)以用于分析。
S3 服务器访问日志会被传输到 S3 存储桶。如需关于设置服务器访问日志记录的更多信息,见 Amazon S3 服务器访问日志记录。
以下截图显示的是存放服务器访问日志的 S3 存储桶结构。
服务器访问日志文件由一系列以换行符分隔的日志记录组成。每条日志记录表示一次请求,并且由以空格分隔的字段组成。以下代码为日志记录的示例:
b8ad5f5cfd3c09418536b47b157851fb7bea4a00486471093a7d765e35a4f8ef s3spendanalysisblog [23/Sep/2018:22:10:52 +0000] 72.21.196.65 arn:aws:iam::<AWS Account #>:user/shayons D5633DAD1063C5CA REST.GET.LIFECYCLE - "GET /s3spendanalysisblog?lifecycle= HTTP/1.1" 404 NoSuchLifecycleConfiguration 332 - 105 - "-" "S3Console/0.4, aws-internal/3 aws-sdk-java/1.11.408 Linux/4.9.119-0.1.ac.277.71.329.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.181-b13 java/1.8.0_181" -
创建外部表
您可以将 S3 服务器访问日志定义为外部表。由于您已经有外部schema,因此要使用以下代码创建外部表。本文使用 RegEx SerDe 创建表,以允许您正确地解析 S3 服务器访问日志中的全部字段。见以下代码:
CREATE EXTERNAL TABLE spectrum_schema.s3accesslogs(
BucketOwner varchar(256),
Bucket varchar(256),
RequestDateTime varchar(256),
RemoteIP varchar(256),
Requester varchar(256),
RequestID varchar(256),
Operation varchar(256),
Key varchar(256),
RequestURI_operation varchar(256),
RequestURI_key varchar(256),
RequestURI_httpProtoversion varchar(256),
HTTPstatus varchar(256),
ErrorCode varchar(256),
BytesSent varchar(256),
ObjectSize varchar(256),
TotalTime varchar(256),
TurnAroundTime varchar(256),
Referrer varchar(256),
UserAgent varchar(256),
VersionId varchar(256))
ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
WITH SERDEPROPERTIES (
'input.regex' = '([^ ]*) ([^ ]*) \\[(.*?)\\] ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) \"([^ ]*) ([^ ]*) ([^ ]*)\" (-|[0-9]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*) (\"[^\"]*\") ([^ ]*)'
)
STORED AS TEXTFILE
LOCATION
's3://s3spendanalysisblog/accesslogs/';
验证数据
您可以验证 Amazon Redshift 中的外部表数据。以下截图显示的是如何使用 Amazon Redshift 控制台的“查询编辑器”执行此操作:
您现在已准备好分析数据。
使用 Amazon Redshift 分析数据
在本文中,您的 S3 存储桶内每天都会有一个 CUR 文件。以月为单位对文件进行整理。见以下截图。
每天的文件由 CUR 数据的以下文件组成:
- myCURReport-1.csv.gz—数据本身的压缩文件
- myCURReport-Manifest.json—含有文件元数据的 JSON 文件
- myCURReport-RedshiftCommands.sql—用于从 Redshift 清单文件创建 CUR 表的 Amazon Redshift 表创建脚本和 COPY 命令
- myCURReport-RedshiftManifest.json—用于创建 CUR 表的 Amazon Redshift 清单文件
使用 Amazon Redshift 是执行此分析的众多方式之一。Amazon Redshift 是一个快速、完全托管的云数据仓库,可简化并使通过标准的 SQL 和您的现有商业智能 (BI) 工具对您的全部数据进行分析变得更具成本效益。Amazon Redshift 为您提供针对使用熟悉的基于 SQL 客户端的结构化数据和使用标准 ODBC 和 JDBC 连接的 BI 工具的快速查询功能。查询分布且并行于多个物理资源。
您现在可以使用 Amazon Redshift SQL 查询编辑器运行 SQL 查询。本文还会使用 psql 客户端工具(PostgreSQL 的基于终端的前端)查询集群中的数据。
要查询数据,执行以下步骤:
- 创建自定义 schema 以包含用于分析的表。见以下代码:
create schema if not exists redshift_schema;
您应该在 schema 中创建您的表,而不是对公众开放以控制用户访问数据库对象。
- 使用 S3 中的 CUR SQL 文件为 Amazon Redshift 中的最近一个月创建 CUR 表。见以下代码:
create table redshift_schema.AWSBilling201910 (
identity_LineItemId VARCHAR(256),
identity_TimeInterval VARCHAR(100),
bill_InvoiceId VARCHAR(100),
bill_BillingEntity VARCHAR(10),
bill_BillType VARCHAR(100),
bill_PayerAccountId VARCHAR(100),
bill_BillingPeriodStartDate TIMESTAMPTZ,
bill_BillingPeriodEndDate TIMESTAMPTZ,
lineItem_UsageAccountId VARCHAR(100),
lineItem_LineItemType VARCHAR(100),
lineItem_UsageStartDate TIMESTAMPTZ,
lineItem_UsageEndDate TIMESTAMPTZ,
lineItem_ProductCode VARCHAR(100),
lineItem_UsageType VARCHAR(100),
lineItem_Operation VARCHAR(100),
lineItem_AvailabilityZone VARCHAR(100),
lineItem_ResourceId VARCHAR(256),
lineItem_UsageAmount DECIMAL(11,2),
lineItem_NormalizationFactor VARCHAR(10),
lineItem_NormalizedUsageAmount DECIMAL(11,2),
lineItem_CurrencyCode VARCHAR(10),
lineItem_UnblendedRate DECIMAL(11,2),
lineItem_UnblendedCost DECIMAL(11,2),
lineItem_BlendedRate DECIMAL(11,2),
lineItem_BlendedCost DECIMAL(11,2),
lineItem_LineItemDescription VARCHAR(100),
lineItem_TaxType VARCHAR(100),
lineItem_LegalEntity VARCHAR(100),
product_ProductName VARCHAR(100),
product_alarmType VARCHAR(100),
product_automaticLabel VARCHAR(100),
product_availability VARCHAR(100),
product_availabilityZone VARCHAR(100),
product_clockSpeed VARCHAR(100),
product_currentGeneration VARCHAR(100),
product_databaseEngine VARCHAR(100),
product_dedicatedEbsThroughput VARCHAR(100),
product_deploymentOption VARCHAR(100),
product_durability VARCHAR(100),
product_ecu VARCHAR(100),
product_edition VARCHAR(100),
product_engineCode VARCHAR(100),
product_enhancedNetworkingSupported VARCHAR(100),
product_eventType VARCHAR(100),
product_feeCode VARCHAR(100),
product_feeDescription VARCHAR(100),
product_fromLocation VARCHAR(100),
product_fromLocationType VARCHAR(100),
product_gpu VARCHAR(100),
product_gpuMemory VARCHAR(100),
product_group VARCHAR(100),
product_groupDescription VARCHAR(100),
product_instanceFamily VARCHAR(100),
product_instanceType VARCHAR(100),
product_instanceTypeFamily VARCHAR(100),
product_io VARCHAR(100),
product_labelingTaskType VARCHAR(100),
product_licenseModel VARCHAR(100),
product_location VARCHAR(100),
product_locationType VARCHAR(100),
product_maxThroughputvolume VARCHAR(100),
product_maxVolumeSize VARCHAR(100),
product_memory VARCHAR(100),
product_messageDeliveryFrequency VARCHAR(100),
product_messageDeliveryOrder VARCHAR(100),
product_minVolumeSize VARCHAR(100),
product_networkPerformance VARCHAR(100),
product_normalizationSizeFactor VARCHAR(100),
product_operation VARCHAR(100),
product_physicalCpu VARCHAR(100),
product_physicalGpu VARCHAR(100),
product_physicalProcessor VARCHAR(100),
product_processorArchitecture VARCHAR(100),
product_processorFeatures VARCHAR(100),
product_productFamily VARCHAR(100),
product_protocol VARCHAR(100),
product_queueType VARCHAR(100),
product_region VARCHAR(100),
product_servicecode VARCHAR(100),
product_servicename VARCHAR(100),
product_sku VARCHAR(100),
product_storage VARCHAR(100),
product_storageClass VARCHAR(100),
product_storageMedia VARCHAR(100),
product_subscriptionType VARCHAR(100),
product_toLocation VARCHAR(100),
product_toLocationType VARCHAR(100),
product_transferType VARCHAR(100),
product_usageFamily VARCHAR(100),
product_usagetype VARCHAR(100),
product_vcpu VARCHAR(100),
product_version VARCHAR(100),
product_volumeType VARCHAR(100),
product_workforceType VARCHAR(100),
pricing_RateId VARCHAR(100),
pricing_publicOnDemandCost DECIMAL(11,2),
pricing_publicOnDemandRate DECIMAL(11,2),
pricing_term VARCHAR(100),
pricing_unit VARCHAR(100),
reservation_AmortizedUpfrontCostForUsage DECIMAL(11,2),
reservation_AmortizedUpfrontFeeForBillingPeriod DECIMAL(11,2),
reservation_EffectiveCost DECIMAL(11,2),
reservation_EndTime TIMESTAMPTZ,
reservation_ModificationStatus VARCHAR(100),
reservation_NormalizedUnitsPerReservation BIGINT,
reservation_RecurringFeeForUsage DECIMAL(11,2),
reservation_StartTime TIMESTAMPTZ,
reservation_SubscriptionId VARCHAR(100),
reservation_TotalReservedNormalizedUnits BIGINT,
reservation_TotalReservedUnits BIGINT,
reservation_UnitsPerReservation BIGINT,
reservation_UnusedAmortizedUpfrontFeeForBillingPeriod DECIMAL(11,2),
reservation_UnusedNormalizedUnitQuantity BIGINT,
reservation_UnusedQuantity BIGINT,
reservation_UnusedRecurringFee DECIMAL(11,2),
reservation_UpfrontValue BIGINT
);
- 使用提供的 CUR 清单文件将最近一个月的数据加载到 Amazon Redshift 当中。见以下代码:
copy AWSBilling201910 from 's3://ss-cur//myCURReport/20191001-20191101/fd76beee-0709-42d5-bcb2-bb45f8ba1aae/myCURReport-RedshiftManifest.json'
credentials 'arn:aws:iam::<AWS_IAM_ROLE>'
GZIP CSV IGNOREHEADER 1 TIMEFORMAT 'auto' manifest;
- 验证被加载到 Amazon Redshift 表的数据。见以下代码:
select * from AWSBilling201910
where lineItem_ProductCode = 'AmazonS3'
and lineItem_ResourceId = 's3spendanalysisblog' limit 10;
以下截图显示的是已被正确加载到 Amazon Redshift 表的数据:
管理数据库安全性
您可以通过控制哪些用户拥有哪些数据库对象的访问权限,在 Amazon Redshift 中管理数据库安全性。要确保您的对象正确无误,创建两个组:FINANCE
和 ADMIN
,两个用户在 FINANCE
中,一个用户在 ADMIN
。请执行以下步骤:
- 创建组,并将用户账户分配到该组。以下代码会创建两个不同的用户组:
create group finance;
create group admin;
要查看所有用户组,查询 PG_GROUP
系统目录表(您应该可以在此处看到 finance 和 admin):
- 创建具有不同权限的三个数据库用户,然后将其添加到组。见以下代码:
create user finance1 password 'finance1Pass'
in group finance;
create user finance2 password 'finance2Pass'
in group finance;
create user admin1 password 'admin1Pass'
in group admin;
验证已成功创建用户。要查看用户列表,查询 PG_USER
目录表:
- 针对
redshift_schema
中的表 AWSBilling201910
,向 FINANCE
组授予 SELECT 权限,向 ADMIN
组授予 ALL 权限。见以下代码:
grant select on table redshift_schema.AWSBilling201910 to group finance;
grant all on table redshift_schema.AWSBilling201910 to group admin;
您可以验证是否已正确执行数据库安全措施。用户 finance1
尝试对 redshift_schema
中的表 AWSBilling201910
进行重命名,但却收到 permission denied
错误消息(由于访问受限)。以下截图显示的是此种情况,以及后续的错误消息:
S3 清单分析示例
根据存储桶对 S3 费用进行拆分。以下查询会识别每个独立 S3 存储桶的数据存储和传输成本:
SELECT
"lineitem_productcode",
"lineitem_usagetype",
"lineitem_resourceid",
b."storage_class",
SUM(CASE
WHEN "lineitem_usagetype" like '%Byte%' THEN "lineitem_usageamount"/1024
ELSE "lineitem_usageamount"
END) as "Usage",
CASE
WHEN "lineitem_usagetype" like '%Byte%' THEN 'TBs'
ELSE 'Requests'
END as "Usage Units",
sum("lineitem_blendedcost") as cost
from awsbilling201902 a
join spectrum_schema.data b
on a.lineItem_ResourceId = b.bucket
where "product_productname" = 'Amazon Simple Storage Service'
group by
"lineitem_productcode",
"lineitem_usagetype",
"lineitem_resourceid",
b."storage_class"
order by
sum("lineitem_blendedcost") desc;
以下截图显示的是执行上述查询的结果:
根据存储类型拆分成本(如,Glacier 和标准存储)。
以下查询会识别不同 S3 存储类(使用量、非混合成本、混合成本)的 S3 数据传输成本(区域内和区域间):
SELECT
lineitem_productcode
,product_fromlocation
,product_tolocation,
b.storage_class
,sum(lineitem_usageamount) usageamount
,sum(lineitem_unblendedcost) unblendedcost
,sum(lineitem_blendedcost) blendedcost
FROM
awsbilling201902 a
join spectrum_schema.data b
ON
a.lineItem_ResourceId = b.bucket
WHERE
a.lineitem_productcode = 'AmazonS3'
AND a.product_productfamily = 'Data Transfer'
GROUP BY
1,2,3,4
ORDER BY
usageamount desc;
以下截图显示的是执行上述查询的结果:
以下查询会识别 S3 费用、API 请求和存储费用:
SELECT
lineitem_productcode
,product_productfamily
,b.storage_class
,sum(lineitem_usageamount) usageamount
,sum(lineitem_unblendedcost) unblendedcost
,sum(lineitem_blendedcost) blendedcost
FROM
awsbilling201902 a
join spectrum_schema.data b
ON
a.lineItem_ResourceId = b.bucket
WHERE
a.lineitem_productcode = 'AmazonS3'
and a.product_productfamily <> 'Data Transfer'
GROUP BY
1,2,3
ORDER BY
usageamount desc;
以下截图显示的是执行上述查询的结果:
服务器访问日志示例分析查询
不同操作类型的 S3 访问日志费用。以下查询会识别每项独立 HTTP 操作的数据存储和传输成本:
SELECT
"lineitem_productcode",
"lineitem_usagetype",
"lineitem_resourceid",
b."operation",
b."httpstatus",
b."bytessent",
SUM(CASE
WHEN "lineitem_usagetype" like '%Byte%'
THEN "lineitem_usageamount" / 1024
ELSE "lineitem_usageamount"
END) as "Usage",
CASE
WHEN "lineitem_usagetype" like '%Byte%'
THEN 'TBs'
ELSE 'Requests'
END as "Usage Units",
sum("lineitem_blendedcost") as cost
from awsbilling201902 a
join spectrum_schema.s3accesslogs b
on a.lineItem_ResourceId = b.bucket
where "product_productname" = 'Amazon Simple Storage Service'
group by
1, 2, 3, 4, 5, 6
order by
sum("lineitem_blendedcost") desc;
以下截图显示的是执行上述查询的结果:
以下查询会识别不同 S3 操作和 HTTP 状态(使用量、非混合成本、混合成本)的 S3 数据传输成本(区域内和区域间):
SELECT
lineitem_productcode
,product_fromlocation
,product_tolocation
,b.operation
,b.httpstatus
,sum(lineitem_usageamount) usageamount
,sum(lineitem_unblendedcost) unblendedcost
,sum(lineitem_blendedcost) blendedcost
FROM
awsbilling201902 a
JOIN spectrum_schema.s3accesslogs b
ON
a.lineItem_ResourceId = b.bucket
WHERE
a.lineitem_productcode = 'AmazonS3'
AND a.product_productfamily = 'Data Transfer'
GROUP BY
1,2,3,4,5
ORDER BY
usageamount desc;
以下截图显示的是执行上述查询的结果:
以下查询会识别 S3 费用、API 请求和存储费用:
SELECT
lineitem_productcode
,product_productfamily
,b.operation
,b.httpstatus
,sum(lineitem_usageamount) usageamount
,sum(lineitem_unblendedcost) unblendedcost
,sum(lineitem_blendedcost) blendedcost
FROM
awsbilling201902 a
JOIN spectrum_schema.s3accesslogs b
ON
a.lineItem_ResourceId = b.bucket
WHERE
a.lineitem_productcode = 'AmazonS3'
and a.product_productfamily <> 'Data Transfer'
GROUP BY
1,2,3,4
ORDER BY
usageamount desc;
以下截图显示的是执行上述查询的结果:
整体数据流图表
下图显示的是此解决方案的完整数据流。
结论
AWS Glue 提供一种简单便利而且安全高效的云原生方式,可以自动发现存储在您的 S3 存储桶中的数据。本文展示了如何通过 AWS Glue 和 Amazon Redshift,整合 AWS CUR、S3 清单报告和 S3 服务器访问日志,从而对 S3 使用情况和支出进行分析。您还将学到通过用户和组在 Amazon Redshift 中管理数据库安全性的最佳做法。使用此框架,您只需要在 AWS 管理控制台上点击几次,花费数分钟就能开始分析您的 S3 存储桶支出!
如果您有任何问题或建议,请在下方评论区留下您的意见。
关于作者
Shayon Sanyal 是 AWS 全球金融服务数据湖的数据架构师。