简易日志系统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
收到日志数据,会将其拆分为多个批次,然后并行发送给多个 ingester
。distributor
通过 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
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
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
下
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
作为隔断日期和时间的标志,也可以使用空格
来代替T
。Z
表示其时区为UTC 0。更多详细的例子:
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
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/