前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Kubernetes集群日志-使用Loki实现高效日志分析和查询

Kubernetes集群日志-使用Loki实现高效日志分析和查询

作者头像
王先森sec
发布2023-12-11 11:15:13
8842
发布2023-12-11 11:15:13
举报
文章被收录于专栏:王先森王先森

虚拟化运维LogKubernetes

Kubernetes集群日志-使用Loki实现高效日志分析和查询

王先森2023-12-102023-12-10

简介

项目地址 官方文档

Grafana Loki 是一个水平可扩展,高可用性,多租户的日志聚合系统,Loki 是基于仅索引有关日志元数据的想法而构建的:标签(就像 Prometheus 标签一样)。日志数据本身被压缩然后并存储在对象存储(例如 S3 或 GCS)的块中,甚至存储在本地文件系统上,轻量级的索引和高度压缩的块简化了操作,并显著降低了 Loki 的成本,Loki 更适合中小团队。由于 Loki 使用和 Prometheus 类似的标签概念,所以如果你熟悉 Prometheus 那么将很容易上手,也可以直接和 Grafana 集成,只需要添加 Loki 数据源就可以开始查询日志数据了。

Loki 还提供了一个专门用于日志查询的 LogQL 查询语句,类似于 PromQL,通过 LogQL 我们可以很容易查询到需要的日志,也可以很轻松获取监控指标。Loki 还能够将 LogQL 查询直接转换为 Prometheus 指标。此外 Loki 允许我们定义有关 LogQL 指标的报警,并可以将它们和 Alertmanager 进行对接。

Grafana Loki 主要由 3 部分组成:

  • loki: 日志记录引擎,负责存储日志和处理查询
  • promtail: 代理,负责收集日志并将其发送给 loki
  • grafana: UI 界面

整体架构

在 Loki 架构中有以下几个概念:

  • Grafana:相当于 EFK 中的 Kibana ,用于 UI 的展示。
  • Loki:相当于 EFK 中的 ElasticSearch ,用于存储日志和处理查询。
  • Promtail:相当于 EFK 中的 Filebeat/Fluentd ,用于采集日志并将其发送给 Loki 。
  • LogQL:Loki 提供的日志查询语言,类似 Prometheus 的 PromQL,而且 Loki 支持 LogQL 查询直接转换为 Prometheus 指标。

运行模式

Loki 运行模式
Loki 运行模式

Loki 针对本地运行(或小规模运行)和水平扩展进行了优化,Loki 带有单一进程模式,可在一个进程中运行所有必需的微服务。单进程模式非常适合测试 Loki 或以小规模运行。为了实现水平可伸缩性,可以将 Loki 的服务拆分为单独的组件,从而使它们彼此独立地扩展。每个组件都产生一个用于内部请求的 gRPC 服务器和一个用于外部 API 请求的 HTTP 服务,所有组件都带有 HTTP 服务器,但是大多数只暴露就绪接口、运行状况和指标端点。

Loki 运行哪个组件取决于命令行中的 -target 标志或 Loki 的配置文件中的 target:<string> 配置。 当 target 的值为 all 时,Loki 将在单进程中运行其所有组件。,这称为单进程单体模式。 使用 Helm 安装 Loki 时,单体模式是默认部署方式。

当 target 未设置为 all(即被设置为 querieringesterquery-frontenddistributor),则可以说 Loki 在水平伸缩微服务模式下运行。

Loki 的每个组件,例如 ingesterdistributors 都使用 Loki 配置中定义的 gRPC 监听端口通过 gRPC 相互通信。当以单体模式运行组件时,仍然是这样的,尽管每个组件都以相同的进程运行,但它们仍将通过本地网络相互连接进行组件之间的通信。

单体模式非常适合于本地开发、小规模等场景,单体模式可以通过多个进程进行扩展,但有以下限制:

  • 当运行带有多个副本的单体模式时,当前无法使用本地索引和本地存储,因为每个副本必须能够访问相同的存储后端,并且本地存储对于并发访问并不安全。
  • 各个组件无法独立缩放,因此读取组件的数量不能超过写入组件的数量。

组件

Loki 组件
Loki 组件

Distributor

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

Hashing

distributor一致性 Hash和可配置的复制因子结合使用,以确定 ingester 服务的哪些实例应该接收指定的数据流。

流是一组与租户和唯一标签集关联的日志,使用租户 ID 和标签集对流进行 hash 处理,然后使用哈希查询要发送流的 ingester

存储在 Consul/Etcd 中的哈希环被用来实现一致性哈希,所有的 ingester 都会使用自己拥有的一组 Token 注册到哈希环中,每个 Token 是一个随机的无符号 32 位数字,与一组 Token 一起,ingester 将其状态注册到哈希环中,状态 JOININGACTIVE 都可以接收写请求,而 ACTIVELEAVINGingester 可以接收读请求。在进行哈希查询时,distributor 只使用处于请求的适当状态的 ingester 的 Token。

为了进行哈希查找,distributor 找到最小合适的 Token,其值大于日志流的哈希值,当复制因子大于 1 时,属于不同 ingester 的下一个后续 Token(在环中顺时针方向)也将被包括在结果中。

这种哈希配置的效果是,一个 ingester 拥有的每个 Token 都负责一个范围的哈希值,如果有三个值为 0、25 和 50 的 Token,那么 3 的哈希值将被给予拥有 25 这个 Token 的 ingester,拥有 25 这个 Token 的 ingester 负责1-25的哈希值范围。

Ingester

ingester 负责接收 distributor 发送过来的日志数据,存储日志的索引数据以及内容数据。此外 ingester 会验证摄取的日志行是否按照时间戳递增的顺序接收的(即每条日志的时间戳都比前面的日志晚一些),当 ingester 收到不符合这个顺序的日志时,该日志行会被拒绝并返回一个错误。

  • 如果传入的行与之前收到的行完全匹配(与之前的时间戳和日志文本都匹配),传入的行将被视为完全重复并被忽略。
  • 如果传入的行与前一行的时间戳相同,但内容不同,则接受该日志行,表示同一时间戳有两个不同的日志行是可能的。

来自每个唯一标签集的日志在内存中被建立成 chunks(块),然后可以根据配置的时间间隔刷新到支持的后端存储。在下列情况下,块被压缩并标记为只读:

  • 当前块容量已满(该值可配置)
  • 过了太长时间没有更新当前块的内容
  • 刷新了

每当一个数据块被压缩并标记为只读时,一个可写的数据块就会取代它。如果一个 ingester 进程崩溃或突然退出,所有尚未刷新的数据都会丢失,Loki 通常配置为多个副本来降低这种风险。

当向持久存储刷新时,该块将根据其租户、标签和内容进行哈希处理,这意味着具有相同数据副本的多个 ingester 实例不会将相同的数据两次写入备份存储中,但如果对其中一个副本的写入失败,则会在备份存储中创建多个不同的块对象。

WAL

上面我们提到了 ingester 将数据临时存储在内存中,如果发生了崩溃,可能会导致数据丢失,而 WAL 就可以帮助我们来提高这方面的可靠性。

在计算机领域,WAL(Write-ahead logging,预写式日志)是数据库系统提供原子性和持久化的一系列技术。

在使用 WAL 的系统中,所有的修改都先被写入到日志中,然后再被应用到系统状态中。通常包含 redo 和 undo 两部分信息。为什么需要使用 WAL,然后包含 redo 和 undo 信息呢?举个例子,如果一个系统直接将变更应用到系统状态中,那么在机器断电重启之后系统需要知道操作是成功了,还是只有部分成功或者是失败了(为了恢复状态)。如果使用了 WAL,那么在重启之后系统可以通过比较日志和系统状态来决定是继续完成操作还是撤销操作。

redo log 称为重做日志,每当有操作时,在数据变更之前将操作写入 redo log,这样当发生断电之类的情况时系统可以在重启后继续操作。undo log 称为撤销日志,当一些变更执行到一半无法完成时,可以根据撤销日志恢复到变更之间的状态。

Loki 中的 WAL 记录了传入的数据,并将其存储在本地文件系统中,以保证在进程崩溃的情况下持久保存已确认的数据。重新启动后,Loki 将重放日志中的所有数据,然后将自身注册,准备进行后续写操作。这使得 Loki 能够保持在内存中缓冲数据的性能和成本优势,以及持久性优势(一旦写被确认,它就不会丢失数据)。

Querier

Querier 接收日志数据查询、聚合统计请求,使用 LogQL 查询语言处理查询,从 ingester 和长期存储中获取日志。

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

Query Frontend

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

查询前端在内部执行一些查询调整,并在内部队列中保存查询。querier 作为 workers 从队列中提取作业,执行它们,并将它们返回到查询前端进行汇总。querier 需要配置查询前端地址,以便允许它们连接到查询前端。

查询前端是无状态的,然而,由于内部队列的工作方式,建议运行几个查询前台的副本,以获得公平调度的好处,在大多数情况下,两个副本应该足够了。

队列

查询前端的排队机制用于:

  • 确保可能导致 querier 出现内存不足(OOM)错误的查询在失败时被重试。这样管理员就可以为查询提供稍低的内存,或者并行运行更多的小型查询,这有助于降低总成本。
  • 通过使用先进先出队列(FIFO)将多个大型请求分配到所有 querier 上,以防止在单个 querier 中进行多个大型请求。
  • 通过在租户之间公平调度查询。

分割

查询前端将较大的查询分割成多个较小的查询,在下游 querier 上并行执行这些查询,并将结果再次拼接起来。这可以防止大型查询在单个查询器中造成内存不足的问题,并有助于更快地执行这些查询。

缓存

查询前端支持缓存查询结果,并在后续查询中重复使用。如果缓存的结果不完整,查询前端会计算所需的子查询,并在下游 querier 上并行执行这些子查询。查询前端可以选择将查询与其 step 参数对齐,以提高查询结果的可缓存性。

读取路径

日志读取路径的流程如下所示:

  • 查询器收到一个对数据的 HTTP 请求。
  • 查询器将查询传递给所有 ingester
  • ingester 收到读取请求,并返回与查询相匹配的数据。
  • 如果没有 ingester 返回数据,查询器会从后端存储加载数据,并对其运行查询。
  • 查询器对所有收到的数据进行迭代和重复计算,通过 HTTP 连接返回最后一组数据。

写入路径

write path
write path

整体的日志写入路径如下所示:

  • distributor 收到一个 HTTP 请求,以存储流的数据。
  • 每个流都使用哈希环进行哈希操作。
  • distributor 将每个流发送到合适的 ingester 和他们的副本(基于配置的复制因子)。
  • 每个 ingester 将为日志流数据创建一个块或附加到一个现有的块上。每个租户和每个标签集的块是唯一的。

Promtail

官方文档

promtail 是 loki 架构中最常用的采集器, 相当于 EFK 中的 filebeat/fluentd

它的主要工作流程:

  • 使用 fsnotify 监听指定目录下(例如:/var/log/*.log)的文件创建与删除
  • 对每个活跃的日志文件起一个 goroutine 进行类似 tail -f 的读取,读取到的内容发送给 channel
  • 有一个单独的 goroutine 会读取 channel 中的日志行,分批并附加上标签后推送给 Loki

安装部署

这次部署的 loki 整体架构如下, loki 使用 StatefulSet 的方式运行, Promtail 以 DaemonSet 的方式运行在 k8s 集群的每个节点。

Promtail部署

NamespaceRBACConfigMapDaemonSet

代码语言:javascript
复制
# kubectl create namespace logging
apiVersion: v1
kind: Namespace
metadata:
  name: logging
代码语言:javascript
复制
apiVersion: v1
kind: ServiceAccount
metadata:
  name: loki-promtail
  labels:
    app: promtail
  namespace: logging

---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  labels:
    app: promtail
  name: promtail-clusterrole
  namespace: logging
rules:
- apiGroups: [""] 
  resources:
  - nodes
  - nodes/proxy
  - services
  - endpoints
  - pods
  verbs: ["get", "watch", "list"]

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: promtail-clusterrolebinding
  labels:
    app: promtail
  namespace: logging
subjects:
  - kind: ServiceAccount
    name: loki-promtail
    namespace: logging
roleRef:
  kind: ClusterRole
  name: promtail-clusterrole
  apiGroup: rbac.authorization.k8s.io
代码语言:javascript
复制
apiVersion: v1
kind: ConfigMap
metadata:
  name: loki-promtail
  namespace: logging
  labels:
    app: promtail
data:
  promtail.yaml: |
    client:                # 配置Promtail如何连接到Loki的实例
      backoff_config:      # 配置当请求失败时如何重试请求给Loki
        max_period: 1s
        max_retries: 20
        min_period: 500ms
      batchsize: 10240        # 发送给Loki的最大批次大小(以字节为单位)
      batchwait: 2s           # 发送批处理前等待的最大时间(即使批次大小未达到最大值)
      external_labels: {}     # 所有发送给Loki的日志添加静态标签
      timeout: 15s            # 等待服务器响应请求的最大时间
    positions:
      filename: /run/promtail/positions.yaml
    server:
      http_listen_port: 3101
    target_config:
      sync_period: 10s
    scrape_configs:
    - job_name: kubernetes-pods-name
      pipeline_stages:
        - docker: {}
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - source_labels:
        - __meta_kubernetes_pod_label_name
        target_label: __service__
      - source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: __host__
      - action: drop
        regex: ''
        source_labels:
        - __service__
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - action: replace
        replacement: $1
        separator: /
        source_labels:
        - __meta_kubernetes_namespace
        - __service__
        target_label: job
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_name
        target_label: pod
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_container_name
        target_label: container
      - replacement: /var/log/pods/*$1/*.log
        separator: /
        source_labels:
        - __meta_kubernetes_pod_uid
        - __meta_kubernetes_pod_container_name
        target_label: __path__
    - job_name: kubernetes-pods-app
      pipeline_stages:
        - docker: {}
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - action: drop
        regex: .+
        source_labels:
        - __meta_kubernetes_pod_label_name
      - source_labels:
        - __meta_kubernetes_pod_label_app
        target_label: __service__
      - source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: __host__
      - action: drop
        regex: ''
        source_labels:
        - __service__
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - action: replace
        replacement: $1
        separator: /
        source_labels:
        - __meta_kubernetes_namespace
        - __service__
        target_label: job
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_name
        target_label: pod
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_container_name
        target_label: container
      - replacement: /var/log/pods/*$1/*.log
        separator: /
        source_labels:
        - __meta_kubernetes_pod_uid
        - __meta_kubernetes_pod_container_name
        target_label: __path__
    - job_name: kubernetes-pods-direct-controllers
      pipeline_stages:
        - docker: {}
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - action: drop
        regex: .+
        separator: ''
        source_labels:
        - __meta_kubernetes_pod_label_name
        - __meta_kubernetes_pod_label_app
      - action: drop
        regex: '[0-9a-z-.]+-[0-9a-f]{8,10}'
        source_labels:
        - __meta_kubernetes_pod_controller_name
      - source_labels:
        - __meta_kubernetes_pod_controller_name
        target_label: __service__
      - source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: __host__
      - action: drop
        regex: ''
        source_labels:
        - __service__
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - action: replace
        replacement: $1
        separator: /
        source_labels:
        - __meta_kubernetes_namespace
        - __service__
        target_label: job
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_name
        target_label: pod
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_container_name
        target_label: container
      - replacement: /var/log/pods/*$1/*.log
        separator: /
        source_labels:
        - __meta_kubernetes_pod_uid
        - __meta_kubernetes_pod_container_name
        target_label: __path__
    - job_name: kubernetes-pods-indirect-controller
      pipeline_stages:
        - docker: {}
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - action: drop
        regex: .+
        separator: ''
        source_labels:
        - __meta_kubernetes_pod_label_name
        - __meta_kubernetes_pod_label_app
      - action: keep
        regex: '[0-9a-z-.]+-[0-9a-f]{8,10}'
        source_labels:
        - __meta_kubernetes_pod_controller_name
      - action: replace
        regex: '([0-9a-z-.]+)-[0-9a-f]{8,10}'
        source_labels:
        - __meta_kubernetes_pod_controller_name
        target_label: __service__
      - source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: __host__
      - action: drop
        regex: ''
        source_labels:
        - __service__
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - action: replace
        replacement: $1
        separator: /
        source_labels:
        - __meta_kubernetes_namespace
        - __service__
        target_label: job
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_name
        target_label: pod
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_container_name
        target_label: container
      - replacement: /var/log/pods/*$1/*.log
        separator: /
        source_labels:
        - __meta_kubernetes_pod_uid
        - __meta_kubernetes_pod_container_name
        target_label: __path__
    - job_name: kubernetes-pods-static
      pipeline_stages:
        - docker: {}
      kubernetes_sd_configs:
      - role: pod
      relabel_configs:
      - action: drop
        regex: ''
        source_labels:
        - __meta_kubernetes_pod_annotation_kubernetes_io_config_mirror
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_label_component
        target_label: __service__
      - source_labels:
        - __meta_kubernetes_pod_node_name
        target_label: __host__
      - action: drop
        regex: ''
        source_labels:
        - __service__
      - action: labelmap
        regex: __meta_kubernetes_pod_label_(.+)
      - action: replace
        replacement: $1
        separator: /
        source_labels:
        - __meta_kubernetes_namespace
        - __service__
        target_label: job
      - action: replace
        source_labels:
        - __meta_kubernetes_namespace
        target_label: namespace
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_name
        target_label: pod
      - action: replace
        source_labels:
        - __meta_kubernetes_pod_container_name
        target_label: container
      - replacement: /var/log/pods/*$1/*.log
        separator: /
        source_labels:
        - __meta_kubernetes_pod_annotation_kubernetes_io_config_mirror
        - __meta_kubernetes_pod_container_name
        target_label: __path__
代码语言:javascript
复制
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: loki-promtail
  namespace: logging
  labels:
    app: promtail
spec:
  selector:
    matchLabels:
      app: promtail
  updateStrategy:
    rollingUpdate:
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: promtail
    spec:
      serviceAccountName: loki-promtail
      containers:
        - name: promtail
          image: grafana/promtail:2.9.2
          imagePullPolicy: IfNotPresent
          args:
          - -config.file=/etc/promtail/promtail.yaml
          - -client.url=http://loki:3100/loki/api/v1/push
          env:
          - name: HOSTNAME
            valueFrom:
              fieldRef:
                apiVersion: v1
                fieldPath: spec.nodeName
          volumeMounts:
          - mountPath: /etc/promtail
            name: config
          - mountPath: /run/promtail
            name: run
          - mountPath: /data/docker/containers
            name: docker
            readOnly: true
          - mountPath: /var/log/pods
            name: pods
            readOnly: true
          ports:
          - containerPort: 3101
            name: http
            protocol: TCP
          securityContext:
            readOnlyRootFilesystem: true
            runAsGroup: 0
            runAsUser: 0
          readinessProbe:
            failureThreshold: 5
            httpGet:
              path: /ready
              port: http
              scheme: HTTP
            initialDelaySeconds: 10
            periodSeconds: 10
            successThreshold: 1
            timeoutSeconds: 1
      tolerations:
      - operator: Exists
      volumes:
        - name: config
          configMap:
            defaultMode: 420
            name: loki-promtail
        - name: run
          hostPath:
            path: /run/promtail
            type: ""
        - name: docker
          hostPath:
            path: /data/docker/containers     # 需要修改docker目录,containerd启动容器可以注视掉。
        - name: pods
          hostPath:
            path: /var/log/pods

scrape_config 配置详解

主要解释一下 promtail 中的匹配规则, 因为采集的日志可以说非常地杂乱, 如何将应用日志分类就尤为重要, 可以说匹配规则是 promtail 的核心所在

通常我们分类 pod 的手段基本为 namespace + labels + controller , 在 loki 中也一样, 在上述 configmap 的配置中将 k8s 中的所有 pod 分为了五类:

  • 定义了 label_name
  • 未定义 label_name, 定义了 label_app
  • 未定义 label_name & label_app, 由 Daemonset 控制
  • 未定义 label_name & label_app, 由非 Daemonset 控制
  • 未定义 label_name & label_app, 由 kubelet 直接控制

对应上述 configmap 中配置的五个 job:

  • kubernetes-pods-name job=namespace/label_name
  • kubernetes-pods-app job=namespace/label_app
  • kubernetes-pods-direct-controllers job=namespace/controller
  • kubernetes-pods-indirect-controllers job=namespace/controller
  • kubernetes-pods-static job=namespace/label_component

每个指标数据将由上述规则分类, 添加一个 job 的 label

然后基于指标数据对应 pod 的所有 label 附加到指标数据上

代码语言:javascript
复制
- action: labelmap
  regex: __meta_kubernetes_pod_label_(.+)

再加上指标数据本身携带的一些 label, 我们就可以对 pod 日志做一个十分细致的区分

Loki部署

RBACConfigMapStorageClassStatefulSetService

代码语言:javascript
复制
apiVersion: v1
kind: ServiceAccount
metadata:
  name: loki
  namespace: logging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: loki
  namespace: logging
rules:
- apiGroups:
  - extensions
  resourceNames:
  - loki
  resources:
  - podsecuritypolicies
  verbs:
  - use
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: loki
  namespace: logging
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: loki
subjects:
- kind: ServiceAccount
  name: loki
代码语言:javascript
复制
apiVersion: v1
kind: ConfigMap
metadata:
  name: loki
  namespace: logging
  labels:
    app: loki
data:
  loki.yaml: |
    auth_enabled: false
    ingester:
      chunk_idle_period: 3m        # 如果块没有达到最大的块大小,那么在刷新之前,块应该在内存中不更新多长时间
      chunk_block_size: 65535
      chunk_retain_period: 1m      # 块刷新后应该在内存中保留多长时间
      max_transfer_retries: 0      # Number of times to try and transfer chunks when leaving before falling back to flushing to the store. Zero = no transfers are done.
      lifecycler:                  # 配置ingester的生命周期,以及在哪里注册以进行发现
        ring:
          kvstore:
            store: inmemory        # 用于ring的后端存储,支持consul、etcd、inmemory
          replication_factor: 1    # 写入和读取的ingesters数量,至少为1(为了冗余和弹性,默认情况下为3)
      wal:
        enabled: true
        dir: /data/wal
    limits_config:
      enforce_metric_name: false
      reject_old_samples: true            # 旧样品是否会被拒绝
      reject_old_samples_max_age: 8h      # 拒绝旧样本的最大时限
    schema_config:                        # 配置从特定时间段开始应该使用哪些索引模式
      configs:
      - from: 2023-12-05           # 创建索引的日期。如果这是唯一的schema_config,则使用过去的日期,否则使用希望切换模式时的日期
        store: boltdb-shipper      # 索引使用哪个存储,如:cassandra, bigtable, dynamodb,或boltdb
        object_store: filesystem   # 用于块的存储,如:gcs, s3, inmemory, filesystem, cassandra,如果省略,默认值与store相同
        schema: v11
        index:                # 配置如何更新和存储索引
          prefix: index_      # 所有周期表的前缀
          period: 24h         # 表周期
    server:
      http_listen_port: 3100
    storage_config:            # 为索引和块配置一个或多个存储
      boltdb_shipper:
        active_index_directory: /data/loki/boltdb-shipper-active
        cache_location: /data/loki/boltdb-shipper-cache
        cache_ttl: 24h
        shared_store: filesystem
      filesystem:
        directory: /data/loki/chunks
    chunk_store_config:             # 配置如何缓存块,以及在将它们保存到存储之前等待多长时间
      max_look_back_period: 0s      # 限制查询数据的时间,默认是禁用的,这个值应该小于或等于table_manager.retention_period中的值
    table_manager:
      retention_deletes_enabled: true   # 日志保留周期开关,用于表保留删除
      retention_period: 48h             # 日志保留周期,保留期必须是索引/块的倍数
    compactor:
      working_directory: /data/loki/boltdb-shipper-compactor
      shared_store: filesystem
    ruler:
      storage:
        type: local
        local:
          directory: /etc/loki/rules/rules1.yaml
      rule_path: /tmp/loki/rules-temp
      alertmanager_url: http://alertmanager-main.monitoring.svc:9093
      ring:
        kvstore:
          store: inmemory
      enable_api: true
      enable_alertmanager_v2: true
代码语言:javascript
复制
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: loki-storage
  labels:
    app: loki
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: loki-local
  labels:
    app: loki
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 10Gi
  storageClassName: loki-storage
  local:
    path: /data/volumes/loki
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - k8s-node1
代码语言:javascript
复制
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: loki
  namespace: logging
  labels:
    app: loki
spec:
  podManagementPolicy: OrderedReady
  replicas: 1
  selector:
    matchLabels:
      app: loki
  serviceName: loki
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: loki
    spec:
      serviceAccountName: loki
      securityContext:
        fsGroup: 10001
        runAsGroup: 10001
        runAsNonRoot: true
        runAsUser: 10001
      initContainers:
        - name: fix-permissions
          image: busybox:latest
          securityContext:
            privileged: true
            runAsGroup: 0
            runAsNonRoot: false
            runAsUser: 0
          command:
          - sh
          - -c
          - >-
            id;
            ls -la /data/;
            mkdir -p /data/global/loki;
            chown 10001:10001 /data/global/loki
          volumeMounts:
          - mountPath: /data
            name: storage
      containers:
        - name: loki
          image: grafana/loki:2.9.2
          imagePullPolicy: IfNotPresent
          args:
            - -config.file=/etc/loki/config/loki.yaml
          volumeMounts:
            - name: config
              mountPath: /etc/loki/config
            - name: storage
              mountPath: "/data"
              subPath: data
          ports:
            - name: http-metrics
              containerPort: 3100
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /ready
              port: http-metrics
              scheme: HTTP
            initialDelaySeconds: 45
            timeoutSeconds: 1
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: http-metrics
              scheme: HTTP
            initialDelaySeconds: 45
            timeoutSeconds: 1
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 3
          securityContext:
            readOnlyRootFilesystem: true
      terminationGracePeriodSeconds: 4800
      volumes:
        - name: config
          configMap:
            defaultMode: 0640
            name: loki
  volumeClaimTemplates:
  - metadata:
      name: storage
      labels:
        app: loki
    spec:
      storageClassName: "loki-storage"   # 注意修改 storageClass 名称
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: "2Gi"
代码语言:javascript
复制
apiVersion: v1
kind: Service
metadata:
  name: loki
  namespace: logging
  labels:
    app: loki
spec:
  type: ClusterIP
  ports:
    - port: 3100
      protocol: TCP
      name: http
      targetPort: http-metrics
  selector:
    app: loki

Grafana部署

grafana 部署请参考 Linkerd服务网格安装部署

配置

在 grafana 中添加 loki 作为 data source, 这里我的 grafana 是直接部署在 k8s 中的, 所以可以通过 <svc-name>.<namespace> 访问到 loki

在 Explore => loki => {job="kube-system/coredns"} 可以看到 k8s 的 coredns 相关日志

Dashboard 示例

Traefik

traefik 部署参考 traefik 系列文章

如下图所示, 已经可以看到收集到的 traefik 日志

我们还可以通过 dashboard 实时展示 traefik 的信息, 在 grafana 导入 13713 号模板

此 dashboard 默认的 traefik 的采集语句是 {job="/var/log/traefik.log"} , 我们需要按照实际情况进行修改, 这里我改成了 {app="traefik-v2"},导入修改好的 json, 选择数据源

image-20231210181546100
image-20231210181546100

可以看到已经可以正常展示数据了

image-20231210181731127
image-20231210181731127

但是还有一个小报错, 是因为这个 dashboard 依赖 grafana-piechart-panel 这个插件, 我们在 grafana 容器内执行安装插件

代码语言:javascript
复制
$ kubectl exec -it grafana-6d946f4d88-cw4b4 -n monitoring -- grafana cli plugins install grafana-piechart-panel
✔ Downloaded grafana-piechart-panel v1.6.4 zip successfully

Please restart Grafana after installing plugins. Refer to Grafana documentation for instructions if necessary.

$ kubectl rollout restart deployment -n monitoring grafana 
deployment.apps/grafana restarted

等待重建 pod, 可以看到这里已经可以正常显示了

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Kubernetes集群日志-使用Loki实现高效日志分析和查询
  • 简介
  • 整体架构
    • 运行模式
      • 组件
        • Distributor
        • Ingester
        • Querier
        • Query Frontend
        • 读取路径
        • 写入路径
      • Promtail
      • 安装部署
        • Promtail部署
          • scrape_config 配置详解
            • Loki部署
              • Grafana部署
                • 配置
            • Dashboard 示例
              • Traefik
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档