Loki 单机 Linux Docker 搭建指南

Loki 单机 Linux Docker 搭建指南

1. 适用场景

本文档适用于以下部署条件:

  • Spring Boot 2.x 项目
  • Java 后端项目
  • 单机 Linux 服务器
  • 使用 Docker / Docker Compose 部署
  • 业务日志目录为 /home/admin/app/logs/visa
  • 需要快速检索日志、查看异常堆栈、按时间范围筛选日志

当前项目的日志格式参考 src/main/resources/logback.xml,核心格式如下:

xml 复制代码
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} %L %msg%n</pattern>

当前日志滚动策略为:

  • 按天切分
  • 单文件超过 20MB 自动继续拆分
  • 错误日志单独输出到 error 子目录

以上策略可以正常接入 Loki + Promtail + Grafana

2. 方案说明

本方案采用以下组件:

  • Loki:日志存储与查询服务
  • Promtail:读取日志文件并推送到 Loki
  • Grafana:图形化查看日志

整体流程如下:

  1. Spring Boot 按现有 logback 配置继续写本地日志文件
  2. Promtail 持续采集 /home/admin/app/logs/visa 目录下的日志
  3. Promtail 将日志推送到 Loki
  4. Grafana 连接 Loki 后提供检索界面

3. 目录规划

建议在 Linux 服务器创建统一的观测目录:

bash 复制代码
mkdir -p /opt/visa-observe/loki/data
mkdir -p /opt/visa-observe/promtail/positions
mkdir -p /opt/visa-observe/grafana-data

建议最终目录结构如下:

text 复制代码
/opt/visa-observe/
├── docker-compose.yml
├── grafana-data/
├── loki/
│   ├── data/
│   └── loki-config.yaml
└── promtail/
    ├── positions/
    └── promtail-config.yaml

4. 前置准备

4.1 检查 Docker

执行以下命令确认 Docker 已安装:

bash 复制代码
docker -v
docker compose version

如果服务器没有安装 Docker 和 Docker Compose,需要先安装。

4.2 确认日志目录

确认业务日志目录已经存在,并且日志文件会持续写入:

bash 复制代码
ls -lh /home/admin/app/logs/visa

如果存在 error 子目录,也建议一并确认:

bash 复制代码
ls -lh /home/admin/app/logs/visa/error

5. 创建 docker-compose.yml

/opt/visa-observe 目录下创建 docker-compose.yml

yaml 复制代码
version: "3.8"

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

  promtail:
    image: grafana/promtail:2.9.8
    container_name: promtail
    restart: unless-stopped
    volumes:
      - /opt/visa-observe/promtail/promtail-config.yaml:/etc/promtail/config.yml
      - /home/admin/app/logs/visa:/home/admin/app/logs/visa:ro
      - /opt/visa-observe/promtail/positions:/tmp
    command: -config.file=/etc/promtail/config.yml
    depends_on:
      - loki

  grafana:
    image: grafana/grafana:10.4.3
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - /opt/visa-observe/grafana-data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_USER=admin
      - GF_SECURITY_ADMIN_PASSWORD=admin123
      - GF_SERVER_ROOT_URL=http://localhost:3000
    depends_on:
      - loki

6. 创建 Loki 配置

/opt/visa-observe/loki 目录下创建 loki-config.yaml

yaml 复制代码
auth_enabled: false

server:
  http_listen_port: 3100

common:
  path_prefix: /loki
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

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

storage_config:
  filesystem:
    directory: /loki/chunks

compactor:
  working_directory: /loki/compactor

limits_config:
  reject_old_samples: true
  reject_old_samples_max_age: 168h
  allow_structured_metadata: false

7. 创建 Promtail 配置

/opt/visa-observe/promtail 目录下创建 promtail-config.yaml

yaml 复制代码
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: visa-log
    static_configs:
      - targets:
          - localhost
        labels:
          job: visa-log
          app: visa
          __path__: /home/admin/app/logs/visa/**/*.log

    pipeline_stages:
      - multiline:
          firstline: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}'

      - regex:
          expression: '^(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \[(?P<thread>[^\]]+)\] (?P<level>[A-Z]+)\s+(?P<logger>\S+) (?P<line>\d+) (?P<message>[\s\S]*)$'

      - timestamp:
          source: time
          format: '2006-01-02 15:04:05.000'

      - labels:
          level:

      - output:
          source: message

8. 为什么这个配置适合当前项目

8.1 支持 20MB 自动拆分后的日志采集

你当前日志采用 SizeAndTimeBasedRollingPolicy,超过 20MB 后会继续生成新文件,例如:

text 复制代码
/home/admin/app/logs/visa/2026-06-26.0.log
/home/admin/app/logs/visa/2026-06-26.1.log
/home/admin/app/logs/visa/2026-06-26.2.log

Promtail 配置中的:

yaml 复制代码
__path__: /home/admin/app/logs/visa/**/*.log

可以匹配:

  • 当前目录下的普通日志
  • error 子目录中的错误日志
  • 后续因为滚动产生的新日志文件

因此超过 20MB 自动拆分后,仍然可以继续监控。

8.2 支持 Java 异常堆栈

Java 异常通常是多行输出,如果不做合并,Grafana 中会看到很多碎片化日志。

这里使用了 multiline 配置:

yaml 复制代码
- multiline:
    firstline: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}'

它会把不是以时间开头的后续行,自动合并到上一条日志中,适合 Java 堆栈输出。

8.3 控制标签数量,避免查询变慢

当前配置只把 level 提升为标签,没有把 threadlogger 提升为标签。

这样做的原因是:

  • thread 变化太多,容易造成高基数
  • logger 也可能很多,标签过多会影响 Loki 查询性能

当前最适合先保留:

  • job
  • app
  • level

9. 启动服务

进入目录后启动:

bash 复制代码
cd /opt/visa-observe
docker compose up -d

查看容器状态:

bash 复制代码
docker ps

查看 Loki 日志:

bash 复制代码
docker logs -f loki

查看 Promtail 日志:

bash 复制代码
docker logs -f promtail

查看 Grafana 日志:

bash 复制代码
docker logs -f grafana

10. 基础验证

10.1 检查 Loki 是否可用

bash 复制代码
curl http://127.0.0.1:3100/ready

返回 ready 说明正常。

10.2 检查 Promtail 是否成功采集

执行:

bash 复制代码
docker logs promtail | tail -n 50

重点关注是否存在以下问题:

  • 无法读取日志目录
  • 配置文件语法错误
  • 无法连接 loki:3100

10.3 检查 Grafana 是否可访问

浏览器访问:

text 复制代码
http://服务器IP:3000

默认账号密码:

  • 用户名:admin
  • 密码:admin123

11. 配置 Grafana 数据源

登录 Grafana 后按以下步骤操作:

  1. 进入 Connections

  2. 选择 Data sources

  1. URL 填写 http://loki:3100

  2. 点击 Save & test

如果显示成功,说明 Grafana 已经可以访问 Loki。

12. 常用查询语句

进入 Grafana 的 Explore 页面后,可以使用以下查询。

12.1 查询全部业务日志

logql 复制代码
{job="visa-log"}

12.2 只看错误日志

logql 复制代码
{job="visa-log", level="ERROR"}

12.3 搜索异常关键字

logql 复制代码
{job="visa-log"} |= "Exception"

12.4 搜索某个业务关键字

logql 复制代码
{job="visa-log"} |= "申请单"

12.5 搜索某个类打印的日志

因为 logger 当前没有作为标签,所以建议直接全文搜索,例如:

logql 复制代码
{job="visa-log"} |= "com.cxwl"

13. 推荐的执行顺序

建议严格按以下顺序执行:

  1. 安装并确认 Docker 环境
  2. 创建 /opt/visa-observe 目录结构
  3. 创建 docker-compose.yml
  4. 创建 loki-config.yaml
  5. 创建 promtail-config.yaml
  6. 启动 docker compose up -d
  7. 登录 Grafana 配置 Loki 数据源
  8. 进入 Explore 页面验证是否已经有日志

14. 与当前 logback 配置相关的建议

14.1 建议把 Linux 日志目录改成绝对路径

你当前 logback.xml 中配置的是:

xml 复制代码
<property name="LOG_HOME" value="../logs" />

这个写法在 Linux Docker 场景下不够稳定,建议改成绝对路径,例如:

xml 复制代码
<property name="LOG_HOME" value="/home/admin/app/logs" />

这样最终输出目录会更明确:

text 复制代码
/home/admin/app/logs/visa

14.2 生产环境建议关闭 Logback 调试

你当前配置中:

xml 复制代码
<configuration debug="true" scan="true">

建议生产环境改为:

xml 复制代码
<configuration debug="false" scan="true">

原因:

  • debug="true" 会输出 Logback 自身内部调试信息
  • 生产环境通常没有必要开启

14.3 不建议立即改成应用直推 Loki

当前场景下,更推荐保持以下模式:

  1. Java 应用继续按本地文件方式写日志
  2. Promtail 负责统一采集

这样做的优点是:

  • 改动小
  • 风险低
  • 出问题时仍可直接登录 Linux 服务器查看本地日志

15. 常见问题

15.1 为什么 Grafana 查不到日志

优先排查以下问题:

  • /home/admin/app/logs/visa 是否真的有日志文件
  • Promtail 是否挂载到了正确目录
  • Promtail 配置文件是否写错
  • Promtail 是否成功连接到 Loki
  • Grafana 数据源 URL 是否写成了 http://loki:3100

15.2 日志滚动后是否会丢失

正常不会。

原因:

  • Promtail 会记录读取位置
  • 新生成的 .log 文件仍会被 __path__ 匹配

但仍建议不要频繁删除正在写入的日志文件。

15.3 历史压缩日志会不会实时采集

不建议依赖 .gz 压缩文件做实时采集。

建议原则如下:

  • 正在写入或刚滚动出的日志,保持 .log
  • 需要长期归档时再压缩

15.4 如果 Java 项目也运行在 Docker 中怎么办

需要确保应用容器已经把日志目录挂载到宿主机,例如:

yaml 复制代码
volumes:
  - /home/admin/app/logs:/home/admin/app/logs

Promtail 采集的是宿主机目录,不是应用容器内部的临时文件系统。

16. 实际调试问题与解决方案

以下内容基于本次实际部署过程中遇到的问题整理,建议在首次搭建时重点关注。

16.1 Loki 启动时报配置文件无权限

问题现象:

text 复制代码
failed parsing config: open /etc/loki/local-config.yaml: permission denied

原因分析:

  • 宿主机挂载到容器内的 loki-config.yaml 权限不足
  • 宿主机上级目录权限不足,导致容器用户无法读取配置文件
  • 某些 Linux 发行版开启了 SELinux,卷挂载可能还需要额外处理

解决方案:

先修正目录和文件权限:

bash 复制代码
chmod 755 /opt
chmod 755 /opt/visa-observe
chmod 755 /opt/visa-observe/loki
chmod 644 /opt/visa-observe/loki/loki-config.yaml
chmod 755 /opt/visa-observe/loki/data
chown -R 10001:10001 /opt/visa-observe/loki/data

如果系统启用了 SELinux,先检查:

bash 复制代码
getenforce

如果返回 Enforcing,建议在卷挂载时增加 :Z,例如:

yaml 复制代码
volumes:
  - /opt/visa-observe/loki/loki-config.yaml:/etc/loki/local-config.yaml:ro,Z
  - /opt/visa-observe/loki/data:/loki:Z

验证方式:

bash 复制代码
docker compose down
docker compose up -d
docker logs --tail=100 loki
curl http://127.0.0.1:3100/ready

16.2 Loki 返回 Ingester not ready

问题现象:

text 复制代码
Ingester not ready: waiting for 15s after being ready

原因分析:

  • 这是 Loki 启动后的短暂预热状态
  • 并不代表配置错误或服务异常

解决方案:

  • 等待 15~30 秒后重新检查

验证方式:

bash 复制代码
curl http://127.0.0.1:3100/ready

当返回以下内容时说明服务正常:

text 复制代码
ready

16.3 Grafana 启动失败并反复重启

问题现象:

text 复制代码
GF_PATHS_DATA='/var/lib/grafana' is not writable.
mkdir: can't create directory '/var/lib/grafana/plugins': Permission denied

同时 docker ps 中可能看到:

text 复制代码
grafana   Restarting (1)

原因分析:

  • 宿主机挂载目录 /opt/visa-observe/grafana-data 对容器内 Grafana 用户不可写
  • Grafana 无法创建插件目录和内部数据文件,因此启动失败

解决方案:

先创建并修正目录权限:

bash 复制代码
mkdir -p /opt/visa-observe/grafana-data
chown -R 472:472 /opt/visa-observe/grafana-data
chmod -R 755 /opt/visa-observe/grafana-data

然后重启容器:

bash 复制代码
cd /opt/visa-observe
docker compose down
docker compose up -d

如果系统启用了 SELinux,建议把卷挂载改为:

yaml 复制代码
volumes:
  - /opt/visa-observe/grafana-data:/var/lib/grafana:Z

验证方式:

bash 复制代码
docker ps
docker logs --tail=100 grafana
curl -I http://127.0.0.1:3000

正常状态下应满足:

  • docker psgrafanaUp
  • 可以看到 0.0.0.0:3000->3000/tcp
  • curl 返回 302 Found 并跳转到 /login

16.4 UFW 已放行但浏览器仍无法访问 Grafana

问题现象:

  • ufw status 已显示 3000/tcp ALLOW
  • 服务器本机访问 http://127.0.0.1:3000 正常
  • 外部浏览器访问 http://公网IP:3000 仍然失败

原因分析:

  • 这通常不是 Linux 本机防火墙问题
  • 更常见原因是云服务器安全组未放行 3000/tcp

解决方案:

先确认本机防火墙已放行:

bash 复制代码
ufw allow 3000/tcp
ufw reload
ufw status

再确认服务本机访问正常:

bash 复制代码
curl -I http://127.0.0.1:3000

如果本机正常但外部仍无法访问,需要到云平台控制台放行安全组:

  • 协议:TCP
  • 端口:3000
  • 来源:0.0.0.0/0 或指定办公公网 IP

验证方式:

  • 服务器本机执行:
bash 复制代码
curl -I http://127.0.0.1:3000
  • 浏览器访问:
text 复制代码
http://公网IP:3000

16.5 Promtail 首次启动 CPU 偏高

问题现象:

  • promtail 初次启动后 CPU 使用率明显升高
  • 宿主机 top 中看到 promtail 持续占用较高 CPU

原因分析:

  • 首次启动时,Promtail 会扫描现有日志目录并补采历史日志
  • 当前配置使用了递归路径 /home/admin/app/logs/visa/**/*.log
  • 同时还启用了 multilineregex 解析,首次扫描成本会更高

解决方案:

  • 如果只是启动初期短时间 CPU 升高,可以先观察几分钟
  • 如果历史日志很多,建议收窄采集范围
  • 可以将普通日志和错误日志分开配置,避免全量递归扫描

示例:

yaml 复制代码
scrape_configs:
  - job_name: visa-log
    static_configs:
      - targets:
          - localhost
        labels:
          job: visa-log
          app: visa
          __path__: /home/admin/app/logs/visa/*.log

  - job_name: visa-error-log
    static_configs:
      - targets:
          - localhost
        labels:
          job: visa-error-log
          app: visa
          __path__: /home/admin/app/logs/visa/error/*.log

验证方式:

bash 复制代码
docker logs --tail=100 promtail
top

观察一段时间后,如果 CPU 明显下降,通常说明补采已完成。

17. 建议的上线后检查项

上线完成后,建议至少检查以下内容:

  1. Grafana 可以打开
  2. Loki 数据源测试通过
  3. Explore 页面能查到最近 15 分钟日志
  4. ERROR 级别日志可以独立筛选
  5. 人为制造一条异常日志后能在 Grafana 中看到完整堆栈
  6. 日志超过 20MB 滚动后,新文件仍能继续被采集

18. 一次性执行命令汇总

如果你想按步骤手工执行,可以参考以下命令顺序。

17.1 创建目录

bash 复制代码
mkdir -p /opt/visa-observe/loki/data
mkdir -p /opt/visa-observe/promtail/positions
mkdir -p /opt/visa-observe/grafana-data

17.2 进入工作目录

bash 复制代码
cd /opt/visa-observe

17.3 创建配置文件

手工创建以下文件:

  • docker-compose.yml
  • loki/loki-config.yaml
  • promtail/promtail-config.yaml

17.4 启动

bash 复制代码
docker compose up -d

17.5 查看状态

bash 复制代码
docker ps
curl http://127.0.0.1:3100/ready
docker logs promtail | tail -n 50

19. 后续可选优化

当前方案适合快速落地。后续如果日志量继续增长,可以考虑:

  • 给 Grafana 配置登录安全策略
  • 给 Loki 配置更长的数据保留周期
  • 单独给错误日志配置不同的抓取任务
  • 增加告警能力,例如发现 ERRORException 自动通知

20. 结论

对于当前项目,最稳妥、改动最小的方式是:

  • Spring Boot 保持本地文件输出
  • 使用 Promtail 采集 /home/admin/app/logs/visa/**/*.log
  • 使用 Loki 做日志存储
  • 使用 Grafana 做查询和检索

该方案已经兼容你当前的:

  • 单机 Linux
  • Docker 部署
  • Spring Boot 2.x
  • 20MB 自动拆分日志
  • Java 异常堆栈查看需求