news 2026/10/3 12:49:47

多台服务器日志分散难查?用Promtail+Loki+Grafana搭建集中检索平台

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多台服务器日志分散难查?用Promtail+Loki+Grafana搭建集中检索平台

线上服务一旦拆到多台机器,日志查询就会变成一件很烦的事。我维护的几个后端服务分布在四台服务器上,平时排查问题基本靠ssh登上去,再tail -f或者grep。单机还好,一旦某个请求跨了多个服务,或者要对比几台机器同一时间段的报错,就得开好几个终端来回切,效率低到让人怀疑人生。

之前也考虑过 ELK,但 Elasticsearch 对内存要求比较高,我的机器上还要跑业务进程,不太想为了查日志再吃掉几个 G 内存。后来选了 Loki 这套方案,它的思路和 ES 不一样:只对标签建立索引,日志正文压缩后直接存对象存储或本地磁盘,资源占用小很多。配合 Promtail 采集、Grafana 查询,整套跑起来内存占用比我预期低。

这篇文章记录一下我从零搭这套平台的过程,包括配置、标签设计、查询语法,以及跑起来之后真实遇到过的问题。

为什么是 Loki 而不是 ELK

先说选型。这不是说 Loki 比 ELK 好,而是场景不同。

方案优点缺点适用场景
ELK(Elasticsearch + Logstash + Kibana)全文检索强,聚合分析能力完整,生态成熟资源占用高,ES 对内存和磁盘 IO 要求大,运维成本高日志量大、需要复杂全文检索和聚合分析的团队
Loki + Promtail + Grafana资源占用低,和 Prometheus/Grafana 体系天然契合,部署简单不支持全文索引,查询依赖标签过滤,聚合能力弱于 ES中小规模、已有 Grafana、主要靠标签定位问题的场景
直接 ssh + grep零部署成本多机、跨服务、历史回溯场景基本不可用单机、临时排查

我这边的情况是:服务数量不算多,日志量每天大概几个 G,查询诉求主要是"某台机器某个服务在某段时间的报错",而不是"全文搜索某个关键词出现在哪些日志里"。这种以标签定位为主的查询,正好是 Loki 的强项。

【关键结论】如果你的查询主要是按服务、主机、时间范围过滤,Loki 性价比很高;如果经常要做全文关键词检索和复杂聚合,ES 更合适。

组件分工

三个组件各管一段:

  1. Promtail:部署在每台被采集的机器上,负责读本地日志文件,打上标签,推送到 Loki。
  2. Loki:接收日志,按标签建索引,把日志内容压缩存储。
  3. Grafana:连到 Loki 作为数据源,提供查询界面和仪表盘。

数据流向大致是:日志文件 → Promtail(采集+打标签)→ Loki(索引+存储)→ Grafana(查询展示)。

部署方式

我用的是 docker-compose,把 Loki 和 Grafana 放在一台机器上(下面叫它日志服务器),Promtail 单独部署在每台业务机上。这样业务机只需要跑一个很轻的 Promtail 容器。

先看 Loki 和 Grafana 这一侧。我用的版本是 Loki 2.9.x 和 Grafana 10.x,这两个版本当前比较稳定,配置写法也是现在主流的。

# docker-compose.yml —— 部署在日志服务器version:"3.8"services:loki:image:grafana/loki:2.9.6container_name:lokiports:-"3100:3100"volumes:-./loki-config.yml:/etc/loki/local-config.yaml-./loki-data:/lokicommand:-config.file=/etc/loki/local-config.yamlrestart:unless-stoppedgrafana:image:grafana/grafana:10.4.2container_name:grafanaports:-"3000:3000"volumes:-./grafana-data:/var/lib/grafanaenvironment:-GF_SECURITY_ADMIN_PASSWORD=change_medepends_on:-lokirestart:unless-stopped

Loki 的配置文件我用了接近单机默认的写法,主要确认存储路径和监听端口:

# loki-config.ymlauth_enabled:falseserver:http_listen_port:3100common:path_prefix:/lokistorage:filesystem:chunks_directory:/loki/chunksrules_directory:/loki/rulesreplication_factor:1ring:kvstore:store:inmemoryschema_config:configs:-from:2024-01-01store:tsdbobject_store:filesystemschema:v13index:prefix:index_period:24hlimits_config:reject_old_samples:truereject_old_samples_max_age:168h

这里有几个点值得说一下。auth_enabled: false是关掉多租户,单机自己用没必要开。schema: v13是当前 Loki 推荐的 schema 版本,和 tsdb 存储配合。reject_old_samples_max_age: 168h是拒绝超过 7 天的旧日志,防止客户端时钟不对导致乱序写入。

【注意】schema_config里的from日期一旦定下来,后续如果要改 schema,需要新增一条配置项而不是改这一条,Loki 是按时间段选择 schema 的。这一点官方文档有说明,改错了会导致旧数据读不出来。

filesystem存储适合单机或者挂载了共享存储的场景。如果日志量再大、想上对象存储(比如 S3、MinIO),把object_store换成对应类型即可,这个我没实际配过,就不展开了。

Promtail 采集配置

业务机上的 Promtail 配置是重点,标签怎么打直接决定了后面查询好不好用。

# promtail-config.yml —— 部署在每台业务机server:http_listen_port:9080grpc_listen_port:0positions:filename:/tmp/positions.yamlclients:-url:http://<日志服务器IP>:3100/loki/api/v1/pushscrape_configs:-job_name:app-logsstatic_configs:-targets:-localhostlabels:job:apphost:server-01__path__:/var/log/myapp/*.log

positions.yaml记录每个文件读到哪一行了,Promtail 重启后从断点继续,不会重复推送。这个文件要保证可写。

clients指向 Loki 的 push 接口。

scrape_configs里,__path__是匹配日志文件的路径,labels里定义的标签会附加到这批日志上。上面这个配置的意思是:采集/var/log/myapp/下所有.log文件,给它们打上job=app、host=server-01两个标签。

每台机器上的配置只有host不一样,其他都一样。实际部署时我用了一个模板,部署脚本里替换 host 值。

标签设计的坑

这里是我踩得比较实的一个问题。一开始我想当然地把一些动态字段也做成了标签,比如把日志里的request_id、user_id提取出来当标签。结果 Loki 很快就报警说标签基数太高,查询也变得很慢。

原因是 Loki 的索引是基于标签组合的。标签的唯一组合数量(基数)一旦爆炸,索引就撑不住了。request_id这种几乎每条都不一样的值,做成标签等于给每条日志建一个索引,完全违背了 Loki 的设计初衷。

【踩坑提醒】标签只放基数低、可枚举的维度,比如host、job、env、level。像request_id、user_id、trace_id这种高基数字段,要么放在日志正文里用查询语法过滤,要么用 Loki 的 structured metadata(需要较新版本,且行为有差异,我没有深入验证)。

调整之后,我的标签就固定在host、job、env这几个上:

labels:job:apphost:server-01env:prod

这样标签组合数量就是"机器数 × 服务数 × 环境数",非常可控。

日志格式建议

Loki 查询时如果日志是结构化 JSON,过滤会方便很多。我这边后端服务用的是 Python,日志输出改成了 JSON 格式,方便后续按字段过滤。

# logging_config.pyimportjsonimportloggingimportsysclassJsonFormatter(logging.Formatter):defformat(self,record:logging.LogRecord)->str:payload={"ts":self.formatTime(record,"%Y-%m-%dT%H:%M:%S%z"),"level":record.levelname,"logger":record.name,"message":record.getMessage(),}# 如果有额外字段(比如 request_id),一并带上ifhasattr(record,"request_id"):payload["request_id"]=record.request_idifrecord.exc_info:payload["exc"]=self.formatException(record.exc_info)returnjson.dumps(payload,ensure_ascii=False)defget_logger(name:str)->logging.Logger:logger=logging.getLogger(name)logger.setLevel(logging.INFO)handler=logging.StreamHandler(sys.stdout)handler.setFormatter(JsonFormatter())logger.addHandler(handler)returnlogger

这样每条日志就是一行 JSON。注意request_id是放在正文里的,不是标签,查询时用 Loki 的 JSON 解析语法过滤。

在 Grafana 里查日志

Grafana 加 Loki 数据源很简单:Configuration → Data Sources → Add data source → 选 Loki,URL 填http://loki:3100(如果 Grafana 和 Loki 在同一个 compose 网络里)或者日志服务器 IP。

配好之后,在 Explore 页面就能查了。Loki 的查询分两部分:标签选择器和过滤器。

标签选择器必须至少有一个非空的匹配条件,比如:

{host="server-01", job="app"}

这会查出这台机器上 app 服务的所有日志。然后可以加过滤器:

{host="server-01", job="app"} |= "ERROR"

|=是包含某个字符串,!=是不包含,|~是正则匹配。这些都是对日志正文做过滤,配合标签先缩小范围,效率会好很多。

如果日志是 JSON,可以用| json解析后按字段过滤:

{host="server-01", job="app"} | json | level="ERROR"

还可以按 request_id 查一条请求的完整链路(即使它跨了多个服务):

{job="app"} | json | request_id="abc-123"

这比挨个 ssh 上去 grep 快太多了。

常用查询整理

几个我平时用得比较多的写法:

  1. 查某台机器最近的报错:
    {host="server-01", job="app"} |= "ERROR"
  2. 查某个服务所有机器上的 500 错误:
    {job="app"} | json | status="500"
  3. 统计每分钟错误日志条数(用于做告警):
    sum(count_over_time({job="app"} |= "ERROR" [1m]))
  4. 排除健康检查这类噪音:
    {job="app"} != "healthcheck"

第 3 条这种是 metric 查询,可以直接配成 Grafana 的告警规则。

实际遇到的问题

标签基数报警

前面提过,一开始把 request_id 做成标签,Loki 日志里出现max label cardinality类似的告警,查询也明显变慢。把高基数字段从标签里去掉之后恢复正常。这是 Loki 使用中最容易犯的错,没有之一。

时间范围查询慢

Loki 查询性能和时间范围强相关。查最近 15 分钟很快,查最近 7 天就会慢,因为它要扫过整个时间段的数据。我的做法是:日常排查控制在几小时内,需要长期回溯的场景单独处理,而不是一上来就查一周。

limits_config里的max_query_length可以限制单次查询的最大时间跨度,我设了一个相对保守的值,防止有人误查超大范围把 Loki 拖垮。这个参数具体取值要结合自己的数据量和机器配置,我没有一个通用数字。

磁盘增长

日志是持续写入的,磁盘会一直涨。Loki 本身有 retention 配置,但我用的是 filesystem 存储,实际清理行为需要结合compactor相关配置一起看,这块我配得比较简单(靠reject_old_samples_max_age控制写入,定期手动清理旧 chunks)。如果对保留策略有严格要求,建议用对象存储配合 retention 配置,或者接一个定时清理脚本。这一点我没有做完整的自动化验证,属于待完善的部分。

是否值得搭

回到最开始的问题。这套方案适合什么样的场景?

  • 服务分布在多台机器,ssh 逐台查已经明显影响效率;
  • 查询以标签+时间范围为主,不是重度全文检索;
  • 机器资源有限,不想为日志系统投入太多内存;
  • 已经在用 Grafana,希望日志和指标在同一个界面看。

如果这几点都符合,Loki 这套组合的性价比很高。如果日志量特别大、或者需要 ES 那种全文检索和复杂聚合,那就老老实实上 ELK,别硬套。

我搭完之后,最直观的变化是排查一个跨服务问题的时间从"开四个终端来回切"变成了"在 Grafana 里一条查询搞定"。尤其是按 request_id 追链路,之前基本靠手动拼,现在直接查。

后续我打算再补两块:一是把错误日志的 metric 查询配上 Grafana 告警,出问题主动通知而不是等人来查;二是把 retention 策略做成自动化的,省得手动清磁盘。这两块落地之后,这套平台才算真正完整。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 12:49:32

在1核2G的Linux服务器上部署MySQL 8需要优化哪些参数?

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/3 12:49:28

AI写论文哪个软件最好?云智变AI毕业论文功能实测科普——云智变AI官网www.yunzhibian.cn,微信公众号搜一搜 云智变ai学术

先给一个可能让你意外的答案 打开搜索引擎搜“AI写论文哪个软件最好”&#xff0c;你会看到三种结果&#xff1a;一种把ChatGPT、Claude、DeepSeek挨个夸一遍&#xff0c;最后说“看个人需求”&#xff1b;一种直接甩出十几个工具清单&#xff0c;从图灵论文到笔灵AI一网打尽&…

作者头像 李华
网站建设 2026/10/3 12:47:27

MySQL索引优化实战|千万级数据表查询提速10倍方案

在后端项目开发中&#xff0c;数据库查询缓慢是最常见的性能问题&#xff0c;尤其是数据量达到百万、千万级后&#xff0c;未优化的SQL语句查询耗时可达数秒&#xff0c;直接导致接口超时、页面加载卡顿。大部分数据库性能问题&#xff0c;根源都不是服务器配置不足&#xff0c…

作者头像 李华
网站建设 2026/10/3 12:47:27

基于Python的美妆销售系统分析与实现

一、选题背景与研究意义近年来&#xff0c;随着国民可支配收入提升与颜值经济的持续升温&#xff0c;中国美妆行业已进入高速增长期。据行业公开数据显示&#xff0c;2024年我国美妆个护市场规模突破5500亿元&#xff0c;线上渠道占比超过45%&#xff0c;抖音、小红书、天猫、京…

作者头像 李华
网站建设 2026/10/3 12:46:41

如何写出好SKILL.md?从SkillClaw进化指南提炼的8条技能编写原则

如何写出好SKILL.md&#xff1f;从SkillClaw进化指南提炼的8条技能编写原则 【免费下载链接】SkillClaw Let Skills Evolve Collectively with Agentic Evolver 项目地址: https://gitcode.com/gh_mirrors/sk/SkillClaw SkillClaw 是一个让 AI Agent 技能"集体进化…

作者头像 李华