应用程序性能监控 (APM) 不仅仅是捕获和跟踪错误和堆栈跟踪。如今,基于云的企业将应用程序部署在各个区域甚至云提供商之间。因此,利用 Elastic APM 代理提供的元数据变得至关重要。利用元数据(包括云区域、提供商和机器类型等关键信息)使我们能够跨应用程序堆栈跟踪成本。在这篇博文中,我们将探讨如何使用云元数据来帮助企业做出更明智、更具成本效益的决策,同时提高资源利用率和用户体验。
首先,我们需要一个示例应用程序,使我们能够有效地监控基础设施变化。我们使用带有 Elastic Python APM 代理的 Python Flask 应用程序。该应用程序是一个简单的计算器,将数字作为 REST 请求接收。我们利用 Locust(一个简单的负载测试工具)来评估不同工作负载下的性能。
下一步包括获取与云服务相关的定价信息。每个云提供商都不同。他们中的大多数都提供通过 API 检索定价的选项。但是今天,我们将重点关注 Google Cloud,并将利用其定价计算器来检索相关的成本信息。
计算器和 Google Cloud 定价
要进行成本分析,我们需要知道所用机器的成本。Google 提供了用于以编程方式获取必要数据的计费 API 和 客户端库。在本博客中,我们不介绍 API 方法。相反,Google Cloud 定价计算器就足够了。在计算器中选择机器类型和区域,并将计数设置为 1 个实例。然后它将报告该机器的总估计成本。对 e2-standard-4 机器类型执行此操作,得出 730 小时运行时间的成本为 107.7071784 美元。
现在,让我们转到我们的 Kibana®,在其中创建 Dev Tools 内的新索引。由于我们不想分析文本,我们将告诉 Elasticsearch® 将每个文本都视为关键字。索引名称为 cloud-billing。我可能想对 Azure 和 AWS 执行相同的操作,然后可以将其附加到同一索引。
PUT cloud-billing
{
"mappings": {
"dynamic_templates": [
{
"stringsaskeywords": {
"match": "*",
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
接下来是制作我们的账单文档
POST cloud-billing/_doc/e2-standard-4_europe-west4
{
"machine": {
"enrichment": "e2-standard-4_europe-west4"
},
"cloud": {
"machine": {
"type": "e2-standard-4"
},
"region": "europe-west4",
"provider": "google"
},
"stats": {
"cpu": 4,
"memory": 8
},
"price": {
"minute": 0.002459068,
"hour": 0.14754408,
"month": 107.7071784
}
}
我们创建一个文档并设置一个自定义 ID。此 ID 与实例名称和区域匹配,因为机器的成本在每个区域中可能不同。自动 ID 可能会有问题,因为我可能想定期更新机器的成本。我可以使用时间戳索引来做到这一点,并且只使用最新匹配的文档。但是通过这种方式,我可以进行更新而不必担心它。我还将价格计算为每分钟和每小时的价格。最重要的是 machine.enrichment 字段,它与 ID 相同。同一实例类型可以存在于多个区域中,但是我们的富集处理器仅限于匹配或范围。我们创建一个匹配的名称,该名称可以显式匹配,例如 e2-standard-4_europe-west4。是否要在其中添加云提供商并将其设为 google_e2-standard-4_europ-west-4 由您决定。
计算成本
在 Elastic Stack 中有多种方法可以实现此目的。在本例中,我们将使用富集策略、提取管道和转换。
富集策略的设置相当简单
PUT _enrich/policy/cloud-billing
{
"match": {
"indices": "cloud-billing",
"match_field": "machine.enrichment",
"enrich_fields": ["price.minute", "price.hour", "price.month"]
}
}
POST _enrich/policy/cloud-billing/_execute
不要忘记在末尾运行 _execute。这对于在提取管道中生成富集使用的内部索引是必需的。提取管道非常简单 — 它调用富集并重命名一个字段。这就是我们的 machine.enrichment 字段的用武之地。关于富集的一个注意事项是,当您向 cloud-billing 索引添加新文档时,您需要重新运行 _execute 语句。最后一部分计算总成本,其中包含所看到的唯一机器的数量。
PUT _ingest/pipeline/cloud-billing
{
"processors": [
{
"set": {
"field": "_temp.machine_type",
"value": "{{cloud.machine.type}}_{{cloud.region}}"
}
},
{
"enrich": {
"policy_name": "cloud-billing",
"field": "_temp.machine_type",
"target_field": "enrichment"
}
},
{
"rename": {
"field": "enrichment.price",
"target_field": "price"
}
},
{
"remove": {
"field": [
"_temp",
"enrichment"
]
}
},
{
"script": {
"source": "ctx.total_price=ctx.count_machines*ctx.price.hour"
}
}
]
}
由于现在所有这些都已配置完成,我们已为转换做好准备。为此,我们需要一个与 APM data_streams 匹配的数据视图。这是 traces-apm*、metrics-apm.*、logs-apm.*。对于转换,请转到 Kibana 中的“转换”UI 并按以下方式进行配置
我们正在进行每小时细分,因此,我每个服务、每小时、每种机器类型都会获得一个文档。有趣的是聚合。我想查看平均 CPU 使用率以及第 75、95、99 个百分位数,以按小时查看 CPU 使用率。这使我能够识别一个小时内的 CPU 使用率。在底部,为转换命名并选择索引 cloud-costs 并选择 cloud-billing 提取管道。
这是整个转换为 JSON 文档
PUT _transform/cloud-billing
{
"source": {
"index": [
"traces-apm*",
"metrics-apm.*",
"logs-apm.*"
],
"query": {
"bool": {
"filter": [
{
"bool": {
"should": [
{
"exists": {
"field": "cloud.provider"
}
}
],
"minimum_should_match": 1
}
}
]
}
}
},
"pivot": {
"group_by": {
"@timestamp": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "1h"
}
},
"cloud.provider": {
"terms": {
"field": "cloud.provider"
}
},
"cloud.region": {
"terms": {
"field": "cloud.region"
}
},
"cloud.machine.type": {
"terms": {
"field": "cloud.machine.type"
}
},
"service.name": {
"terms": {
"field": "service.name"
}
}
},
"aggregations": {
"avg_cpu": {
"avg": {
"field": "system.cpu.total.norm.pct"
}
},
"percentiles_cpu": {
"percentiles": {
"field": "system.cpu.total.norm.pct",
"percents": [
75,
95,
99
]
}
},
"avg_transaction_duration": {
"avg": {
"field": "transaction.duration.us"
}
},
"percentiles_transaction_duration": {
"percentiles": {
"field": "transaction.duration.us",
"percents": [
75,
95,
99
]
}
},
"count_machines": {
"cardinality": {
"field": "cloud.instance.id"
}
}
}
},
"dest": {
"index": "cloud-costs",
"pipeline": "cloud-costs"
},
"sync": {
"time": {
"delay": "120s",
"field": "@timestamp"
}
},
"settings": {
"max_page_search_size": 1000
}
}
创建并运行转换后,我们需要一个索引为 cloud-costs 的 Kibana 数据视图。对于事务,请在 Kibana 中使用自定义格式化程序,并将其格式设置为“微秒”中的“持续时间”。
这样,一切都安排好了,可以开始了。
观察基础设施变化
下面我创建了一个仪表板,使我们能够识别
- 某个服务产生多少成本
- CPU 使用率
- 内存使用率
- 事务持续时间
- 识别节省成本的潜力
从左到右,我们希望关注第一个图表。我们看到柱状图表示 CPU,其中绿色表示平均值,蓝色表示顶部的第 95 个百分位数。它从 0 到 100% 不等,并且已归一化,这意味着即使有 8 个 CPU 内核,它仍然会读取 100% 的使用率,而不是 800%。折线图表示事务持续时间,其中红色表示平均值,紫色表示第 95 个百分位数。最后,我们在底部有一个橙色区域,它是该主机上的平均内存使用率。
我们立即意识到我们的计算器不需要大量的内存。将鼠标悬停在图表上会显示 2.89% 的内存使用率。我们正在使用的 e2-standard-8 机器有 32 GB 的内存。在第 95 个百分位数时,我们偶尔会将 CPU 使用率飙升至 100%。当这种情况发生时,我们看到平均事务持续时间飙升至 2.5 毫秒。但是,这台机器每小时花费我们大约 30 美分。利用这些信息,我们现在可以缩小规模以更好地适应。平均 CPU 使用率约为 11-13%,而第 95 个百分位数并没有那么远。
因为我们正在使用 8 个 CPU,所以现在可以说 12.5% 代表一个完整的核心,但这只是纸上谈兵的假设。尽管如此,我们知道有很大的余地,我们可以大幅缩减规模。在这种情况下,我决定使用 2 个 CPU 和 2 GB 的 RAM,即 e2-highcpu2。这应该更适合我的计算器应用程序。我们几乎没有触及 RAM,32GB 中的 2.89% 大约是 1GB 的使用量。在更改并重启计算器机器后,我启动了相同的 Locust 测试,以确定我的 CPU 使用率,更重要的是,我的事务是否变慢,如果变慢,则变慢多少。最终,我想决定 1 毫秒的额外延迟是否值得每小时多支付 10 美分。我将此更改作为 Lens 中的注释添加。
在运行一段时间后,我们现在可以确定较小主机的影响。在这种情况下,我们可以看到平均值没有变化。但是,第 95 个百分位数(即 95% 的事务都低于此值)确实飙升了。再次,乍一看很糟糕,但检查一下,它从 ~1.5 毫秒增加到 ~2.10 毫秒,增加了 ~0.6 毫秒。现在,您可以决定这 0.6 毫秒的增加是否值得每月多支付约 180 美元,或者当前的延迟是否足够好。
结论
可观测性不仅仅是收集日志、指标和跟踪。将用户体验与云成本联系起来,可以让您的企业识别出可以省钱的领域。拥有合适的工具可以帮助您快速生成这些见解。就如何优化云成本并最终改善用户体验做出明智的决策是最终目标。
仪表板和数据视图可以在我的 GitHub 存储库中找到。您可以下载 .ndjson 文件,并使用 Kibana 中 Stack Management 内的 Saved Objects 导入它。
注意事项
定价仅适用于不包含任何磁盘信息、静态公共 IP 地址以及任何其他额外成本(例如操作系统许可)的基础机器。此外,它不包括竞价定价、折扣或免费信用额度。此外,服务之间的数据传输成本也不包括在内。我们仅根据服务运行的分钟费率进行计算 — 我们不检查 Google Cloud 的计费间隔。在我们的例子中,无论 Google Cloud 有什么,我们都会按分钟计费。使用唯一 instance.ids 的计数按预期工作。但是,如果一台机器只运行一分钟,我们将根据小时费率进行计算。因此,一台运行一分钟的机器,其成本与运行 50 分钟的机器相同 — 至少我们是这样计算的。转换使用日历小时间隔;因此,它是上午 8 点到上午 9 点,上午 9 点到上午 10 点,依此类推。
本帖子中描述的任何功能或特性的发布和时间安排仍由 Elastic 自行决定。任何当前不可用的功能或特性都可能无法按时交付,甚至根本无法交付。