让我们来谈谈新手刚刚开始使用基于 Prometheus 监控指标时应避免的 6 个常见错误:
1
错误 1:基数炸弹
这是每个人在开始使用 Prometheus 时至少会遇到一次的典型陷阱。一旦您发现 Prometheus 基于标签的数据模型的实用性,您可能会试图按各种有用的标签维度拆分指标,直到您创建的时间序列远远超过 Prometheus 服务器可以处理的数量。
例如,您可能有一个 HTTP 服务器公开一个按 HTTP 方法拆分的请求计数器:
http_requests_total{method="POST"}
http_requests_total{method="GET"}
http_requests_total{method="PUT"}
http_requests_total{method="DELETE"}
这完全没问题,只要您确保只在标签中存储有效的方法值即可 method。但是,现在您可能已经想到了按请求用户的 ID 拆分指标的好方法:
http_requests_total{method="POST",user_id="1"}
http_requests_total{method="POST",user_id="2"}
http_requests_total{method="POST",user_id="3"}
[…many more…]
http_requests_total{method="POST",user_id="16434313"}
http_requests_total{method="GET",user_id="1"}
http_requests_total{method="GET",user_id="2"}
http_requests_total{method="GET",user_id="3"}
[…many more…]
http_requests_total{method="GET",user_id="16434313"}
[…many more…]
假设您拥有超过一小撮用户,那么您现在将遭受一次严重的基数爆炸,这将使您的 Prometheus 服务器的内存使用量爆炸,直至 OOM(内存不足)崩溃。
因此,请始终牢记:指标上的每个唯一标签组合都会自动创建一个时间序列,Prometheus 必须提取、索引、存储和处理该时间序列。虽然您可以放入单个标签的值数量没有绝对的硬性限制,但您需要将其保持在 Prometheus 服务器可以处理的时间序列总预算之内。例如,大型 Prometheus 服务器可能能够处理多达几百万个时间序列,因此您需要选择标签、它们所针对的指标以及相应地公开这些指标的目标数量,以便您能够保持在该服务器的时间序列总预算之内。
特别是,您需要避免存储无界的高基数值,例如:
公共 IP 地址或电子邮件地址
完整的 HTTP 路径(如果这些路径包含 ID 或其他无界基数信息)
进程 ID(除非明确限定在特定集合内)
有时,您可以通过减少标签值的基数来解决标签的高基数问题,而无需完全删除它,同时仍保留最有用的信息。例如,对于形式为 的 HTTP 路径/api/users/739567637385/posts/28388445,您至少可以将路径中基数最高的组件(本例中为用户和帖子 ID)替换为最终标签值中的占位符,以产生更通用的模式,这样可以/api/users/{user_id}/posts/{post_id}生成更少的不同标签值,从而生成更少的时间序列。
2
错误 2:在表达式中聚合有价值的标签
在编写汇总整个服务的警报表达式时,一个常见的做法是汇总掉任何您不需要立即确定是否存在问题的标签。例如,如果您想确定服务的整体错误率(跨所有标签维度)是否过高,您可以编写如下规则:
sum(rate(errors_total{job="my-job"}[5m])) > 10
默认情况下,sum()聚合器会生成单个输出系列,不带任何标签。这不仅会删除您实际想要聚合掉的维度(例如实例、错误类型等),还会删除所有输入系列中通用的所有标签,这些标签以后可能会对 Alertmanager 中的警报路由或降低噪音有用。特别是 job 标签(或等效服务分组标签)是通用路由标签,因为作业通常与负责处理其警报的团队相关联。因此,我建议尽可能在聚合中保留此标签:
sum by(job) (rate(errors_total{job="my-job"}[5m])) > 10
一个更好的选择是保留您不想明确删除的任何标签 by(),通过使用聚合修饰符用排除列表方法替换 without()聚合修饰符:
sum without(instance, type) (rate(errors_total{job="my-job"}[5m])) > 10
这样,您未明确聚合的输入系列上的任何标签仍可在 Alertmanager 中用于警报聚合和路由,以及帮助您了解警报的来源。
3
错误 3:使用无作用域的“裸”选择器
编写 PromQL 查询时(尤其是用于警报!),您需要格外小心,只从您打算为其编写查询的作业或服务中选择数据。多个不相关的服务可能会公开相同的指标名称,甚至可能具有完全不同的语义含义。即使您最初编写查询时不是这种情况,明天可能会出现具有冲突指标名称的新服务,从而完全破坏您的警报规则或仪表板。
为了避免意外提取不相关作业的数据,请务必使用标签匹配器来限定选择器的范围,这些标签匹配器会明确指定您要引用的作业或服务的命名空间。通常,这将是 job 标签。
例如,不安全的“裸”选择器可能会从您意想不到的其他作业中选择具有相同指标名称的数据:
rate(errors_total[5m]) > 10
更安全的范围选择器将限制所选指标为来自作业的指标 my-job:
rate(errors_total{job="my-job"}[5m]) > 10
这样,您还可以避免这样的情况:一旦另一项服务被抓取并产生指标名称冲突,您的警报规则或仪表板就会在稍后开始出现告警故障。
4
错误 4:for 警报规则中没有持续时间
警报规则中的字段 for 允许您指定输出时间序列需要在连续的规则评估周期中存在多长时间才能从待处理警报转变为触发警报。这实际上允许您向警报规则添加每个系列的时间容差。现在您可能想知道哪些警报应该有 for 持续时间,以及持续时间应该有多长。在大多数警报上完全省略持续时间可能是一个好主意吗?
例如:up 考虑一个使用指标来查找无法成功抓取的目标的警报规则,并 for 省略可选的修饰符:
alert: InstanceDown
expr: up == 0
一次失败的抓取(这种情况很容易发生)将导致触发此规则。通常,您需要让警报规则不那么容易触发,并等待至少几分钟以查看问题是否仍然存在,然后再通知人工:
alert: InstanceDown
expr: up == 0
for: 5m # An instance needs to be down / unreachable for 5 minutes before creating an alert for it.
for 对于已经嵌入一些时间平均的警报表达式,不指定持续时间甚至可能会有问题。考虑在高错误率时发出此警报:
alert: HighErrorRate
expr: rate(my_errors_total{job="my-job"}[5m]) > 10
此规则将在第一次评估时触发,其中错误率较高,该错误率是在 5 分钟的可用数据时间内平均得出的。虽然 5 分钟平均值已经引入了一些稳健性,但考虑当 Prometheus 服务器完全是最新的或有一段时间没有收集数据时会发生什么:
5 分钟 rate()窗口仅会考虑几个最近的样本,而不是实际上对 5 分钟的数据进行平均。该规则现在可以针对尚未包含五分钟数据的系列立即发出警报。引入 for 持续时间可以解决这个问题:
alert: HighErrorRate
expr: rate(my_errors_total{job="my-job"}[5m]) > 10
for: 5m
大多数警报规则都有类似的争论。因此,为了使您的警报规则更加强大,您几乎总是希望将 for 持续时间设置为至少几分钟。请记住,这也会导致警报的反应时间变慢,因此找到时间容忍度的平衡点 for 很重要。
5
rate()错误 5:窗口太短
在计算时间窗口过短的速率时,您是否曾因缺口或完全空白的图表而感到沮丧,例如 rate(my_counter[1m])和 rate()其他 PromQL 函数(如 increase()、、)会告诉您时间序列在给定时间窗口内上升或下降的速度,它们都需要在输入时间窗口下至少有两个样本,才能告诉您序列在这两个样本之间的发展情况。如果您将时间窗口设置得太小,您可能会面临窗口下只有一个或零个样本的风险,在这种情况下输出将变为空。
例如,如果您采用 rate()每 15 秒抓取一次的计数器指标的 20 秒范围,那么很有可能这 20 秒通常不会覆盖两个样本,因此您会得到一个间隙率:
极端一点来说:如果你将速率窗口降低到 16 秒,则只有当两个相隔 15 秒的点恰好落入任意对齐的 16 秒窗口时,你才会偶尔获得输出点:
如果进一步减小窗口,最终您将根本没有机会覆盖两个样本,因此您会得到一个完全空的查询输出:
因此,您需要选择足够大的输入窗口 - 不仅仅是 2 倍的抓取间隔,而且您还需要在偶尔出现抓取失败和不幸的窗口对齐时保持稳健。通常,选择速率窗口大小至少为抓取间隔的 4 倍是一种很好的做法:
提示:使用 Grafana 时,您还可以使用$__rate_interval 模板变量自动为您选择安全间隔。
6
错误 6:使用 rate()错误的指标类型
虽然 PromQL 在其他一些方面是静态类型的,但它无法直接告诉您是否将错误类型的指标传递给了并非设计用于处理该指标的函数。最突出的例子是:
6.1
rate()与 Counter 指标一起使用
rate()、irate()和函数仅 increase()设计用于计数器指标。计数器是跟踪累积计数的指标,除了 0 跟踪过程重新启动时偶尔重置外,这些计数只会上升(而不会下降)。为了在速率计算中尽可能地消除计数器重置,这些函数会尝试将提供的时间窗口内样本值的任何减少解释为重置并对其进行补偿(另请参阅我们关于速率计算的详细文章)IT技术更多内容。这种计数器重置检测和补偿意味着您只会 0 从这些函数中获得正结果(或)。如果您不小心传递了一个自然上升和下降的计量指标(如内存使用率),PromQL 将无法分辨出这个错误,而只会返回一个错误的输出值。这是因为每次您的计量指标下降时,都会将 rate()其解释为计数器重置并错误地“纠正”它。
6.2
deriv()与 Counter 指标一起使用
您可以将函数视为 deriv()与 大致相同 rate(),但适用于仪表。deriv()告诉您仪表指标每秒上升或下降的速度,以在输入时间窗口内测量。虽然并不常见,但与 相同的陷阱 rate()可能会在这里反过来困扰您:由于该 deriv()函数没有实现任何围绕计数器重置的逻辑(仪表没有这些!),因此尝试计算 deriv()计数器指标的 会给您不正确的结果,有时甚至是负面的结果,只要在提供的窗口下有计数器重置。
6.3
如何避免传递错误的指标类型?
由于 PromQL 无法自动检测这种不正确的使用,遗憾的是,您在使用这些函数时必须格外小心。教育在这里有所帮助,但像最近开源的 PromLens 查询生成器这样的工具也会使用基于指标名称的启发式方法来尝试猜测您是否传递了错误的指标类型:
7
结论
希望这些技巧对您开始使用 Prometheus 有所帮助!如果您想以结构化的方式从头开始学习 Prometheus,请务必参考Prometheus官方教程,这些教程涵盖 Prometheus 的基础知识以及高级用例和集成。
推荐
运维技能大合集
原创不易,随手关注或者”在看“,诚挚感谢!