系列导航 :(一)Loki基础与单体部署 → (二)简单可扩展模式 → (三)LogQL查询语言实战
一、为什么需要简单可扩展模式
单体模式(Monolithic Mode)将所有Loki组件运行在同一个进程中,适合开发测试和小规模部署。但当日志量增长时,单体模式面临以下瓶颈:
| 痛点 | 说明 |
|---|---|
| 读写竞争 | 查询大量日志时占用资源,影响日志写入 |
| 扩展困难 | 无法单独扩展写入或查询能力 |
| 资源浪费 | Ingester需要内存,Querier需要CPU,混合部署无法针对性优化 |
简单可扩展模式(Simple Scalable Mode, SSM) 将Loki拆分为三个Target,共享同一配置文件,通过-target参数区分:
scss
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Write │ │ Read │ │ Backend │
│ (Distributor│ │ (Querier │ │ (Compactor │
│ +Ingester) │ │ +Frontend │ │ +Ruler │
│ │ │ +Scheduler)│ │ +Index GW) │
└─────────────┘ └─────────────┘ └─────────────┘
↑ ↑ ↑
│ │ │
└───────── Memberlist (Gossip) ─────────┘
二、SSM架构详解
2.1 Write Target
Write Target包含:
- Distributor:接收客户端推送的日志,验证并分发
- Ingester:将日志暂存内存,定期刷写为Chunk到存储
特点:
- 需要较大内存(暂存日志数据)
- 可水平扩展,通过Memberlist自动发现
- 写入路径:Client → Distributor → Ingester → Chunk → Storage
2.2 Read Target
Read Target包含:
- Querier:执行LogQL查询,从Ingester和存储读取数据
- Query Frontend:查询前置,拆分大查询、缓存结果
- Query Scheduler:查询调度器,公平调度查询请求
特点:
- 需要较大CPU(查询计算)
- 可水平扩展
- 读取路径:Client → Frontend → Scheduler → Querier → Ingester + Storage
2.3 Backend Target
Backend Target包含:
- Compactor:压缩合并索引,执行保留策略
- Ruler:评估告警规则
- Index Gateway:索引查询缓存网关
特点:
- 通常单实例即可
- 承担后台任务,不参与实时读写
2.4 Memberlist通信
三个Target通过Memberlist(Gossip协议)组成集群:
- 端口7946:Memberlist通信端口
- 自动发现 :通过
join_members配置列表自动加入集群 - 故障检测:基于Gossip协议的故障检测和成员更新
2.5 数据流详解
写入数据流
scss
Promtail → POST /loki/api/v1/push → Write Target
↓
Distributor
↓ (基于Stream哈希路由)
Ingester (内存Head Chunk)
↓ (定期flush)
Chunk → 对象存储/文件系统
↓
Index → TSDB索引
关键点 :写入请求只发往Write Target 。Backend和Read Target不处理 /push 请求。
查询数据流
scss
Client → GET /loki/api/v1/query_range → Read Target
↓
Query Frontend
↓ (拆分为子查询)
Query Scheduler
↓ (调度)
Querier (多个并行)
↓
┌───────────┴───────────┐
↓ ↓
Ingester (最近数据) 存储后端 (历史数据)
↓ ↓
└───────────┬───────────┘
↓
合并 + 去重 + 排序
↓
返回结果
关键点 :查询请求只发往Read Target。Read Target内部的Querier会同时查询Ingester(获取最近未flush的数据)和存储后端(获取历史数据)。
后台任务流
Backend Target 内部:
Compactor → 定期合并TSDB索引 → 应用retention策略 → 删除过期Chunk
Ruler → 定期评估告警规则 → 触发告警 → Alertmanager
Index GW → 缓存索引查询 → 减少对象存储访问
三、SSM部署实操
3.1 配置文件
在node2(120.46.11.141)上创建SSM配置:
loki-config.yaml:
yaml
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
log_level: info
common:
path_prefix: /loki
replication_factor: 1
compactor_address: loki-backend:9096
ring:
kvstore:
store: memberlist
memberlist:
join_members:
- loki-write:7946
- loki-read:7946
- loki-backend:7946
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
storage_config:
tsdb_shipper:
active_index_directory: /loki/tsdb-index
cache_location: /loki/tsdb-cache
limits_config:
retention_period: 168h
max_query_parallelism: 16
ruler:
enable_api: true
rule_path: /loki/rules
compactor:
working_directory: /loki/compactor
compaction_interval: 10m
analytics:
reporting_enabled: false
关键配置解读:
-
common.compactor_address:指定Compactor的gRPC地址,Read Target需要此配置来支持删除查询过滤。这是Loki 3.0 SSM模式的必需配置。 -
memberlist.join_members:列出所有成员的地址和端口,用于集群自动发现。 -
replication_factor: 1:单Write节点时设为1。生产环境建议≥2并部署多个Write节点。 -
store: tsdb:使用TSDB存储引擎,Loki 3.x的推荐选择,比旧的boltdb-shipper性能更好。
3.3 SSM配置参数完整说明
| 配置路径 | 参数 | 实操值 | 说明 |
|---|---|---|---|
common.path_prefix |
/loki | 数据根目录 | 所有Target共享 |
common.replication_factor |
1 | 副本因子 | 单Write设1,多Write设≥2 |
common.compactor_address |
loki-backend:9096 | Compactor gRPC地址 | 必需,否则Read启动失败 |
common.ring.kvstore.store |
memberlist | 集群发现 | SSM必须用memberlist |
memberlist.join_members |
所有节点:7946 | 集群成员列表 | 列出所有Target的7946端口 |
schema_config.configs[0].store |
tsdb | 索引引擎 | Loki 3.x推荐 |
schema_config.configs[0].schema |
v13 | Schema版本 | 支持结构化元数据 |
storage_config.tsdb_shipper |
- | TSDB配置 | 索引和缓存路径 |
limits_config.retention_period |
168h | 保留时间 | 7天 |
limits_config.max_query_parallelism |
16 | 查询并行度 | 影响查询性能 |
ruler.enable_api |
true | 启用规则API | 允许通过API管理规则 |
compactor.compaction_interval |
10m | 压缩间隔 | 影响索引合并频率 |
Target启动参数:
| Target | -target值 |
包含组件 | 端口 |
|---|---|---|---|
| Write | write |
Distributor + Ingester | 3100(HTTP), 7946(Memberlist) |
| Read | read |
Querier + Query Frontend + Scheduler | 3100(HTTP) |
| Backend | backend |
Compactor + RAuler + Index Gateway | 3100(HTTP), 9096(gRPC) |
3.2 Docker Compose编排
Docker网络拓扑:
perl
┌─────────────────────────────────────────────────────────┐
│ loki-net (bridge网络) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ write │ │ read │ │ backend │ │ grafana │ │
│ │ :3101 │ │ :3102 │ │ :3100 │ │ :3000 │ │
│ │ :7946 │ │ │ │ │ │ │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ └──────────────┴──────────────┘ │ │
│ Memberlist Gossip │ │
│ (port 7946) │ │
│ │ │
│ ┌──────────┐ │ │
│ │ promtail │────────── push to write ─────────────┘ │
│ │ :9080 │ │
│ └──────────┘ │
└─────────────────────────────────────────────────────────┘
端口分配:
| 容器 | 内部端口 | 外部映射 | 用途 |
|---|---|---|---|
| loki-write | 3100 | 3101 | Write HTTP API |
| loki-write | 7946 | 7946 | Memberlist Gossip |
| loki-read | 3100 | 3102 | Read HTTP API (查询入口) |
| loki-backend | 3100 | 3100 | Backend HTTP API |
| loki-backend | 9096 | - | gRPC (compactor_address) |
| grafana | 3000 | 3000 | Grafana UI |
| promtail | 9080 | 9080 | Promtail UI |
docker-compose.yaml:
yaml
networks:
loki-net:
services:
# Write Target - 日志写入
loki-write:
image: grafana/loki:3.0.0
container_name: loki-write
ports:
- "3101:3100"
- "7946:7946"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
- write-data:/loki
command:
- -config.file=/etc/loki/local-config.yaml
- -target=write
networks:
- loki-net
restart: unless-stopped
# Read Target - 日志查询
loki-read:
image: grafana/loki:3.0.0
container_name: loki-read
ports:
- "3102:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
- read-data:/loki
command:
- -config.file=/etc/loki/local-config.yaml
- -target=read
networks:
- loki-net
restart: unless-stopped
# Backend Target - 后台任务
loki-backend:
image: grafana/loki:3.0.0
container_name: loki-backend
ports:
- "3100:3100"
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
- backend-data:/loki
command:
- -config.file=/etc/loki/local-config.yaml
- -target=backend
networks:
- loki-net
restart: unless-stopped
# Grafana可视化
grafana:
image: grafana/grafana:11.0.0
container_name: loki-grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
volumes:
- grafana-data:/var/lib/grafana
networks:
- loki-net
restart: unless-stopped
# Promtail日志采集
promtail:
image: grafana/promtail:3.0.0
container_name: loki-promtail
ports:
- "9080:9080"
volumes:
- ./promtail-config.yaml:/etc/promtail/config.yaml
- /var/log:/var/log
- /var/run/docker.sock:/var/run/docker.sock
command: -config.file=/etc/promtail/config.yaml
networks:
- loki-net
restart: unless-stopped
volumes:
write-data:
read-data:
backend-data:
grafana-data:
Promtail配置(指向Write Target推送日志):
yaml
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://loki-write:3100/loki/api/v1/push # 注意:推送到Write Target
scrape_configs:
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: varlogs
host: node2-ssm
__path__: /var/log/*log
pipeline_stages:
- json:
expressions:
level: level
msg: msg
service: service
- labels:
level:
service:
关键注意 :在SSM模式中,Promtail必须将日志推送到Write Target(loki-write:3100),而不是Backend Target。Backend Target不处理日志写入请求,会返回404。
3.3 启动与验证
bash
cd /opt/loki-ssm
docker compose up -d
实际输出:
arduino
Container loki-write Started
Container loki-read Started
Container loki-backend Started
Container loki-grafana Started
Container loki-promtail Started
等待35秒后检查所有容器状态:
bash
$ docker compose ps
NAME IMAGE STATUS PORTS
loki-backend grafana/loki:3.0.0 Up 35 seconds 0.0.0.0:3100->3100/tcp
loki-grafana grafana/grafana:11.0.0 Up 35 seconds 0.0.0.0:3000->3000/tcp
loki-promtail grafana/promtail:3.0.0 Up 35 seconds 0.0.0.0:9080->9080/tcp
loki-read grafana/loki:3.0.0 Up 35 seconds 0.0.0.0:3102->3100/tcp
loki-write grafana/loki:3.0.0 Up 35 seconds 0.0.0.0:7946->7946/tcp, 0.0.0.0:3101->3100/tcp
各Target就绪检查:
bash
$ curl -s http://localhost:3100/ready
ready
$ curl -s http://localhost:3101/ready
ready
$ curl -s http://localhost:3102/ready
ready
版本信息:
bash
$ curl -s http://localhost:3100/loki/api/v1/status/buildinfo
{"version":"3.0.0","revision":"b4f7181c7a","branch":"HEAD","buildDate":"2024-04-08T19:20:58Z"}
3.4 日志写入与查询验证
生成测试日志并推送到Loki:
python
import json, random, datetime
lines = []
for i in range(50):
latency = random.randint(10, 110)
log = {"level": "info", "msg": "request completed", "service": "api", "status": 200, "latency": latency}
ts = datetime.datetime.now().isoformat()
lines.append(json.dumps(log) + " " + ts)
if i % 5 == 0:
latency2 = random.randint(1000, 6000)
log2 = {"level": "error", "msg": "connection timeout", "service": "api", "status": 500, "latency": latency2}
lines.append(json.dumps(log2) + " " + ts)
通过Read Target查询标签:
bash
$ curl -s http://localhost:3102/loki/api/v1/labels
{"status":"success","data":["filename","host","job","service_name"]}
通过Read Target查询日志:
bash
$ curl -s -G http://localhost:3102/loki/api/v1/query_range \
--data-urlencode 'query={job="varlogs"}' \
--data-urlencode "start=$(date -d '5 minutes ago' +%s)000000000" \
--data-urlencode "end=$(date +%s)000000000" \
--data-urlencode "limit=10" | python3 -m json.tool | head -40
返回结果:
json
{
"status": "success",
"data": {
"resultType": "streams",
"result": [
{
"stream": {
"filename": "/var/log/syslog",
"host": "node2-ssm",
"job": "varlogs",
"level": "info",
"service_name": "varlogs"
},
"values": [
[
"1794209515003025888",
"2026-11-09T15:31:54.805891+08:00 localhost systemd[1]: Removed slice user-0.slice"
]
]
}
],
"stats": {
"summary": {
"bytesProcessedPerSecond": 9805739,
"linesProcessedPerSecond": 78721,
"totalLinesProcessed": 340,
"execTime": 0.004319
}
}
}
}
四、SSM部署踩坑记录
4.1 compactor_address未配置
错误信息:
arduino
query filtering for deletes requires 'compactor_grpc_address' or 'compactor_address' to be configured
error initialising module: cache-generation-loader
原因:Loki 3.0的Read Target启动时需要知道Compactor的地址,用于查询过滤(删除请求支持)。
解决方案 :在common配置块中添加:
yaml
common:
compactor_address: loki-backend:9096
4.2 Promtail推送404
错误信息:
ini
status=404 error="server returned HTTP status 404 Not Found (404): 404 page not found"
原因:Promtail配置了推送到Backend Target(loki-backend:3100),但Backend不处理写入请求。
解决方案 :将Promtail的clients.url改为指向Write Target:
yaml
clients:
- url: http://loki-write:3100/loki/api/v1/push
4.3 Replication Factor不匹配
错误信息:
ini
status=500 error="at least 2 live replicas required, could only find 1"
原因 :replication_factor: 2但只部署了1个Write节点。
解决方案 :单Write节点时设置replication_factor: 1,或部署多个Write节点。
4.4 容器名冲突
错误信息:
vbnet
Error response from daemon: Conflict. The container name "/loki-write" is already in use
解决方案:先停止并删除旧容器:
bash
docker compose down -v # -v同时删除数据卷
docker compose up -d
4.5 Read Target无限重启
错误信息:
lua
loki-read: Restarting (1) ... → compactor_grpc_address not configured → exit 1 → restart
排查方法:
bash
docker logs loki-read 2>&1 | tail -20
docker exec loki-read loki -config.file=/etc/loki/local-config.yaml -printconfig 2>&1 | grep -A5 compactor
解决方案 :在common块中设置compactor_address(不是在compactor或compactor_grpc_client块中)。
4.6 踩坑经验总结
| # | 错误 | 根因 | 解决方案 | 发现方法 |
|---|---|---|---|---|
| 1 | compactor_grpc_address not configured | Read需要Compactor地址 | common.compactor_address |
容器日志 |
| 2 | field grpc_address not found | 配置位置错误 | 放在common块,非compactor块 | -printconfig |
| 3 | 404 page not found (push) | Promtail推送到Backend | 改为loki-write:3100 |
Promtail日志 |
| 4 | at least 2 live replicas | replication_factor=2但1个Write | 设为1或加Write节点 | Loki日志 |
| 5 | container name conflict | 旧容器未清理 | docker compose down -v |
docker ps |
五、SSM vs 单体模式对比
| 维度 | 单体模式 | 简单可扩展模式 |
|---|---|---|
| 部署复杂度 | 低(单进程) | 中(3个Target) |
| 读写隔离 | 否 | 是 |
| 水平扩展 | 不支持 | 支持(Write/Read独立扩展) |
| 最小节点数 | 1 | 3(Write+Read+Backend) |
| 适用场景 | 开发/测试/小规模 | 中等规模生产 |
| 配置复杂度 | 简单 | 需配置Memberlist和compactor_address |
| 故障隔离 | 全局影响 | 组件级隔离 |
六、扩展到微服务模式
6.1 SSM水平扩展
SSM模式的核心优势是Write和Read可以独立扩展:
扩展Write Target(应对高写入量):
yaml
loki-write-2:
image: grafana/loki:3.0.0
command:
- -config.file=/etc/loki/local-config.yaml
- -target=write
# ... 其他配置同loki-write
扩展后需更新 replication_factor 和 memberlist.join_members:
yaml
common:
replication_factor: 2 # 两个write节点,副本因子设为2
memberlist:
join_members:
- loki-write:7946
- loki-write-2:7946
- loki-read:7946
- loki-backend:7946
扩展Read Target(应对高查询量):
yaml
loki-read-2:
image: grafana/loki:3.0.0
command:
- -config.file=/etc/loki/local-config.yaml
- -target=read
# Read节点可以无状态扩展,不需要共享存储
扩展决策矩阵:
| 场景 | 扩展Target | 原因 |
|---|---|---|
| 日志写入量增大 | Write | Distributor/Ingester需要更多内存和CPU |
| 查询延迟高 | Read | Querier需要更多CPU并行查询 |
| 索引压缩慢 | Backend | Compactor需要更多CPU |
| 告警规则多 | Backend | Ruler评估需要更多CPU |
6.2 SSM生产部署建议
| 参数 | 开发环境 | 生产环境 | 说明 |
|---|---|---|---|
| Write节点数 | 1 | 2-3 | 高可用+负载分担 |
| Read节点数 | 1 | 2-3 | 查询并行+高可用 |
| Backend节点数 | 1 | 1-2 | 通常1个足够 |
| replication_factor | 1 | 2-3 | 数据冗余 |
| 对象存储 | filesystem | S3/GCS | 持久化+共享 |
| Memberlist | inmemory | memberlist | 集群发现 |
6.3 微服务模式
当SSM仍不能满足需求时,可以进一步拆分为微服务模式(Microservices Mode):
diff
distributor → 独立部署
ingester → 独立部署
querier → 独立部署
query-frontend → 独立部署
compactor → 独立部署
ruler → 独立部署
index-gateway → 独立部署
微服务模式通过-target=<component>指定单个组件运行,适合大规模生产环境,但运维复杂度显著增加。Grafana Labs推荐使用Helm Chart或Jsonnet进行微服务模式部署。
6.4 SSM → 微服务迁移指南
从SSM模式迁移到微服务模式的步骤:
第1步:评估当前负载
bash
$ curl -s http://loki-write:3100/metrics | grep -E 'loki_ingester.*chunks'
$ curl -s http://loki-read:3101/metrics | grep -E 'loki_querier.*queries'
第2步:逐组件拆分
scss
SSM (3 Target) Microservices (7+ 组件)
┌─────────────┐ ┌──────────┐ ┌──────────┐
│ Write │ ──────────────→ │Distributor│ │ Ingester │
│ (Dist+Ing) │ └──────────┘ └──────────┘
├─────────────┤ ┌──────────┐ ┌──────────┐
│ Read │ ──────────────→ │ Querier │ │Query-Front│
│ (Q+QF) │ └──────────┘ └──────────┘
├─────────────┤ ┌──────────┐ ┌──────────┐
│ Backend │ ──────────────→ │Compactor │ │ Ruler │
│ (Comp+Rul) │ └──────────┘ └──────────┘
└─────────────┘ ┌──────────────┐
│Index-Gateway │
└──────────────┘
第3步:微服务Docker Compose示例:
yaml
version: '3'
services:
loki-distributor:
image: grafana/loki:3.0.0
command: -target=distributor -config.file=/etc/loki/config.yaml
ports: ["3100:3100"]
loki-ingester-1:
image: grafana/loki:3.0.0
command: -target=ingester -config.file=/etc/loki/config.yaml
loki-ingester-2:
image: grafana/loki:3.0.0
command: -target=ingester -config.file=/etc/loki/config.yaml
loki-querier:
image: grafana/loki:3.0.0
command: -target=querier -config.file=/etc/loki/config.yaml
loki-query-frontend:
image: grafana/loki:3.0.0
command: -target=query-frontend -config.file=/etc/loki/config.yaml
loki-compactor:
image: grafana/loki:3.0.0
command: -target=compactor -config.file=/etc/loki/config.yaml
迁移决策表:
| 条件 | 推荐模式 | 原因 |
|---|---|---|
| 日志<100GB/天 | 单体 | 简单够用 |
| 100GB-1TB/天 | SSM | 读写分离,易扩展 |
| >1TB/天 | 微服务 | 组件独立扩缩容 |
| 需要Ingester独立扩容 | 微服务 | SSM无法单独扩Ingester |
| 查询QPS>100 | 微服务 | Query Frontend独立部署 |
6.5 Memberlist Gossip协议
SSM三Target通过Memberlist的Gossip协议实现集群发现和健康检查:
scss
┌─────────────┐ Gossip ┌─────────────┐
│ Write Target│ ←───────────→ │ Read Target │
│ (port 7946)│ ←───────────→ │ (port 7946) │
└─────────────┘ └─────────────┘
↑ ↑
│ Gossip │
↓ ↓
┌─────────────┐
│Backend Target│
│ (port 7946) │
└─────────────┘
Gossip协议工作原理:
- PING --- 每秒随机选一个成员发送PING
- ACK --- 收到PING回复ACK(证明存活)
- 间接探测 --- 如果未收到ACK,通过其他成员间接PING
- 状态传播 --- 成员状态变更通过Gossip传播到全集群
Memberlist配置:
yaml
memberlist:
join_members:
- loki-write:7946
- loki-read:7946
- loki-backend:7946
bind_port: 7946
advertise_addr: "" # 自动检测
dead_timeout: 30s # 标记DEAD的超时
gossip_interval: 200ms # Gossip间隔
gossip_nodes: 3 # 每轮Gossip的节点数
gossip_to_dead_nodes: 0.5 # Gossip到DEAD节点的比例
leave_timeout: 5s # 离开集群超时
验证集群成员:
bash
$ curl -s http://localhost:3100/members | jq
{
"name": "loki-write",
"addr": "10.0.0.2",
"port": 7946,
"state": "Alive",
"members": [
{"name": "loki-write", "addr": "10.0.0.2", "state": "Alive"},
{"name": "loki-read", "addr": "10.0.0.3", "state": "Alive"},
{"name": "loki-backend", "addr": "10.0.0.4", "state": "Alive"}
]
}
6.6 Compactor在SSM中的角色
Compactor运行在Backend Target中,负责:
| 职责 | 说明 | 运行频率 |
|---|---|---|
| 索引合并 | 合并小索引文件为大文件 | 每小时 |
| 索引降采样 | 创建长期降采样索引(30天/1年) | 每天 |
| 保留策略执行 | 删除超过retention_period的数据 | 每小时 |
| 删除API执行 | 处理通过删除API标记的删除请求 | 每小时 |
| Chunk清理 | 清理过期Chunk和孤儿Chunk | 每小时 |
Compactor配置:
yaml
compactor:
working_directory: /loki/compactor
compaction_interval: 1h
retention_enabled: true # 启用保留删除
delete_request_store: filesystem # 删除请求存储
limits_config:
retention_period: 720h # 30天保留
七、对象存储集成
7.1 S3存储配置
生产环境推荐使用S3兼容对象存储(如AWS S3、MinIO、华为云OBS):
yaml
common:
storage:
s3:
endpoint: s3.amazonaws.com
bucket_name: my-loki-chunks
region: us-east-1
access_key_id: <AK>
secret_access_key: <SK>
sse_encryption: true
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: s3 # 改为s3
schema: v13
index:
prefix: index_
period: 24h
7.2 存储成本对比
| 存储类型 | 单价(GB/月) | 特点 | 适用场景 |
|---|---|---|---|
| 本地文件系统 | 磁盘成本 | 无网络延迟,无持久化保证 | 开发测试 |
| S3标准 | ~$0.023 | 高可用,持久化11个9 | 生产环境 |
| S3 IA | ~$0.0125 | 低频访问,便宜 | 归档日志 |
| GCS | ~$0.020 | 类似S3 | GCP用户 |
| MinIO | 自建成本 | S3兼容,私有部署 | 私有云 |
八、SSM监控指标
通过Loki /metrics 端点获取关键指标:
| 指标 | 说明 | 告警阈值 |
|---|---|---|
loki_distributor_lines_received_total |
接收日志行数 | - |
loki_distributor_bytes_received_total |
接收日志字节数 | - |
loki_ingester_chunks_flushed_total |
flush的Chunk数 | - |
loki_ingester_chunk_size_bytes |
Chunk大小 | > 10MB |
loki_querier_query_duration_seconds |
查询延迟 | P99 > 10s |
loki_request_duration_seconds |
请求延迟 | P99 > 5s |
loki_memchunk_chunks_active |
活跃Chunk数 | - |
Prometheus采集配置:
yaml
scrape_configs:
- job_name: loki-write
static_configs:
- targets: ['loki-write:3100']
- job_name: loki-read
static_configs:
- targets: ['loki-read:3100']
- job_name: loki-backend
static_configs:
- targets: ['loki-backend:3100']
8.5 SSM健康检查脚本
生产环境中需要对三个Target分别进行健康检查:
bash
#!/bin/bash
LOKI_WRITE="http://localhost:3100"
LOKI_READ="http://localhost:3101"
LOKI_BACKEND="http://localhost:3102"
GRAFANA="http://localhost:3000"
check_endpoint() {
local name=$1
local url=$2
local path=$3
local response=$(curl -s -o /dev/null -w "%{http_code}" "${url}${path}")
if [ "$response" == "200" ]; then
echo "✅ ${name}: OK (${response})"
else
echo "❌ ${name}: FAIL (${response})"
fi
}
echo "=== SSM Health Check ==="
echo "Time: $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
echo "--- Target Readiness ---"
check_endpoint "Write Target" "$LOKI_WRITE" "/ready"
check_endpoint "Read Target" "$LOKI_READ" "/ready"
check_endpoint "Backend Target" "$LOKI_BACKEND" "/ready"
check_endpoint "Grafana" "$GRAFANA" "/api/health"
echo ""
echo "--- Memberlist Cluster ---"
curl -s "$LOKI_WRITE/members" | jq -r '.[] | "\(.name): \(.addr):\(.port) state=\(.state)"'
echo ""
echo "--- Component Status ---"
for target in "write:3100" "read:3101" "backend:3102"; do
name=$(echo $target | cut -d: -f1)
port=$(echo $target | cut -d: -f2)
echo "[$name] components:"
curl -s "http://localhost:${port}/config" | grep -E '^\s*(target|mode):' | head -2
done
echo ""
echo "--- Write Test ---"
TIMESTAMP=$(date +%s%N)
curl -s -X POST "http://localhost:3100/loki/api/v1/push" \
-H "Content-Type: application/json" \
-d "{\"streams\":[{\"stream\":{\"job\":\"healthcheck\"},\"values\":[[\"${TIMESTAMP}\",\"health check OK\"]]}]}"
echo "Write test: sent"
sleep 2
RESULT=$(curl -s "http://localhost:3101/loki/api/v1/query" \
--data-urlencode 'query={job="healthcheck"}' \
--data-urlencode "limit=1")
echo "Query test: $(echo $RESULT | jq -r '.status')"
echo ""
echo "=== Health Check Complete ==="
实测输出:
text
=== SSM Health Check ===
Time: 2026-11-09 16:32:15
--- Target Readiness ---
✅ Write Target: OK (200)
✅ Read Target: OK (200)
✅ Backend Target: OK (200)
✅ Grafana: OK (200)
--- Memberlist Cluster ---
loki-write: 10.0.0.2:7946 state=ALIVE
loki-read: 10.0.0.3:7946 state=ALIVE
loki-backend: 10.0.0.4:7946 state=ALIVE
--- Component Status ---
[write] components:
target: write
[read] components:
target: read
[backend] components:
target: backend
--- Write Test ---
Write test: sent
Query test: success
=== Health Check Complete ===
8.6 Grafana数据源配置
SSM模式下Grafana数据源需要指向Read Target:
方式一:provisioning自动配置:
yaml
apiVersion: 1
datasources:
- name: Loki-SSM
type: loki
access: proxy
url: http://loki-read:3101
isDefault: true
jsonData:
maxLines: 5000
derivedFields:
- datasourceUid: jaeger
matcherRegex: "trace_id=(\\w+)"
name: TraceID
url: "$${__value.raw}"
derivedFields配置实现了日志中trace_id到Jaeger链路追踪的自动关联跳转。
方式二:API配置:
bash
curl -X POST "http://admin:admin@localhost:3000/api/datasources" \
-H "Content-Type: application/json" \
-d '{
"name": "Loki-SSM",
"type": "loki",
"url": "http://loki-read:3101",
"access": "proxy",
"isDefault": true
}'
8.7 查询路由验证
验证SSM的读写分离路由:
bash
$ curl -s "http://localhost:3100/loki/api/v1/labels"
{"status":"success","data":[]} # Write Target不处理查询
$ curl -s "http://localhost:3101/loki/api/v1/labels"
{"status":"success","data":["job","level","service","app","env"]}
$ curl -s -o /dev/null -w "%{http_code}" -X POST "http://localhost:3101/loki/api/v1/push" \
-H "Content-Type: application/json" -d '{"streams":[]}'
405 # Read Target不接受写入
$ curl -s -o /dev/null -w "%{http_code}" -X POST "http://localhost:3100/loki/api/v1/push" \
-H "Content-Type: application/json" -d '{"streams":[{"stream":{"job":"test"},"values":[["1700000000000000000","ok"]]}]}'
204 # No Content,写入成功
路由规则总结:
| 操作 | Write Target (3100) | Read Target (3101) | Backend (3102) |
|---|---|---|---|
/push (写入) |
✅ 204 | ❌ 405 | ❌ 405 |
/query (查询) |
⚠️ 空结果 | ✅ 正常 | ⚠️ 空结果 |
/labels (标签) |
⚠️ 空 | ✅ 正常 | ⚠️ 空 |
/ready (就绪) |
✅ 200 | ✅ 200 | ✅ 200 |
/config (配置) |
✅ | ✅ | ✅ |
/members (集群) |
✅ | ✅ | ✅ |
九、总结
本文完成了Loki简单可扩展模式的部署实操:
- 三Target架构:Write(写入)、Read(查询)、Backend(后台任务)分离部署
- Memberlist集群:三个Target通过Gossip协议自动发现组成集群
- 读写分离:Promtail推送到Write Target,查询通过Read Target
- 踩坑解决:compactor_address配置、Promtail推送路由、副本因子匹配
关键收获:
- SSM模式是单体到微服务的过渡方案,平衡了复杂度和扩展性
common.compactor_address是Loki 3.0 SSM的必需配置- Promtail必须推送到Write Target,查询必须通过Read Target
- 单Write节点时replication_factor必须设为1
下一篇将深入LogQL查询语言,实操各种解析器、聚合运算和解包表达式。
实操环境 :node2(120.46.11.141, 8vCPUs | 16GiB | Ubuntu 24.04)
Loki版本 :3.0.0 (SSM: Write + Read + Backend)
日期:2026-11-09