基于Loki的日志收集系统

K8S轻量级日志收集方案LPG(Loki+Promtail+Grafana)

1、EFK vs LPG

架构和组件

  • Loki:Loki 是一个开源的水平可扩展日志聚合系统,由 Promtail、Loki 和 Grafana组成。
  • EFK:EFK 是一个集成的解决方案,由 Elasticsearch、Fluentd 和 Kibana 组成。

存储和查询

  • Loki:Loki 使用了基于日志流的存储方式,将日志数据存储为可压缩的块文件,并达到高度压缩效率。
  • EFK:EFK 使用 Elasticsearch 作为中心化的日志存储和索引引擎。

可扩展性和资源消耗

  • Loki:Loki 的水平可扩展性非常好,可以处理大规模的日志数据。
  • EFK:Elasticsearch 是一个高度可扩展的分布式存储系统,但它对硬件资源的要求较高,特别是在存储大规模日志数据时。

配置和部署复杂性

  • Loki:Loki 的配置和部署较为简单。通过使用 Promtail 收集日志,并使用 Grafana进行查询和可视化,可以相对快速地启动和使用。
  • EFK:EFK 的配置和部署相对复杂一些。需要配置 Fluentd 的输入、过滤和输出插件,以及 Elasticsearch 和 Kibana 的集群设置。

2、LPG简介

Grafana Loki:https://grafana.com/docs/loki/latest/Github

Loki:https://github.com/grafana/helm-charts/tree/main/charts/loki-stack

1、Loki架构

  1. Promtail(采集器):Loki 默认客户端,负责采集并上报日志。
  2. Distributor(分发器): Distributor 是 Loki 的入口组件,负责接收来自客户端的日志数据,并将其分发给不同的 ingester 节点。
  3. Ingester(摄取器): Ingester 负责接收并保存来自 Distributor 的日志数据。它将数据写入本地存储,并将索引相关的元数据发送给 index 组件。
  4. Index(索引): Index 组件负责管理和维护 Loki 中的索引数据结构。
  5. Chunks(块文件): Chunks 是 Loki 中日志数据的物理存储形式。
  6. Querier(查询器): Querier 是用于查询 Loki 中日志数据的组件。
基于Loki的日志收集系统

2、日志收集方式

Promtail 客户端采集日志数据,将其索引并存储在后端持久化存储中。

用户可以使用 LogQL 查询语言来过滤和检索特定的日志记录,并通过 Grafana 的集成来进行可视化分析。

基于Loki的日志收集系统

3、部署配置

1、数据配置

添加 Loki 的 Chart 仓库:

$ helm repo add grafana https://grafana.github.io/helm-charts
$ helm repo update

获取 loki-stack 的 Chart 包并解压:

$ helm search repo loki
$ helm pull grafana/loki-stack --untar --version 2.9.10

修改所需的 values.yaml

test_pod:
  enabled: true
  image: bats/bats:1.8.2
  pullPolicy: IfNotPresent
...

loki:
  enabled: true
  persistence:
    enabled: true
    storageClassName: nfs-storage
    accessModes:
      - ReadWriteOnce
    size: 30Gi
  isDefault: true
  url: http://{{(include "loki.serviceName" .)}}:{{ .Values.loki.service.port }}
...

promtail:
  enabled: true
  config:
    logLevel: info
    serverPort: 3101
    clients:
      - url: http://{{ .Release.Name }}:3100/loki/api/v1/push
    limits_config:
      ingestion_rate_strategy: local
      ingestion_rate_mb: 15
      ingestion_burst_size_mb: 20  
...

grafana:
  enabled: true
  persistence:
    enabled: true
    storageClassName: nfs-storage
    accessModes:
      - ReadWriteOnce
    size: 10Gi

2、部署验证

$ kubectl create ns logging
$ helm upgrade --install loki -n logging -f values.yaml . 

查看验证:

$ kubectl get pods -n logging |grep loki
$ kubectl -n logging get svc |grep loki

获取grafana的密码:

$ kubectl get secret --namespace logging loki-grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

创建ing:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  namespace: logging
  name: grafana-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: grafana-logging.kubernets.cn
    http:
      paths:
        - pathType: Prefix
          backend:
            service:
              name: loki-grafana
              port:
                number: 80
          path: /

测试验证:

$ curl grafana-logging.kubernets.cn -i

使用用户名admin和上面的获取的密码即可登录 Grafana;

由于 Helm Chart 已经为 Grafana 配置好了 Loki 的数据源,所以我们可以直接获取到日志数据了。

点击左侧Explore菜单,然后就可以筛选 Loki 的日志数据了:

基于Loki的日志收集系统
基于Loki的日志收集系统
基于Loki的日志收集系统

使用 Helm 安装的 Promtail 默认已经帮我们做好了配置,已经针对 Kubernetes 做了优化,我们可以查看其配置:

$ kubectl get secret loki-promtail -n logging -o json | jq -r '.data."promtail.yaml"' | base64 --decode
server:
  log_level: info
  http_listen_port: 3101


clients:
  - url: http://loki:3100/loki/api/v1/push

positions:
  filename: /run/promtail/positions.yaml

scrape_configs:
  # See also https://github.com/grafana/loki/blob/master/production/ksonnet/promtail/scrape_config.libsonnet for reference
  - job_name: kubernetes-pods
    pipeline_stages:
      - cri: {}
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      - source_labels:
          - __meta_kubernetes_pod_controller_name
        regex: ([0-9a-z-.]+?)(-[0-9a-f]{8,10})?
        action: replace
        target_label: __tmp_controller_name
      - source_labels:
          - __meta_kubernetes_pod_label_app_kubernetes_io_name
          - __meta_kubernetes_pod_label_app
          - __tmp_controller_name
          - __meta_kubernetes_pod_name
        regex: ^;*([^;]+)(;.*)?$
        action: replace
        target_label: app
      - source_labels:
          - __meta_kubernetes_pod_label_app_kubernetes_io_instance
          - __meta_kubernetes_pod_label_release
        regex: ^;*([^;]+)(;.*)?$
        action: replace
        target_label: instance
      - source_labels:
          - __meta_kubernetes_pod_label_app_kubernetes_io_component
          - __meta_kubernetes_pod_label_component
        regex: ^;*([^;]+)(;.*)?$
        action: replace
        target_label: component
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: node_name
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        replacement: $1
        separator: /
        source_labels:
        - namespace
        - app
        target_label: job
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_name
        target_label: pod
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_container_name
        target_label: container
      - action: replace
        replacement: /var/log/pods/*$1/*.log
        separator: /
        source_labels:
        - __meta_kubernetes_pod_uid
        - __meta_kubernetes_pod_container_name
        target_label: __path__
      - action: replace
        regex: true/(.*)
        replacement: /var/log/pods/*$1/*.log
        separator: /
        source_labels:
        - __meta_kubernetes_pod_annotationpresent_kubernetes_io_config_hash
        - __meta_kubernetes_pod_annotation_kubernetes_io_config_hash
        - __meta_kubernetes_pod_container_name
        target_label: __path__

4、Loki查询案例

1、日志选择器

对于查询表达式的标签部分,将其用大括号括起来{},然后使用键值语法选择标签。多个标签表达式用逗号分隔:

= 完全相等。
!= 不相等。
=~ 正则表达式匹配。
!~ 不进行正则表达式匹配。

# 根据任务名称来查找日志
{app="ingress-nginx"}
{job="devops/metallb"}
{namespace="default",app="podstdr2"}
{app=~"kube-state-metrics|prometheus|zookeeper"}

2、使用日志过滤器来查找

编写日志流选择器后,您可以通过编写搜索表达式来进一步过滤结果

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

regex表达式接受RE2语法。默认情况下,匹配项区分大小写,并且可以将regex切换为不区分大小写的前缀(?i)。

1. 精确查找名称空间为logging下container为zookeeper且包含有INFO关键字的日志
{namespace="logging",container="zookeeper"} |= "INFO"

2. 正则查找
{job="huohua/svc-huohua-batch"} |~ "(duration|latency)s*(=|is|of)s*[d.]+"

3. 不包含。
{job="mysql"} |= "error" != "timeout"

5、常见问题

问题一

提示找不到/var/log/pods目录下的日志文件,无法tail。

level=error ts=2023-07-17T03:22:11.682802445Z caller=filetarget.go:307 msg="failed to tail file, stat failed" error="stat /var/log/pods/kube-system_kube-apiserver-master3_a8daf137c2a2ea7ef925aaef1e82ac16/kube-apiserver/13.log: no such file or directory" filename=/var/log/pods/kube-system_kube-apiserver-master3_a8daf137c2a2ea7ef925aaef1e82ac16/kube-apiserver/13.log
level=error ts=2023-07-17T03:22:11.682823944Z caller=filetarget.go:307 msg="failed to tail file, stat failed" error="stat /var/log/pods/kube-system_kube-scheduler-master3_bdef86673f60f833d12eb8a3ad337fac/kube-scheduler/1.log: no such file or directory" filename=/var/log/pods/kube-system_kube-scheduler-master3_bdef86673f60f833d12eb8a3ad337fac/kube-scheduler/1.log
{
    "name": "docker",
    "hostPath": {
        "path": "/var/lib/docker/containers",
        "type": ""
    }
},
    {
    "name": "pods",
    "hostPath": {
        "path": "/var/log/pods",
        "type": ""
    }
}

但是我们这边真实的企业场景是将docker的数据目录挂载磁盘/data目录下,所以需要修改默认volumes配置。

修改步骤:

$ vim values.yaml
promtail:
  enabled: true
  extraVolumes:
    - name: docker
      hostPath:
        path: /data/docker/containers
  extraVolumeMounts:
    - name: docker
      mountPath: /data/docker/containers
      readOnly: true
  config:
    logLevel: info
    serverPort: 3101
    clients:
      - url: http://{{ .Release.Name }}:3100/loki/api/v1/push
[root@node1 log]# ll /var/log/pods/monitoring_promtail-bs5cs_5bc5bc90-bac9-480d-b291-4caadeff2236/promtail/
total 4
lrwxrwxrwx 1 root root 162 Dec 17 14:04 0.log -> /data/docker/containers/db45d5118e9508817e1a2efa3c9da68cfe969a2b0a3ed42619ff61a29cc64e5f/db45d5118e9508817e1a2efa3c9da68cfe969a2b0a3ed42619ff61a29cc64e5f-json.log

问题二

Loki日志系统收集日志报429错误:

level=warn ts=2023-07-17T03:42:34.456086325Z caller=client.go:369 component=client host=loki:3100 msg="error sending batch, will retry" status=429 error="server returned HTTP status 429 Too Many Requests (429): Ingestion rate limit exceeded for user fake (limit: 4194304 bytes/sec) while attempting to ingest '5381' lines totaling '1048504' bytes, reduce log volume or contact your Loki administrator to see if the limit can be increased"
level=warn ts=2023-07-17T03:42:35.144739805Z caller=client.go:369 component=client host=loki:3100 msg="error sending batch, will retry" status=429 error="server returned HTTP status 429 Too Many Requests (429): Ingestion rate limit exceeded for user fake (limit: 4194304 bytes/sec) while attempting to ingest '5381' lines totaling '1048504' bytes, reduce log volume or contact your Loki administrator to see if the limit can be increased"

收集的日志太多了,超过了 loki 的限制,所以会报 429 错误,如果你要增加限制可以修改 loki 的配置文件:

promtail:
  enabled: true
  extraVolumes:
    - name: docker
      hostPath:
        path: /data/docker/containers
  extraVolumeMounts:
    - name: docker
      mountPath: /data/docker/containers
      readOnly: true
  config:
    logLevel: info
    serverPort: 3101
    clients:
      - url: http://{{ .Release.Name }}:3100/loki/api/v1/push
    limits_config:
      # 将直接将日志数据发送到运行在本地的 Loki 实例
      ingestion_rate_strategy: local
      # 每个用户每秒的采样率限制
      ingestion_rate_mb: 15
      # 每个用户允许的采样突发大小
      ingestion_burst_size_mb: 20
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
EFKStack云原生

海量数据下的EFK架构优化升级

2025-7-15 4:27:36

Kubernetes云原生

基于Kubernetes的Ingress-Nginx解决方案

2025-7-15 6:19:04

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索
本站支持IPv6访问