1、Loki架构
- Loki的逻辑分组,read path、write path、backend
1.1、read path(读取路径)
- 包括 Query Frontend、Querier组件
1、Query Frontend(查询前端)
- 作为 Read 路径的 “入口”,负责接收用户的查询请求(如 LogQL 语句),并进行前置处理,包括
- 解析和校验查询语法,过滤无效请求;
- 对复杂查询进行拆分(例如按时间范围分片),生成可并行执行的子查询;
- 将子查询分发到多个 Querier 实例,实现负载均衡和并行处理。
- 对查询结果缓存(缓存热门结果,减少重复计算);
2、Querier(查询器)
- 负责实际执行查询逻辑,接收 Query Frontend 分发的子查询,从两个数据源获取数据,分别是近期数据、历史数据
- 近期数据:直接从 Ingester 的内存中读取(未持久化到 Backend 的新日志);
- 历史数据:从 Backend(如对象存储)读取已持久化的日志块
- 对获取的日志数据执行过滤、匹配等 LogQL 逻辑,处理后将结果返回给 Query Frontend;
- 支持水平扩展(增加实例数)以提升查询吞吐量。
3、Read 路径的完整流程
- 用户查询(如 Grafana 发起)→ Query Frontend(接收→校验→拆分→分发)→ 多个 Querier(并行执行子查询)→ 从 Ingester(近期数据)和 Backend(历史数据)读取数据 → Querier 处理结果 → 返回给 Query Frontend(聚合结果→缓存→返回用户)。
1.2、 write path(写入路径)
- 包括 Distributor、Ingester组件
1、 Distributor(分配者)
- Distributor 是写入流程的第一个组件, Promtail采集日志后,会将日志发送到 Distributor。Distributor 的核心作用是接收请求并完成初步处理,包括验证、分片、路由
- 验证日志的合法性(如租户信息、格式);
- 根据日志的标签集(labels)计算哈希值,与ingester数量取模,模相同则日志被路由到相同的 Ingester 实例
- 若 Ingester 集群有节点故障,Distributor 会自动将日志路由到健康节点,保证写入可用性。
2、 Ingester(摄入器)
- Ingester接收 Distributor 转发的日志后,承担数据暂存、压缩和持久化触发的关键职责:
- 将 日志流 暂存于内存(形成 “活跃块”),并实时进行压缩(减少存储成本)
- 当内存中的数据达到阈值(如时间窗口到期、块大小达标)时,Ingester 会将 “活跃块” 转换为 “只读块”,并异步写入 Backend(如 S3、GCS 等持久化存储);。
3、 总结:Write 链路的完整流程
- 客户端 → Distributor(接收与路由) → Ingester(暂存、压缩、写入 Backend) → Backend(最终持久化)。
1.3、backend
1、Index Gateway(索引网关)
- 作用:避免 Query 组件直接访问后端索引存储(减少对存储的压力)。统一管理索引访问,提升查询效率。
- Query Frontend 组件查询 Index Gateway 获取查询的日志量,以便决定如何对查询进行分片。
- Querier 组件查询 Index Gateway 以便知道要获取和查询哪些块
2、Compactor(压缩器)
- Compactor 会定期从对象存储下载文件,将它们合并为一个文件,上传新创建的索引,并清理旧文件。
- Compactor 还负责日志保留和日志删除,并指定相应的策略
3、Query Scheduler(查询调度器)
- Query Frontend 主要负责查询的解析、拆分(将大查询拆分为可并行的子查询)和结果聚合,而 Query Scheduler 则专注于子查询任务的队列管理和分发。当启用 Query Scheduler 后,Frontend 不再直接将子查询分配给 Querier,而是将任务推给 Scheduler 的内存队列,由 Scheduler 统一调度。
- 每个租户独立队列的设计,避免了 “大租户查询挤占小租户资源” 的问题。例如,一个产生大量日志的租户发起复杂查询时,不会占用所有 Querier 进程,其他租户的查询仍能按优先级或顺序得到处理,确保多租户环境下的资源公平性。
- 没有 Scheduler 时,Querier 通常被动接收 Frontend 的任务;启用 Scheduler 后,Querier 作为 “工作节点” 主动从 Scheduler 拉取任务
- 适合大规模、多租户、查询量高的场景,能显著提升查询稳定性
4、Ruler(规则器)
- Ruler 负责执行预定义的 LogQL 告警规则(基于日志内容生成告警)。
- 它会定期查询 Loki 中的日志数据(包括 近期数据 和 历史数据),当满足告警条件时触发通知。
2、loki采集日志的流程
- Loki 采集日志并展示的工作流程可分为 “日志采集→处理转发→存储→查询展示” 四个核心环节,配合 Grafana 实现完整的日志可视化链路。以下是详细流程
2.1 日志采集(由 Promtail 或 Grafana Alloy 完成)
- 发现日志:通过配置 promtail-config.yaml 文件,scrape_configs 字段下的 _ _ path _ _来指定日志采集路径
- 添加标签:为日志添加元数据标签,用于后续快速筛选
- 处理日志:对原始日志进行简单清洗(如过滤空行、截断过长日志),并按 “时间戳 + 日志内容” 格式封装。
2.2、 日志转发(发送到 Loki)
- 采集工具(Promtail/Alloy)将处理后的日志发送到 Loki 的 Distributor 组件(Loki 入口)。默认使用 HTTP/JSON 协议,支持压缩传输以减少网络带宽消耗。
2.3、 日志存储(Loki 内部处理)
- Loki 接收日志后,Distributor 验证日志格式和标签合法性,按 “一致性哈希” 将日志分片转发到多个 Ingester 节点。
- Ingester 将日志按 “时间窗口” 和 “标签” 聚合为 “块(Chunk)”(默认每 2 小时一个块)。并对块进行压缩(使用 ZSTD 算法,压缩率通常达 10:1 以上)。先写入本地 WAL(Write-Ahead Log)确保不丢失,再异步刷写到后端存储。
- 存储的内容有元数据和日志块,元数据:存储标签索引、块位置信息(依赖 Etcd/Consul 等 KV 存储)。日志块:存储压缩后的原始日志(依赖对象存储如 S3/MinIO/ 本地文件)。
- Compactor:后台定期合并小日志块、删除过期日志(按配置的 retention_period),优化存储效率。
2.4、 日志查询与展示(Grafana + Loki)
- 用户在 Grafana 中选择 Loki 数据源,编写 LogQL 查询语句来获取日志(先选标签,再输入要查找的内容)
- Grafana 将查询请求发送到 Loki 的 Querier 组件。
- Querier 根据标签索引定位相关日志块,从后端存储加载并解压。对日志内容执行过滤、排序等操作,返回结果给 Grafana。
promitail-config.yaml 示例
server:
http_listen_port: 9080 # Promtail 自身 HTTP 监控端口
grpc_listen_port: 0 # 关闭 gRPC 端口(默认不启用)
positions:
filename: /var/lib/promtail/positions.yaml # 记录日志读取位置(避免重启后重复采集)
sync_period: 10s # 位置信息同步到文件的间隔
clients:
- url: http://loki-distributor:3100/loki/api/v1/push # 发送日志到 Loki 的 Distributor 地址
batchsize: 102400 # 单批日志大小上限(字节),默认 100KB
batchwait: 1s # 批处理等待时间,超时后即使未达 batchsize 也发送
timeout: 10s # 发送请求超时时间
backoff_config: # 失败重试策略
min_period: 1s # 初始重试间隔
max_period: 5s # 最大重试间隔
max_retries: 10 # 最大重试次数
scrape_configs:
# 采集系统日志(如 /var/log/messages)
- job_name: system-logs
static_configs:
- targets:
- localhost # 目标节点(本地)
labels:
job: system # 固定标签:任务名
env: prod # 固定标签:环境
__path__: /var/log/*.log # 日志文件路径(支持通配符)
# 采集 K8s Pod 日志(需部署在 K8s 节点)
- job_name: kubernetes-pods
kubernetes_sd_configs: # 自动发现 K8s 资源
- role: pod # 发现 Pod 资源
relabel_configs: # 动态标签处理(从 Pod 元数据提取标签)
# 保留 Pod 名称作为标签
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: pod
# 保留 Namespace 作为标签
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: namespace
# 保留容器名作为标签
- source_labels: [__meta_kubernetes_pod_container_name]
action: replace
target_label: container
# 构建日志路径(K8s 容器日志默认路径)
- replacement: /var/log/pods/*$1/*.log
separator: /
source_labels: [__meta_kubernetes_pod_uid, __meta_kubernetes_pod_container_name]
regex: ^(.+)$
target_label: __path__
k8s中自动暴露的元数据标签
- 标签前缀为 _ _ meta_kubernetes _ _
- k8s容器日志的标准化存储路径:/var/log/pods/< namespace >< pod-name >< pod-uid >/< container-name >/< 序号 >.log
# Pod 相关
__meta_kubernetes_pod_name # Pod 名称
__meta_kubernetes_pod_namespace # Pod 所在命名空间
__meta_kubernetes_pod_uid # Pod 唯一 ID
__meta_kubernetes_pod_ip # Pod IP 地址
__meta_kubernetes_pod_label_<key> # Pod 自定义标签的键值(如 Pod 有 app=nginx,则自动暴露 __meta_kubernetes_pod_label_app=nginx)
__meta_kubernetes_pod_annotation_<key> # Pod 注解的键值(如注解 promtail.io/scrape=true,则暴露 __meta_kubernetes_pod_annotation_promtail_io_scrape=true)
# 工作负载相关(Deployment/StatefulSet 等)
__meta_kubernetes_pod_owner_kind # Pod 所属控制器类型
__meta_kubernetes_pod_owner_name # Pod 所属控制器名称
# Service/Endpoint 相关
__meta_kubernetes_service_name # Service 名称
__meta_kubernetes_service_namespace # Service 所在命名空间
__meta_kubernetes_endpoint_port_name # Endpoint 的端口名称
__meta_kubernetes_endpoint_port_protocol # Endpoint 端口协议(TCP/UDP)
# 节点相关
__meta_kubernetes_node_name # 节点名称
__meta_kubernetes_node_label_<key> # 节点自定义标签的键值(如节点标签 env=prod,则暴露 __meta_kubernetes_node_label_env=prod)
loki配置文件示例
全局设置
# 全局开关
auth_enabled: false # 是否启用认证(影响所有组件的访问控制)
server:
http_listen_port: 3100 # HTTP 监听端口
grpc_listen_port: 9095 # GRPC 监听端口
log_level: info # 服务日志级别
http_server_read_timeout: 5m # HTTP 读超时
http_server_write_timeout: 5m # HTTP 写超时
# 全局限制配置(所有组件共享的资源和行为限制)
limits_config:
retention_period: 72h # 日志全局保留时间
ingestion_rate_mb: 10 # 单租户每秒最大摄入速率
ingestion_burst_size_mb: 20 # 摄入突发上限
max_query_length: 72h # 最大查询时间范围
max_entries_limit_per_query: 5000 # 单查询返回条目上限
query_timeout: 2m # 查询超时时间
max_label_name_length: 1024 # 标签名最大长度
max_label_value_length: 2048 # 标签值最大长度
# 存储 schema 配置(定义日志存储格式和周期,全局生效)
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
# 存储后端配置(全局存储路径和类型,所有组件依赖)
storage_config:
boltdb_shipper:
active_index_directory: /loki/index
cache_location: /loki/cache
filesystem:
directory: /loki/chunks
distributor 组件(日志分发)
distributor:
ring: # 分布式环配置(用于分片和路由)
kvstore:
store: inmemory # 环存储方式(inmemory/consul/etcd)
replication_factor: 3 # 日志副本数
remote_timeout: 5s # 与 ingester 通信超时
ingester 组件(日志摄入与暂存)
ingester:
lifecycler: # 生命周期管理(与分布式环交互)
ring:
kvstore:
store: inmemory
replication_factor: 3
final_sleep: 0s # 关闭前最终等待时间
chunk_idle_period: 5m # 日志块空闲超时后写入存储
max_chunk_age: 1h # 日志块最大存活时间
chunk_target_size: 1.5MB # 日志块目标大小
wal: # 预写日志配置(防止数据丢失)
directory: /loki/wal
enabled: true
querier 组件(日志查询)
querier:
query_ingesters_within: 12h # 查询 ingester 中未持久化数据的时间范围
engine:
timeout: 1m # 查询引擎超时
max_concurrent: 20 # 最大并发查询数
query_range 组件(范围查询优化)
query_range:
split_queries_by_interval: 15m # 按时间间隔拆分查询
cache_results: true # 是否缓存查询结果
results_cache:
cache:
type: inmemory # 结果缓存类型(inmemory/redis)
max_freshness: 10s # 缓存最大新鲜度
ruler 组件(告警规则)
ruler:
storage: # 告警规则存储
type: local
local:
directory: /loki/rules
rule_path: /tmp/loki/rules-temp # 规则临时目录
alertmanager_url: http://alertmanager:9093 # Alertmanager 地址
evaluation_interval: 1m # 规则评估间隔
compactor 组件(日志块压缩)
compactor:
working_directory: /loki/compactor # 工作目录
retention_enabled: true # 是否启用基于保留期的清理
compaction_interval: 10m # 压缩间隔
max_compaction_objects: 1000000 # 单次压缩最大对象数
table_manager 组件(索引表管理,旧版本用)
table_manager:
retention_deletes_enabled: true # 启用索引删除
retention_period: 72h # 索引保留时间(与 limits_config 配合)
memberlist
- 用于 分布式集群成员管理 的核心配置,主要用于组件(如 Ingester、Distributor 等)之间的节点发现、状态同步和集群健康检查
memberlist:
join_members: ["read", "write", "backend"] # 初始加入的集群成员地址列表(可填域名或IP:端口)
dead_node_reclaim_time: 30s # 标记为"死亡"的节点被彻底移除的时间(回收资源)
gossip_to_dead_nodes_time: 15s # 对已标记为"死亡"的节点继续发送gossip消息的时间(确保状态同步)
left_ingesters_timeout: 30s # Ingester节点主动离开后,保留其状态的超时时间(避免数据丢失)
bind_addr: ['0.0.0.0'] # 本地绑定的IP地址(通常为0.0.0.0表示监听所有网卡)
bind_port: 7946 # 用于memberlist通信的端口(gossip协议端口)
gossip_interval: 2s # 节点间发送gossip消息的间隔(控制同步频率,影响集群一致性和网络开销)
common
- 用于定义全局共享参数的配置块,主要作用是将多个组件(如 Ingester、Distributor、Compactor 等)共用的基础配置集中管理,避免重复定义
- 通常出现在分布式部署场景中
common:
path_prefix: /loki # 所有组件的基础路径前缀(用于存储数据、缓存等)
replication_factor: 1 # 全局默认的副本数(日志数据或元数据的冗余副本数量)
compactor_address: http://backend:3100 # Compactor 组件的访问地址(供其他组件调用)
storage: # 全局存储后端配置(所有组件共享的存储参数)
s3: # 使用 S3 兼容存储(如 MinIO)作为对象存储
endpoint: minio:9000 # S3 服务地址(这里是 MinIO 地址)
insecure: true # 是否允许非 HTTPS 连接(开发环境常用)
bucketnames: loki-data # 存储日志数据的桶名称
access_key_id: loki # 访问密钥 ID
secret_access_key: supersecret # 访问密钥
s3forcepathstyle: true # 强制使用路径风格访问(兼容 MinIO 等服务)
ring: # 全局分布式环配置(集群成员管理的基础参数)
kvstore:
store: memberlist # 分布式环的存储方式(这里使用 memberlist 协议)
Loki生产环境/大规模场景部署方式建议
- distributor 和 ingester 通常需要多实例部署(通过负载均衡器Service 或 LoadBalancer 暴露入口),实现负载均衡。
- compactor 可以单实例部署(但建议主从备份,避免因 compactor 故障导致日志块无法合并),通常与其他组件分开部署(因其计算和 I/O 负载较低)。
- 所有组件可部署在独立的机器或容器(如 Kubernetes Pod)中,通过网络通信(需配置相同的 ring 信息,确保组件间能发现彼此)。
Loki的查询语言:LogQL
- LogQL 是 Loki 专用的日志查询语言,语法类似 PromQL,核心能力是,通过标签过滤日志 + 对日志内容进行模式匹配
1. 日志流查询
- 通过标签筛选出目标日志流,是所有查询的基础,语法与 Prometheus 指标选择器完全一致:
# 筛选出 "prod" 环境下 "backend" 应用的日志
{env="prod", app="backend"}
2. 日志内容过滤
- 在日志流的基础上,对日志原文进行模糊匹配或正则匹配,支持以下运算符:
- |=:包含指定字符串(如 |= "error" 筛选含"error"的日志)
- !=:不包含指定字符串(如 != "debug" 排除含"debug"的日志)
- !~:不匹配正则表达式
# 筛选 prod 环境 backend 应用的 ERROR 日志
{env="prod", app="backend"} |= "ERROR"
# 筛选 prod 环境 backend 应用中,含 "timeout" 且不含 "test" 的日志
{env="prod", app="backend"} |= "timeout" != "test"
3. 聚合与统计
- 支持对日志流进行聚合计算(如统计日志条数、按标签分组),常用函数:
- count_over_time():统计指定时间窗口内的日志条数
- sum(count_over_time()) by (pod):按 pod 标签分组,统计每个 Pod 的日志总条数
- topk(5, count_over_time()):取日志量前 5 的标签组
# 统计 5 分钟内 backend 应用的日志量
count_over_time({app="backend"}[5m])
# 统计 prod 环境下,每个 backend Pod 过去 10 分钟的 ERROR 日志数,并取前 3
topk(3, sum(count_over_time({env="prod", app="backend"} |= "ERROR" [10m])) by (pod))