手把手搭建云原生日志平台:Loki + Promtail + Grafana 实战
手把手搭建云原生日志平台: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 数据源(手动方式)
- 左侧菜单 → Connections → Data Sources
- 点击 Add data source,搜索 Loki
- 填入配置:
- Name:
Loki - URL:
http://loki:3100(Docker 内部网络,不是 localhost) - Maximum lines:
1000
- 点击 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 中查询日志
- 左侧菜单 → Explore
- 数据源选择 Loki
- 在 Label filters 中选:
job = tomcat - 点击 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: filesystem为s3,对接对象存储。
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 自动从日志内容中检测 level 和 service_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 中:
- 创建 Alert Rule
-
进入 Alerting → Alert Rules → New alert rule
-
数据源选 Loki,LogQL:
count_over_time({job="tomcat"} |= "ERROR" [5m]) -
条件:
IS ABOVE 5(5分钟内 ERROR 超过5条触发)
- 配置 Contact Points(通知渠道)
- Alerting → Contact Points → Add
- 选择 DingTalk(钉钉)
- 填入 Webhook URL:
https://oapi.dingtalk.com/robot/send?access_token=xxx
- 绑定 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 | 作者:实战博客系列
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)