Grafana Loki搭建日志中心

Grafana loki

Loki + Promtail + Grafana(简称LPG),LPG日志收集方案内存占用很少,它不像ELK日志系统那样为日志建立索引,而是为每个日志流设置一组标签。下面分别介绍下它的核心组件:

  • Promtail:日志收集器,负责收集日志并将其发送给Loki,对标ELK中的Logstash。
  • Loki:聚合并存储日志数据和处理查询,可以作为Grafana的数据源,为Grafana提供可视化数据,对标ELK中的Elasticsearch
  • Grafana:从Loki中获取日志信息,进行可视化展示,对标ELK中的Kibana

lokiflow

LPG相较于ELK Stack有以下优势:

  • Elasticsearch中的数据作为非结构化JSON对象存储在磁盘上,Loki以二进制的形式存储。
  • Elasticsearch采用全文索引,倒排索引的切分和共享的成本较高。Loki仅索引元数据,比如标签。
  • 和Prometheus无缝集成。

官方文档:Grafana Loki docs

扩展阅读:

架构

日志存储架构

读写

日志数据的写主要依托的是 Distributor 和 Ingester 两个组件,整体的流程如下:

write

Distributor

一旦 Promtail 收集日志并将其发送给 Loki,Distributor 就是第一个接收日志的组件。

由于日志的写入量可能很大,所以不能在它们传入时将它们写入数据库,这会毁掉数据库,需要批处理和压缩数据。

Loki 通过构建压缩数据块来实现这一点,方法是在日志进入时对其进行 Gzip 操作,组件 Ingester 是一个有状态的组件,负责构建和刷新 Chunck,当 Chunk 达到一定的数量或者时间后,刷新到存储中去。

每个流的日志对应一个 Ingester,当日志到达 Distributor 后,根据元数据和 Hash 算法计算出应该到哪个 Ingester 上面。

此外,为了冗余和弹性,会将其复制 n(默认情况下为 3)次。

Distributor

Ingester

Ingester 接收到日志并开始构建 Chunk:

Ingester

将日志进行压缩并附加到 Chunk 上面。一旦 Chunk“填满”(数据达到一定数量或者过了一定期限),Ingester 将其刷新到数据库。

Loki对块和索引使用单独的数据库,因为它们存储的数据类型不同。

刷新一个 Chunk 之后,Ingester 然后创建一个新的空 Chunk 并将新条目添加到该 Chunk 中。

Ingester1

Querier

由 Querier 负责给定一个时间范围和标签选择器,Querier 查看索引以确定哪些块匹配,并通过 greps 将结果显示出来。它还从 Ingester 获取尚未刷新的最新数据。

对于每个查询,一个查询器将显示所有相关日志。实现了查询并行化,提供分布式 grep,使即使是大型查询也是足够的

Querier

部署

使用docker compose 统一部署需Loki、Promtail、Grafana这些服务

工作目录

创建工作目录

1
2
3
4
5
mkdir -p lpg/loki/data
mkdir -p lpg/promtail/logs
mkdir -p lpg/grafana/data/
chmod -R 777 lpg
cd lpg

docker-compose.yml

创建docker-compose.yml

1
vi docker-compose.yml

将以下内容复制进去

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
version: "3"

networks:
loki:

services:
loki:
container_name: lpg-loki
image: grafana/loki:latest
restart: always
networks:
- loki
ports:
- "3100:3100"
volumes:
- $PWD/loki/:/etc/loki/
- $PWD/loki/data/:/tmp/loki/
command: -config.file=/etc/loki/loki.yaml

promtail:
container_name: lpg-promtail
image: grafana/promtail:latest
restart: always
networks:
- loki
volumes:
- $PWD/promtail:/etc/promtail/
- $PWD/promtail/logs/:/var/log/
command: -config.file=/etc/promtail/promtail.yml

grafana:
container_name: lpg-grafana
image: grafana/grafana:latest
restart: always
ports:
- "3000:3000"
depends_on:
- loki
- promtail
networks:
- loki
environment:
# - GF_SERVER_ROOT_URL=http://gct-china.com
- GF_SECURITY_ADMIN_PASSWORD=bb123456
volumes:
- $PWD/grafana/grafana.ini:/etc/grafana/grafana.ini
- $PWD/grafana/data/:/var/lib/grafana

把Loki、Promtail、Grafana的配置文件挂载到宿主机上,在运行之前,需要先准备好这3个配置文件;

loki.yml

Loki的配置文件$PWD/loki/loki.yml内容如下,使用的是默认配置

1
vi loki/loki.yml

loki.yml

这里使用文件存储,映射目录为/tmp/loki

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
auth_enabled: false

server:
http_listen_port: 3100

ingester:
lifecycler:
address: 127.0.0.1 # loki访问路径
ring:
kvstore:
store: inmemory
replication_factor: 1
final_sleep: 0s
chunk_idle_period: 5m
chunk_retain_period: 30s
wal:
dir: /tmp/wal

schema_config:
configs:
- from: 2020-05-15
store: boltdb
object_store: filesystem
schema: v11
index:
prefix: index_
period: 168h

storage_config:
boltdb:
directory: /tmp/loki/index #自定义boltdb目录(在loki目录下新建data文件来存放

filesystem:
directory: /tmp/loki/chunks #自定义filesystem目录(在loki目录下新建data文件来存放)

limits_config:
enforce_metric_name: false
reject_old_samples: true
reject_old_samples_max_age: 168h

官方默认配置文件

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
auth_enabled: false

server:
http_listen_port: 3100

common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory

schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h

ruler:
alertmanager_url: http://localhost:9093

配置示例

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
auth_enabled: false

server:
http_listen_port: 3100

ingester:
lifecycler:
address: 127.0.0.1
ring:
kvstore:
store: inmemory
replication_factor: 1
final_sleep: 0s
chunk_idle_period: 1h # 在这段时间内没有接收到新日志的任何块都将被刷新
max_chunk_age: 1h # 所有块刷新实践,默认为 1h
chunk_target_size: 1048576 # Loki 将尝试构建最大 1.5MB 的块,如果首先达到 chunk_idle_period 或 max_chunk_age,则首先刷新
chunk_retain_period: 30s # 如果使用索引缓存,则必须大于索引读取缓存 TTL(默认索引读取缓存 TTL 为 5m)
max_transfer_retries: 0 # 块传输已禁用

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/boltdb-shipper-active
cache_location: /loki/boltdb-shipper-cache
cache_ttl: 24h # Can be increased for faster performance over longer query periods, uses more disk space
shared_store: filesystem
filesystem:
directory: /loki/chunks #自定义filesystem目录(在loki目录下新建data文件来存放)

boltdb:
directory: /tmp/loki/index #自定义boltdb目录(在loki目录下新建data文件来存放

compactor:
working_directory: /loki/boltdb-shipper-compactor
shared_store: filesystem

limits_config:
reject_old_samples: true # 是否拒绝旧样本
reject_old_samples_max_age: 168h # 168小时之前的样本被拒绝
max_entries_limit_per_query: 9999 # 最大查询行数

chunk_store_config:
max_look_back_period: 0s # 为避免查询超过保留期的数据,必须小于或等于下方的时间值

table_manager:
retention_deletes_enabled: false # 保留删除是否删除
retention_period: 0s # 72h 超过72h的块数据将被删除

ruler:
storage:
type: local
local:
directory: /loki/rules
rule_path: /loki/rules-temp
alertmanager_url: http://localhost:9093
ring:
kvstore:
store: inmemory
enable_api: true

官方配置示例:https://grafana.com/docs/loki/latest/configuration/examples/

存储切换:https://juejin.cn/post/6870533848644616206

集群部署:https://zhuanlan.zhihu.com/p/373178364

promtail.yml

Promtail的配置文件$PWD/promtail/promtail.yml内容如下,使用的也是默认配置,这里的clients.url需要注意下,由于使用的是docker-compose部署,可以将服务名称loki作为域名来访问Loki服务

1
vi promtail/promtail.yml

promtail.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
server:
http_listen_port: 9080
grpc_listen_port: 0

positions:
filename: /tmp/positions.yaml

clients:
- url: http://loki:3100/loki/api/v1/push

scrape_configs:
- job_name: system
static_configs:
- targets:
- localhost
labels:
job: varlogs
host: promtail
__path__: /var/log/*log

配置示例:https://grafana.com/docs/loki/latest/clients/promtail/configuration/#example-static-config

grafana.ini

Grafana的配置文件$PWD/grafana/grafana.ini内容如下,使用的也是默认配置

1
curl -sfL https://cdn.jonty.top/img/grafana.ini -o ./grafana/grafana.ini

image-20220812143852426

启动

运行docker-compose.yml脚本安装所有服务,使用如下命令即可;

1
2
docker compose up -d
docker compose ps

image-20220812132457250

使用

Grafana

部署完成后登录Grafanahttp://192.168.2.97:3000

初始账号密码:admin/admin (根据配置参数:bb123456)

image-20220812132735723

修改新的密码后进入

image-20220812132835834

添加数据源

image-20220812132952221

选择Loki,可以看到Grafana也支持Elasticseach

image-20220812133044599

保存并测试

image-20220812164333736

NuGet集成

添加Serilog 的Loki扩展

  • Serilog
  • Serilog.AspNetCore
  • Serilog.Sinks.Grafana.Loki
1
dotnet add package Serilog.Sinks.Grafana.Loki

仓库:https://github.com/serilog-contrib/serilog-sinks-grafana-loki

案例:https://github.com/serilog-contrib/serilog-sinks-grafana-loki/tree/master/sample

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
using Serilog.Debugging;

namespace Serilog.Sinks.Grafana.Loki.Sample;

public static class Program
{
private const string OutputTemplate =
"{Timestamp:dd-MM-yyyy HH:mm:ss} [{Level:u3}] [{ThreadId}] {Message}{NewLine}{Exception}";

public static void Main(string[] args)
{
SelfLog.Enable(Console.Error);

Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.Enrich.WithThreadId()
.Enrich.WithProperty("meaning_of_life", 42)
.WriteTo.Console(outputTemplate: OutputTemplate)
.WriteTo.GrafanaLoki(
"http://192.168.2.97:3100",
new List<LokiLabel> { new() { Key = "app", Value = "console1" } }, // label用户查询
credentials: null)
.CreateLogger();

Log.Debug("This is a debug message");

var person = new Person("Billy", 42);

Log.Information("Person of the day: {@Person}", person);

Log.Warning("道路千万条,安全第一条,行车不规范,亲人两行泪");

try
{
throw new AccessViolationException("Access denied");
}
catch (Exception ex)
{
Log.Error(ex, "An error occured");
}

Log.CloseAndFlush();
}
}

image-20220812174006119

查询日志

官方文档:https://grafana.com/docs/loki/latest/logql/

image-20220812174254586

扩展

把应用程序日志目录挂载到Promtail > $PWD/promtail/logs/ 目录上,Promtail可以收集到日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: "3.9"

volumes:
server_logs:
server_sysfiles:

networks:
net:

services:
hostapi:
image: host_server:dev
networks:
- net
volumes:
- - "server_logs:/app/App_Data/Logs"
+ - $PWD/promtail/logs/:/app/App_Data/Logs
- "server_sysfiles:/app/wwwroot/SysFiles"

参考文档:

https://blog.csdn.net/wayne_primes/article/details/112467639