轻量级日志系统-Loki

日志系统

常见日志系统:

  • GLA

  • ELK

  • 数仓

日志处理流程:采集 ==> 存储 ==> 检索 ==> 可视化

Loki

介绍

Loki 是一个可水平扩展、高可用性、多租户日志聚合系统,其灵感来自 Prometheus。Loki 与 Prometheus 的不同之处在于,它专注于日志而不是指标,并通过推送而不是拉取来收集日志。

Loki 的设计非常经济高效,并且具有高度可扩展性。与其他日志系统不同,Loki 不会对日志内容进行索引,而只会对日志的元数据进行索引,将其作为每个日志流的一组标签。

日志流是一组共享相同标签的日志。标签帮助Loki在您的数据存储中找到日志流,因此拥有一组高质量的标签是高效查询执行的关键。

然后,日志数据被压缩并以块的形式存储在对象存储中,如亚马逊简单存储服务(S3)或谷歌云存储(GCS),甚至为了开发或概念验证,存储在文件系统上。小索引和高度压缩的块简化了操作,显著降低了Loki的成本。

日志结构

  • Loki数据存储格式
    • index:索引;存储Loki标签,如日志级别、来源、分组
    • chunk:块;存储日志条目本身

image-20250111160127963

架构

image-20250111160220319

Agent:

  • 代理或客户端,例如Grafana Alloy或Promtail,随Loki一起分发。代理抓取日志,通过添加标签将日志转换为流,并通过HTTP API将流推送到Loki

Loki主服务器:

  • 负责摄取和存储日志以及处理查询。它可以部署在三种不同的配置中,有关更多信息,请参阅部署模式。

Grafana:

  • 用于查询和显示日志数据。您还可以使用LogCLI或直接使用Loki API从命令行查询日志。

快速入门

image-20250111160316610

用 Docker Compose 部署以上服务,快速体验Loki生态:

  • flog: 生成日志行。flog是常见日志格式的日志生成器。
  • Grafana Alloy: 它从flog上抓取日志线,并通过网关将它们推送到Loki。
  • 网关(nginx): 接收请求并根据请求的URL将它们重定向到适当的容器。
  • Loki read组件: 它运行一个查询前端和一个查询器。
  • Loki write组件: 它运行一个分发器和一个Ingester。
  • Loki 后端组件: 它运行索引网关、压缩器、标尺、Bloom压缩器(实验)和Bloom网关(实验)。
  • Minio: Loki用来存储其索引和块。

流程

image-20250111160406475

写流程

  1. distributor 接收带有流和日志行的HTTP POST请求。
  2. distributor 会 hash 计算请求中包含的每个流,决定发给 一致性 hash 环 中的哪个 ingester
  3. distributor 把每个流 发给合适处理它的 ingester 和其副本
  4. ingester 接收带有日志行的流,并为流的数据创建一个块或附加到现有块。每个租户和每个标签集,块都是唯一的
  5. ingester 回复写操作结果
  6. distributor 等待大多数 ingester 确认写入完成。
  7. distributor 在收到至少法定数量的确认写入时响应成功(2xx状态码)。或者在写入操作失败时响应错误(4xx或5xx状态码)。

读流程

  1. 查询前端(query frontend) 接受到 携带 LogQL 的 HTTP GET 请求
  2. 查询前端 将查询拆分为子查询并将它们传递给查询调度程序(query scheduler)。
  3. querier (查询器)从调度程序(scheduler)中提取子查询。
  4. querier 将查询传递给 所有保存数据的 ingester。
  5. ingester 返回与查询匹配的 记忆数据(如果有)。
  6. 如果 ingester 没有返回或返回的数据不足,querier 会延迟从后备存储加载数据并对其运行查询。
  7. querier 遍历所有接收到的数据并进行重复数据删除,将子查询的结果返回到查询前端。
  8. 查询前端 等待 查询的所有子查询完成 并由 querier 返回。
  9. 查询前端将两个结果合并为最终结果并将其返回给客户端。

Alloy

架构

image-20250111160617804

Grafana Alloy是一个多功能的可观测性收集器,可以摄取各种格式的日志并将其发送到Loki。我们推荐Alloy作为向Loki发送日志的主要方法,因为它为构建高度可扩展和可靠的可观测性流水线提供了更强大和特征丰富的解决方案。

组件

TypeComponent
Collectorloki.source.api
Collectorloki.source.awsfirehose
Collectorloki.source.azure_event_hubs
Collectorloki.source.cloudflare
Collectorloki.source.docker
Collectorloki.source.file
Collectorloki.source.gcplog
Collectorloki.source.gelf
Collectorloki.source.heroku
Collectorloki.source.journal
Collectorloki.source.kafka
Collectorloki.source.kubernetes
Collectorloki.source.kubernetes_events
Collectorloki.source.podlogs
Collectorloki.source.syslog
Collectorloki.source.windowsevent
Collectorotelcol.receiver.loki
Transformerloki.relabel
Transformerloki.process
Writerloki.write
Writerotelcol.exporter.loki
Writerotelcol.exporter.logging

实践

准备

1
2
3
4
5
6
7
8
# 1、准备目录
mkdir evaluate-loki
cd evaluate-loki

# 2、下载默认配置文件
wget https://raw.githubusercontent.com/grafana/loki/main/examples/getting-started/loki-config.yaml -O loki-config.yaml
wget https://raw.githubusercontent.com/grafana/loki/main/examples/getting-started/alloy-local-config.yaml -O alloy-local-config.yaml
wget https://raw.githubusercontent.com/grafana/loki/main/examples/getting-started/docker-compose.yaml -O docker-compose.yaml

配置文件下载不下来,可以复制如下,不一定是最新的

loki-config.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
server:
http_listen_address: 0.0.0.0
http_listen_port: 3100

memberlist:
join_members: ["read", "write", "backend"]
dead_node_reclaim_time: 30s
gossip_to_dead_nodes_time: 15s
left_ingesters_timeout: 30s
bind_addr: ['0.0.0.0']
bind_port: 7946
gossip_interval: 2s

schema_config:
configs:
- from: 2023-01-01
store: tsdb
object_store: s3
schema: v13
index:
prefix: index_
period: 24h
common:
path_prefix: /loki
replication_factor: 1
compactor_address: http://backend:3100
storage:
s3:
endpoint: minio:9000
insecure: true
bucketnames: loki-data
access_key_id: loki
secret_access_key: supersecret
s3forcepathstyle: true
ring:
kvstore:
store: memberlist
ruler:
storage:
s3:
bucketnames: loki-ruler

compactor:
working_directory: /tmp/compactor

alloy-local-config.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
discovery.docker "flog_scrape" {
host = "unix:///var/run/docker.sock"
refresh_interval = "5s"
}

discovery.relabel "flog_scrape" {
targets = []

rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "container"
}
}

loki.source.docker "flog_scrape" {
host = "unix:///var/run/docker.sock"
targets = discovery.docker.flog_scrape.targets
forward_to = [loki.write.default.receiver]
relabel_rules = discovery.relabel.flog_scrape.rules
refresh_interval = "5s"
}

loki.write "default" {
endpoint {
url = "http://gateway:3100/loki/api/v1/push"
tenant_id = "tenant1"
}
external_labels = {}
}

docker-compose.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
version: "3"

networks:
loki:

services:
read:
image: grafana/loki:3.1.0
command: "-config.file=/etc/loki/config.yaml -target=read"
ports:
- 3101:3100
- 7946
- 9095
volumes:
- ./loki-config.yaml:/etc/loki/config.yaml
depends_on:
- minio
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3100/ready || exit 1" ]
interval: 10s
timeout: 5s
retries: 5
networks: &loki-dns
loki:
aliases:
- loki

write:
image: grafana/loki:3.1.0
command: "-config.file=/etc/loki/config.yaml -target=write"
ports:
- 3102:3100
- 7946
- 9095
volumes:
- ./loki-config.yaml:/etc/loki/config.yaml
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3100/ready || exit 1" ]
interval: 10s
timeout: 5s
retries: 5
depends_on:
- minio
networks:
<<: *loki-dns

alloy:
image: grafana/alloy:latest
volumes:
- ./alloy-local-config.yaml:/etc/alloy/config.alloy:ro
- /var/run/docker.sock:/var/run/docker.sock
command: run --server.http.listen-addr=0.0.0.0:12345 --storage.path=/var/lib/alloy/data /etc/alloy/config.alloy
ports:
- 12345:12345
depends_on:
- gateway
networks:
- loki

minio:
image: minio/minio
entrypoint:
- sh
- -euc
- |
mkdir -p /data/loki-data && \
mkdir -p /data/loki-ruler && \
minio server /data
environment:
- MINIO_ROOT_USER=loki
- MINIO_ROOT_PASSWORD=supersecret
- MINIO_PROMETHEUS_AUTH_TYPE=public
- MINIO_UPDATE=off
ports:
- 9000
volumes:
- ./.data/minio:/data
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:9000/minio/health/live" ]
interval: 15s
timeout: 20s
retries: 5
networks:
- loki

grafana:
image: grafana/grafana:latest
environment:
- GF_PATHS_PROVISIONING=/etc/grafana/provisioning
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
depends_on:
- gateway
entrypoint:
- sh
- -euc
- |
mkdir -p /etc/grafana/provisioning/datasources
cat <<EOF > /etc/grafana/provisioning/datasources/ds.yaml
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://gateway:3100
jsonData:
httpHeaderName1: "X-Scope-OrgID"
secureJsonData:
httpHeaderValue1: "tenant1"
EOF
/run.sh
ports:
- "3000:3000"
healthcheck:
test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1" ]
interval: 10s
timeout: 5s
retries: 5
networks:
- loki

backend:
image: grafana/loki:3.1.0
volumes:
- ./loki-config.yaml:/etc/loki/config.yaml
ports:
- "3100"
- "7946"
command: "-config.file=/etc/loki/config.yaml -target=backend -legacy-read-mode=false"
depends_on:
- gateway
networks:
- loki


gateway:
image: nginx:latest
depends_on:
- read
- write
entrypoint:
- sh
- -euc
- |
cat <<EOF > /etc/nginx/nginx.conf
user nginx;
worker_processes 5; ## Default: 1

events {
worker_connections 1000;
}

http {
resolver 127.0.0.11;

server {
listen 3100;

location = / {
return 200 'OK';
auth_basic off;
}

location = /api/prom/push {
proxy_pass http://write:3100\$$request_uri;
}

location = /api/prom/tail {
proxy_pass http://read:3100\$$request_uri;
proxy_set_header Upgrade \$$http_upgrade;
proxy_set_header Connection "upgrade";
}

location ~ /api/prom/.* {
proxy_pass http://read:3100\$$request_uri;
}

location = /loki/api/v1/push {
proxy_pass http://write:3100\$$request_uri;
}

location = /loki/api/v1/tail {
proxy_pass http://read:3100\$$request_uri;
proxy_set_header Upgrade \$$http_upgrade;
proxy_set_header Connection "upgrade";
}

location ~ /loki/api/.* {
proxy_pass http://read:3100\$$request_uri;
}
}
}
EOF
/docker-entrypoint.sh nginx -g "daemon off;"
ports:
- "3100:3100"
healthcheck:
test: ["CMD", "service", "nginx", "status"]
interval: 10s
timeout: 5s
retries: 5
networks:
- loki


flog:
image: mingrammer/flog
command: -f json -d 200ms -l
networks:
- loki

部署

1
docker compose up -d

验证

Read组件: http://localhost:3101/ready

Write组件:http://localhost:3102/ready

Grafana Alloy UI:http://localhost:12345

Grafana

查看日志

可以使用LogCli或者Grafana可视化界面查看日志

使用 Grafana 查询 Loki 数据源的数据:

  1. 访问Grafana:http://localhost:3000/
  2. 已经整合了Loki数据源
  3. 点击 Explore 查看
  4. 使用Code模式,编写 LogQL 查询

查询示例

标签检索

1
2
3
4
# 查看 container 标签值 为 evaluate-loki-flog-1 的日志
{container="evaluate-loki-flog-1"}

{container="evaluate-loki-grafana-1"}

包含值

1
2
# 查看 container 标签值 为 evaluate-loki-flog-1 ,且 json 格式中 status字段值为404
{container="evaluate-loki-flog-1"} | json | status=`404`

计算

1
sum by(container) (rate({container="evaluate-loki-flog-1"} | json | status=`404` [$__auto]))

其他

1
2
3
4
5
6
7
8
9
{container="evaluate-loki-flog-1"}

{container="evaluate-loki-flog-1"} |= "GET"

{container="evaluate-loki-flog-1"} |= "POST"

{container="evaluate-loki-flog-1"} | json | status="401"

{container="evaluate-loki-flog-1"} != "401"

更多:https://grafana.com/docs/loki/latest/query/query_examples/