Furion平台中fagent(多pod)需要上报日志信息,其中包括:

  1. fagent java程序运行的信息(方便用户定时执行失败等问题)
  2. fagent执行的请求样本sample(方便用户查看具体的请求数据)

如何解决日志信息的诉求,使用到一个最新开源的项目Loki

Loki介绍

Loki的第一个稳定版本于2019年11月19日发布,是Grafana Labs 团队最新的开源项目,是一个水平可扩展,高可用性,多租户的日志聚合系统。它的设计非常经济高效且易于操作,因为它不会为日志内容编制索引,而是为每个日志流编制一组标签。项目受 Prometheus 启发,官方的介绍就是:Like Prometheus, but for logs.,类似于 Prometheus 的日志系统

Loki 日志系统由以下3个部分组成:

  • loki是主服务器,负责存储日志和处理查询
  • promtail是专为loki定制的代理,负责收集日志并将其发送给 loki
  • Grafana用于查询和显示日志

整体架构

iotop 记录日志 loki 日志系统_linux

Loki 日志存储架构:

iotop 记录日志 loki 日志系统_loki_02

如图所示,Loki 包含Distributor、Ingester、Querier和可选的Query frontend五个组件。每个组件都会起一个用于处理内部请求的 gRPC 服务器和一个用于处理外部 API 请求的 HTTP/1服务器

i. Distributor
Distributor 是客户端连接的组件,用于收集日志
在 promtail 收集并将日志发送给Loki 之后, Distributor 就是第一个接收它们的组件,每秒可以接收数百万次写入。Distributor会对接收到的日志流进行正确性校验,并将验证后的chunk日志块分批并行发送到Ingester。

ii. Ingester

Ingester 接收来自Distributor的日志流,并将日志压缩后存放到所连接的存储后端。

iotop 记录日志 loki 日志系统_loki_03


Ingester接受日志流并构建数据块,其操作通常是压缩和追加日志。每个Ingester 的生命周期有PENDING, JOINING, ACTIVE, LEAVING 和 UNHEALTHY 五种状态。处于JOINING和ACTIVE状态的Ingester可以接受写请求,处于ACTIVE和LEAVING状态时可以接受读请求。

Ingester 将收到的日志流在内存中打包成 chunks ,并定期同步到存储后端。由于存储的数据类型不同,Loki 的数据块和索引可以使用不同的存储

当满足以下条件时,chunks 会被标记为只读:

当前 chunk 达到配置的最大容量
当前 chunk 长时间没有更新
发生了定期同步
当旧的 chunk 经过了压缩并被打上了只读标志后,新的可写的 chunk 就会生成

iii. Querier
Querier 用来查询日志,可以直接从 Ingester 和后端存储中查询数据。当客户端给定时间区间和标签选择器之后,Querier 就会查找索引来确定所有匹配 chunk ,然后对选中的日志进行 grep并返回查询结果。查询时,Querier先访问所有Ingester用于获取其内存数据,只有当内存中没有符合条件的数据时,才会向存储后端发起同样的查询请求。
需要注意的是,对于每个查询,单个 Querier 会 grep 所有相关的日志。目前 Cortex 中已经实现了并行查询,该功能可以扩展到 Loki,通过分布式的 grep 加速查询。此外,由于副本因子的存在,Querier可能会接收到重复的数据,所以其内置了去重的功能,对拥有同样时间戳、标签组和消息内容的日志进行去重处理

Loki与其他日志聚合系统差别:

  • 不对日志进行全文本索引。通过存储压缩的,非结构化的日志以及仅索引元数据,Loki更加易于操作且运行成本更低
  • 使用与Prometheus相同的标签对日志流进行索引和分组,从而使您能够使用与Prometheus相同的标签在指标和日志之间无缝切换。
  • 特别适合存储Kubernetes Pod日志。诸如Pod标签之类的元数据会自动被抓取并建立索引
  • 在Grafana中原生支持(需要Grafana v6.0及以上)

集群部署

Loki官方提供了 4 种安装方式:

  1. 通过 Tanka 安装
  2. 通过 Helm 安装
  3. 通过Docker 或Docker Compose安装
  4. 二进制包安装

接下来使用二进制包安装的方式进行安装部署

一、loki

#下载压缩文件
curl -O -L "https://github.com/grafana/loki/releases/download/v2.0.0/loki-linux-amd64.zip"
#解压文件
unzip "loki-linux-amd64.zip"
#执行文件授权
chmod a+x "loki-linux-amd64"
#下载Loki和Promtail的配置文件
wget https://raw.githubusercontent.com/grafana/loki/master/cmd/promtail/promtail-local-config.yaml

简单版本的yaml文件配置如下:

auth_enabled: false

server:
  http_listen_port: 3100
  grpc_listen_port: 39095 #grpc监听端口,默认为9095
  grpc_server_max_recv_msg_size: 15728640  #grpc最大接收消息值,默认4m
  grpc_server_max_send_msg_size: 15728640  #grpc最大发送消息值,默认4m

ingester:
  lifecycler:
    address: 127.0.0.1 #IP地址
    ring:
      kvstore:
        store: inmemory
      replication_factor: 1
    final_sleep: 0s
  chunk_idle_period: 5m
  chunk_retain_period: 30s
  max_transfer_retries: 0
  max_chunk_age: 20m  #一个timeseries块在内存中的最大持续时间。如果timeseries运行的时间超过此时间,则当前块将刷新到存储并创建一个新块

schema_config:
  configs:
    - from: 2018-04-15
      # index 使用的存储
      store: boltdb
      # chunks 使用的存储
      object_store: filesystem
      schema: v11
      index:
        prefix: index_
        period: 168h

# 指定 index 和 chunks 可能使用的存储配置,具体使用哪个由 schema_config配置决定
storage_config:
  boltdb:
    directory: /opt/loki/loki_data/index
#启动Loki命令
nohup ./loki-linux-amd64 -config.file=loki-local-config.yaml  > loki.log 2>&1 &
#查看启动是否成功(查看3100端口的进程是否存在)
netstat -tunlp | grep 3100
#或者根据名称查找进程(执行命令后有下边的显示,则启动成功)
ps -ef | grep loki-linux-amd64
$ root     11037 22022  0 15:44 pts/0    00:00:55 ./loki-linux-amd64 -config.file=loki-local-config.yaml
#或者查看日志信息loki.log

二、promtail

#下载压缩文件
curl -O -l "https://github.com/grafana/loki/releases/download/v2.0.0/promtail-linux-amd64.zip"
#解压文件
unzip promtail-linux-amd64.zip
#执行文件授权
chmod a+x promtail-linux-amd64
#下载Promtail的配置文件
wget https://raw.githubusercontent.com/grafana/loki/master/cmd/promtail/promtail-local-config.yaml

更多参数请看官网https://grafana.com/docs/loki/latest/clients/promtail/configuration/

简单版本的yaml文件配置如下:

# 配置promtail作为一个服务器,开启一个http端口
server:
  http_listen_port: 9080
  grpc_listen_port: 0

# 指明promtail的配置文件在什么地方生成,重启的时候会读取一些信息
positions:
  filename: /tmp/positions.yaml

# 配置promtail怎么连接loki,它作为loki的客户端
clients:
  - url: http://172.30.162.116:3100/loki/api/v1/push  #loki服务器地址

# 配置一些常用的抓取策略
scrape_configs:
- job_name: jmxlog
  static_configs:
  - targets:
      - localhost
    labels:
      job: jmxlogs
      __path__: /app/jmxlog/*.log   # 收集日志的位置
# Promtail启动
nohup ./promtail-linux-amd64 -config.file=promtail-local-config.yaml > promtail.log 2>&1 &
# 确认服务启动
# 可以通过查看日志文件promtail.log或者查看端口9080进程是否启动成功

三、grafana

#下载安装grafana命令,下列命令执行成功后。在/usr/sbin文件夹下会有grafana-server执行文件
wget https://dl.grafana.com/oss/release/grafana-7.3.2-1.x86_64.rpm
# 安装grafana包
yum install grafana-7.3.2-1.x86_64.rpm
#启动grafana,grafana会占用服务器3000端口,记得保证3000端口不被占用
cd /usr/sbin
# 启动
service grafana-server start

添加数据源

1. 访问web页面

http://localhost:3000/ 进行登录(账号密码都是admin),点击下图中的位置,找到Loki,配置数据源

iotop 记录日志 loki 日志系统_loki_04


2. 保存数据源

填写数据源的访问地址并保存

iotop 记录日志 loki 日志系统_数据源_05


3. 日志查看

配置好数据源之后就可以点击下图中的位置,进行日志查看

iotop 记录日志 loki 日志系统_linux_06

4. 日志查看效果

iotop 记录日志 loki 日志系统_iotop 记录日志_07

此处搜索的{job=“jmxlogs”}为promtail配置

5. 查询基本语法
= 完全相同。
!= 不平等。
=~ 正则表达式匹配。
!~ 不要正则表达式匹配。

{job=“mysql”} |= “error”
 {name=“kafka”} |~ “tsdb-ops.*io:2017”

fagent日志

环境已搭建ok,那么接下来要解决的是把需要监控和分析的信息生成并写入到相应的位置
前面有说到目前需要捕获的信息分两部分,一个是fagent java程序日志信息,还有一个是fagent执行的请求样本

1. fagent java程序日志信息
fagent使用的是Spring Boot框架,引入SLF4J,然后写入日志信息

@Slf4j

log.info("agent心跳上报: " + slaveHeartBeatEntity.toString());

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%level][%C:%L][%traceId][%userId] %m%n"/>
        </Console>
        <RollingFile name="RollingFile" fileName="logs/swqa-fagent.log"
                     filePattern="logs/swqa-fagent-%d{yyyy-MM-dd}.log">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%level][%C:%L][%traceId][%userId] %m%n" />
            <Policies>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
        <Async name="Async">
            <AppenderRef ref="Console"/>
            <AppenderRef ref="RollingFile"/>
        </Async>
    </Appenders>

    <Loggers>
        <Root level="INFO">
            <AppenderRef ref="Async"/>
        </Root>
    </Loggers>
</Configuration>

2. fagent执行的请求样本

JmeterRunServiceImpl

public class JmeterRunServiceImpl{
	public void jmeterExecute(ExecParamDto execParamDto) {
		// 测试结果文件路径
        // jmx用例文件夹对应的相对路径名
        String jmxDir = fileName.substring(0, fileName.lastIndexOf("."));
        // 测试结果文件csv文件的名称
        String currentTimeStr = System.currentTimeMillis() + "";
        String Suffix4 = currentTimeStr.substring(currentTimeStr.length() - 4);
        String csvName = jmxDir.substring(jmxDir.lastIndexOf(File.separator) + 1) + Suffix4 + ".csv";
        // 测试结果文件csv文件的真实路径
        String csvPath = casePath + File.separator + jmxDir + File.separator + csvName;
	}
}

JmeterResultCollector

public class JmeterResultCollector extends ResultCollector {
	@Override
    public void sampleOccurred(SampleEvent sampleEvent) {
		super.sampleOccurred(sampleEvent);
	}
}

问题排查

在搭建过程中遇到一些奇奇怪怪的问题,下面记录其中一些问题,mark一下

一、grafana添加loki数据源报502错误

iotop 记录日志 loki 日志系统_loki_08


报错502,开始以为是链路啥问题,查看了loki的启动日志

iotop 记录日志 loki 日志系统_linux_09


loki的yaml文件的读取失败,修改了config的信息,解决问题

二、grafana添加loki数据源报400错误

iotop 记录日志 loki 日志系统_linux_10


看报错信息,现在是的时间阶段,开始判断是否因为查询的时间太长,但是调整时间和yaml文件的初始时间,错误仍然存在查看github上的issue,大部分的报错都是这个400,且提示时间段的信息

那还是通过日志看看吧

iotop 记录日志 loki 日志系统_iotop 记录日志_11


原来是loki的yaml文件中auth_enabled: false,漏了首字母a导致,略坑

三、grafana启动失败

根据网上的文档,下载7.3.2版本的grafana,然后通过

cd /usr/sbin
./grafana-server start

命令启动提示失败

iotop 记录日志 loki 日志系统_数据源_12


更换几种启动服务的方式

cd /usr/sbin
./grafana-server web

失败

/etc/init.d/grafana-server start

失败

最后使用的启动命令:

cd /usr/sbin
service ./grafana-server start

iotop 记录日志 loki 日志系统_服务器_13


启动成功