如何使用 Prometheus 在 GitLab 中进行异常检测

Prometheus 查询语言的最基本功能之一是实时汇总时间序列数据。GitLab基础架构团队的杰出工程师 Andrew Newdigate 认为 Prometheus 查询语言也能够用于检测时间序列数据中的异常。本博客文章解释了异常检测如何与 Prometheus 一块儿工做,并包括您须要在本身的系统上亲自尝试的代码片断。php

为何异常检测有效?html

异常检测对 GitLab 很是重要的四个关键缘由:git

  • 诊断事件:咱们能够快速找出哪些服务执行超出正常范围,并减小检测事件的平均时间(MTTD),从而更快地解决问题。web

  • 检测应用程序性能回归:例如,若是引入 n+1 回归,发现一个服务以很高的速率调用另外一个服务,能够迅速找到问题并加以解决。api

  • 识别并解决滥用问题:GitLab 提供免费计算(GitLab CI/CD)和托管(GitLab Pages),会被一小部分用户加以利用。安全

  • 安全性:异常检测对于发现 GitLab 时间序列数据中的异常趋势相当重要。网络

因为以上以及其余许多缘由,Andrew 研究了是否能够仅经过使用 Prometheus 查询和规则对 GitLab 时间序列数据执行异常检测。架构

正确的聚合级别是什么?ide

首先,时间序列数据必须正确聚合。尽管能够将相同的技术应用到许多其余类型的指标,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 同样。

接下来,您必须为正在使用的数据选择正确的聚合级别。这有点像“金发姑娘问题”-太多,太少仍是恰到好处-但这对于发现异常相当重要。过多地汇总数据,数据将缩减为太小的维度,从而产生两个潜在的问题:

  1. 可能会错过真正的异常,由于聚合隐藏了数据子集中出现的问题。

  2. 若是确实检测到异常,则不对异常进行更多调查,则很难将其归因于系统的特定部分。

可是,聚合的数据汇太少,最终可能会获得一系列样本量很是小的数据。如此可能致使误报,并可能将真实数据标记为离群值(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,则尾巴太长,结果将倾斜。还要记住,这须要在聚合而不是非聚合的序列上运行。可能没有正态分布的指标包括诸如错误率、等待时间、队列长度等,可是不管如何,在固定阈值下告警,许多这些指标都趋向于工做的很好。

使用季节性检测异常

尽管时间序列数据为正态分布时,计算 z-score 效果很好,可是还有第二种方法能够产生更准确的异常检测结果。季节性是时间序列指标的一个特征,其中该指标会经历按期且可预测的变化,这些变化会在每一个周期重复出现。

周一至周日连续四个星期的每秒 Gitaly 请求(RPS)

该图说明了连续四周的周一到周日的 7 天中 Gitaly 的 RPS(每秒请求数)速率。七天范围称为“偏移”,表示须要度量的模式。图上的每一个星期都有不一样的颜色。数据的季节性由图表中所示趋势的一致性表示 —— 每一个星期一早晨,RPS 速率都会上升,而在星期五晚上,RPS 速率会逐渐降低,每周如此。

经过利用时间序列数据中的季节性,能够建立更准确的预测,从而更好地进行异常检测。

如何利用季节性?

使用 Prometheus 计算季节性,须要在一些不一样的统计原理上迭代。

在第一次迭代中,咱们经过将目前滚动的一周的增加趋势(注:平均值)与前一周的值相加来计算。经过从目前滚动的一周平均值中减去上周的滚动一周平均值来计算增加趋势。

- record: job:http_requests:rate5m_prediction
  expr: >
    job:http_requests:rate5m offset 1w                     # Value from last period
    + job:http_requests:rate5m:avg_over_time_1w            # One-week growth trend
    - job:http_requests:rate5m:avg_over_time_1w offset 1w

第一次迭代有点狭窄;咱们使用本周和上周的五分钟窗口来得出咱们的预测。

在第二次迭代中,将上周的四个小时平均值做为平均值,并将其与本周进行比较,以扩大范围。所以,若是要预测一个星期一上午 8 点的指标值,不是使用一周前的相同五分钟窗口,而是使用前一周早上的上午 6 点至上午 10 点的指标平均值。

- record: job:http_requests:rate5m_prediction
  expr: >
    avg_over_time(job:http_requests:rate5m[4h] offset 166h) # Rounded value from last period
    + job:http_requests:rate5m:avg_over_time_1w             # Add 1w growth trend
    - job:http_requests:rate5m:avg_over_time_1w offset 1w

在查询中使用 166 个小时而不是一周,由于要根据一天中的当前时间使用四个小时,所以须要将偏移减小两个小时。

两周的 Gitaly 服务 RPS(黄色)vs 预测(蓝色)

将实际的 Gitaly RPS(黄色)与 预测(蓝色)进行比较代表,计算至关准确。可是,这种方法有缺陷。由于 5 月 1 日是国际劳动节,一个许多国家庆祝的节日,GitLab 的使用量低于日常的星期三。因为增加率是由前一周的使用状况决定的,所以咱们对下周(5 月 8 日,星期三)RPS 的预测会比 若是 5 月 1 日(星期三)没有假期更低。

能够经过在 5 月 1 日(星期三)以前连续三周(以前的星期三,再以前的星期三和三周以前的星期三)进行三个预测来解决此问题。查询保持不变,但偏移量已调整。

- record: job:http_requests:rate5m_prediction
  expr: >
   quantile(0.5,
     label_replace(
       avg_over_time(job:http_requests:rate5m[4h] offset 166h)
       + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 1w
       , "offset", "1w", "", "")
     or
     label_replace(
       avg_over_time(job:http_requests:rate5m[4h] offset 334h)
       + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 2w
       , "offset", "2w", "", "")
     or
     label_replace(
       avg_over_time(job:http_requests:rate5m[4h] offset 502h)
       + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 3w
       , "offset", "3w", "", "")
   )
   without (offset)

三个星期三的三个预测与实际 5 月 8 日(星期三,国际劳动节以后的一周)的 Gitaly RPS

在该图上,绘制了 5 月 8 日星期三和 5 月 8 日以前连续三个星期的三个预测。能够看到其中两个预测是好的,可是 5 月 1 日的预测仍远未达到基准。

并且,咱们不须要三个预测,咱们想要一个预测。取平均值是不可行的,由于它将被倾斜的 5 月 1 日 RPS 数据所稀释。相反,咱们要计算中位数。Prometheus 没有中位数查询,但可使用分位数聚合来代替中位数。该方法的一个问题是,试图在一个聚合中包括三个系列,而这三个系列实际上在三周内都是相同的系列。换句话说,它们都具备相同的标签,所以链接它们很棘手。为避免混淆,咱们建立了一个名为 offset 的标签,并使用 label-replace 函数为三个星期添加 offset。接下来,在分位数聚合中,将其去除,以得到了三个中间值。

- record: job:http_requests:rate5m_prediction
  expr: >
   quantile(0.5,
     label_replace(
       avg_over_time(job:http_requests:rate5m[4h] offset 166h)
       + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 1w
       , "offset", "1w", "", "")
     or
     label_replace(
       avg_over_time(job:http_requests:rate5m[4h] offset 334h)
       + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 2w
       , "offset", "2w", "", "")
     or
     label_replace(
       avg_over_time(job:http_requests:rate5m[4h] offset 502h)
       + job:http_requests:rate5m:avg_over_time_1w - job:http_requests:rate5m:avg_over_time_1w offset 3w
       , "offset", "3w", "", "")
   )
   without (offset)

如今,从三个聚合系列中得出中值的预测更加准确。

中位数预测与实际 Gitaly RPS 的比较,5 月 8 日(星期三,国际劳动节以后的一周)

怎么知道预测是真正准确的?

为了测试预测的准确性,能够返回 z-score。可使用 z-score 来测量样本与标准误差预测值之间的差距。偏离预测的标准误差越多,则特定值是异常可能性就越大。

Gitaly 服务的预测正常范围 ±1.5σ

咱们能够更新 Grafana 图表以使用季节性预测而不是每周滚动平均值。一天中特定时间的正常范围以绿色阴影显示。任何落在绿色阴影区域以外的东西都被认为是异常值。在这种状况下,离群值发生在周日下午,此时咱们的云提供商遇到了一些网络问题。在咱们的预测的任一侧使用±2σ的边界是肯定季节性预测的异常值的一种很好的方法。

如何使用 Prometheus 设置警报

若是要为异常事件设置警报,能够对 Prometheus 应用一个很是简单的规则,该规则检查指标的 z-score 是否在标准误差 +2 或 -2 之间。

- alert: RequestRateOutsideNormalRange
  expr: >
   abs(
     (
       job:http_requests:rate5m - job:http_requests:rate5m_prediction
     ) / job:http_requests:rate5m:stddev_over_time_1w
   ) > 2
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: Requests for job {
   
   { $labels.job }} are outside of expected operating parameters

在 GitLab,咱们使用了自定义路由规则,该规则会在检测到任何异常时 pings Slack,但不会寻呼值班的支持人员。

总结

  • Prometheus 可用于某些类型的异常检测

  • 正确级别的数据聚合是异常检测的关键

  • 若是数据具备正态分布,则 z-score 是一种有效的方法

  • 季节性指标能够为异常检测提供出色的结果

译文连接:https://www.cyningsun.com/01-22-2020/use-prometheus-for-anomaly-detection.html

Kubernetes管理员认证(CKA)培训

本次CKA培训将于11月20到22日在北京开课,培训基于最新考纲,经过线下授课、考题解读、模拟演练等方式,帮助学员快速掌握Kubernetes的理论知识和专业技能,并针对考试作特别强化训练,让学员能从容面对CKA认证考试,使学员既能掌握Kubernetes相关知识,又能经过CKA认证考试,学员可屡次参加培训,直到经过认证。点击下方图片或者阅读原文连接查看详情。

相关文章
相关标签/搜索