轻量级日志系统-Loki
日志系统
常见日志系统:
日志处理流程:采集 ==> 存储 ==> 检索 ==> 可视化
Loki
介绍
Loki 是一个可水平扩展、高可用性、多租户日志聚合系统,其灵感来自 Prometheus。Loki 与 Prometheus 的不同之处在于,它专注于日志而不是指标,并通过推送而不是拉取来收集日志。
Loki 的设计非常经济高效,并且具有高度可扩展性。与其他日志系统不同,Loki 不会对日志内容进行索引,而只会对日志的元数据进行索引,将其作为每个日志流的一组标签。
日志流是一组共享相同标签的日志。标签帮助Loki在您的数据存储中找到日志流,因此拥有一组高质量的标签是高效查询执行的关键。
然后,日志数据被压缩并以块的形式存储在对象存储中,如亚马逊简单存储服务(S3)或谷歌云存储(GCS),甚至为了开发或概念验证,存储在文件系统上。小索引和高度压缩的块简化了操作,显著降低了Loki的成本。
日志结构
- Loki数据存储格式
- index:索引;存储Loki标签,如日志级别、来源、分组
- chunk:块;存储日志条目本身
架构
Agent:
- 代理或客户端,例如Grafana Alloy或Promtail,随Loki一起分发。代理抓取日志,通过添加标签将日志转换为流,并通过HTTP API将流推送到Loki。
Loki主服务器:
- 负责摄取和存储日志以及处理查询。它可以部署在三种不同的配置中,有关更多信息,请参阅部署模式。
Grafana:
- 用于查询和显示日志数据。您还可以使用LogCLI或直接使用Loki API从命令行查询日志。
快速入门
用 Docker Compose 部署以上服务,快速体验Loki生态:
- flog: 生成日志行。flog是常见日志格式的日志生成器。
- Grafana Alloy: 它从flog上抓取日志线,并通过网关将它们推送到Loki。
- 网关(nginx): 接收请求并根据请求的URL将它们重定向到适当的容器。
- Loki read组件: 它运行一个查询前端和一个查询器。
- Loki write组件: 它运行一个分发器和一个Ingester。
- Loki 后端组件: 它运行索引网关、压缩器、标尺、Bloom压缩器(实验)和Bloom网关(实验)。
- Minio: Loki用来存储其索引和块。
流程
写流程
- distributor 接收带有流和日志行的HTTP POST请求。
- distributor 会 hash 计算请求中包含的每个流,决定发给 一致性 hash 环 中的哪个 ingester
- distributor 把每个流 发给合适处理它的 ingester 和其副本
- ingester 接收带有日志行的流,并为流的数据创建一个块或附加到现有块。每个租户和每个标签集,块都是唯一的
- ingester 回复写操作结果
- distributor 等待大多数 ingester 确认写入完成。
- distributor 在收到至少法定数量的确认写入时响应成功(2xx状态码)。或者在写入操作失败时响应错误(4xx或5xx状态码)。
读流程
- 查询前端(query frontend) 接受到 携带 LogQL 的 HTTP GET 请求
- 查询前端 将查询拆分为子查询并将它们传递给查询调度程序(query scheduler)。
- querier (查询器)从调度程序(scheduler)中提取子查询。
- querier 将查询传递给 所有保存数据的 ingester。
- ingester 返回与查询匹配的 记忆数据(如果有)。
- 如果 ingester 没有返回或返回的数据不足,querier 会延迟从后备存储加载数据并对其运行查询。
- querier 遍历所有接收到的数据并进行重复数据删除,将子查询的结果返回到查询前端。
- 查询前端 等待 查询的所有子查询完成 并由 querier 返回。
- 查询前端将两个结果合并为最终结果并将其返回给客户端。
Alloy
架构
Grafana Alloy是一个多功能的可观测性收集器,可以摄取各种格式的日志并将其发送到Loki。我们推荐Alloy作为向Loki发送日志的主要方法,因为它为构建高度可扩展和可靠的可观测性流水线提供了更强大和特征丰富的解决方案。
组件
实践
准备
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
|
部署
验证
Read组件: http://localhost:3101/ready
Write组件:http://localhost:3102/ready
Grafana Alloy UI:http://localhost:12345
Grafana
查看日志
可以使用LogCli或者Grafana可视化界面查看日志
使用 Grafana 查询 Loki 数据源的数据:
- 访问Grafana:http://localhost:3000/
- 已经整合了Loki数据源
- 点击 Explore 查看
- 使用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/