从零搭一套可观测的最小日志栈:Loki + Promtail + Grafana

举报
yd_290186593 发表于 2026/10/31 19:50:24 2026/10/31
【摘要】 用 345MB 内存搭起日志聚合:Loki 只索引标签不索引内容,Promtail 自动发现容器,Grafana 一键查询。附标签基数踩坑与实测资源占用。

线上出问题时最痛苦的不是没日志,而是日志散在五台机器上,得挨个 ssh 上去 grep。这篇记录一次最小可用的日志聚合搭建:Loki 存日志、Promtail 采集、Grafana 查询,全部跑在一台 2 核 4G 的机器上,内存占用约 400MB。

为什么选 Loki 而不是 ELK

ELK(Elasticsearch + Logstash + Kibana)功能强,但代价也大:单机跑 ES 至少要 4G 内存,索引还会持续膨胀。Loki 的设计思路完全不同 —— 它只索引标签,不索引日志内容。

维度 ELK Loki
索引对象 全文索引 仅标签(label)
单机内存 4G 起步 200-500MB
存储增长 快(索引膨胀) 慢(压缩日志块)
查询能力 全文检索强 标签筛选 + 正则 grep

对个人项目和小团队来说,99% 的查询场景是「看某个服务最近半小时的日志」,用标签筛选完全够用。真要全文检索,Loki 也支持 |= 和 |~ 做行内过滤。

部署结构

三个组件用 docker compose 起,数据落在本地目录:

version: "3.8"
services:
  loki:
    image: grafana/loki:2.9.3
    command: -config.file=/etc/loki/local-config.yaml
    ports:
      - "3100:3100"
    volumes:
      - ./loki-data:/loki
      - ./loki-config.yaml:/etc/loki/local-config.yaml
    restart: unless-stopped

  promtail:
    image: grafana/promtail:2.9.3
    volumes:
      - /var/log:/var/log:ro
      - ./promtail-config.yaml:/etc/promtail/config.yaml
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
    command: -config.file=/etc/promtail/config.yaml
    depends_on:
      - loki
    restart: unless-stopped

  grafana:
    image: grafana/grafana:10.2.3
    ports:
      - "3000:3000"
    volumes:
      - ./grafana-data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=changeme
    depends_on:
      - loki
    restart: unless-stopped

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: v12
      index:
        prefix: index_
        period: 24h

limits_config:
  retention_period: 168h        # 保留 7 天
  ingestion_rate_mb: 10
  ingestion_burst_size_mb: 20

compactor:
  working_directory: /loki/compactor
  retention_enabled: true
  delete_request_store: filesystem

关键参数说明:

  • retention_period: 168h —— 日志保留 7 天,超过自动删。不设这个磁盘会被吃满。
  • replication_factor: 1 —— 单机必须设 1,否则会等副本超时。
  • ingestion_rate_mb —— 限制写入速率,防止某个服务刷日志把 Loki 打挂。

Promtail 采集配置

两路数据:系统日志和 Docker 容器日志。

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: syslog
          host: vps-01
          __path__: /var/log/*.log

  # Docker 容器日志(自动发现)
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: ['__meta_docker_container_name']
        regex: '/(.*)'
        target_label: container
      - source_labels: ['__meta_docker_container_log_stream']
        target_label: stream
    pipeline_stages:
      # 尝试解析 JSON 日志,提取 level 字段作为标签
      - json:
          expressions:
            level: level
            msg: message
      - labels:
          level:

docker_sd_configs 是自动发现,新起的容器不用改配置就能采到日志。pipeline_stages 里把 JSON 日志的 level 提成标签,这样就能在 Grafana 里按 {level="error"} 精确筛选。

一个容易踩的坑:标签基数爆炸

Loki 靠标签索引,但如果把高基数变量当标签,会直接把它拖死。典型错误:

# 千万不要这样写
labels:
  request_id: ${request_id}     # 每个请求都不同
  user_id: ${user_id}           # 用户量大了就炸
  timestamp: ${ts}              # 时间戳更不行

标签的基数应该控制在几百以内。request_id、user_id、trace_id 这类高基数字段只能放在日志正文里,查询时用正则匹配:

{job="docker"} |= "request_id=abc123"

判断标准很简单:如果一个标签的取值超过 1000 种,就不该做标签。

Grafana 查询示例

配好 Loki 数据源(URL 填 http://loki:3100)后,几个常用查询:

看某服务最近 10 分钟的错误日志:

{container="myapp"} |= "ERROR"

统计错误数量趋势(用于告警):

sum(count_over_time({container="myapp"} |= "ERROR" [1m]))

正则过滤多个关键词:

{job="syslog"} |~ "(?i)error|fail|timeout"

解析 JSON 后按字段筛选:

{container="myapp"} | json | level="error" | line_format "{{.msg}}"

line_format 很好用,可以把 JSON 日志只显示 msg 字段,避免一行里塞满元数据。

资源占用实测

跑在一台 2 核 4G、40G 盘的 Debian 12 上,日志量约每天 200MB:

组件 内存 CPU
loki ~180MB 3%
promtail ~45MB 1%
grafana ~120MB 2%
合计 ~345MB ~6%

磁盘方面,200MB/天 × 7 天保留 ≈ 1.4GB,压缩后约 600MB。40G 盘完全够用。

加个告警

Grafana 里可以直接配告警规则,不用额外部署 Alertmanager:

sum(count_over_time({container="myapp"} |= "ERROR" [5m])) > 10

超过 10 条错误就触发,通知渠道支持邮件、Webhook、钉钉。Webhook 的 payload 是标准 JSON,接自己的机器人很方便。

小结

这套栈的价值不在技术复杂度,而在把「找日志」从 10 分钟降到 10 秒。三个容器 345MB 内存,一台最便宜的 VPS 就能跑。

几个关键决策点:

  1. 选 Loki 不选 ELK —— 单机场景内存差 10 倍,功能足够
  2. 必须设 retention —— 不设保留期磁盘必爆
  3. 标签基数控制在 1000 以内 —— 高基数字段放正文,用 |= 过滤
  4. 用 docker_sd 自动发现 —— 新容器零配置接入

真正的门槛不是部署,是养成先看日志再猜原因的习惯。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。