Logging Operator - 优雅的云原生日志管理方案 (三)

1,775 阅读9分钟

前文:
Logging Operator 的文章去年拖更很久了,原以为不会再有进度,不过最近在自己的KubeGems项目中遇到处理日志可观察性部分的需求时,又重新研究了下它,于是有了本系列的第三篇。

Logging Operator是BanzaiCloud下开源的一个云原生场景下的日志采集方案。它在 2020 年 3 月的时候经过重构后的 v3 版本,底层凭借高效的 fluentbit 和插件丰富的 flunetd,Logging Operator几乎已经完美的适配了 kubernetes 模式下的日志采集场景,未来可期。去年偶然间发现Rancher 在 2.5 版本之后也采用了 Logging Operator 作为统一的日志解决方案,足以说明它正在被一些以 Kubernetes 为核心的管理平台接受,并集成至内部(也包括小白的 KubeGems)。

本文作为前面两篇的续篇,主要跟大家谈谈最近小白用 Logging Operator 来解决用户需求时的案例以及感受,所以我不打算花篇幅对其架构和使用再做描述,感兴趣的同学可以往前翻翻小白的文章。

关于指标

应用在容器化的过程中,由于容器文件系统的临时性,开发者始终面临自己的日志文件落盘和输出 stdout 的两难选择,当研发将应用日志管理的权利交给平台时,意味着平台要做的东西远比应用一对一采集要复杂。在众多需求中,某天有SRE同学提问:“我们在阿里云中可以看到日志采集的实时速率,我们需要为此定制质量监控指标”。这个问题也点醒了我,对于我们在做私有云时,站在平台外面对日志采集的管道内部的观察一直是处于信息缺失的盲区。好在 fluentbit 和 fluentd 都有独立的prometheus 插件来暴露内部的指标,不过在用 Logging Operator 后,它的指标采集依托 prometheus operator, 且架构足够清晰,因此我们也可以有更多的选择来满足研发的需求。

img

首先,我们在定义 logging 时可以让 fluent bit(d)开启 prometheus的采集

spec:
  fluentdSpec:
    metrics:
      serviceMonitor: true
      serviceMonitorConfig:
        honorLabels: true    // 打开honorLabels主要是为了保持组件原有的label,避免标签被覆盖。
  fluentbitSpec:
    metrics:
      serviceMonitor: true

这里可以看到 Logging Operator 主要依靠ServiceMonitor来做采集端的服务发现,这里需要集群内部运行 Prometheus Operator 以支持该 CRD。如果集群内部没有改资源类型,也可以借助 Prometheus 自身的服务发现机制来完成指标的发现和采集。

不过这里仅仅只声明了采集端的指标入口,这里面默认只包含了 Fluent bit(d)内部基本的运行状态,如果要进一步实现对日志速率的监控,就得需要 Flunetd 出马了。早些年谷歌的GKE上还在用 Fluentd 作为日志采集器时,偶然间胡乱看~~(有目的的抄袭)~~的一条 Prometheus 插件配置引起了我的兴趣

<filter **>
  @type prometheus
  <metric>
    type counter
    name logging_entry_count
    desc Total number of log entries generated by either application containers or system components
  </metric>
  <labels>
    container: $.kubernetes.container_name
    pod: $.kubernetes.pod_name
  </labels>
</filter>

这条规则将匹配所有进入Fluentd的日志, 并进入到 Prometheus 这个 filter 进行计数处理。并将统计后的指标以logging_entry_count命名,按照日志中的一些元数据信息作为指标的 label,用于区分来至不同的容器。

由于需要解析日志的 kubernetes 元数据,这里又需要 Fluentd 的 kubernetes-metadata-filter插件来做容器元数据的提取。在 Logging Operator中 Kubernetes 的元数据在 Fluent Bit 中解析,无需再在 Fluentd 额外添加该插件

虽然现在谷歌 GKE 现在也将日志采集器换成 Fluent Bit,但上面这条配置在 Logging Operator 中并不“过时”。有了前车之鉴,我们可以在租户的日志采集器(Flow / ClusterFlow)中将 Prometheus 插件引入进来用于分析日志速率。当中最简单的实践如下:

apiVersion: logging.banzaicloud.io/v1beta1
kind: Flow
metadata:
  name: default
  namespace: demo
spec:
  - prometheus:
      labels:
        container: $.kubernetes.container_name
        namespace: $.kubernetes.namespace_name
        node: $.kubernetes.host
        pod: $.kubernetes.pod_name
      metrics:
      - desc: Total number of log entries generated by either application containers
          or system components
        name: logging_entry_count
        type: counter
  globalOutputRefs:
  - containers-console
  match:
  - select:
      labels:
        what.you.want: collect

当上述指标入库 Prometheus 之后,我们便可以通过这条语句查出当前集群下日志采集器的应用速率

sum by (pod) (rate(logging_entry_count[1m]))

此时,如果云平台是基于多租户多环境架构,那么你甚至可以按照租户环境、租户级别分别来聚合日志的速率。

file

上述仅仅是对日志总体速率的采集监控,如果我们需要对日志内出现的特定的内容或者对日志的byte 进行统计时,就需要结合其它的插件进行组合。当前 Logging Operator 支持的插件还远不如 Fluentd 丰富,不过我们可以参照官方文档,编写需要的plugin集成至 Operator。Logging Operator Developers手册

对于日志组件内部的监控和告警,Logging Operator 有一套自己的规则,可以在 logging 这个 CR 中启用这个功能

spec:
  fluentbitSpec:
    metrics:
      prometheusRules: true
  fluentdSpec:
    metrics:
      prometheusRules: true

这里的prometheusRules同样是 Prometheus Operator 管理的资源,如果集群内没有此资源类型,可手动为 Prometheus 配置 Rules

回到最初的问题,如果需要将日志的采集速率作为应用的量化指标时,利用logging_entry_count即可。

关于采样

大多数情况下,日志架构不应该对业务日志采取一些不可控的策略,造成应用日志的不完整,例如采样。在这里显然我也不推荐你在现有的架构中启用此功能。不过有时候,或者是部分魔法师无法有效控制程序的“洪荒之力”而疯狂输出时,平台对于这类俏皮的应用时就可以采样的方案,毕竟保证整个日志通道的可用性是平台第一优先要考虑因素。

Logging Operator 在日志采样方面,采用了Throttle这个插件限速器,用一句话总结这个插件就是为每个进入到 filter 日志的管道引入了漏桶算法,让其丢弃到超过速率限制的日志。

apiVersion: logging.banzaicloud.io/v1beta1
kind: Flow
metadata:
  name: default
  namespace: demo
spec:
  - throttle:
      group_bucket_limit: 3000
      group_bucket_period_s: 10
      group_key: kubernetes.pod_name
  globalOutputRefs:
  - containers-console
  match:
  - select:
      labels:
        what.you.want: collect
  • group_key: 日志采样的聚合 key,通常我们按照pod 名称来做聚合,亦或直接填写kubenretes.metadata 的其它值聚合也行
  • group_bucket_period_s: 采样的时间范围,默认 60s
  • group_bucket_limit: 采样期间的日志桶最大容量

日志的采样速率由公式group_bucket_limit / group_bucket_period_s计算,当group_key中的日志速率超过值时则会丢到后续日志。

由于 Throttle 并没有采用令牌桶的算法,所以它不会有 burst 来应对突发日志量的采集。

关于日志落盘

前面说到,所有基于容器的应用程序,日志的最佳实践是将日志定向到stdoutstderr中,但并不是所有“魔法师”都会遵循此约定,日志文件落盘仍然是当下多数研发的选择。虽然理论上来说容器的标准 (错误)输出也是将日志流集中重定到/var/log/containers 下的日志文件上,但仍然受限于运行时配置或者其他硬盘原因带来不可控的因素。

对于日志落盘的场景,当前业界也无统一的解决方案,但归总起来其实也就2个实现方式:

  • sidecar 方案

    此方案是将日志采集器跟随应用容器一同运行在pod 当中,通过 volume 共享日志路径的方式采集。常见的是方式是为 kubernetes 开发一套单独的控制器,并采用MutatingWebhook在pod 启动阶段将 sidecar 的信息注入。

    sidecar 的方案优势在于为每个应用的 sidecar 配置相对独立,但劣势除占用过多资源外,采集器的更新迭代需跟随应用的生命周期,对于持续维护不太优雅。

  • node agent 方案

    此方案将日志采集器以 DaemonSet 的方式运行在每个 Node 当中,然后在操作系统层面进行集中采集。通常此方案需要平台的研发需要做一定路径策略,将固定 hostpath的 vulume 挂载给容器用于应用日志落盘时使用。除此之外,我们知道所有 Kubernetes 默认的存储类型或者第三方的遵循 CSI 标准的存储,都会将Volume挂载到/var/lib/kubelet/pods/<pod_id>/volumes/kubernetes.io~<type>/<pvc_id>/mount目录下,所以对于 node agent 更加优雅一点的方案是在 node agent 中赋予调用 Kubernetes API 的权限,让其知晓被采集容器日志映射在主机下的路径。

    node agent的方案的优势在于配置集中管理,采集器跟应用容器解耦,互不影响。缺点在于采集器存在吞吐不足的风险。

可以看到上述两个方案中,不管哪一个都与 Logging Operator 沾不上关系。确实,当下社区对此场景下还没有一个行之有效的方案,不过按照其思路,我们可以将日志文件转成标准(错误)输出流来变相处理这个问题。

tail 来举个直观的例子来说明上述的方案。

...
containers:
- args:
  - -F
  - /path/to/your/log/file.log
  command:
  - tail
  image: busybox
  name: stream-log-file-[name]
  volumeMounts:
  - mountPath: /path/to/your/log
    name: mounted-log
...

虽然tail是一个极其简单粗暴的方式,且无法解决日志轮转等问题,但它的确为Logging Operator提供了一个新的日志落盘场景下的方案。虽然看上去与 sidecar 如出一辙,不过最大的区别在于,此方案能与 Logging Operator 现有的日志管道无缝兼容,日志被采集后仍然能在 flow 阶段进行处理。

总结

Logging Operator 站在自动化运维的角度确实有效解决了 Kubernetes 场景下日志架构复杂和应用日志采集难的问题,虽然当下对落盘日志的支持还不够全面。但随着接入的用户逐渐成长,现在的问题在将来或许还有更好的解决方案。不过,当下它的确不失为最好的云原生日志架构之一。


关注公众号【云原生小白】,回复「入群」加入Loki学习群