监控改造之路5

监控改造之路5

日志篇

·

3 min read

终于到了日志了,这也是监控改造系列中被笔者诟病最多,前后断断续续调研所花时间、精力最多的一环。

历史背景

说到历史背景,其实是有个认知过程、分几个阶段的

初识

刚进公司时,发现日志收集器使用的是log-collector+es的方案

log-collector?真没听说过,es,太重了,作为一个“瘦身”爱好者,天然地认为loki比es轻巧。但是彼时,日志属于非核心业务模块,没人关注,即使当时日志模块经常会被测试的同学吐槽。

再识

再次折腾log-collector,是因为信创认证的需要,平台得支持arm64、适配高版本k8s以支持收集containerd运行时容器的日志,时间还非常紧,我被摊上了这个活。经测试我发现log-collector不支持收集containerd运行时容器日志。然后开始了log-collector的考古之旅。翻阅项目中的log-collector的代码,惊奇地发现这竟然是一个被重命名的项目,来源于福报厂的一个开源项目:log-pilot

log-pilot is an awesome docker log tool. With log-pilot you can collect logs from docker hosts and send them to your centralized log system such as elasticsearch, graylog2, awsog and etc. log-pilot can collect not only docker stdout but also log file that inside docker containers.

然后被同事把项目名、mod名等改为了log-collector,真的不太理解这样的操作意义在哪?难道要假装自研?惊喜还在后头,因为团队开发的平台需要收集K8s和docker部署的实例日志,如何收容器中的日志呢

  • K8s:log-collector中含有这样一段逻辑, 容器中env变量中以“manager_log”为前缀的,即为需要收集的容器日志路径,例如:manager_log_xyz=/opt/xyz/*.log

  • Docker: log-collector中含有这样一段逻辑, 容器的label中以“manager.log”为前缀的,即为需要收集的容器日志路径,例如:manager.logs.abc\=/opt/abc/*.log

所以其实是对log-collector做了一些“改造”,但这个改造看起来并不那么优雅,再具体一些的原因已经无从考证了。

回到需要containerd运行时容器的日志这件事上来。首先从社区看log-pilot这个项目最近一次提交是4年前的,看起来不活跃,也没人维护,不支持containerd是真,然后我向TL反映这个情况,TL告诉我:这个很简单啊,把log-collector中docker的sdk替换成containerd的sdk不就可以了……

开启搜索大法,找到相关的一篇宝藏blog: [ 原创 ] containerd 迁移二三事, 原来博主自己从log-pilot fork出一个分支,强力支持了containerd的日志收集。按图索骥,找到源码,稍加改造,调试一番,终于赶在截止时间前完成了适配(多线作战、不想回顾的日子)。于是,人生第一次,真诚地给博主写了一封致谢邮件。

平替与调研

我对日志收集方案的需求按优先级如下:

  1. 多平台支持:至少需要支持arm64/amd64

  2. 多容器运行时支持: docker与containerd

  3. 轻最好、

  4. go写的最好(如有必要,适合二开)

  5. 性能不弱

在知道log-collector实际上是log-pilot之前,我一直想替换log-collector,毕竟也是在云原生领域混了些日子,这玩意我都没听过,天然地会产生拒绝和厌恶(事后来看,在没有充分认识背景的情况下,去否定一件事情,没有思考,这可相当不好)。

我是在调研完之后才考虑平替的,然后才发现自己小看了这件事情

fluentbit

Fluent Bit is a super fast, lightweight, and highly scalable logging and metrics processor and forwarder. It is the preferred choice for cloud and containerized environments.

先调研它,自然是因为其轻巧,c写的。还有一个原因是当时我在团队致力于推进平台轻量化的策略,fluentbit同时还兼有node-exporter的功能,在fluent-bit.conf中新增如下配置即可

[INPUT]
    name            node_exporter_metrics
    tag             node.metrics
    scrape_interval 2

[OUTPUT]
    name            prometheus_exporter
    match           *.metrics
    host            0.0.0.0
    port            2021

但是测试后发现,这样获得的node metric丰富程度不如prometheus/node_exporter, 最主要团队的grafana的主机模板当时固化了,平替无望,于是fluentbit就此搁置

vector

A lightweight, ultra-fast tool for building observability pipelines

一次很偶然的机会发现了vector, 看介绍,rust写的非常厉害,性能表现优秀,社区也非常活跃,也自带node-exporter,需要在vector.toml中新增的配置也简明易懂

[sources.my_source_id]
type = "host_metrics"
collectors = [
  "cgroups",
  "cpu",
  "disk",
  "filesystem",
  "load",
  "host",
  "memory",
  "network"
]
namespace = "host"
scrape_interval_secs = 15


[sinks.my_sink_id]
type = "prometheus_exporter"
inputs = [ "my_source_id" ]
address = "0.0.0.0:9598"
flush_period_secs = 15

与fluentbit相比,vector功能更丰富,生态支持更好,社区更活跃,与fluentbit一样,node metric的丰富度也不够。细细想来,当时我的目的首先是替代log-collector,至于node-exporter,那只是锦上添花而已,不能偏离主线。

回到日志收集这个需求主线上我才发现,无论是fluentbit、vector,还是loki社区的promtail,文档中并没有提及如何在不通过挂载的情况下,收集容器中的日志文件,考虑到fluentbit、vector均没有集成docker/containerd的sdk,没有集成这些sdk就没法顺利获取容器的env/label,进而进行日志采集了。看文档,加上自己各种测试,翻社区issue等,最终确定它们均无法支持收集容器中的日志文件。

openobserve

又是一次偶然的机会,发现了openobserve这个项目,曾经自己心心念的Logs, Metrics, Traces大一统平台,rust写的,这介绍,是真牛:

🚀 10x easier, 🚀 140x lower storage cost, 🚀 high performance, 🚀 petabyte scale - Elasticsearch/Splunk/Datadog alternative for 🚀 (logs, metrics, traces, RUM, Error tracking, Session replay).

很快我试用了一下,集成度很高,项目迭代也很快,就是看起来很强大,若要全套使用这个方案,改造成本大,风险高,毕竟贵司是面向政企和银行的,喜欢稳,不喜欢潮流,而且确实偏离了我的主线目标。

ilogtail

正当我以为走入绝境时,偶然发现了ilogtail,福报厂家出品的

iLogtail 为可观测场景而生,拥有的轻量级、高性能、自动化配置等诸多生产级别特性,在阿里巴巴以及外部数万家阿里云客户内部广泛应用。你可以将它部署于物理机,虚拟机,Kubernetes等多种环境中来采集遥测数据,例如logs、traces和metrics。

记得初次使用时,是1.7.0这个版本,经测试,可以支持容器内的日志文件收集,并且无需将文件挂载出来,核心用C++,插件系统使用go,配置支持热加载,看介绍性能也是很厉害的样子。比如收集docker部署容器中的日志,配置也较简单

enable: true
inputs:
  - Type: file_log
    LogPath: /opt/logs/
    FilePattern: "*.log"
    ContainerInfo:
      IncludeLabel:
        app: example
#  ……

为了达到平替的目的,我需要获取容器中的manager_log为前缀的环境变量/manager.log为前缀的容器label,然后将其作为日志路径注入到ilogtail的配置中。做法是劫持/修改LogPath和FilePattern这两块配置的地方,我主要参阅了

pkg/helper

这个目录下的代码,试图使用正则的方式尽可能减少对原项目的改动,又能支持我的需求。可能是这个项目同时使用了c++和go,我发现ilogtail的开发环境以及调试并不友好,也可能是我水平还不够,还没找到关键逻辑,断断续续花了几周的空余时间,始终不得法。彼时想给社区提issue求帮助,但团队的用法太小众了,很难成为一个feature,权衡再三,遂放弃了这个想法,也放弃了对ilogtail的二次开发。当时想的是:本来项目里面对日志的用法就缺少规划,使用上技术绑定太严重了,不应该为了过去因调研缺失而并不规范的用法,而花费更多的时间执著于平替这件事,投入产出比太低了,毕竟日志属于项目中的边缘任务。

做好了心理建议,我的目标变成了测试ilogtail社区的活跃和支持度上。做法也比较简单,提了一个大概率会被merge的pr。我注意到ilogtail中使用的go版本是1.19,许多地方还在使用"io/ioutil",社区已经不推荐这么用了

"io/ioutil" is deprecated: As of Go 1.16, the same functionality is now provided by package io or package os, and those implementations should be preferred in new code

于是,接下来了事情也就变成纯体力活了,将"io/ioutil"替换为io即可, Sep 23, 2023提pr,Sep 25, 2023我的代码被合入主干,感觉社区处理问题速度还行,基本确定使用ilogtail这个日志选型了。

接下来就是各种测试,以减少团队项目的主服务因日志选型造成的改动了。这里记录了一些我在测试k8s部署的容器日志收集遇到了问题

  1. Loki 不支持 label 中包含

    还好有解决办法,这里给出我在项目中的配置示例

    enable: true
    inputs:
      - Type: file_log
        LogPath: /opt/zookeeper/logs/
        FilePattern: "*.log"
        ContainerInfo:
          IncludeEnv:
            # 仅收集存在环境变量名为role_name的容器
            role_name: "^.+$"
          ExternalEnvTag:
            # 为了在loki中将env转为label使用
            role_name: role_name

    flushers:
      - Type: flusher_loki
        DynamicLabels:
          - tag.role_name
          - tag.container_name
        Convert:
          TagFieldsRename:
            # 将容器名转换以支持loki
            container.name: container_name
  1. inputs不支持列表

    从配置看inputs是支持列表的,本以为可以支持以下写法

         inputs:
           - Type: file_log
             LogPath: /opt/zookeeper/logs/
             FilePattern: "*.log"
           - Type: file_log
             LogPath: /opt/kafka/logs/
             FilePattern: "*.log"
    

    事实上并不支持,只能"在user_yaml_config.d/下可以创建多个yaml",这就免不了造成许多冗余配置了。

  2. 使用示例少

    比如文档中描述FilePattern支持通配符

    待采集的日志文件路径列表(目前仅限1个路径)。路径中支持使用*和通配符,其中通配符仅能出现一次且仅限用于文件名前

但不知是我使用姿势的问题还是根本就没实现,测试过程中使用通配符并不能得到我想要的结果。总体上文档的使用示例还是比较匮乏的,很多时候我不得不一边翻文档、代码、issue,一边测试印证,耗费了不少时间。


番外篇:日志告警

去年,为了XX认证的需求,匆匆上马了日志告警这个需求,当时负责该该需求的同事,选用了esalert, 然后esalert最近一次更新已经是4年前了,社区活跃度实在不高。可是也没有办法,匆忙的需求导致粗糙的技术造型与实现,至于为了更新日志告警配置而在项目主服务中直接操作configmap导致esalert必须跟k8s绑定在一起,这就是后话了。

日志告警这个事情我一直认为是边缘需求,特别是在大数据这个场景下,花了半天时间调研grafana对es来源的日志告警的支持,无果,转到loki,发现是可行的,跟到这个issue: https://github.com/grafana/loki/issues/5844

这也难怪,loki与grafana系出同门,不支持反而怪了。但是告警在grafana里面配置的示例偏少,特别是关于日志内容的告警的使用示例,几乎没看到过。

一次偶然的机会,发现了Nightingale这个项目

Nightingale | 夜莺监控是一个 All-in-One 的云原生监控工具,集合了 Prometheus 和 Grafana 的优点,你可以在 UI 上管理和配置告警策略,也可以对分布在多个 Region 的指标、日志、链路追踪数据进行统一的可视化和分析

第一次听说夜莺,还是在微医,记得那时我在做监控告警的技术造型,我的tl扔过来一个夜莺的介绍链接让我参考(只能说幸好当时我没选夜莺)……

夜莺也想做大而全的平台,但我只想要它的告警模块,docker-compose启动试用后发现,告警模块的使用体验还挺好,符合我的需求。不小心瞄到夜莺也支持监控的可视化,支持导入grafana模板,我一下子兴奋了:这不就可以替代grafana了吗。好,立马导入一个grafana模板测试了一下,好家伙,兼容性太差了,pass。现在想到当时的兴奋点就觉得莫名好笑。呃,所以,grafana还是香的,毕竟grafana还支持日志下载,目前项目中的日志下载是在主服务中调用es的接口完成做的……

总结

日志选型的过程中,经历了认知的变化,真正深入到了需求和细节,一步一步拆解、发现与测试验证,fluentbit、vector、promtail甚至于后期发现的loggie,都很棒,满足我对轻量化的要求,但都与最小化代价平替log-collector这一主线需求偏差过大。ilogtail基本满足主线需求,现在也迭代到了2.0.0,变得越来越好了。虽然我的ilogtail+loki+grafana的日志采集、低成本日志中心、日志告警的方案通过了技术评审,但还是因为主服务需要改动而延后没有放在最近的主版本迭代中。监控改造系列基本上告一段落了,接下来要开新坑了。


参考: