Prometheus 查询语言的最基本功能之一是实时汇总时间序列数据。GitLab基础架构团队的杰出工程师 Andrew Newdigate
认为 Prometheus 查询语言也可以用于检测时间序列数据中的异常。本博客文章解释了异常检测如何与 Prometheus 一起工作,并包括您需要在自己的系统上亲自尝试的代码片段。
为什么异常检测有效?
异常检测对 GitLab 非常重要的四个关键原因:
- 诊断事件:我们可以快速找出哪些服务执行超出正常范围,并减少检测事件的平均时间(MTTD),从而更快地解决问题。
- 检测应用程序性能回归:例如,如果引入 n+1 回归,发现一个服务以很高的速率调用另一个服务,可以迅速找到问题并加以解决。
- 识别并解决滥用问题:GitLab 提供免费计算(GitLab CI/CD)和托管(GitLab Pages),会被一小部分用户加以利用。
- 安全性:异常检测对于发现 GitLab 时间序列数据中的异常趋势至关重要。
由于以上以及其他许多原因,Andrew 研究了是否可以仅通过使用 Prometheus 查询和规则对 GitLab 时间序列数据执行异常检测。
正确的聚合级别是什么?
首先,时间序列数据必须正确聚合。尽管可以将相同的技术应用到许多其他类型的指标,Andrew 使用了标准计数器 http_requests_total
作为数据源进行演示。
http_requests_total{ job="apiserver", method="GET", controller="ProjectsController", status_code="200", environment="prod" }
上述示例指标有一些额外的维度:method
,controller
,status_code
,environment
,如同 Prometheus 添加的维度 instance
和 job
一样。
接下来,您必须为正在使用的数据选择正确的聚合级别。这有点像“金发姑娘问题”-太多,太少还是恰到好处-但这对于发现异常至关重要。过多地汇总数据,数据将缩减为过小的维度,从而产生两个潜在的问题:
- 可能会错过真正的异常,因为聚合隐藏了数据子集中出现的问题。
- 如果确实检测到异常,则不对异常进行更多调查,则很难将其归因于系统的特定部分。
但是,聚合的数据汇太少,最终可能会得到一系列样本量非常小的数据。如此可能导致误报,并可能将真实数据标记为离群值(outliers)。
恰到好处:我们的经验表明,正确的聚合级别是服务级别,因此我们将 job
和 environment
标签标签包括进来,但删除了其他维度。在本演讲的以下部分中使用的数据聚合包括:job
、http requests
、五分钟速率
(基本上是五分钟窗口中覆盖 job
和 environment
之上的速率)。
- record: job:http_requests:rate5m expr: sum without(instance, method, controller, status_code) (rate(http_requests_total[5m])) # --> job:http_requests:rate5m{job="apiserver", environment="prod"} 21321 # --> job:http_requests:rate5m{job="gitserver", environment="prod"} 2212 # --> job:http_requests:rate5m{job="webserver", environment="prod"} 53091
使用 z-score 进行异常检测
一些统计学的主要原理可以应用于 Prometheus 检测异常。
如果知道 Prometheus 序列的平均值和标准偏差(σ),则可以使用该系列中的任何样本来计算 z-score
。z-score
表示为:与平均值的标准偏差值。因此 z-score 为 0 表示 z-score 与具有正态分布的数据的平均值相同,而 z-score 为 1 则相对于平均值为 1.0σ,依此类推。
假设基础数据是正态分布的,则 99.7% 的样本的 z-score 应介于 0 到 3 之间。z-score 距离 0 越远,它越不可能出现。我们将此特性应用于检测 Prometheus 序列中的异常。
- 使用样本数量较大的数据计算指标的平均值和标准偏差。在此示例中,我们使用了一周的数据。如果假设我们每分钟评估一次记录规则,那么一周的时间,能获得 10,000 多个样本。
# Long-term average value for the series - record: job:http_requests:rate5m:avg_over_time_1w expr: avg_over_time(job:http_requests:rate5m[1w]) # Long-term standard deviation for the series - record: job:http_requests:rate5m:stddev_over_time_1w expr: stddev_over_time(job:http_requests:rate5m[1w])
- 一旦有了聚合的平均值和标准差,就可以计算 Prometheus 查询的 z-score。
# Z-Score for aggregation ( job:http_requests:rate5m - job:http_requests:rate5m:avg_over_time_1w ) / job:http_requests:rate5m:stddev_over_time_1w
根据正态分布的统计原理,我们可以假设任何超出大约 +3 到 -3 范围的值都是异常。我们可以围绕这一原则建立警报。例如,当聚合超出此范围超过五分钟时,我们将收到警报。
GitLab.com 页面服务 48 小时的 RPS, ±3 z-score 区域为绿色
z-score 在图形上难以解释,因为它们没有度量单位。但是此图表上的异常很容易检测。出现在绿色区域(表示 z-score 在 +3 或 -3 范围内)之外的任何值都是异常。
如果不是正态分布怎么办?
但是,请稍等:我们大跃进的假设潜在的聚合具有正态分布。如果我们使用非正态分布的数据计算 z-score,结果将不正确。有许多统计技术可以测试您的数据是否为正态分布,但是最好的选择是测试您的潜在数据的 z-score 约为 +4 到 -4。
( max_over_time(job:http_requests:rate5m[1w]) - avg_over_time(job:http_requests:rate5m[1w]) ) / stddev_over_time(job:http_requests:rate5m[1w]) # --> {job="apiserver", environment="prod"} 4.01 # --> {job="gitserver", environment="prod"} 3.96 # --> {job="webserver", environment="prod"} 2.96 ( min_over_time(job:http_requests:rate5m[1w]) - avg_over_time(job:http_requests:rate5m[1w]) ) / stddev_over_time(job:http_requests:rate5m[1w]) # --> {job="apiserver", environment="prod"} -3.8 # --> {job="gitserver", environment="prod"} -4.1 # --> {job="webserver", environment="prod"} -3.2
两个 Prometheus 查询测试 z-score 的最小和最大值。
如果结果返回的范围是 +20 到 -20,则尾巴太长,结果将倾斜。还要记住,这需要在聚合而不是非聚合的序列上运行。可能没有正态分布的指标包括诸如错误率、等待时间、队列长度等,但是无论如何,在固定阈值下告警,许多这些指标都趋向于工作的很好。