Kubernetes 日志解决方案 Grafana Loki 「Helm 部署案例」

2024-03-27 21:21:34 浏览数 (1)

Loki 是一款易于水平扩展、高可用的日志聚合系统。

对比其他日志聚合系统,Loki 具有的优势:

  • 不对日志进行全文索引,通过存储压缩的非结构化日志并且仅索引元数据,Loki 的操作更简单且运行成本更低。
  • 使用与 Prometheus 相同的标签对日志流进行索引和分组。
  • 与 Kubernetes 完美结合,存储 Pod 日志,Pod 标签等元数据会自动抓取并建立索引。

环境准备

  • Kubernetes 1.22
  • Helm 3.3
  • S3 对象存储 Bucket、访问密钥及授权

我们使用 Grafana 官方提供的 Helm Charts 部署一套 Loki 日志系统,包括以下组件:

  • 微服务架构的 Loki 后端服务
  • Promtail 日志采集客户端
  • Grafana 可视化前端工具

对象存储

兼容 AWS S3 API 的对象存储都可以,例如:

  • AWS S3
  • 腾讯云 COS
  • 阿里云 OSS
  • MinIO

Loki 与存储之间需要有大带宽,不推荐跨云跨区域部署。

需要具有以下权限的访问密钥:

  • ListBucket
  • PutObject
  • GetObject
  • DeleteObject

不同云平台授权策略有差异,如果遇到权限不足,请改成整个 Bucket 完整读写权限。

获取 Helm 仓库

代码语言:shell复制
## 添加加速镜像仓库
helm repo add grafana "https://helm-charts.itboon.top/grafana" --force-update
helm repo update grafana

## 添加官方仓库
# helm repo add grafana "https://grafana.github.io/helm-charts" --force-update
# helm repo update grafana

加速镜像仓库 完全从官方克隆

Helm 部署

Loki Values

代码语言:yaml复制
## values-loki.yaml


fullnameOverride: loki

rbac:
  namespaced: true

loki:
  auth_enabled: false
  image:
    # registry: docker.io
    # repository: grafana/loki
    tag: 2.9.6

  readinessProbe:
    initialDelaySeconds: 10
    periodSeconds: 5
    timeoutSeconds: 1

  ## loki.config
  commonConfig:
    ## set to 1, otherwise more replicas are needed to connect to grafana
    replication_factor: 1
  storage:
    bucketNames:
      chunks: Your_Loki_Bucket
      ruler: Your_Loki_Bucket
      admin: Your_Loki_Bucket
    type: s3
    s3:
      ## s3 access, AWS S3 或者兼容 S3 API 的对象存储都可以
      endpoint: cos.ap-guangzhou.myzijiebao.com
      accessKeyId: Your_Access_Key_ID
      secretAccessKey: Your_Secret_Access_Key

  compactor:
    working_directory: /var/loki/retention
    shared_store: s3
    compaction_interval: 10m
    retention_enabled: true
    retention_delete_delay: 2h
    retention_delete_worker_count: 150

  ingester:
    max_transfer_retries: 0
    # max_chunk_age: 2h    # default = 2h
    # chunk_idle_period: 30m    # default = 30m
    # chunk_target_size: 1572864
    chunk_encoding: zstd
    autoforget_unhealthy: true
  storage_config:
    tsdb_shipper:
      active_index_directory: /var/loki/tsdb-index
      cache_location: /var/loki/tsdb-cache
      cache_ttl: 72h
      shared_store: s3
  schemaConfig:
    configs:
      - from: 2024-01-01
        store: tsdb
        object_store: s3
        schema: v12
        index:
          prefix: loki_tsdb_index_
          period: 24h
  limits_config:
    ingestion_rate_strategy: local    # default = "global"
    # retention_period: 240h
    ingestion_rate_mb: 20    # default = 4
    ingestion_burst_size_mb: 100    # default = 6
    per_stream_rate_limit: 6MB    # default = 3MB
    per_stream_rate_limit_burst: 50MB    # default = 15MB
    max_concurrent_tail_requests: 20
    max_cache_freshness_per_query: 10m
    max_query_length: 72h
    max_line_size: 256kb
    max_line_size_truncate: true
    ## [max_streams_matchers_per_query: <int> | default = 1000]
    ## [max_query_bytes_read: <int> | default = 0B]
    shard_streams:
      enabled: true
      desired_rate: 2097152    #2MiB
  
  server:
    grpc_server_max_recv_msg_size: 32000100    # default = 4194304
    grpc_server_max_send_msg_size: 32000100    # default = 4194304


test:
  enabled: false

monitoring:
  lokiCanary:
    enabled: false
  dashboards:
    enabled: false
  selfMonitoring:
    enabled: false
    grafanaAgent:
      installOperator: false
  serviceMonitor:
    enabled: false

#sidecar:
#  resources:
#    limits:
#      cpu: 200m
#      memory: 500Mi
#    requests:
#      cpu: 10m
#      memory: 50Mi

gateway:
  replicas: 1
  #resources:
  #  limits:
  #    cpu: 1
  #    memory: 2Gi
  #  requests:
  #    cpu: 50m
  #    memory: 200Mi
  #service:
  #  type: LoadBalancer

backend:
  replicas: 1
  persistence:
    size: 20Gi
    enableStatefulSetAutoDeletePVC: false
    #storageClass: null
  #resources:
  #  limits:
  #    cpu: 2
  #    memory: 8Gi
  #  requests:
  #    cpu: 100m
  #    memory: 500Mi


write:
  replicas: 1
  persistence:
    size: 20Gi
    #storageClass: null
  #resources:
  #  limits:
  #    cpu: 2
  #    memory: 8Gi
  #  requests:
  #    cpu: 500m
  #    memory: 4Gi

read:
  replicas: 3
  resources:
    limits:
      cpu: 1
      memory: 2Gi
    requests:
      cpu: 50m
      memory: 100Mi
  autoscaling:
    enabled: true
    minReplicas: 3
    maxReplicas: 15
    targetCPUUtilizationPercentage: 90

部署 Loki

  • 将上面的 Values 保存到 values-loki.yaml 文件。
  • 正确修改 loki.storage 相关字段: endpoint, bucketnames, access_key_id, secret_access_key

正确的 endpoint 字段不含 Bucket 名称

代码语言:shell复制
## 部署 Loki
helm upgrade --install loki  
  --namespace loki 
  --create-namespace 
  -f values-loki.yaml 
  --version 5.47.1 
  grafana/loki

Promtail Values

代码语言:yaml复制
## values-promtail.yaml


image:
  tag: 2.9.6

#resources:
#  limits:
#    cpu: 2
#    memory: 2Gi
#  requests:
#    cpu: 100m
#    memory: 512Mi

config:
  clients:
    - url: http://loki-gateway/loki/api/v1/push
  snippets:
    extraLimitsConfig: |
      max_line_size: 256kb
      max_line_size_truncate: true
      readline_rate_enabled: true
      readline_rate: 110000    # default = 10000
      readline_burst: 810000    # default = 10000
    pipelineStages:
      - cri: {}
      # - match:
      #     ## 过滤某些 namespace
      #     selector: '{namespace=~"Your_Namespace"}'
      #     action: drop
      # - match:
      #     ## 过滤某些 container
      #     selector: '{container=~"Your_Container_Name"}'
      #     action: drop
      - json:
          expressions:
            log: log
      - output:
          source: log
      - multiline:
          ## 跨行合并
          firstline: '^S'
          max_wait_time: 3s
          max_lines: 2000
      #- match:
      #    ## 匹配标签的日志将进行额外管道处理
      #    selector: '{container=~"java.*"}'
      #    stages:
      #    - multiline:
      #        firstline: '^[A-Z]{3,9}  ['
      #        max_wait_time: 3s
      #        max_lines: 2000
      #    - regex:
      #        expression: '^(?P<level>[A-Z]{3,9})  [.*'
      #        #source: log
      #    - labels:
      #        level:

部署 Promtail

将上面的 Values 保存到 values-promtail.yaml 文件。

代码语言:shell复制
## 部署 Promtail
helm upgrade --install promtail  
  --namespace loki 
  --create-namespace 
  -f values-promtail.yaml 
  --version 6.15.5 
  grafana/promtail

Grafana Values

代码语言:yaml复制
## values-grafana.yaml


datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Loki
      type: loki
      url: http://loki-gateway
      access: proxy
      jsonData:
        maxLines: 500

# ingress:
#   enabled: true
#   ingressClassName: nginx
#   annotations: {}
#     # kubernetes.io/ingress.class: nginx
#     # kubernetes.io/tls-acme: "true"
#   hosts:
#     - grafana.example.com

resources:
  limits:
    cpu: 100m
    memory: 1Gi
  requests:
    cpu: 20m
    memory: 128Mi

部署 Grafana

将上面的 Values 保存到 values-grafana.yaml 文件。

代码语言:shell复制
## 部署 Grafana
helm upgrade --install grafana  
  --namespace loki 
  --create-namespace 
  -f values-grafana.yaml 
  --version 7.3.7 
  grafana/grafana

获取 Grafana 初始 admin 密码:

代码语言:shell复制
## 获取 Grafana 初始 admin 密码
kubectl get secret -n loki grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

Grafana 日志查询面板Grafana 日志查询面板

部署优化

  • 数据压缩
  • 标签和索引
  • Querier HPA 扩容

上面的部署案例已经在数据压缩、索引、HPA 等方面进行优化了。

数据压缩

Loki 默认使用 gzip 压缩算法,但是 gzip 解压速度比较慢,推荐使用 zstd 压缩,两者的比较请参考 从 gzip 切换到 zstd

代码语言:yaml复制
loki:
  ingester:
    chunk_encoding: zstd

TSDB 索引

Loki v2.8 引入的 TSDB 比旧版本的索引方案具有更优的性能,请参考 官方文档

代码语言:yaml复制
loki:
  storage_config:
    tsdb_shipper:

增加吞吐量

代码语言:yaml复制
loki:
  limits_config:
    ingestion_rate_strategy: local    # default = "global"
    # retention_period: 240h
    ingestion_rate_mb: 20    # default = 4
    ingestion_burst_size_mb: 100    # default = 6
    per_stream_rate_limit: 6MB    # default = 3MB
    per_stream_rate_limit_burst: 50MB    # default = 15MB
    max_concurrent_tail_requests: 20
    max_cache_freshness_per_query: 10m
    max_query_length: 72h
    max_line_size: 256kb
    max_line_size_truncate: true
    ## [max_streams_matchers_per_query: <int> | default = 1000]
    ## [max_query_bytes_read: <int> | default = 0B]
    shard_streams:
      enabled: true
      desired_rate: 2097152    #2MiB
  
  server:
    grpc_server_max_recv_msg_size: 32000100    # default = 4194304
    grpc_server_max_send_msg_size: 32000100    # default = 4194304

自定义标签

假如日志格式是这样的:

代码语言:INFO复制
[INFO] Reloading
[INFO] plugin/reload: Running configuration SHA512 = 637429c29e6628b9c013e5f8bd5211d4564943dd9f2dd6eeb506a5a2993177f61da856d85e9ed3c8020ebde2b4abbc232f057d9ba5d9df8247d706893feb8754
[INFO] Reloading complete
[ERROR] plugin/errors: 2 cm-cn-central-00001.albatross.10086.cn. HTTPS: read tcp 10.4.63.250:47580->100.100.2.136:53: i/o timeout

在部署 Promtail 的时候可以提取日志级别作为标签:

代码语言:yaml复制
- match:
    ## 匹配容器名以 java 开头的容器
    selector: '{container=~"java.*"}'
    stages:
    - multiline:
        ## 跨行合并
        firstline: '^[[A-Z]{3,9}] '
        max_wait_time: 3s
        max_lines: 2000
    - regex:
        expression: '^[(?P<level>[A-Z]{3,9})] '
        #source: log
    - labels:
        level:

上面这个案例会提取每条日志从行首开始中括号里的大写字母并打上 level 标签,查询条件加上 level 标签可以显著提升查询效率。

自定义标签需要根据日志格式和内容进行设置,这里仅仅演示一下实现方法。

不要使用动态标签,关于动态标签并没有固定的标准。假如一个 Pod 一小时日志量有 100 万条,某个标签会产生超过 1 万个值,那这个标签肯定就属于动态标签。标签需要把日志分类打散,但是不能太散。

性能瓶颈

查询的语句的标签和时间区段决定了 Loki 需要从存储中取出并处理的数据量。如果这个数据量在 10GB 以下,那问题不大。如果达到百GB级别,那就存在性能问题了。

假如有一个 pod coredns-79f4544dbb-8ck2b, 一天产生超过 100GB 的日志(压缩前 100GB),我们一次性查询一天的日志数据,从中找出符合条件的内容。

  • 使用 pod 标签 {pod="coredns-79f4544dbb-8ck2b"} 查询一天的日志,Loki 一次处理 100GB 日志会比较慢。
  • 假如增加了 level 标签,INFO 90GB、WARN 9GB、ERROR 1GB,单独查询 ERROR 级别的日志 {pod="coredns-79f4544dbb-8ck2b",level="ERROR} 就很轻松了。

解决的办法就是合理设置标签,比如日志级别、功能模块、事件类型等。

查询语句优化

查询的语句的标签和时间区段决定了 Loki 需要处理的数据量,标签越具体查询效率越高,以下列举一些查询语句及评分:

  • 25 分: {namespace="kube-system"} |= "timeout" 时间区段最近 24 小时
  • 50 分: {namespace="kube-system",pod=~"coredns-.*"} |= "timeout" 时间区段最近 24 小时
  • 60 分: {pod="coredns-79f4544dbb-8ck2b"} |= "timeout" 时间区段最近 24 小时
  • 70 分: {pod="coredns-79f4544dbb-8ck2b"} |= "timeout" 时间区段最近 1 小时
  • 90 分: {pod="coredns-79f4544dbb-8ck2b",level="ERROR} |= "timeout" 时间区段最近 1 小时

网络带宽和存储IO

如果日志量比较大,建议 Kubernetes 节点选择 10G 以上的网络带宽,因为 Querier 可以分布到多个节点,所以节点网络带宽的压力可以稀释,带宽瓶颈会转移到后端存储。如果云平台对象存储的性能不能满足需求,可以考虑裸机部署 MinIO 存储。

查询性能存在瓶颈并不能掩盖 Loki 独特的优势,实际上微服务架构中的 Loki 异常坚固,哪怕多个大型查询并发造成堵塞,Loki 依然可以稳定地摄入新数据。

课后作业

  • 给日志增加自定义标签,提升查询效率。
  • 在 Loki 微服务架构中,查询日志时 Querier 需要消耗大量 CPU 和网络带宽,但它并不需要稳定的机器。可以把 querier 调度到廉价的 spot 节点,并尽可能均匀地分布到多台节点上,用较低的成本大幅度提升查询速度。
  • 加快 Querier HPA 扩容速度。

后续章节会完善上述内容,敬请期待。

0 人点赞