简易日志系统LPG生产环境实践指南 | 坑我已经帮你们踩好了

2022-06-07 20:22:09 浏览数 (2)

简易日志系统LPG生产环境实践指南 | 坑我已经帮你们踩好了

前言

最近在构建日志系统,对比了ELK还有LPG,发现LPG更加适合我们系统。奈何网上可靠的文章真是太少了,大多都是抄来抄去,整个过程躺过无数坑,特记录一下,回馈给读者。文章的所有配置文件都可以直接使用,并且配置做了优化,不会出现莫名其妙的问题。

简介

LPG即:Loki,Promtail,Grafana。Loki日志收集器,Promtail日志收集器,Gafana日志的可视化展示和查询。重点组件是Loki,Loki 是 Grafana Labs 团队最新的开源项目,是一个水平可扩展,高可用性,多租户的日志聚合系统。它的设计非常经济高效且易于操作,因为它不会为日志内容编制索引,而是为每个日志流配置一组标签。项目受 Prometheus 启发,官方的介绍就是:Like Prometheus, but for logs,类似于 Prometheus 的日志系统。

我们为什么需要日志系统?

可能很多人都会有这个疑问。查看日志直接上主机grep不就行了么。实则不然,但我们生产环境主机众多,服务众多,那ssh到各主机上看日志将会是个极其麻烦的一件事,然后你还要使用grep 层层过滤,有时候这台主机看了发现没有问题,又要去另外一台主机上看,况且你还不一定有权限。

所有日志系统可以大大简化这个过程,日志系统提供的日志聚合,可视化展示和查询,高效的查询和过滤语句等可以极大提升运维效率,所有日志只需一个查询入口即可。有了日志系统,运维排查问题的步骤变成了这样:

所以我们常说日志系统和监控系统一样,都是属于公司的基建。

因为我们目前系统规模不算大,不需要对日志进行太复杂的操作,加上ELK成本较高,所以我们采用了LPG来搭建日志系统。

LPG架构

了解构架和软件运行原理对运维排查问题至关重要。

我们将构建一个使用Promtail采集收集使用Loki存储日志,使用Grafana可视化查询日志的日志系统。Loki和prometheus类似,都可以通过labels来区分和检索日志,我们可以在Promtail收集时定义好日志标签。

Loki 内部架构

Distributor

distributor 服务负责处理客户端写入的日志,它本质上是日志数据写入路径中的第一站,一旦 distributor 收到日志数据,会将其拆分为多个批次,然后并行发送给多个 ingesterdistributor 通过 gRPC 与 ingester 通信,它们都是无状态的,可以根据需要扩大或缩小规模。

Igester

ingester 服务负责将日志数据写入长期存储后端(DynamoDB、S3、Cassandra 等)。此外 ingester 会验证摄取的日志行是按照时间戳递增的顺序接收的(即每条日志的时间戳都比前面的日志晚一些),当 ingester 收到不符合这个顺序的日志时,该日志行会被拒绝并返回一个错误。

query-frontend

查询前端是一个可选的服务,提供 querier 的 API 端点,可以用来加速读取路径。当查询前端就位时,应将传入的查询请求定向到查询前端,而不是 querier, 为了执行实际的查询,群集中仍需要 querier 服务

Querier

Querier 查询器服务使用 LogQL 查询语言处理查询,从 ingesters 和长期存储中获取日志。

查询器查询所有 ingesters 的内存数据,然后再到后端存储运行相同的查询。由于复制因子,查询器有可能会收到重复的数据。为了解决这个问题,查询器在内部对具有相同纳秒时间戳、标签集和日志信息的数据进行重复数据删除。

部署与配置

安装loki

代码语言:javascript复制
wget https://github.com/grafana/loki/releases/download/v2.x.0/loki-linux-amd64.zip # 下载
unzip loki-linux-amd64.zip # 解压
mv loki-linux-amd64 /monitor/loki # 挪到 $PATH下

编写配置文件,vim loki-local-config.yml

代码语言:javascript复制
auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 9096

ingester:
  lifecycler:
    address: 127.0.0.1
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
    final_sleep: 0s
  chunk_idle_period: 10m
  chunk_retain_period: 30s
schema_config:
  configs:
  - from: 2020-05-15
    store: boltdb
    object_store: filesystem
    schema: v11
    index:
      prefix: index_
      period: 168h
storage_config:
  boltdb:
    directory: /monitor/loki/index
  filesystem:
    directory: /monitor/loki/chunks  # 块存储路径

limits_config:
  enforce_metric_name: false
  reject_old_samples: true          # 是否拒绝老样本
  reject_old_samples_max_age: 168h  # 168小时之前的样本将会被删除
  ingestion_rate_mb: 200
  ingestion_burst_size_mb: 300
  per_stream_rate_limit: 1000MB
  max_entries_limit_per_query: 10000
chunk_store_config:
  max_look_back_period: 168h        # 为避免查询超过保留期的数据,必须小于或等于下方的时间值
table_manager:
  retention_deletes_enabled: true   # 保留删除开启
  retention_period: 168h            # 超过168h的块数据将被删除

ruler:
  storage:
    type: local
    local:
      directory: /monitor/loki/rules
  rule_path: /monitor/loki/rules-temp
  alertmanager_url: http://192.168.x.x:9093 # alertmanager地址
  ring:
    kvstore:
      store: inmemory
  enable_api: true
  enable_alertmanager_v2: true

更多配置项的解释看这里:https://grafana.com/docs/loki/latest/configuration/[1]

启动脚本

代码语言:javascript复制
# cat restart-loki.sh 
#!/bin/bash
echo "stop loki"
ps -ef | grep loki-linux-amd64 | grep -v grep | awk '{print $2}'| xargs kill -9 

echo "Begin start loki"
sleep 1
str=$"n"
nohup ./loki-linux-amd64 --config.file=loki-local-config.yml &
sstr=$(echo -e $str)
echo $sstr

在需要采集日志的主机上都安装Promtail

代码语言:javascript复制
wget https://github.com/grafana/loki/releases/download/v2.x.0/promtail-linux-amd64.zip # 下载
unzip loki-linux-amd64.zip # 解压
mv promtail-linux-amd64 /usr/local/sbin/promtail # 挪到 $PATH下

编写配置文件,vim promtail-local-config.yml

代码语言:javascript复制
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /opt/promtail/positions.yaml

clients:
  - url: http://192.168.xx.xx:3100/loki/api/v1/push # 填写好Loki地址

scrape_configs:
- job_name: messagelog
  static_configs:
  - targets:
      - localhost
    labels:
      #job: messagelog
      host: namenode01
      __path__: /var/log/messages

- job_name: agentlog
  static_configs:
  - targets:
      - localhost
    labels:
      #job: agentlog
      host: namenode01
      __path__: /ssd/ssd0/agentlog/agent.log
- job_name: datanodelog
  static_configs:
  - targets:
      - localhost
    labels:
#      #job: datanodelog
      host: namenode01
      __path__: /ssd/ssd0/datanodelog/*datanode*.log 

关于labels,官网建议labels个数越少越好,过多的labels将会影响Loki日志的检索速度。

启动脚本:

代码语言:javascript复制
# cat restart-promtail.sh 
#!/bin/bash
echo "Begin stop promtail"
ps -ef | grep promtail-linux-amd64 | grep -v grep | awk '{print $2}' | xargs kill -9

echo "Begin start promtail...."
nohup ./promtail-linux-amd64 --config.file=promtail-local-config.yml > ./promtail-9080.log 2>&1 &

使用指南

除了使用Gafana查询Loki之外,它还提供了linux终端的查询客户端:logcli。实际上查询Loki的还有Alertmanager

Gafana对接Loki

我们可以使用Grafana对接Loki进行可视化的日志检索和分析,如果你更愿意使用命令行,那也可以使用logcli来查询日志,详见下一节《logcli的使用》。

首先我们需要添加Grafana Loki数据源

grafana上显示的日志行数与DataSource设置保持一致

loki datasource的设置查询显示行数:

但直方图上并不会显示所选时间内所有日志情况,只会显示5000行日志的情况,如果查询结果日志条数小于5000,直方图则会显示所有日志

这部分其实很简单,下面介绍一下Logql的语法:

其表达式主要分为两部分,日志标签匹配和条件过滤表示式。这里的标签可以在Grafana直接看到:

如果是命令行,可以使用logcli labels 查询。

日志流选择器(即第一部分:标签匹配)

  • =: 完全匹配
  • !=: 不匹配
  • =~: 正则表达式匹配
  • !~: 正则表达式不匹配

举例:

代码语言:javascript复制
{name!~`hadoop-d `,env="prod"}

filter expression

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

举例

代码语言:javascript复制
{name="hadoop"} |~  `error=w `

Metric queries

区间向量

LogQL同样也支持有限的区间向量度量语句,使用方式也和PromQL类似,常用函数主要是如下4个:

  • rate: 计算每秒的日志条目
  • count_over_time: 对指定范围内的每个日志流的条目进行计数
  • bytes_rate: 计算日志流每秒的字节数
  • bytes_over_time: 对指定范围内的每个日志流的使用的字节

** 聚合函数**

  • sum:求和
  • min:最小值
  • max:最大值
  • avg:平均值
  • stddev:标准差
  • stdvar:标准方差
  • count:计数
  • bottomk:最小的k个元素
  • topk:最大的k个元素

对于需要对标签进行分组时,我们可以用without或者by来区分,比如:

代码语言:javascript复制
#计算nginx的qps,并按照pod_name来分组
sum(rate({filename="/var/log/nginx/access.log"}[5m])) by (pod_name)

还有更多,可以阅读官网:https://grafana.com/docs/loki/latest/logql/[2] ,这里就不一一列举了。

一般日常工作上述语句差不多够用了。

logcli使用

安装logcli

https://github.com/grafana/loki/releases/tag/v2.4.2[3] 下载logcli-linux-amd64.zip文件,解压,重命名

代码语言:javascript复制
mv logcli-linux-amd64 logcli

将解压的文件链接到/usr/local/bin/logcli

代码语言:javascript复制
ln -s /your/path/to
查询有哪些labels
代码语言:javascript复制
# logcli labels
__name__
filename
host

使用query功能,配合label查询对应的日志文件。日志显示顺序默认是时间倒序。

代码语言:javascript复制
logcli query '{host="db04",filename="/ssd/ssd0/agentlog/P4-node2.log"}' --limit 1000

默认查询是返回30条日志,可以使用**--limit**** 参数来修改返回行数。不管行数设置的是多大,默认只打印一个小时的日志。可以使用****--since**** 参数来指定打印多长时间的日志。**

如果你将查询的--limit 参数(默认为30)设置为一个较大的数,比如 10000,那么 logcli 会自动将此请求分批发送到 Loki,默认的批次大小是 1000。

可使用-o jsonl 让日志输出为json格式。

指定时间输出日志
代码语言:javascript复制
logcli query '{host="db04",filename="/var/log/messages"}' --limit=100000 --from="2022-03-17T14:00:05Z"  --to="2022-03-18T14:30:05Z"

以上时间是以UTC为时区的时间,如果需要中国时间,使用以下格式:

代码语言:javascript复制
logcli query '{host="db04",filename="/var/log/messages"}' --limit=10000 --from="2022-03-12T14:00:05 08:00"  --to="2022-03-18T14:30:05 08:00"

--from--to 后面的时间需要符合IETF RFC-3339[4]格式。T 作为隔断日期和时间的标志,也可以使用空格来代替TZ 表示其时区为UTC 0。更多详细的例子:

代码语言:javascript复制
2019-10-12T07:20:50.52Z (UTC 0)
2019-10-12T07:20:50.52 00:00 (UTC 0)
2019-10-12T14:20:50.52-04:00 (UTC-4)

一开始并不明白为何要如此复杂的去设置时间,后面看到以下文章才明白这个标准的重要性。

对RFC 3339的时间、时区格式详解[5]

logcli query同样支持Logql语法
代码语言:javascript复制
logcli query '{host="db04",filename="/var/log/messages"}|="Removed"' --limit=10000 --since=2h

注意:能查出来的最远的日志在Loki配置文件有定义,即块存储周期配置 retention_period: 168h # 超过168h的块数据将被删除。

排错

1. 日志量大推送错误

当你搭建完成 promtail,并且启动发送日志到 loki 的时候很有可能会碰到这个错误,因为你要收集的日志太多了,超过了 loki 的限制,所以会报429。

代码语言:javascript复制
level=warn ts=2022-03-13T07:00:13.558415458Z caller=client.go:344 component=client host=10.101.249.246:5654 msg="error sending batch, will retry" status=429 error="server returned HTTP status 429 Too Many Requests (429): Ingestion rate limit exceeded (limit: 4194304 bytes/sec) while attempting to ingest '10793' lines totaling '1048548' bytes, reduce log volume or contact your Loki administrator to see if the limit can be increased"

loki 配置文件添加 ingestion_rate_mb: 15,旧版添加 ingestion_rate: 25000

代码语言:javascript复制
limits_config:
  reject_old_samples: true
  reject_old_samples_max_age: 168h
  # 每秒允许promtail传输32MB,默认为4
  ingestion_rate_mb: 32
  ingestion_burst_size_mb: 64

一般用我的配置文件不会出现这个问题。

2. Grafana只显示1000行日志

1000行日志有时候1s钟的日志量都没有,需要显示更多可修改Grafana数据源的以下配置。

我的经验是大于10000行Grafana就会变得非常卡。

所以建议使用条件过滤。

总结

总而言之,对于一个小规模系统而言,LPG对我们定位问题起到了极大的作用,特别是当要检索多个日志文件是否存在某一条同样的日志时,可以给运维节约大量时间,再也不需要一个一个文件去grep了。连我们的乙方看到我们这个系统后都说要回去整一套。

注:

  • Storage中bolt-shipper与bolt的区别:

Loki2.0版本之后,对于使用boltdb存储索引部分做了较大的重构,采用新的boltdb-shipper模式,可以让Loki的索引存储在S3上,而彻底摆脱Cassandra或者谷歌的BigTable。此后服务的横向扩展将变得更加容易。详见:https://grafana.com/docs/loki/latest/operations/storage/boltdb-shipper/[6]

参考资料

[1]

https://grafana.com/docs/loki/latest/configuration/: https://grafana.com/docs/loki/latest/configuration/

[2]

https://grafana.com/docs/loki/latest/logql/: https://grafana.com/docs/loki/latest/logql/

[3]

https://github.com/grafana/loki/releases/tag/v2.4.2: https://github.com/grafana/loki/releases/tag/v2.4.2

[4]

IETF RFC-3339: https://www.rfc-editor.org/rfc/rfc3339

[5]

对RFC 3339的时间、时区格式详解: https://www.jianshu.com/p/f50005a2410c

[6]

https://grafana.com/docs/loki/latest/operations/storage/boltdb-shipper/: https://grafana.com/docs/loki/latest/operations/storage/boltdb-shipper/

0 人点赞