一、背景

  • 非K8S集群节点,单独部署某个特殊应用或Job的虚机;
  • 各个应用间的日志数据非同一目录;
  • 当前K8S环境中已经部署了Loki日志管理系统;

二、非K8S集群虚机日志收集

2.1 安装和配置Promtail

创建数据目录

mkdir -p /mnt/config

数据采集数据配置 /mnt/config/promtail-config.yaml

[root@master01 ~]# vim /mnt/config/promtail-config.yaml
#用于提供服务并接收来自其他组件或客户端的请求
server:    
  http_listen_port: 9080
  grpc_listen_port: 0

#用于配置追踪和同步日志文件读取位置的设置
positions:              
  filename:  /tmp/positions.yaml #指定位置信息存储的文件路径和文件名
  sync_period: 10s

clients:        #指定与Loki服务器进行通信的客户端配置
  - url: http://loki.zhang-qing.com/loki/api/v1/push  #推送的数据接口

scrape_configs:   #用于配置抓取日志的目标和标签的设置。
- job_name: system
  static_configs: #指定静态目标(如主机)的配置列表
  - targets:
      - localhost
    labels:
      job: varlogs
      app: varlogs
      __path__: /var/log/*.log #需要分析的日志目录

容器化Promtail

# 启动容器
[root@master01 ~]# docker run -d --name promtail -v /mnt/config/:/mnt/config -v /var/log/:/var/log/ grafana/promtail:2.7.4 -config.file=/mnt/config/promtail-config.yaml

# 验证
[root@master01 ~]# docker ps 
CONTAINER ID   IMAGE                    COMMAND                  CREATED         STATUS         PORTS     NAMES
3782afe55735   grafana/promtail:2.7.4   "/usr/bin/promtail -…"   5 seconds ago   Up 4 seconds             promtail

针对Loki开启服务暴露,用来支持数据接收

# 定义ingress
[root@master01 9]# vim loki-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: logging
  name: loki-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: loki.zhang-qing.com
    http:
      paths:
        - pathType: Prefix
          backend:
            service:
              name: loki
              port:
                number: 3100
          path: /

# 应用
[root@master01 9]# kaf loki-ingress.yaml

# 验证
[root@master01 9]# kgi -n logging | grep loki.zhang-qing.com
loki-ingress      nginx   loki.zhang-qing.com              10.0.0.11   80      81s

2.2 测试验证

[root@master01 ~]# curl loki.zhang-qing.com/loki/api/v1/label
{"status":"success","data":["app","component","container","filename","instance","job","namespace","node_name","pod","stream"]}

虚机日志数据:

[root@master01 ~]# ls  /var/log/*.log
/var/log/boot.log              /var/log/vmware-network.5.log  /var/log/vmware-network.log
/var/log/vmware-network.1.log  /var/log/vmware-network.6.log  /var/log/vmware-vmsvc-root.log
/var/log/vmware-network.2.log  /var/log/vmware-network.7.log  /var/log/vmware-vmtoolsd-root.log
/var/log/vmware-network.3.log  /var/log/vmware-network.8.log  /var/log/yum.log
/var/log/vmware-network.4.log  /var/log/vmware-network.9.log

Grafana测试验证:

方法一:通过label进行筛选

image-20250416132105778

方法二:直接输入

{job="varlogs",filename="/var/log/mysqld.log"}

{job="varlogs",filename="/var/log/yum.log"}

Day09-可观察性-ELK&Loki-图27

Day09-可观察性-ELK&Loki-图28

三、使用Promtail收集Java应用日志发送给Loki

场景是一个Spring Boot的Java程序,日志框架使用了Logback。这个程序使用Logback将日志以文件形式写到磁盘上。

Logback的配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false" scan="false" scanPeriod="30 seconds">

    <contextName>myapp-name</contextName>

    <property name="logsPath" value="logs" />
    <timestamp key="currentMonth" datePattern="yyyyMM" />

    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
      <file>${logsPath}/${currentMonth}/myapp.log</file>
      <encoder>
        <charset>UTF-8</charset>
        <pattern>
          <![CDATA[
          %d{yyyy-MM-dd HH:mm:ss.SSS,+00:00} [%t] ${SPRING_PROFILES_ACTIVE} %p %logger ${CONTEXT_NAME} - %m%n
          ]]>
        </pattern>
      </encoder>
    </appender>

    <logger name="com.myapp" additivity="false" level="INFO">
      <appender-ref ref="FILE" />
    </logger>

    <root level="WARN">
      <appender-ref ref="FILE" />
    </root>
</configuration>

其中FileAppenderencoder.pattern决定了日志文件中每行日志的格式。处理java应用的日志是需要关注多行日志模式的,即一条日志可能由多行文本组成。例如:

2023-07-18 20:43:20     
[pool-8-thread-1] dev ERROR com.myapp.ProjectQuery myapp-name - query error
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
        at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)
        at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)
        at com.sun.proxy.$Proxy108.selectOne(Unknown Source)
        at org.mybatis.spring.SqlSessionTemplate.selectOne(SqlSessionTemplate.java:166)
        at org.apache.ibatis.binding.MapperMethod.execute(MapperMethod.java:83)
        at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:59)
...

针对这个例子,Java应用使用logback将日志按照固定格式落盘,与应用在同一服务器节点上的Promtail将跟踪日志文件,解析日志并将日志发送到Loki。

promtail.yaml配置如下:

server:
  disable: true

clients:
- url: http://loki.zhang-qing.com/loki/api/v1/push
  tenant_id: org1

positions:
  filename: /app/logs/positions.yaml

target_config:
  sync_period: 10s

scrape_configs:
- job_name: java_logs
  static_configs:
  - targets:
      - localhost
    labels:
      job: java_logs
      __path__: /app/logs/*/*.log
  pipeline_stages:
  - multiline:
      firstline: '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}'
      max_lines: 256
      max_wait_time: 5s
  - regex:
      expression: '^(?P<time>\d{4}\-\d{2}\-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) (?P<message>\[(?P<thread>.*?)\] (?P<profileName>[^\s]+) (?P<level>[^\s]+) (?P<logger>[^\s]+) (?P<contextName>[^\s]+) - [\s\S]*)'
  - labels:
      contextName:
      profileName:
      level:
  - timestamp:
      source: time
      format: '2006-01-02 15:04:05.999'
      location: "UTC"
  - drop:
      older_than: 120h
      drop_counter_reason: "line_too_old"
  - labeldrop:
    - filename
  -  output:
      source: message

server.disable=true:禁用Promtail的HTTP和gRPC服务监听

clients:块配置了Promtail如何连接到Loki的实例,配置了loki write的地址,以及使用的租户id

positions.filename:设置了Promtail读取日志文件时记录读取位置的文件

scrape_configs:中配置了一个job java_logs,将跟踪匹配 /app/logs/*/*.log的日志,并为其配置了 multiline, regex, labels, timestamp, drop, labeldrop, output等7个 pipeline stage。

  • multiline:将多行合并成一个多行块,然后将其传递到管道中的下一个阶段。

  • regex:接受一个正则表达式,并提取捕获的命名分组,以便在后续阶段中使用。

  • labels:从前面regex阶段获取到contextName, profileName, level作为日志的label标记。

  • timestamp:从提取的map中解析数据,并覆盖Loki存储的日志的最终时间值。如果没有这个阶段,Promtail将日志条目的时间戳与读取该条目的时间关联起来。

  • drop:是一个筛选阶段,可以根据多个选项丢弃日志。这里配置的是丢弃120小时

之前的日志。

  • labeldrop:从发送到Loki的日志条目的标签集合中删除标签,这里我们将filename这个标签删除了。

  • output:从提取的map中获取数据并更改将发送到Loki的日志行。

四、生产环境中Loki的优化

4.1 Loki 中保留日志时长

当日志传送到 Loki,由 Loki 来存储日志,不可能将日志永久的存储在 Loki 服务器,也需要按照实际需求做数据保留!

在 Loki 配置文件中,做如下配置:

limits_config:
  reject_old_samples: true   #是否拒绝旧样本
  reject_old_samples_max_age: 72h   #72小时之前的样本被拒绝

chunk_store_config:
  max_look_back_period: 72h  #为避免查询超过保留期的数据,必须小于或等于下方的时间值
table_manager:
  retention_deletes_enabled: true   #保留删除开启
  retention_period: 72h  #超过72h的块数据将被删除

4.2 Grafana中Loki日志显示行数

Grafana中Loki日志的默认显示行数为 1000,很多博文中都说在下图中更改即可,只不过查询时间较长。

Day09-可观察性-ELK&Loki-图30

对于 Loki 配置来说,默认最大值是 5000 行,这里无法显示超过 1000 行,还需修改 Grafana 的 Data Sources 中的最大行数的值为 5000 以内。

依次点击【Configuration】-【Data sources】

image-20250416133245127

点击【Loki】

image-20250416133408562

找到Maximum lines,设置为5000

Day09-可观察性-ELK&Loki-图31

如果查询比 5000 更大的行数,需要修改 Loki 服务的配置文件:

limits_config:
  # 没有该配置添加即可,数值改为自己想要的最大查询行数
  max_entries_limit_per_query: 9999

最后重启服务,再次修改 Grafana 的 Data Sources 中的行数限制即可。