自建日志聚合实战:Loki + Promtail + Grafana,把散落各处的日志收进一个可搜索的仪表盘

上一期我们搭好了 Prometheus + …

上一期我们搭好了 Prometheus + Grafana 的监控大盘,CPU、内存、磁盘这些指标看得清清楚楚。但指标只能告诉你”哪里不对劲”,真要定位到具体原因,还得看日志。可自建环境里日志散得到处都是:每个容器的 stdout、每台机器的 /var/log、每个应用自己写的那份独立文件,出问题时你得一台一台 SSH 上去 tailgrep,特别磨人。

今天 IT 大叔就带你把日志也收进来,用 Loki + Promtail + Grafana 搭一套轻量级日志聚合系统。和重量级的 ELK(Elasticsearch + Logstash + Kibana)相比,Loki 只索引标签、不索引正文,存储成本低一个数量级,几 GB 内存就能跑得动,特别适合自建、单机或小规模集群的场景。

三个角色的分工

先把角色说清楚,免得后面绕晕:Promtail 是采集端,部署在每台要收日志的机器上,负责 tail 日志文件、给日志打标签、再推给 Loki;Loki 是存储与查询端,负责接收日志、按标签建索引、响应查询;Grafana 是可视化端,用 LogQL 语言像查 PromQL 一样查日志。

这套架构最巧的一点是:Loki 不索引日志正文,只索引你打上去的标签(比如 hostjobapp),正文按时间切块压缩存储。查询时先靠标签快速定位到块,再在块内扫描关键词。所以它的铁律是——标签要少而精,高基数标签(user_idrequest_id 这种取值成千上万的)千万别当标签用,否则索引会爆炸;而查询关键词则可以随便写。

第一步:Docker Compose 一把梭

三个服务用一个 docker-compose.yml 全搞定。存好之后 docker compose up -d 启动即可。

services:
  loki:
    image: grafana/loki:3.4.2
    container_name: loki
    restart: unless-stopped
    command: -config.file=/etc/loki/local-config.yaml
    volumes:
      - ./loki/loki-config.yaml:/etc/loki/local-config.yaml:ro
      - loki-data:/loki
    ports:
      - "3100:3100"

  promtail:
    image: grafana/promtail:3.4.2
    container_name: promtail
    restart: unless-stopped
    command: -config.file=/etc/promtail/promtail-config.yaml
    volumes:
      - ./promtail/promtail-config.yaml:/etc/promtail/promtail-config.yaml:ro
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - promtail-positions:/tmp

  grafana:
    image: grafana/grafana:11.4.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana

volumes:
  loki-data:
  grafana-data:
  promtail-positions:

这里注意两点:一是版本号我写的是示例,实际部署前请去 Docker Hub 确认最新稳定版再固定,别图省事用 latest,生产环境固定版本是底线;二是 Promtail 的 positions 文件一定要持久化——它记录”每个日志文件读到第几行”,丢了就会重复收或漏收。

第二步:Promtail 配置,两个 job 各司其职

server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: system
    static_configs:
      - targets:
          - localhost
        labels:
          job: varlogs
          host: node01
          __path__: /var/log/*log

  - job_name: docker
    static_configs:
      - targets:
          - localhost
        labels:
          job: docker
          __path__: /var/lib/docker/containers/*/*-json.log
    pipeline_stages:
      - json:
          expressions:
            output: log
            stream: stream
            attrs: attrs
      - json:
          expressions:
            tag: attrs.tag
          source: attrs
      - labels:
          tag:
          stream:
      - output:
          source: output

两个 job 分工明确:system 负责收系统日志(/var/log 下的各种 *.log),docker 负责收容器的 json 日志。docker 那个 job 里的 pipeline_stages 是关键——Docker 的日志是 JSON 格式,外层套了 logstreamtime 这些字段,得先用 json 阶段把它剥开、取出真正的日志正文,再把镜像名 tag 提出来当标签。不写这段的话,你查到的日志会是一坨转义过的 JSON,看着头疼。

第三步:Loki 配置

auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

limits_config:
  retention_period: 168h

Loki 的配置在 3.x 里精简了不少,common 块把单机和集群共用的存储、ring 配置集中到了一起。单机部署就抓三个要点:filesystem 存储(日志块写本地磁盘)、inmemory 的 ring(单机不需要 etcd/consul 这类协调组件)、replication_factor: 1(不复制,只存一份)。retention_period: 168h 表示日志保留一周自动清理,防止磁盘被悄悄撑爆。

第四步:Grafana 接数据源,用 LogQL 查日志

Grafana 起好后,进 Connections → Data sources → Add data source,搜 Loki,URL 填 http://loki:3100(三个容器在同一个 compose 网络里,直接用服务名互访),保存。之后就能在 Explore 里用 LogQL 查了,几个常用姿势:

# 圈定某台机器的系统日志,再过滤出含 error 的行
{job="varlogs", host="node01"} |= "error"

# 正则匹配,(?i) 表示不区分大小写
{job="docker"} |~ "(?i)error|failed|panic"

# 结构化日志:先 json 解析,再按字段过滤
{job="docker", tag="nginx"} | json | status_code >= 500

# 范围聚合:最近5分钟 error 出现次数
count_over_time({job="varlogs"} |= "error" [5m])

# 按主机分组统计,画成时序图
sum by (host) (count_over_time({job="varlogs"}[5m]))

这些查询的逻辑很直白:{job="docker"} 是标签选择器,先圈定范围;|= 是包含匹配,|~ 是正则匹配;count_over_time 是范围聚合,配合 sum by (host) 就能画出一张”各节点错误数”的时序图。有了时序图,日志就不再是只能翻的一堆文本,而是能当指标来看趋势了。

几个避坑要点

  • 标签少而精:只放 hostjobapp 这类低基数维度,request_idtrace_id 请用 structured metadata,别塞进标签。
  • 查询先收窄:正文不建索引,直接全文扫关键词会慢,先加标签缩小时间范围和日志源,再上关键词。
  • 多节点部署:每台机器各装一个 Promtail,标签里的 host 改成对应主机名,日志统一推到一台 Loki 即可。
  • 磁盘别裸奔retention_period 一定要配,再顺手挂个监控看 Loki 的磁盘占用。
  • Promtail 的接班人:官方现在主推 Grafana Alloy 作为统一采集器,Promtail 仍在维护、够用,等 Alloy 生态成熟了再迁移不迟。

小结

搭好这套之后,出问题时的排查路径就清爽了:先看 Grafana 大盘定位是哪台机器、哪个服务异常,再切到 Explore 用 LogQL 按 host + 关键词把相关日志捞出来,前后几分钟搞定。以前要 SSH 十几台机器翻日志的事,现在一个页面就办了。

如果你已经在用 Prometheus + Grafana,那 Loki 几乎是零学习成本的顺延——同一种查询思路、同一套可视化、同样的告警体系(Loki 也支持用 LogQL 写告警规则)。日志收进来之后,下一步就可以接 Alerting,比如”5 分钟内某服务 error 超过 100 条就通知我”,这个咱们下回接着聊。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注