Loki日志栈入门与实践(二):简单可扩展模式部署与组件解析

系列导航 :(一)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

关键配置解读:

  1. common.compactor_address:指定Compactor的gRPC地址,Read Target需要此配置来支持删除查询过滤。这是Loki 3.0 SSM模式的必需配置。

  2. memberlist.join_members:列出所有成员的地址和端口,用于集群自动发现。

  3. replication_factor: 1:单Write节点时设为1。生产环境建议≥2并部署多个Write节点。

  4. 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协议工作原理:

  1. PING --- 每秒随机选一个成员发送PING
  2. ACK --- 收到PING回复ACK(证明存活)
  3. 间接探测 --- 如果未收到ACK,通过其他成员间接PING
  4. 状态传播 --- 成员状态变更通过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简单可扩展模式的部署实操:

  1. 三Target架构:Write(写入)、Read(查询)、Backend(后台任务)分离部署
  2. Memberlist集群:三个Target通过Gossip协议自动发现组成集群
  3. 读写分离:Promtail推送到Write Target,查询通过Read Target
  4. 踩坑解决: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