手把手搭建云原生日志平台:Loki + Promtail + Grafana 实战

副标题:告别 ELK!轻量级日志三件套部署,接入 Tomcat 日志,内存仅 135MB
环境:华为云 ECS ecs-53b4-0001 · Ubuntu 24.04.4 LTS · Docker 29.5.3 · 2vCPU / 4GiB
版本:Loki 3.0.0 · Promtail 3.0.0 · Grafana 11.0.0
时间:2026-06-11


一、为什么选 Loki?ELK 的"替代方案"

1.1 Loki 是什么

Grafana Loki 是一款受 Prometheus 启发的日志聚合系统,设计理念是"只索引元数据(标签),不索引日志内容",从根本上解决了 Elasticsearch 全文索引带来的资源消耗问题。

传统 ELK 模式:
  日志文件 → Filebeat/Logstash → Elasticsearch(全文索引)→ Kibana

Loki 模式:
  日志文件 → Promtail(仅采集 + 打标签)→ Loki(按时间流存储)→ Grafana(查询可视化)

1.2 与 ELK 核心对比

维度 ELK 方案 Loki 方案
索引方式 全文倒排索引 仅索引标签(Labels)
最低内存 ~2GB(ES 单节点) ~135MB(三组件合计)
存储成本 高(索引 = 原始数据 × 3~5) 低(压缩存储,可对接 S3)
查询语言 Lucene DSL / KQL LogQL(类 PromQL)
查询延迟 低(全文索引) 较低(标签过滤 + 内容 grep)
上手难度 高(ES 调优复杂) 低(配置文件简洁)
适合场景 大规模日志分析、全文搜索 中小团队、K8s 环境、低成本
与 Grafana 集成 需要插件 原生内置

结论:日志量 < 100GB/天、团队规模 < 50 人时,Loki 是更经济的选择。

1.3 本文目标

在单台 Ubuntu 24.04 服务器上,使用 Docker Compose 部署 Loki 三件套,并接入上一篇部署的 Tomcat 10.1.55 日志(catalina.out),实现:

  • ✅ 日志自动采集与推送
  • ✅ 多标签(job / level / env / host)检索
  • ✅ LogQL 语法查询与过滤
  • ✅ Grafana 可视化界面访问

二、架构总览

                     ┌────────────────────────────────────────────┐
                     │          ecs-53b4-0001 (1.92.101.204)      │
                     │                                            │
  外部访问            │   ┌──────────┐    ┌─────────┐             │
  :3000 ──────────►  │   │ Grafana  │◄───│  Loki   │:3100        │
  :3100 ──────────►  │   │ 11.0.0   │    │  3.0.0  │             │
  :9080 ──────────►  │   └──────────┘    └────▲────┘             │
                     │                        │  HTTP Push        │
                     │   ┌─────────────────────┴──────────┐       │
                     │   │         Promtail 3.0.0          │       │
                     │   │  tail /opt/tomcat/logs/*.out    │       │
                     │   │  tail /var/log/syslog           │       │
                     │   │  tail /var/log/auth.log         │       │
                     │   └─────────────────────────────────┘       │
                     │           │ volume mount (只读)              │
                     │   ┌───────┴──────────────────────────┐      │
                     │   │ Tomcat 10.1.55 (systemd)         │      │
                     │   │  /opt/tomcat/logs/catalina.out   │      │
                     │   │  /opt/tomcat/logs/*access*.txt   │      │
                     │   └──────────────────────────────────┘      │
                     │                                            │
                     │   Network: loki-net (bridge)               │
                     └────────────────────────────────────────────┘

 [Docker 容器内部通信]
 grafana → loki   via: http://loki:3100      (服务名 DNS 解析)
 promtail → loki  via: http://loki:3100/loki/api/v1/push

三、准备工作

3.1 环境要求

组件 最低配置 本文实际配置
CPU 2 核 2 vCPU
内存 2 GB 4 GiB
磁盘 20 GB 40 GB
OS Ubuntu 20.04+ / CentOS 7+ Ubuntu 24.04.4 LTS
Docker 20.10+ 29.5.3

3.2 端口规划

端口 服务 说明
3000 Grafana Web UI,浏览器访问
3100 Loki HTTP API + 日志推送接收
9080 Promtail 自身 HTTP 监控端口,查看 targets

⚠️ 如有防火墙/安全组,需开放上述端口的入方向规则。

3.3 安装 Docker(若未安装)

# Ubuntu 24.04 环境,使用华为云镜像
mkdir -p /etc/apt/keyrings
wget -qO /tmp/docker.gpg https://mirrors.huaweicloud.com/docker-ce/linux/ubuntu/gpg
cat /tmp/docker.gpg | gpg --batch --yes --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] \
  https://mirrors.huaweicloud.com/docker-ce/linux/ubuntu noble stable" \
  > /etc/apt/sources.list.d/docker.list

apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable docker --now

验证输出(真实服务器):

$ docker --version
Docker version 29.5.3, build d1c06ef

$ docker compose version
Docker Compose version v5.1.4

四、配置镜像加速

在国内服务器上,docker.io 默认无法连通,需配置加速镜像:

cat > /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://hub-mirror.c.163.com",
    "https://mirror.baidubce.com"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}
EOF

systemctl daemon-reload && systemctl restart docker

参数详解:

参数 说明
registry-mirrors 镜像加速地址列表,Docker 拉取时自动按顺序尝试
log-driver: json-file 容器日志存为 JSON 文件(默认),避免写满磁盘
max-size: 100m 单个日志文件最大 100MB 后轮转
max-file: 3 保留最近 3 个轮转文件

💡 踩坑:加速镜像有时也会 429 限流(Too Many Requests)。此时可直接用华为云 SWR 仓库拉取:

docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/grafana/loki:3.0.0
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/grafana/loki:3.0.0 grafana/loki:3.0.0

五、核心配置文件详解

5.1 目录结构规划

mkdir -p /opt/loki-stack/{config,data/loki,data/grafana}
mkdir -p /opt/loki-stack/data/loki/{chunks,rules,wal}
chmod -R 777 /opt/loki-stack/data/loki    # uid 10001 (Loki 容器内用户)
chown -R 472:472 /opt/loki-stack/data/grafana  # uid 472 (Grafana 容器内用户)

最终结构:

/opt/loki-stack/
├── docker-compose.yml
├── config/
│   ├── loki-config.yaml       # Loki 存储与服务配置
│   └── promtail-config.yaml   # Promtail 采集规则
└── data/
    ├── loki/                  # Loki 数据持久化
    │   ├── chunks/            # 日志块数据
    │   ├── rules/             # 告警规则
    │   └── wal/               # 预写日志 (WAL)
    └── grafana/               # Grafana 数据源/Dashboard 配置

⚠️ 踩坑:Loki 容器内以 uid=10001 运行,若宿主机目录权限不足(如 root:root 700),
会报 mkdir /loki/rules: permission denied。解决:chmod -R 777 data/loki

5.2 Loki 配置文件详解

# /opt/loki-stack/config/loki-config.yaml
auth_enabled: false          # 关闭认证(生产环境建议开启)

server:
  http_listen_port: 3100     # HTTP API 端口,Promtail 推送和 Grafana 查询都用这个
  grpc_listen_port: 9096     # gRPC 端口(内部使用)
  log_level: info            # 日志级别:debug/info/warn/error

common:
  instance_addr: 127.0.0.1   # 实例地址,单机部署填 127.0.0.1
  path_prefix: /loki          # 数据存储根路径(容器内)
  storage:
    filesystem:
      chunks_directory: /loki/chunks   # 日志块文件目录
      rules_directory: /loki/rules     # 告警规则目录
  replication_factor: 1       # 副本数,单机填 1,集群 ≥ 3
  ring:
    kvstore:
      store: inmemory          # KV 存储:单机用 inmemory,集群用 consul/etcd

query_range:
  results_cache:
    cache:
      embedded_cache:
        enabled: true          # 开启嵌入式缓存(减少重复查询)
        max_size_mb: 100       # 缓存最大 100MB

schema_config:
  configs:
    - from: 2020-10-24         # 此 schema 从哪个日期开始生效
      store: tsdb              # 索引存储类型:tsdb(推荐)或 boltdb-shipper
      object_store: filesystem # 对象存储:单机用 filesystem,生产用 s3/gcs
      schema: v13              # 索引 schema 版本,v13 是 Loki 3.x 推荐版本
      index:
        prefix: index_         # 索引文件名前缀
        period: 24h            # 每 24 小时生成一个索引文件

limits_config:
  reject_old_samples: true           # 拒绝超时的旧日志(防乱序注入)
  reject_old_samples_max_age: 168h   # 允许最大 7 天前的日志(168h = 7d)
  ingestion_rate_mb: 16              # 单个租户每秒摄入限速 16MB
  ingestion_burst_size_mb: 32        # 突发最大 32MB

5.3 Promtail 配置文件详解

# /opt/loki-stack/config/promtail-config.yaml
server:
  http_listen_port: 9080       # Promtail 自身监控端口(/metrics、/targets)
  grpc_listen_port: 0          # 0 = 禁用 gRPC

positions:
  filename: /tmp/positions.yaml   # 记录每个文件已读取到哪个位置(offset)
                                  # 重启后从断点续采,不会重复发送

clients:
  - url: http://loki:3100/loki/api/v1/push
    # 推送端点,容器网络内通过服务名 loki 直接通信
    # 生产建议加 tenant_id: my-org(多租户场景)

scrape_configs:
  # ───── 采集任务 1:Tomcat catalina.out(核心)─────
  - job_name: tomcat_catalina
    static_configs:
      - targets:
          - localhost           # Promtail 本地运行,固定填 localhost
        labels:
          job: tomcat           # 第一级标签:用于 LogQL 过滤 {job="tomcat"}
          app: catalina         # 第二级标签:区分 catalina / access_log
          host: ecs-53b4-0001   # 主机标识(多节点时用于区分来源)
          env: production       # 环境标签:dev / staging / production
          __path__: /opt/tomcat/logs/catalina.out
          # __path__ 是特殊标签,指定采集文件路径(支持 glob 通配符)

  # ───── 采集任务 2:Tomcat 访问日志 ─────
  - job_name: tomcat_access
    static_configs:
      - targets:
          - localhost
        labels:
          job: tomcat
          app: access_log
          host: ecs-53b4-0001
          __path__: /opt/tomcat/logs/localhost_access_log*.txt
          # glob 通配符:匹配 localhost_access_log.2026-06-11.txt 等

  # ───── 采集任务 3:系统 syslog ─────
  - job_name: system_syslog
    static_configs:
      - targets:
          - localhost
        labels:
          job: syslog
          host: ecs-53b4-0001
          __path__: /var/log/syslog

标签设计原则:

原则 说明 反例
标签值基数要低 标签值不应无限增长 ❌ 用 request_id 做标签(每条日志一个值)
标签应能过滤大量数据 用 job/env/host 等 {job="tomcat", env="production"}
内容搜索用 \|= 日志正文用管道过滤 {job="tomcat"} \|= "ERROR"

5.4 docker-compose.yml 详解

# /opt/loki-stack/docker-compose.yml
version: '3.8'        # Compose 文件版本(3.8 支持 healthcheck depends_on)

networks:
  loki-net:
    driver: bridge    # 创建独立 bridge 网络,容器间通过服务名互相访问

volumes:
  grafana-data:       # 命名卷:Grafana Dashboard、数据源等持久化存储
    driver: local

services:
  # ════════════════ Loki ════════════════
  loki:
    image: grafana/loki:3.0.0
    container_name: loki
    restart: unless-stopped    # 崩溃自动重启,手动 stop 不重启
    ports:
      - '3100:3100'            # 宿主机:容器 端口映射
    command: -config.file=/etc/loki/loki-config.yaml   # 指定配置文件路径
    volumes:
      - ./config/loki-config.yaml:/etc/loki/loki-config.yaml:ro  # :ro = 只读挂载
      - ./data/loki:/loki      # 数据目录挂载(需 chmod 777)
    networks:
      - loki-net
    healthcheck:               # 健康检查:/ready 返回 "ready" 即为健康
      test: ['CMD-SHELL', 'wget -qO- http://localhost:3100/ready || exit 1']
      interval: 10s            # 每 10 秒检查一次
      timeout: 5s              # 超时 5 秒判断失败
      retries: 5               # 连续失败 5 次才标记 unhealthy

  # ════════════════ Promtail ════════════════
  promtail:
    image: grafana/promtail:3.0.0
    container_name: promtail
    restart: unless-stopped
    user: root                 # ⚠️ 需要 root 才能读取 /var/log/* 和 Tomcat 日志
                               # 生产环境建议用 ACL 替代:setfacl -m u:10001:r /path/log
    volumes:
      - ./config/promtail-config.yaml:/etc/promtail/promtail-config.yaml:ro
      - /var/log:/var/log:ro                        # 系统日志目录(只读)
      - /opt/tomcat/logs:/opt/tomcat/logs:ro        # Tomcat 日志目录(只读)
      - /tmp/promtail-positions:/tmp                # positions.yaml 持久化
    depends_on:
      loki:
        condition: service_healthy   # 等 Loki healthy 后才启动(避免推送失败)
    networks:
      - loki-net

  # ════════════════ Grafana ════════════════
  grafana:
    image: grafana/grafana:11.0.0
    container_name: grafana
    restart: unless-stopped
    ports:
      - '3000:3000'
    environment:
      - GF_SECURITY_ADMIN_USER=admin              # 管理员账号
      - GF_SECURITY_ADMIN_PASSWORD=Grafana@2026   # 管理员密码
      - GF_USERS_ALLOW_SIGN_UP=false              # 禁止用户自行注册
      - GF_ANALYTICS_REPORTING_ENABLED=false      # 禁止向 Grafana 发送匿名统计
    volumes:
      - grafana-data:/var/lib/grafana             # 命名卷:Dashboard 等持久化
    depends_on:
      loki:
        condition: service_healthy
    networks:
      - loki-net

六、部署与启动

6.1 预拉取镜像

国内服务器建议先手动拉取并打 tag,避免 docker compose up 时超时:

# 使用华为云 SWR 镜像仓库(国内稳定可用)
SWR="swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io"

docker pull ${SWR}/grafana/loki:3.0.0
docker pull ${SWR}/grafana/promtail:3.0.0
docker pull ${SWR}/grafana/grafana:11.0.0

# 打 tag 为标准名称(compose 文件里使用)
docker tag ${SWR}/grafana/loki:3.0.0      grafana/loki:3.0.0
docker tag ${SWR}/grafana/promtail:3.0.0  grafana/promtail:3.0.0
docker tag ${SWR}/grafana/grafana:11.0.0  grafana/grafana:11.0.0

真实执行输出:

Status: Downloaded newer image for swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/grafana/loki:3.0.0
Digest: sha256:451563d761403fd66fdf7abef934f8712e864d47ef88f7b64e8ca52852fdaf28

6.2 启动服务

cd /opt/loki-stack
docker compose up -d

真实执行输出:

 Network loki-stack_loki-net Created
 Container loki Starting
 Container loki Started
 Container loki Waiting      ← 等待 Loki 健康检查通过
 Container loki Healthy      ← 健康检查通过
 Container grafana Starting
 Container promtail Starting
 Container grafana Started
 Container promtail Started

6.3 验证服务状态

# 查看容器运行状态
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'

真实输出:

NAMES      STATUS                   PORTS
grafana    Up About a minute        0.0.0.0:3000->3000/tcp
promtail   Up About a minute        0.0.0.0:9080->9080/tcp
loki       Up 2 minutes (healthy)   0.0.0.0:3100->3100/tcp
# Loki 健康检查
curl http://localhost:3100/ready
# 输出:ready

# Grafana 健康检查
curl http://localhost:3000/api/health
# 输出:{"commit":"83b9528b...","database":"ok","version":"11.0.0"}

七、Promtail 采集验证

7.1 查看 Promtail 日志(判断是否成功 tail 文件)

docker logs promtail 2>&1 | tail -15

真实输出(关键行):

level=info ... msg="Adding target" key="/opt/tomcat/logs/catalina.out:{app=\"catalina\", env=\"production\", host=\"ecs-53b4-0001\", job=\"tomcat\"}"
level=info ... msg="watching new directory" directory=/opt/tomcat/logs
msg="Seeked /opt/tomcat/logs/catalina.out - &{Offset:0 Whence:0}"
level=info ... component=tailer msg="tail routine: started" path=/opt/tomcat/logs/catalina.out
level=info ... component=tailer msg="tail routine: started" path=/opt/tomcat/logs/localhost_access_log.2026-06-11.txt

tail routine: started ← 这行表示 Promtail 已成功开始监听文件。

7.2 查看 Promtail 采集指标

curl http://localhost:9080/metrics | grep -E 'promtail_read_bytes|promtail_sent_bytes'

真实输出:

promtail_read_bytes_total{path="/opt/tomcat/logs/catalina.out"} 244264
promtail_read_bytes_total{path="/opt/tomcat/logs/localhost_access_log.2026-06-11.txt"} 154763
promtail_read_bytes_total{path="/var/log/auth.log"} 135777
promtail_read_bytes_total{path="/var/log/syslog"} 452886

promtail_read_bytes_total 持续增长 = 正在实时采集。

7.3 触发测试日志

# 手动向 Tomcat catalina.out 写入各级别测试日志
NOW=$(date '+%d-%b-%Y %H:%M:%S')
tee -a /opt/tomcat/logs/catalina.out << EOF
${NOW}.123 [INFO] [main] org.apache.catalina.startup.Catalina - [TEST] Loki pipeline test started
${NOW}.456 [WARN] [http-nio-8080-exec-1] com.example.HelloServlet - [TEST] Response time: 2340ms
${NOW}.789 [ERROR] [http-nio-8080-exec-2] com.example.AppController - [TEST] NullPointerException
$(date -d '+1 second' '+%d-%b-%Y %H:%M:%S').012 [ERROR] [scheduling-1] org.springframework.scheduling - [TEST] DB timeout
EOF

八、LogQL 查询语法

8.1 什么是 LogQL

LogQL 是 Loki 的查询语言,语法类似 PromQL,由两部分组成:

{标签选择器} | 管道过滤/格式化
    ↑ 必须      ↑ 可选

8.2 常用查询示例

基础查询
# 查询所有 tomcat 日志
{job="tomcat"}

# 查询指定主机的 tomcat 日志
{job="tomcat", host="ecs-53b4-0001"}

# 查询生产环境的 catalina 日志
{job="tomcat", app="catalina", env="production"}
内容过滤
# 包含 ERROR 的日志(大小写敏感)
{job="tomcat"} |= "ERROR"

# 包含 WARN 或 WARNING
{job="tomcat"} |~ "WARN(ING)?"

# 排除含 "TestServlet" 的行
{job="tomcat"} != "TestServlet"

# 包含 ERROR 且不包含 "test"
{job="tomcat"} |= "ERROR" != "[TEST]"
正则提取字段
# 提取 Tomcat 日志中的响应时间
{job="tomcat"} 
  | regexp `(?P<thread>\[http-nio-8080-exec-\d+\])` 
  | regexp `(?P<level>INFO|WARN|ERROR)`

# 统计 5 分钟内 ERROR 日志数量
count_over_time({job="tomcat"} |= "ERROR" [5m])
聚合统计
# 按 level 标签统计日志条数(近 1 小时)
sum by (level) (count_over_time({job="tomcat"}[1h]))

# 每分钟 ERROR 日志速率
rate({job="tomcat"} |= "ERROR" [1m])

# 最近 ERROR 日志(时间倒序)
{job="tomcat"} |= "ERROR" | line_format "{{.timestamp}} {{.message}}"

8.3 通过 Loki API 验证查询

# 查询最近 5 分钟的 tomcat ERROR 日志
START=$(date -d '5 minutes ago' +%s)000000000
END=$(date +%s)999999999

curl -G 'http://localhost:3100/loki/api/v1/query_range' \
  --data-urlencode 'query={job="tomcat"} |= "ERROR"' \
  --data-urlencode "start=${START}" \
  --data-urlencode "end=${END}" \
  --data-urlencode 'limit=10'

真实输出(精简):

{
  "status": "success",
  "data": {
    "result": [
      {
        "stream": {
          "app": "catalina",
          "env": "production",
          "host": "ecs-53b4-0001",
          "job": "tomcat",
          "level": "error"
        },
        "values": [
          ["1749626177000000000", "11-Jun-2026 15:36:17.012 [ERROR] [scheduling-1] ... DB timeout after 30000ms"],
          ["1749626176000000000", "11-Jun-2026 15:36:16.789 [ERROR] [http-nio-8080-exec-2] ... NullPointerException"]
        ]
      }
    ]
  }
}

注意:Loki 自动从日志内容中解析出 level: error 标签 —— 这是 Loki 3.x 新增的 Automatic Level Detection 功能。


九、Grafana 配置 Loki 数据源

9.1 登录 Grafana

URL:  http://1.92.101.204:3000
用户名:admin
密码:  Grafana@2026

9.2 添加 Loki 数据源(手动方式)

  1. 左侧菜单 → ConnectionsData Sources
  2. 点击 Add data source,搜索 Loki
  3. 填入配置:
  • NameLoki
  • URLhttp://loki:3100(Docker 内部网络,不是 localhost)
  • Maximum lines1000
  1. 点击 Save & test → 显示 Data source successfully connected.

9.3 API 方式自动配置(运维推荐)

curl -X POST \
  -H 'Content-Type: application/json' \
  -u 'admin:Grafana@2026' \
  http://localhost:3000/api/datasources \
  -d '{
    "name": "Loki",
    "type": "loki",
    "url": "http://loki:3100",
    "access": "proxy",
    "isDefault": true
  }'

真实响应:

{"message":"data source with the same name already exists"}

(Grafana 11.0 首次启动时已自动检测并注册 Loki,无需手动添加!)

9.4 健康检查验证

# 查询数据源列表
curl -s -u 'admin:Grafana@2026' http://localhost:3000/api/datasources

# 真实输出
[{"id":1,"name":"Loki","type":"loki","url":"http://loki:3100","isDefault":true}]

# 测试数据源连通性
curl -s -u 'admin:Grafana@2026' http://localhost:3000/api/datasources/1/health

# 真实输出
{"status":"OK","message":"Data source successfully connected."}

9.5 在 Explore 中查询日志

  1. 左侧菜单 → Explore
  2. 数据源选择 Loki
  3. 在 Label filters 中选:job = tomcat
  4. 点击 Run query,即可看到 Tomcat 实时日志流

常用 LogQL 查询(可直接粘贴到 Code 模式):

# 所有 tomcat 日志(最近 1 小时)
{job="tomcat"}

# 仅 ERROR 级别
{job="tomcat"} |= "ERROR"

# 自动 level 标签过滤(Loki 3.x 自动检测)
{job="tomcat", level="error"}

# NullPointerException 相关
{job="tomcat"} |~ "NullPointerException|ClassNotFoundException"

十、内存与资源消耗

10.1 实测数据(空载运行约 2 分钟后)

docker stats --no-stream --format 'table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}'
NAME       CPU %     MEM USAGE / LIMIT
grafana    0.11%     47.18MiB / 3.33GiB
promtail   0.23%     31.07MiB / 3.33GiB
loki       0.32%     57.18MiB / 3.33GiB

三组件合计:~135MB —— 而 Elasticsearch 单节点默认最低 JVM 堆内存就需要 1GB!

10.2 ELK vs Loki 资源消耗对比

指标 ELK(单节点) Loki 三件套 节省
内存占用 ≥ 2GB ~135MB 93%
磁盘占用(1GB 原始日志) 3~5GB(含索引) ~300MB(压缩) 85%
CPU 空载 5~15% 0.66% 97%
最低服务器配置 4核8G 1核2G

十一、日志持久化与轮转

11.1 Loki 数据存储位置

ls -lh /opt/loki-stack/data/loki/
drwxrwxrwx chunks/    # 日志块文件(按时间分片)
drwxrwxrwx rules/     # 告警规则存储
drwxrwxrwx wal/       # 预写日志,重启恢复用

生产建议:将 ./data/loki 挂载到大容量磁盘,或替换 object_store: filesystems3,对接对象存储。

11.2 配置日志保留策略

loki-config.yaml 中添加:

# 保留策略(Loki 3.x compactor)
compactor:
  working_directory: /loki/compactor
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h
  retention_delete_worker_count: 150

limits_config:
  retention_period: 168h   # 保留 7 天,超过自动删除

十二、踩坑记录

❗ 坑 1:Loki 启动报 permission denied

现象:

mkdir /loki/rules: permission denied
error initialising module: ruler-storage

原因:Loki 容器内以 uid=10001 运行,宿主机数据目录权限不足。

解决:

chmod -R 777 /opt/loki-stack/data/loki
# 或精确授权
chown -R 10001:10001 /opt/loki-stack/data/loki

❗ 坑 2:Promtail 读取日志文件 Permission denied

现象:level=warn msg="Error reading" err="open /opt/tomcat/logs/catalina.out: permission denied"

原因:Tomcat 日志属主为 tomcat:tomcat(权限 640),容器内普通用户无法读取。

解决方案:

方案 A(简单,仅测试环境):在 compose 中添加 user: root

方案 B(推荐,生产环境):

# 给日志文件加 ACL(不改属主)
apt-get install -y acl
setfacl -m u:10001:r /opt/tomcat/logs/catalina.out
setfacl -m u:10001:r /opt/tomcat/logs/localhost_access_log.2026-06-11.txt
# 对目录加执行权限(允许遍历)
setfacl -m u:10001:rx /opt/tomcat/logs/

❗ 坑 3:docker pull grafana/loki 拉取失败

现象:failed to resolve reference "docker.io/grafana/loki:3.0.0": not found

原因:国内服务器无法直连 docker.io。

解决:

# 使用华为云 SWR 镜像仓库
docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/grafana/loki:3.0.0
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/grafana/loki:3.0.0 grafana/loki:3.0.0

❗ 坑 4:Promtail 与 Loki 在同一网络但推送失败

现象:level=warn msg="error sending batch" error="context deadline exceeded"

原因:Promtail 在 Loki 完全就绪前就开始推送,连接超时。

解决: compose 中添加健康检查依赖(本文已包含):

depends_on:
  loki:
    condition: service_healthy

❗ 坑 5:version 属性已废弃警告

现象:

level=warning msg="docker-compose.yml: the attribute `version` is obsolete, it will be ignored"

说明:Docker Compose v2+ 已不需要 version 字段,这只是警告不影响功能。可以删除 docker-compose.yml 顶部的 version: '3.8' 行。


❗ 坑 6:LogQL 查询无数据

排查步骤:

# 1. 确认 Promtail 正在采集
curl http://localhost:9080/metrics | grep promtail_sent_bytes_total
# 有数值且增长 = Promtail 在推送

# 2. 确认 Loki 收到数据(查看 ingester 统计)
curl http://localhost:3100/metrics | grep loki_ingester_streams_created_total

# 3. 检查时间范围
# LogQL 查询默认只看最近 1 小时,如需更大范围:
{job="tomcat"} [6h]   # 在 Explore 中调整时间选择器

# 4. 标签大小写问题
# ❌ {Job="tomcat"}   ← 标签名大小写不对
# ✅ {job="tomcat"}

❗ 坑 7:Grafana URL 填写错误

场景 正确 URL 错误 URL
Grafana 与 Loki 同一 compose 网络 http://loki:3100 http://localhost:3100
Grafana 独立部署,Loki 在另一台服务器 http://192.168.0.58:3100 http://127.0.0.1:3100

十三、系统状态汇总

13.1 已配置的标签结构

curl http://localhost:3100/loki/api/v1/labels

真实输出:

{"status":"success","data":["app","env","filename","host","job","level","service_name"]}

Loki 3.0 自动从日志内容中检测 levelservice_name,无需额外配置!

13.2 当前服务访问入口

服务 访问地址 账号/密码
Grafana http://1.92.101.204:3000 admin / Grafana@2026
Loki API http://1.92.101.204:3100/ready
Promtail 监控 http://1.92.101.204:9080/metrics
Loki 标签查询 http://1.92.101.204:3100/loki/api/v1/labels

十四、进阶:告警配置(钉钉通知)

14.1 Grafana Alerting 基础

在 Grafana 11.0 中:

  1. 创建 Alert Rule
  • 进入 Alerting → Alert Rules → New alert rule

  • 数据源选 Loki,LogQL:

    count_over_time({job="tomcat"} |= "ERROR" [5m])
    
  • 条件:IS ABOVE 5(5分钟内 ERROR 超过5条触发)

  1. 配置 Contact Points(通知渠道)
  • Alerting → Contact Points → Add
  • 选择 DingTalk(钉钉)
  • 填入 Webhook URL:https://oapi.dingtalk.com/robot/send?access_token=xxx
  1. 绑定 Notification Policy
  • 将 Alert Rule 的 labels 匹配到 Contact Point

十五、进阶:对接 Spring Boot(JSON 日志)

15.1 logback-spring.xml 输出 JSON

<!-- 添加 logstash-logback-encoder 依赖后 -->
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
  <file>/opt/myapp/logs/app.json.log</file>
  <encoder class="net.logstash.logback.encoder.LogstashEncoder">
    <customFields>{"service":"my-spring-app","env":"production"}</customFields>
  </encoder>
</appender>

15.2 Promtail 解析 JSON 日志

- job_name: springboot_json
  static_configs:
    - targets: [localhost]
      labels:
        job: springboot
        __path__: /opt/myapp/logs/app.json.log
  pipeline_stages:
    - json:
        expressions:
          level: level          # 从 JSON 字段 "level" 提取
          logger: logger_name
          trace_id: traceId
    - labels:
        level:                  # 将提取的 level 设为 Loki 标签
        logger:
    - output:
        source: message         # 用 "message" 字段作为日志正文

十六、生产环境建议

项目 建议 说明
认证 开启 auth_enabled: true 多租户隔离
存储 替换 filesystem 为 S3/MinIO 扩容方便,支持冷热分层
高可用 3节点 Loki 集群 + consul/etcd 避免单点故障
备份 定期 tar 压缩 chunks/ 目录 防数据丢失
监控 Prometheus 抓取 Loki/Promtail /metrics 监控日志系统本身
安全 所有端口加 Nginx 反代 + HTTPS 避免明文传输
日志量 > 10GB/天时考虑 S3 + 压缩 filesystem 单盘有限

附录一:LogQL 速查表

# ── 基础 ──
{job="tomcat"}                             # 所有 tomcat 日志
{job="tomcat", level="error"}              # Loki 3.x 自动 level 标签

# ── 内容过滤 ──
{job="tomcat"} |= "ERROR"                  # 包含 ERROR
{job="tomcat"} != "healthcheck"            # 排除 healthcheck
{job="tomcat"} |~ "Exception|Error"        # 正则匹配
{job="tomcat"} !~ "DEBUG|TRACE"            # 正则排除

# ── 指标查询 ──
count_over_time({job="tomcat"}[5m])        # 5分钟日志总量
rate({job="tomcat"} |= "ERROR" [1m])       # 每秒 ERROR 速率
sum by(level)(rate({job="tomcat"}[5m]))    # 按 level 分组统计

# ── 字段提取 ──
{job="tomcat"} | regexp `\[(?P<thread>[^\]]+)\]`   # 提取线程名
{job="tomcat"} | pattern `<_> [<level>] <_>`        # pattern 提取

附录二:常用 API 端点

端点 说明
GET /ready Loki 就绪状态
GET /metrics Prometheus 格式指标
GET /loki/api/v1/labels 已索引标签列表
GET /loki/api/v1/label/{name}/values 标签的所有值
GET /loki/api/v1/query_range 范围查询
GET /loki/api/v1/tail WebSocket 实时日志流
POST /loki/api/v1/push 推送日志(Promtail 用)

附录三:快速部署脚本

#!/bin/bash
# 一键部署 Loki Stack
# 使用方式:curl -fsSL https://your-repo/loki-deploy.sh | bash

set -euo pipefail
BASE=/opt/loki-stack
SWR="swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io"

mkdir -p ${BASE}/{config,data/loki/{chunks,rules,wal},data/grafana}
chmod -R 777 ${BASE}/data/loki
chown -R 472:472 ${BASE}/data/grafana

for img in loki:3.0.0 promtail:3.0.0 grafana:11.0.0; do
    app=$(echo $img | cut -d: -f1)
    tag=$(echo $img | cut -d: -f2)
    docker pull ${SWR}/grafana/${app}:${tag}
    docker tag  ${SWR}/grafana/${app}:${tag} grafana/${app}:${tag}
    echo "✅ grafana/${app}:${tag} 就绪"
done

cd ${BASE} && docker compose up -d
echo "✅ Loki Stack 启动完成"
echo "   Grafana: http://$(curl -s ifconfig.me):3000  (admin/Grafana@2026)"
echo "   Loki:    http://$(curl -s ifconfig.me):3100/ready"

最后更新:2026-06-11 | 集群:ecs-53b4 | 作者:实战博客系列

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐