那是我,而且可能在未来的某个时候还会是我。随着我们继续扩大在Grafana实验室构建和运行Grafana Loki的团队,我决定记录我是如何发现和诊断Loki的问题的。这将是一石二鸟之举。它迫使我写下我在调查中看到的模式,并且我可以与我们不断增长的团队和社区分享这些知识--那些在工作中、在家里或在任何地方运行Loki的人。
在这里,我将通过我为Grafana Cloud Logs所做的实际调查来说明我们如何定期使用指标来为我们指出正确的方向,然后如何使用日志来验证我们的猜测。
在下面概述的场景中,一个租户过度消耗资源,使所有人的速度都变慢了。这个案例研究使用了Grafana仪表盘、Prometheus和Loki,它在Kubernetes集群中运行。
找出问题所在
当我们的团队被呼唤时,我们要做的第一件事就是弄清楚我们所看到的是什么类型的问题。基本上,请求是在完成还是在缓慢进行?一堆HTTP 5xx's比一些缓慢的查询要可怕得多,所以让我们从这个角度来解决这个问题。
我们将从检查Loki / Reads
仪表板开始,它包括在loki-mixin
:
看起来,在这段时间里,我们的QPS(每秒查询次数)几乎停止了,与此同时,我们的延迟急剧上升。这让我觉得我们的读取路径拥堵,排队查询,导致执行的查询非常慢。我们注意到,所有的查询类型在这个激增期都出现了延迟,这支持了这样一个观点:要么没有足够的查询器,要么查询器超载。
接下来,让我们转到Loki / Reads Resources
仪表盘,它也包括在loki-mixin
。这个仪表盘显示了资源的使用情况:
哦,那是一个很大的内存!看起来在这个高资源使用量的时间段里,查询器豆荚直接跳到了它们的极限。再转到Loki / Operational
仪表盘上,我们还可以看到这导致了一堆查询器的重启:
好吧,这是一个从坏到坏的方法。
在这一点上,我觉得很有信心,我们的读取路径完全被昂贵的查询所饱和,导致查询器因内存耗尽而重新启动。这反过来又阻止了其他租户使用这些资源,直到查询器重新启动。之后,我们看到更多的查询在查询调度器中排队等待:
我们已经确定症状是拥堵,那么我们应该采取什么应对措施?看看等待请求的队列,扩大我们的读取路径,特别是查询器的规模是一个好主意。这将增加这个Loki集群的查询处理能力,应该有助于在短期内缓解一些负载。
追踪罪魁祸首
在添加了一些查询器节点之后,我们使用同样的仪表盘来注意到问题并没有消散。即使我们增加了查询器,成功处理的QPS的数量也没有变化。这是一个很大的迹象,如果我们在高速公路上增加了车道,但更多的汽车无法通过,很可能是出现了交通堵塞。
我的第一个想法(仅仅是因为我以前经历过这种情况)是,也许一个租户提交的查询逃脱了我们的噪音邻居控制。在一个多租户环境中,我们无法区分分配给一个租户和另一个租户的内存或CPU的数量,我们如何确定这个问题?
我们先到Grafana仪表盘上选择Prometheus指标的适当时间范围,然后使用 "探索 "来捕捉时间范围。
有了适当的时间范围,切换到Loki数据源,从查询器日志中提取这同一时间段的一些度量。这些指标将帮助我们回答这个问题。在集群的读取路径中,哪些租户消耗了大部分的处理时间?这里是LogQL查询:
# Who is eating the read path >:(
topk(10, sum(sum_over_time(
{cluster="", namespace="", container="querier"}
|= "metrics.go"
| logfmt
| unwrap duration(duration)
[5m]
)) by (org_id))
查询结果向我们显示如下。租户标识符被省略了:
正如我们在这张图中所看到的,有一个租户从其他租户中脱颖而出!然后,我运行了这个LogQL查询,并填写了<集群>、<命名空间>和<违规租户>字段,以查看正在运行的查询类型:
{cluster="", namespace="", container="query-frontend"}
|= "metrics.go"
| logfmt
| org_id=""
结果发现,有许多昂贵的查询具有极长的回看间隔。想想看,quantile_over_time(0.99, {foo=”bar”} | json | unwrap duration(response_time) [7d])
。Loki按时间分割这个查询,然后需要为每个数据点处理价值7天的数据,其中大部分是重复使用。尽管我们分割的时间间隔约为30分钟,但每个30分钟的时间段都需要抓取过去7天的日志。
Voilà!我们有了我们的根本原因。
缓解措施
在短期内,我们可以:
- 扩大读取路径的规模。
- 减少违规租户的
max_query_parallelism
,这样他们就不能一次安排那么多的查询,为邻近的租户腾出更多的读取路径。 - 建议他们使用记录规则来处理这些重复的、昂贵的查询。
从长期来看,我们可以:
- 扩大读取路径的规模。
- 更好地检测读取路径,以更快、更深入地识别/了解这些问题。在这次调试之后,我打开了这个PR!
- 找到更有效的方法来并行化像这样昂贵的查询,特别是那些在长间隔时间内的查询。
- 实施更好的QoS(服务质量)控制,使一个租户在集群处于负载状态时更难过度消耗他们公平的资源份额。
分离感想
呼,写了这么多。谢谢你能坚持到现在我总是低估了记录问题和解决方案的难度,但我希望这能帮助其他人理解我的思考过程,更快地排除日志问题。
作为奖励,我还会留下一个有趣的疑问。(请不要嘲笑我对 "有趣 "这个词的定义):
# out of slo instant queries by tenant
sum(count_over_time({cluster="prod-us-central-0",job="loki-prod/query-frontend"} |= "metrics.go" | logfmt | range_type="instant" and duration > 10s [1h])) by (org_id)
/ on () group_left
sum(count_over_time({cluster="prod-us-central-0",job="loki-prod/query-frontend"} |= "metrics.go" | logfmt | range_type="instant" and duration > 10s [1h]))
* 100
这个查询告诉我们哪一个百分比的SLO以外的即时查询是由哪个租户引起的。我今天用这个方法取得了很好的效果,发现我们有一个租户在一个小时内对98%的out-of-SLO即时查询负有责任!这就是为什么我们要用这个方法。