37DATA

Grafana Loki 轻量级日志系统小记

前言

最近几个月,广告团队一直在 Web 模块化、服务化的方向进行探寻。在这一背景下,我们对现有系统的模块进行了划分,(在一定程度上)理清了模块间的边界与关系,并采用“服务化架构”的对 37 网游 UAD 广告后台进行了重构,落地了“开天辟地、继往开来”的“盘古”系统。

”盘古“一期的开发涉及到3个微服务,意味着我在本地开发的过程中如果需要涉及到整个链路的调整/验证的话,至少需要跑起来 3 个站点,以前简单的单文件日志查找也变得繁琐了起来。转念一想,我们的服务日志无论是在本地开发环境、测试环境还是在生产环境都已经对输出字段做了标准化处理,并且采用 JSON 格式输出,那么能否在本地搭建一套类似 ELK 的日志系统用于统一收集、检索我们的日志呢?想法自然是没有问题的,但是对于本地开发机配置不高、又需要多开 IDE、浏览器的我而言,要跑起来 ElasticSearch 这个极耗内存的”庞然大物“似乎不太现实,几经查找,终于寻得一套极具性价比的日志系统解决方案 Grafana Loki,很好的解决了我在本地开发环境下日志查询效率低下的问题。

对于经常关注业界新闻的小伙伴应该对 Loki 不陌生,它是我们数据部看板所采用的工具 Grafana 的官方团队最新的开源项目之一。下面我就站在一个小白的角度,与大家浅谈一下 Loki 是什么,它与我们熟悉的 ELK 方案(如运维平台的海纳日志系统)相比有什么优缺点以及怎么部署、接入与检索吧。

简介

Loki 是 Grafana Labs 团队最新的开源项目,是一个水平可扩展,高可用性,多租户的日志聚合系统。与其他日志系统不同,Loki 的构想是仅对日志建立标签索引,而使原始日志消息保持未索引状态。这意味着 Loki 的运营成本更低,并且效率更高。

Image

Loki 具有下面的一些特性

  • 不对日志进行全文索引

  • Loki中存储的是压缩后的非结构化日志,并且只对元数据建立索引,因此Loki 具有操作简单、低成本的优势

  • 使用与 Prometheus 相同的标签。Loki通过标签对日志进行索引和分组,这使得日志的扩展和操作效率更高

  • Grafana 原生支持

与 ELK 相比

  • Elasticsearch

  1. 数据作为非结构化 JSON 对象存储在磁盘上

  2. 每个对象的键和每个键的内容都被索引。然后可以使用 JSON 对象或定义为 Lucene 的查询语言来查询数据

  • Loki

    1. 在单二进制模式下可以将数据存储在磁盘上,并且在水平可伸缩模式下,可以将数据存储在云存储系统(例如 S3,GCS 或 Cassandra )中

    2. 日志以纯文本格式存储,并带有一组标签名称和值,其中仅对标签对进行索引。这种折衷使得它比全索引更具备成本优势,并且允许开发人员在程序中更加积极地进行日志记录,并使用 LogQL 查询 Lok i中的日志

    3. 由于这种设计的折衷,基于内容(即日志行中的文本)进行过滤的LogQL查询需要加载搜索窗口中与查询中定义的标签匹配的所有块

    部署

    基于 Loki 的日志系统想要在本地开发机跑起来,需要这 3 大模块:Promtai、Loki、Grafana。 其中 Promtail 的功能与 ELK 他们家的 Filebeat 功能相似,用于采集日志文件使其转换为流;Loki 则是日志系统中负责收集、存储、检索的模块;而 Grafana 自然是日志内容的呈现以及检索入口。这三者之间的关系如图所示:

    Image

    为了在本地实现简单快捷的部署,我直接采用了官方的 docker 镜像,shell 命令参考如下:

    ## 部署 promtaildocker run --name promtail -d -v /Users/rytia/Software/dev/Loki:/mnt/config -v /Users/rytia/37Code/uad-quantum/logs:/uad-quantum/logs grafana/promtail:2.3.0 -config.file=/mnt/config/promtail-config.yaml## 部署 lokidocker run -v /Users/rytia/Software/dev/Loki:/mnt/config -v /Users/rytia/37Code/uad-quantum/logs:/uad-quantum/logs -d --name loki -p 3100:3100 grafana/loki:2.3.0 -config.file=/mnt/config/loki-config.yaml## 部署 grafanadocker run -d --name grafana -p 3000:3000 \    -e GF_INSTALL_PLUGINS="grafana-clock-panel:1.1.0,grafana-kubernetes-app,worldpring=https://github.com/raintank/worldping-app/releases/download/v1.2.6/worldping-app-release-1.2.6.zip" \   bitnami/grafana:latest

    其中:

    • /Users/rytia/Software/dev/Loki 是我开发机存放 Loki 配置文件的地方(下一步将会用上)

    • /Users/rytia/37Code/uad-quantum/logs 是我开发机存放日志文件的地方

    • (偷偷告诉你,换下路径,复制粘贴就这 3 行命令真的就能跑起来了,就这么简单)

    接入

    在接入前,请保证你的程序已经使用 JSON 格式打日志了,格式要求与运维平台的海纳日志系统一致,如图所示:

    Image

    一、Promtail 部分

    • 官方文档:Promtail | Grafana Labs(https://grafana.com/docs/loki/latest/clients/promtail/)

    • 配置参考:

    # cat /Users/rytia/Software/dev/Loki/promtail-config.yamlserver: http_listen_port: 9080 grpc_listen_port: 0positions: filename: /tmp/positions.yamlclients: - url: http://172.17.0.1:3100/loki/api/v1/pushscrape_configs:- job_name: uad-quantum static_configs: - targets:   - localhost   labels:      # 自定义标签(job是一个自定义标签,比较好的是环境名称、应用名称或者微服务标识     job: ad-logs      # 配置要将什么位置的日志发送到Loki     __path__: /uad-quantum/logs/* pipeline_stages:   - match:       selector: '{job="ad-logs"}'       stages:       - json:           expressions:             level:   level_name       - labels:           level:  level
    • 整体与 Prometheus 配置相似,如果之前为自己的项目接入过 Prometheus 的话应该会有一种熟悉的感觉。需要关注的地方有以下几个:

    1. 配置项 clients.url 是 loki 的接口地址,如果是按照我上文所描述的方法进行部署的话,接口地址是 http://172.17.0.1:3100/loki/api/v1/push(其中 172.17.0.1 是 docker 网关的地址,不确定的话可以使用 ifconfig 查看;3100 是 loki 默认的工作端口)

    2. 配置项 scrape_configs.job_name 是实例的名称,可以自己取;scrape_configs.job_name.static_configs.labels定义了采集时所需带的标签以及路径

    3. 配置项scrape_configs.job_name.pipeline_stages 可以理解为日志清洗的流水线规则。上面这份配置文件中,我将日志文件 JSON 结构体中 level_name 这个字段,转换为 loki 的 level 标签,如果有其他的字段需要作为索引,可以在此处进行调整。

    二、Loki 部分

    • 官方文档:Loki Documentation | Grafana Labs(https://grafana.com/docs/loki/latest/)

    • 配置参考:

    # cat /Users/rytia/Software/dev/Loki/loki-config.yamlauth_enabled: falseserver: http_listen_port: 3100 grpc_listen_port: 9096ingester: wal:   enabled: true   dir: /tmp/wal lifecycler:   address: 127.0.0.1   ring:     kvstore:       store: inmemory     replication_factor: 1   final_sleep: 0s chunk_idle_period: 1h       # Any chunk not receiving new logs in this time will be flushed max_chunk_age: 1h           # All chunks will be flushed when they hit this age, default is 1h chunk_target_size: 1048576  # Loki will attempt to build chunks up to 1.5MB, flushing first if chunk_idle_period or max_chunk_age is reached first chunk_retain_period: 30s    # Must be greater than index read cache TTL if using an index cache (Default index read cache TTL is 5m) max_transfer_retries: 0     # Chunk transfers disabledschema_config: configs:   - from: 2020-10-24     store: boltdb-shipper     object_store: filesystem     schema: v11     index:       prefix: index_       period: 24hstorage_config: boltdb_shipper:   active_index_directory: /tmp/loki/boltdb-shipper-active   cache_location: /tmp/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: /tmp/loki/chunkscompactor: working_directory: /tmp/loki/boltdb-shipper-compactor shared_store: filesystemlimits_config: reject_old_samples: true reject_old_samples_max_age: 168hchunk_store_config: max_look_back_period: 0stable_manager: retention_deletes_enabled: false retention_period: 0sruler: storage:   type: local   local:     directory: /tmp/loki/rules rule_path: /tmp/loki/rules-temp alertmanager_url: http://localhost:9093 ring:   kvstore:     store: inmemory enable_api: true
    • 这份文件来源于官网示例,采用了“文件”作为持久化的介质。如果仅仅只作为本地开发环境的日志系统,建议直接使用这份官方的配置即可,不用进行额外的调整。

    三、Grafana 部分

    • 官方文档:Grafana documentation | Grafana Labs(https://grafana.com/docs/grafana/latest/)

    • 面板市场:Grafana Dashboards | Grafana Labs(https://grafana.com/grafana/dashboards) (以 Loki 作为关键字检索即可看到不少面板,可以直接使用)

    • 接入配置:

    1. 添加数据源

      前往 Grafana 的 Data Sources 设置界面新增数据与,类型选择 “Loki”

      HTTP URL 填入 docker 网关的地址,加上 Loki 默认的 3100 端口,并点击底部的“Save & Test”即可。具体配置详见上文。

    Image

    2.新增面板

    与其他我们所熟悉的数据源一致,新增面板(或者从官方市场导入面板也可以)

    新增 Panel,数据源选择 Loki,根据自己所需要的图形、表格进行可视化的调整即可。(如有特殊需求,具体查询语法见下文)

    Image

    3.整体效果

    本人的本地开发环境接入“盘古”微服务的日志后,效果如图

    Image

    • 能够展示日志详情,并选择性呈现所需要的字段(这也是目前 ElasticSearch作为数据源暂时做的还不够好的地方)

    Image

    • 能够根据日志级别的不同,采用不同的颜色进行区分,并对筛选的内容进行高亮处理

    在这里附带一下官方面板市场中大牛们采用 Loki 做出来的面板效果图供大家参考,我们常用的几大数据呈现形式基本都能通过 Loki 实现:

    Image

    检索

    官方文档:LogQL | Grafana Labs(https://grafana.com/docs/loki/latest/logql/)

    与 ElasticSearch 之于 lucene query 相似,Loki 也有自己的查询语言 LogQL。基本的 LogQL 查询由两部分组成:日志流选择器(Log Stream Selector)、搜索表达式(Filter Expression)

    日志流选择器(Log Stream Selector)

    它由一个或多个键值对组成,每个键是一个日志标签,值的话是标签的值,例如:

    {job="ad-logs",level="warning"}

    在这个例子中,记录具有的标签 job,其值是采集时所赋予的 ad-logs ;另一个标签 level 来源于采集时对 JSON 格式日志文件的解析。适用于Prometheus标签选择器的相同规则也适用 于Loki日志流选择器

    搜索表达式(Filter Expression)

    在使用日志流选择器(Log Stream Selector)进行初步的筛选之后,可以使用搜索表达式(Filter Expression)进一步过滤生成的日志集

    搜索表达式可以只是文本或正则表达式,举个简单的例子如下:

    {job=“mysql”} |= “error”
    {name=“kafka”} |~ “tsdb-ops.*io:2003”
    {instance=~“kafka-[23]”,name=“kafka”} !=
    kafka.server:type=ReplicaManager

    |=:日志行包含字符串。
    !=:日志行不包含字符串。
    |~:日志行匹配正则表达式。
    !~:日志行与正则表达式不匹配。

    此外,与 Prometheus 的 PromQL 一样,LogQL 支持内置聚合运算符如 max()、avg() 等。

    后记

    行文至此,已经深夜 2:19。本文所述为自己经历所沉淀,只是粗浅的介绍了一下 Loki 以及基于 docker 且使用文件进行持久化的一种部署方式,并没有对 Loki 本身的模块设计以及实现方式展开过多讨论。回到“盘古”的项目开发本身,Loki 在我不需要修改一行程序代码的前提下,用极低的机器资源占用将我的日志做了统一收集,并且无缝接入了我们熟悉的 Grafana 面板,提高了我在本地环境排查问题的效率。希望能给目前尚不了解 Loki 或者同样有本地开发微服务需求的小伙伴开拓一下视野。

    同时由于本人才学疏浅,文章难免会出现疏漏的地方,还请各位前辈不吝赐教。

    • 参考文献

      • Loki - 知乎 (zhihu.com)(https://www.zhihu.com/column/c_1262077765433348096)