# 监控改造之路5

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

## **历史背景**

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

### **初识**

刚进公司时，发现日志收集器使用的是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](https://docs.fluentbit.io/manual/pipeline/inputs/node-exporter-metrics)\=/opt/abc/\*.log
    

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

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

开启搜索大法，找到相关的一篇宝藏blog: [\[ 原创 \] containerd 迁移二三事](https://colstuwjx.github.io/2021/08/%E5%8E%9F%E5%88%9B-containerd-%E8%BF%81%E7%A7%BB%E4%BA%8C%E4%B8%89%E4%BA%8B/), 原来博主自己从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](https://docs.fluentbit.io/manual/pipeline/inputs/node-exporter-metrics)的功能，在`fluent-bit.conf`中新增如下配置即可

```yaml
[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](https://github.com/prometheus/node_exporter), 最主要团队的grafana的主机模板当时固化了，平替无望，于是fluentbit就此搁置

### **vector**

> A lightweight, ultra-fast tool for building observability pipelines

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

```yaml
[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部署容器中的日志，配置也较简单

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

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

```yaml
pkg/helper
```

这个目录下的代码，试图使用正则的方式尽可能减少对原项目的改动，又能支持我的需求。可能是这个项目同时使用了c++和go，我发现ilogtail的[开发环境](https://ilogtail.gitbook.io/ilogtail-docs/developer-guide/development-environment)以及调试并不友好，也可能是我水平还不够，还没找到关键逻辑，断断续续花了几周的空余时间，始终不得法。彼时想给社区提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 中包含](https://github.com/alibaba/ilogtail/issues/1020)
    
    还好有解决办法，这里给出我在项目中的配置示例
    

```yaml
    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不支持列表](https://github.com/alibaba/ilogtail/issues/805)
    
    从配置看inputs是支持列表的，本以为可以支持以下写法
    
    ```yaml
        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](https://github.com/Yelp/elastalert), 然后esalert最近一次更新已经是4年前了，社区活跃度实在不高。可是也没有办法，匆忙的需求导致粗糙的技术造型与实现，至于为了更新日志告警配置而在项目主服务中直接操作configmap导致esalert必须跟k8s绑定在一起，这就是后话了。

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

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

一次偶然的机会，发现了[Nightingale](https://n9e.github.io/zh/)这个项目

> 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的日志采集、低成本日志中心、日志告警的方案通过了技术评审，但还是因为主服务需要改动而延后没有放在最近的主版本迭代中。监控改造系列基本上告一段落了，接下来要开新坑了。

---

参考：

* [log-pilot](https://github.com/AliyunContainerService/log-pilot)
    
* [fluentbit](https://fluentbit.io/)
    
* [vector](https://vector.dev/)
    
* [promtail](https://github.com/grafana/loki/)
    
* [loggie](https://loggie-io.github.io/docs/main/)
    
* [ilogtail](https://github.com/alibaba/ilogtail)
    
* [openobserve](https://openobserve.ai/)
    
* [封面图片](https://codersociety.com/blog/articles/metrics-tracing-logging)
