10、Prometheus技术栈拓展

Prometheus入门 / 2022-06-30

本文系朱政科《Prometheus云原生监控:运维与开发实战》学习笔记

原书理论较多,尤其在集群方面几无实战演示,需自己摸索。

对高可用、数据一致性、扩展性等要求越高,架构就会越来越复杂,毕竟鱼与熊掌不可得兼。

Prometheus作为Google内部监控系统Borgmon的开源版本,在高可用和历史数据存储上存在一些缺陷,但它优秀的设计思想和理念也影响着很多开源软件。

在集群建设上,本章节将会在第八章节Prometheus集群的基础上,补充Thanos开源的基于sidecar模式的大规模Prometheus集群解决方案及Uber开源的时序数据库M3DB的相关知识。

在Prometheus原理的拓展上,本章节还会介绍Grafana公司基于Prometheus的理念推出的Loki开源日志解决方案。

本章节还会介绍Prometheus其它相关技术和最佳实践,希望能拓展知识面,丰富知识体系。

一、Thanos架构与监控实战

待补充实战演示

Thanos和Loki都是美国漫威超级英雄电影《复仇者联盟》中出现过的角色。Thanos音译为萨诺斯,即灭霸。它出生于泰坦星的永恒一族,实力极其强大,几乎无法被摧毁,还拥有六颗无限宝石。而Improbable开源了他们的Prometheus高可用解决方案Thanos,表示它和灭霸一样强大,提供Prometheus的无缝集成功能,并拥有全局视图和不受限制的历史数据存储能力。

Thanos的Store API采用的是gRPC协议,只能走4层(传输层)通信,在OpenShit 3.11版本下无法通过router(ingress)暴露给集群外部的Query等组件。此时采用nodePort方式暴露,但通常nodePort并不是一个很好的方式,因为它占用了主机端口。

1.1、Thanos架构解析

Thanos架构图如下所示:

Thanos架构图

如图所示,Thanos主要由5个组件组成:

1.1.1、Query

查询请求的分发、数据合并组件。

  1. Query也可以简单看成Prometheus的查询,Query的UI和Prometheus基本一致,它基于gRPC接口实现通过一个接口地址查询其它组件的数据,如Sidecar和Store Gateway。
  2. 它会将查询请求发送给所有Store和Sidecar,然后各组件会把查询结果返回给Query。因为发送给所有的Store和Sidecar时,Query会对多个Prometheus的数据进行合并,这就解决了多个Prometheus抓取后查询不到的问题。
  3. Query会通过Store API获取每个Sidecar上的监控数据,Thanos每个组件之间都通过gossip通信机制联通,这样可以动态添加组件、动态删除组件、组件间共享信息等节点发现功能就是基于gossip通信机制实现的。
1.1.2、Store Gateway

用于获取来自对象存储的历史数据,它使得Query可以获取历史数据,并通过Grafana展示,其中Sstore部分可以通过设置时间切面来查询高可用文件系统中的数据。

Store Gateway通过智能查询计划以及仅缓存必要索引部分的数据块的方式,能够根据Prometheus存储引擎的数据格式,将一些复杂查询降级成一个针对对象存储里的文件进行的最小数量的HTTP范围请求(HTTP range request),这可以在不影响请求响应时间的情况下将原始请求的数量降低很多,从整体看,很难区分这种查询和查询本地SSD请求数量有什么差异。

1.1.3、Compact

用于历史数据的压缩和降准采样,提高在Grafana上执行zoom-in/zoom-out的效率。因为Prometheus本身自带压缩功能,因此在使用Thanos时必须先关闭Prometheus自带的压缩功能,由Thanos的Compact组件接管。

1.1.4、Ruler

配置全局告警相关的内容,Ruler的UI和ALertmanager原生UI基本一致,Ruler组件基于Query组件进行查询。

1.1.5、Sidecar

Thanos的Sidecar目前仅支持Prometheus 2.0+版本的数据格式

与Prometheus一起部署,一是为Query提供未持久化到对象存储的数据;二是将超期的数据持久化到对象存储,如上图中所示的S3、Ceph等。

在kubernetes集群中,对每个Prometheus节点来说都会配置一个Sidecar组件,Thanos一般会和Prometheus以相邻容器的形式集成到一个Pod中,如下图所示:

Thanos的Sidecar原理

这样的架构有以下好处:

  • 可以让Prometheus获取本地数据的全局视图,可以横向扩展,且无状态的Querier组件会直接通过Sidecar代理进行本地数据的读取。
  • Sidecar可以将Prometheus本地监控的数据通过对象存储接口保存到对象存储(object storage)中,如下图所示:

Thanos获取Prometheus原理

之前介绍过,Prometheus的存储引擎大约每2h会将最近的内存数据写入磁盘,持久化的数据块包含固定时间范围内的所有数据且不可变。Thanos Sidecar可以简单地监听Prometheus的数据目录变化时将它们上传到对象存储桶中。Sidecar模块通过轮询的形式每30秒读取一次本地元数据,如果有新的监控数据,就读取本地数据块并上传到对象存储桶中,标记最新的读取时间并通过本地JSON文件保存已上传的块,从而避免重复上传。

1.2、Thanos实践

以上1.1小节介绍的所有组件都是基于Sidecar模式配置的,但Thanos还有一种不太常用的非默认方案:Receive模式。

SIdecar模式对网络连通性要求比较高,如果是在多租户环境、云环境、租户与控制面隔离等场景下,那么对象存储(历史数据)Query组件在控制面板进行权限校验和接口服务封装,而Sidecar和Prometheus却在集群内的用户侧完成这一操作,如果遇到网络限制,就会导致控制面和用户侧无法使用Sidecar模式。

Sidecar模式下,2h内的数据需要依赖Prometheus来获取。Receive模式采用的remote write就规避了2h存储块的问题,可以根据这个特性灵活使用Thanos的Receive模式。

如果不使用Thanos,Prometheus的主要流程即:采集–>存储–>查询,如下图所示:

Prometheus主要流程

采用Thanos后,Prometheus主要流程中的采集、存储不变,但会增加一个环节:Thanos会根据Prometheus的数据目录变化,将本地数据通过Sidecar代理进行处理,而远程数据在新的数据块出现时会被上传到对象存储中。

Thanos Compact模块主要负责针对类似S3这样的对象进行压缩,将Prometheus的本地压缩机制应用到对象存储里的历史数据(将历史小对象block合并压缩成大文件对象,然后删除小文件block)上,并且可以作为一个简单的定时批处理任务来执行。如果采集到的数据达到了亿级别导致无法渲染全时间维度的数据,Compact还会支持降准采样的功能,如下图所示:

Prometheus主要流程

Compact会持续不断地以5m和1h为时间维度对时序数据进行聚合。针对用TSDB的XOR压缩编码的每个原始数据块,Compact将会存储几个不同类型的聚合数据。例如,单一数据块的最小值、最大值、总和。这使得Querier能够针对给定的PromQL自动选择合适的聚合数据。从用户的角度来看,使用降准采样后的数据不需要做什么特殊配置。当用户缩小(zoom in)或拉大(zoom out)时,比如要查询1天、1个月、1年的数据,则需要通过降准的方式进行,Querier会自动在不同的时间维度和原始数据之间进行切换。用户也可以通过在查询参数中指定一个自定义的step来直接控制Querier。由于技术不断向前发展,1GB的存储成本是微乎其微的,在默认情况下,Thanos将会始终在存储里维持原始的5m1h时间维度的数据,因而无须删除原始数据。

1.2.2、Thanos降准

Thanos会将原始数据降准汇聚,以30s为周期采集的监控数据,如果进行一次10倍的压缩,即5m的数据,然后再进行一次12倍的压缩,即1h的数据。

Thanos降准方式如下:

  • count:对压缩时间段内的监控指标的个数进行求和
  • sum:对监控指标的数值进行求和
  • avg:平均值,可以通过sum/count计算得到。
  • min:一定时间段内监控数据的最小值
  • max:一定时间段内的监控数据的最大值

单独组件Ruler,会基于Thanos Querier执行规则并发出警告。通过公开的Store API,查询节点得以访问计算出的指标数据。随后,这些数据会备份到对象存储并通过Store Gateway访问。除了支持Prometheus原生的记录规则和告警规则,该组件还支持全局的告警规则(例如当3个集群中的2个以上集群里的服务宕机时发出告警)、超出单台Prometheus本地保留数据的规则,它还具备将所有规则和告警存储在一个地方的功能,如下图所示:

Thanos Ruler组件

1.2.3、京东方案

Thanos的主要设计思路就是把Prometheus的功能组件化、集群化,解决Prometheus原生架构在集群上的全局视图和历史数据存储等问题。

Thanos方案对Prometheus零侵入,但依赖对象存储服务,这就会带来很大的开发成本和技术问题,仅适合中大型企业。

在京东开源项目Chubao FS中,就联合使用了Prometheus联邦集群和Thanos集群的架构方案。因为京东发现,使用Prometheus自带的联邦集群功能对监控集群进行扩展时,如果被监控集群的节点过多,联邦集群的根节点会因为内存消耗过多而被容器杀掉,导致集群规模无法横向扩展,因此京东结合Thanos解决了这个问题。

在大多数场景下,可以在不引入Thanos的情况下,在联邦集群模式中可通过功能扩展水平分片等方式进行优化。京东Chubao FS项目中采用的Prometheus和Thanos结合的解决方案示意图如下所示:

京东Chubao FS方案

京东方案中,Prometheus节点使用ChubaoFS Node IP进行分区,以保证每个Chubao FS Node能均衡采集被监控节点的数据。前端通过Thanos聚合Grafana查询请求,内存容易占满进而导致宕机,不过Thanos提供了一种分布式部署的解决方案,可以实现容易扩展的效果。

1.3、Thanos存在的问题

作者书籍出版时间为2020年10月

Thanos项目还在继续发展中,它还存在一些问题,还需要不断解决、优化及提升,其存在的两个最主要的问题就是存储和性能问题。

针对存储问题,如果使用OSS,最好是纯云环境托管,比如S3,但查询历史数据时可能会因为跨度大、耗时长而导致查询中断。阿里云S3目前还在评审中,如果要兼容阿里云的S3,可以自建Minio,这会加大运维成本。存储上,京东使用的是远程存储Ceph,如下图所示:

Prometheus数据存储至对象存储

针对性能问题,首先需要评估Prometheus服务器内存、CPU使用情况。根据使用情况具体问题具体分析,每台告警机器的CPU型号都可能不一样。有些Prometheus使用者有20GB、10GB、100MB等各种不同配置,具体可以参照Prometheus官方文章进行评估(第九章节的存储进阶也有提到)。有些公司自建IDC机房,资源充足,可以分配上百台98核350GB的宿主机给Prometheus使用;也有公司使用16核64GB的主机,节点数200以上,其中6台专门用于监控。

无论是哪种情况,都不建议使用16GB以下的内存,至少得64GB内存;集群、中间件、数据库、物理机、K8S、JVM、Spark HBASE、Hadoop等可以分开监控。挂卷也是推荐的,因为不使用挂卷很快就会出现性能问题。

在进行Thanos和Prometheus集群部署的时候,Thanos store和Compact组件是最吃CPU和内存的,Sidecar不会消耗,Querier也还好。社区有用户反馈,仅仅Thanos store就占用了40GB的内存,加上Prometheus本身就非常消耗内存,二者结合,处理不当就会雪上加霜。

如果实际只有4台4核16GB的机器,该怎么办?建议是不要开启远程存储,直接使用本地存储!

下面分享几个性能提升的技巧:

  1. 降低采集频率,且不同job根据实际情况分别设置采集频率。
  2. 通过Prometheus参数设置降低查询线程数量
  3. 采集数据超时,则调整采集参数和指标过滤。
  4. 远程读写会导致loadavg问题,此时可以用Sidecar进行处理(京东从remote到opentsdb导致loadavg达到20s)。通过自研的Sidecar可将相关数据搬运至Ceph,再通过批处理的方式进行block搬运。
  5. Thanos和Prometheus都是资源耗费型产品,故应尽量避免使用topn、topk,可以通过recording rule做预算

二、M3DB技术详解

Prometheus的remote_write无论使用何种远程存储,都建议过滤配置信息,降低远程存储负载。

因本人使用Tdengine作为远程存储,因此原作者介绍的M3DB不做实操。

2.1、M3简述

1.1小节的架构图中可以看到,Thanos官方当前支持的远程存储主要有Google cloud storage、S3(AWS S3、Ceph、DO等)、Azure blob storage、OpenStack swift、Tencent COS等。但是这些存储要么主要面对国外用户,要么都是云服务商的产品。在此前的学习过程中有提到M3DB,本小节将进一步拓展此开源项目。

Uber M3是一个已经在Uber使用多年的指标平台,M3可以在较长的保留时间内可靠地存储大规模指标数据。同时M3也可以作为Prometheus后端存储,旨在为Prometheus指标提供安全、可扩展且可配置的多租户存储。

M3作为离散组件可以提供多种功能,故其成为大规模时序数据的理想平台。其主要包括4个组件:M3 Coordinator、M3DB、M3 Query、M3 Aggregator。

  • M3DB:分布式时序数据库,可为时序数据和反向索引提供可伸缩、可扩展的存储。
  • M3 Coordinator:辅助服务,用于协调上游系统(如Prometheus和M3DB)之间的读写操作。它是用户访问M3DB的桥梁,例如长期存储和与其它监控系统(如Prometheus)的多DC(数据中心)设置。
  • M3 Query:分布式查询引擎,它对PromQL和graphite原生支持(即将推出M3QL)。它包含一个分布式查询引擎,用于查询实时和历史指标,支持多种不同的查询语言。它旨在支持低延迟查询和可能需要更长时间执行的查询,聚合更大的数据集,用于分析用例。
  • M3 Aggregator:聚合层,作为专用度量聚合器运行的服务,它基于存储在etcd中的动态规则提供基于流的下采样。它使用领导者选举和聚合窗口跟踪,利用etcd来管理此状态,从而可靠地为低采样度量标准至少发送一次聚合到长期存储设备。这提供了成本有限且可靠的下采样和汇总指标。这些功能也存在于M3 Coordinator中,但专用聚合器是分片复制的,在部署M3 Coordinator时需要谨慎。

2.2、M3DB特性

M3DB具有如下特性:

  1. 分布式时间序列存储,单个节点使用WAL提交日志并独立保存每个分片的时间窗口。
  2. 集群管理建立在etcd之上
  3. 内置同步复制功能,具有可配置的持久化和读取一致性(一个、多个、全部)。
  4. 自定义压缩算法M3TSZ float64(灵感来自于Gorilla TSZ)可配置为无损压缩或有损压缩,且时间精度可自行配置,范围从秒到纳秒均可;其是一种可配置的无序写入,当前受限于已配置时间窗口的块的大小。

2.3、M3DB限制

M3DB在使用时有以下限制:

  1. M3DB主要是为了减少摄取和存储数十亿条时间序列的成本并提供快速可伸缩的读取,因此目前存在一些限制,使M3DB不适合用作通用时间序列数据库。
  2. M3DB旨在尽可能避免压缩!目前,M3DB仅在可变压缩时间序列窗口(默认2h)内执行压缩。因此,无序写入仅限于单个压缩时间序列窗口的大小,且无法回填大量数据。
  3. M3DB针对float64值的存储和检索进行了优化,目前尚无法将其用作包含任意数据结构的通用时序数据库。

2.4、M3DM分布式架构

M3DB的分布式架构如下图所示:

M3DB分布式架构

M3DB共有3种角色类型:

  1. Coordinator:M3 Coordinator用于协调集群中所有主机之间的数据读取和写入。这是一个轻量级的过程,不存储任何数据。该角色通常与Prometheus实例一起运行,或者被嵌入收集器代理中,Prometheus的指令都通过M3 Coordinator下发到集群的主机中。
  2. Storage Node:在这些主机上运行的M3 Node进程用于存储数据,并提供读写功能。
  3. Seed Node:首先,这些主机本身就是存储节点。除了存储职责外,它们还运行嵌入式etcd服务器,这是为了允许跨集群运行的各种M3DB进程以一致的方式推断集群的拓补/配置。

2.5、M3DB使用问题

M3DB的使用过程中,也是存在一些问题的。

  • 推荐一个M3DB技术聊天软件,建议多读读里面关于M3DB的聊天记录,一些用户从0.3版本一路使用过来,问题还是比较多;也有一些用户直接放弃M3DB改用Thanos。
  • M3DB在实际生产部署中需要挂卷,比如挂载SSD,SSD有一定的加速作用。
  • Prometheus的remote_write模式下,不需要的指标同样需要过滤,没有必要且数量庞大的指标不建议占用M3DB的存储空间。
  • M3DB在实际使用中需要提前进行压测,CPU、磁盘、内存、QPS等都需要根据实际情况进行容量评估。
  • 从国内中大型互联网公司的技术分享中可得知,它们的Prometheus集群架构并没有使用M3DB的。
  • 对于海量(上亿级别)的数据场景,可以考虑使用clickhouse等技术方案,不必拘泥于M3DB。

三、Loki入门

3.1、Loki的前世今生

Loki人称云原生的“亲儿子”,它是由Grafana公司在2018年北美站KubeCon上发布的,Loki在GitHub上的主页简介就是 “like Prometheus, but for logs”(类似Prometheus,但专为日志监控服务)。

Loki开源项目在正式启动之前作为Grafana labs的内部项目进行运作,Grafana labs用其监控所有的基础设施,每天处理十亿行共1.5TB的日志。Grafana官方提到,Loki的开发受开源监控解决方案Prometheus启发,Loki不会索引日志的内容,而是为每个日志串流加上一组标签,用以压缩且以非结构化的方式存储日志内容,其仅索引元数据。

Grafana labs提到,Loki处理日志的方式带来了高成本效益,Loki通过使用LogQL(类似PromQL)可让开发者进一步将Loki整合到云端原生应用中。官方还提到,Loki进入1.0版本,将遵循语义版本控制规则,提升Loki操作体验及其稳定性。官方不会再对HTTP API进行重大更改,同时也宣布,后续会进行次要版本和错误修正版本的更新,但不会继续维护Go API的稳定性,因此要导入Loki,函式库的开发者要做好收到报错通知的准备。

对于Grafana来说,Loki是CNCF可观察性生态圈上非常重要的一环,Prometheus+Grafana已经成为metrics领域的事实标准,Loki则成为log领域的生态战略。仅剩的trace领域,Grafana在Sides中已经提到在设计开发中。

由于指标是事件回应的关键,警示则通常会以时间串行为条件来写入。但是指标只能用于披露可预期的行为,需要预先声明而且基数有限,因此指标只能用来描述仪表盘事故的一半原因,为了了解完整的事故原因,工程师通常会使用日志来获得更详细的信息。日志作为可观察的资料,在开发、运维、测试、审计等过程中起到非常重要的作用。著名的应用开发十二要素中提到:

  1. 日志使得应用程序运行的动作变得透明,应用本身从不考虑存储自己的输出流。
  2. 不应该试图去写或者管理日志文件
  3. 每一个运行的进程都会直接输出到标准输出(stdout)
  4. 每个进程的输出流由运行环境截获,并将其它输出流整理到一起,然后一并发送给一个或多个最终处理的程序,用于查看或长期存档。

总体来说,Loki是一款受Prometheus启发的水平可扩展、高可用的多租户日志聚合系统。它的设计具有很高的成本效益,易于操作。它不索引日志的内容,而是为每个日志流设置一组标签。

Loki理念:不索引日志,仅索引日志流!

3.2、Loki特性

Loki作者为Tomwilkie,他也是Cortex的作者,因此Loki的设计在很多方面都和Cortex相似。Cortex主要支持读写分离,写入端分两层:第一层Distributor做一致性散列,将负载分发到第二层ingester上,ingester在内存中缓存指标数据,并将其异步写入storage backend。Prometheus、Cortex、Loki都以chunk作为基础存储对象,都可以使用S3等作为远程存储。

3.2.1、类Prometheus

正是因为Loki是受到Prometheus的启发所研发,所以它具有很多和Prometheus类似的特性,比如:

  • kubernetes紧密集成
  • 与Prometheus共享label
  • 与Prometheus类似的LogQL
  • 与Prometheus类似的查询函数
  • 可直接在Grafana里直接查看和检索Loki的日志数据
3.2.2、同类相比

除此之外,Loki还可以和Cortex共享组件,与其它日志聚合系统相比,Loki具有如下特点:

  • 不对日志进行全文索引。通过存储压缩过的非结构化日志和仅对元数据进行索引,Loki操作起来更简单,更省成本。
  • 通过使用与Prometheus相同的标签记录流对日志进行的索引和分组,这使得日志的扩展和操作效率更高。
  • 尤其适合存储kubernetes Pod日志;诸如Pod标签之类的元数据会被自动删除和编入索引。
  • Grafana 6.0+原生支持Loki

3.3、Loki架构简介

Loki日志架构设计主要由三部分组成:

  • Loki:server端(类似Prometheus),负责存储日志和处理查询。
    • Distributor:接收代理端日志
    • Ingester:构建压缩日志
  • Promtail:代理端(类似exporter),必须部署到所有需要日志收集的节点上,负责收集日志并将其发送给Loki,支持与Prometheus相同的配置。
  • Grafana:UI展示

Loki日志收集

Loki最初设计借鉴了构建和运行Cortex的经验,是直接面向kubernetes的。通过在每个节点上运行一个日志收集代理端来收集日志,并与kubernetes API进行交互,以找出日志的正确元数据,并将它们发送到集中式服务,从而在Grafana展示收集到的日志。

Loki整套由代理端promtail、服务端(Distributor、ingester、Querier,3者均运行在Loki主进程中)组成,通过gRPC进行组件间通信,如下图所示:

Loki架构图

3.3.1、Distributor

promtail收集到日志后会将其发送给Loki,Distributor组件负责接受日志。从标签和日志数据汇总,用户端会产生一致的散列,并且会被发送到多个ingester中。

3.3.2、Ingester

ingester是一个负责构建压缩数据库的有状态的组件,ingester接收条目会组成一组具有特别标签与时间跨度的日志,并以gzip的形式进行压缩。ingester使用元数据而非日志内容建立索引,以便简单地供用户查询,它还能与时间串行的指标的标签相关联。经处理的日志会定义更新至S3这类对象存储中,并指向Cassandra、Bigtable、DynamoDB等数据库。

3.3.3、Querier

Querier组件会根据给定时间区间和标签选择器进行索引查找,以便确定要匹配哪些块,接着进行grep操作,然后提供查询结果。它还会从ingester那里获取尚未被冲刷过的最新数据。

Querier的数据结构与算法:Loki中的日志存为大量文件,合并输出时采取堆的数据结构,时间正序采用最小堆,时间逆序采用最大堆。

AWS与GCP等公有云供应商提供了自定义的指标萃取方法,AWS还提供从指标导航到日志的功能,两者都使用不同的查找语言来查找日志数据。Loki可以简化查找的过程,并解决了短暂来源kubernetes pod崩溃时日志丢失的问题。

3.4、Loki实践

推荐结合官方GitHub文档来使用Loki,此外第三方网站也有Grafana上各种使用方法。

Loki主要有二进制、Tanka、Helm、Docker、Docker compose这几种安装方式,具体参照官方文档。promtail组件需要指定日志保存路径,如/var/log。

应用 监听端口 说明
Loki 3100 HTTP API
Loki 9096 gRPC通信端口
promtail 9080 HTTP API
3.4.1、安装Loki

本次测试选用Docker方式部署,系统环境:Deepin 20.6(Debian系列),Loki为最新版2.5.0。

3.4.1.1、下载yml
# loki配置
wget https://raw.githubusercontent.com/grafana/loki/v2.5.0/cmd/loki/loki-local-config.yaml -O loki-config.yaml
# promtail配置
wget https://raw.githubusercontent.com/grafana/loki/v2.5.0/clients/cmd/promtail/promtail-docker-config.yaml -O promtail-config.yaml
3.4.2、启动
# 启动Loki容器
docker run --name loki -d -v $(pwd):/mnt/config -p 3100:3100 grafana/loki:2.5.0 -config.file=/mnt/config/loki-config.yaml
# 启动promtail容器
docker run --name promtail -d -v $(pwd):/mnt/config -v /var/log:/var/log --link loki grafana/promtail:2.5.0 -config.file=/mnt/config/promtail-config.yaml

3.4.2、配置Grafana

Grafana首页–>Confguration–>Datasource–>Add Datasource,添加数据源

image-20220621173637252

根据实际情况填写相应配置,填写完毕后点击右下角save&test,保存并测试配置。

image-20220621174024503

点击首页–>Explore,在顶部选择Loki数据源,然后就可以类似Prometheus那样使用LogQL查询日志了。

image-20220621180028216

四、ELk入门

介绍了Prometheus日志领域的技术后,不得不提一下大名鼎鼎的日志解决方案ELK!ELK已经成为目前最流行的集中式日志解决方案,它主要由Beats、Logstash、Elasticsearch、Kibana等组件组成,用于实时日志的收集、存储、展示等,可谓是运维人员的大杀器。ELK是旧称,因其早期基础架构由ElastICsearch、Logstash、Kibana组成,但发展至今,ELK已经不仅仅只有这3个组件,越来越多的组件加入了这个大家庭,只是大家习惯了称呼为ELK。

4.1、ELK架构简述

4.1.1、Beats

Beats组件集合了多种单一用途的数据收集器,它们从海量机器和系统上向Logstash或Elasticsearch发送数据。这些收集器包括filebeat、metricbeat、packbeat、winlogbeat、auditbeat、heartbeat、functionbeat,本节重点介绍filebeat。

filebeat是一款轻量级的占用服务资源很少的数据收集引擎,是ELK家族的新成员,可以代替Logstash成为应用服务端的日志收集引擎,支持将收集到的数据输出到Kafka、Redis等队列。

4.1.2、Logstash

Logstash是开源的服务端数据处理管道,能够同时从多个来源采集数据、转换数据,然后将其发送到任意“存储库”中,进行数据的搜集、分析、过滤。

同样作为数据收集引擎,相比较filebeat,Logstash比较重量级,它集成了大量的插件,支持丰富的数据源收集,可以对收集的数据进行过滤、分析、格式化日志等操作,一般采用C/S架构,Client端安装在需要收集日志的主机上,Server端负责对收到的各个节点上的日志进行过滤、修改等操作后再一并发送到ELasticsearch。

4.1.3、Elasticsearch

Elasticsearch是一款分布式、Restful风格的搜索和数据分析引擎,能够解决不断涌现的各种用例,主要提供搜索、分析、存储数据三大功能。

作为Elastic Stack的核心,它集中存储用户数据,帮助用户发现意料之中以及意料之外的情况。作为一款分布式数据搜索引擎,它基于Apache Lucene实现,可进行集群化,可提供数据的集中式存储、分析,以及强大的数据搜索和聚合功能。它特点有分布式、零配置、自动发现、索引自动分片、索引副本机制、Restful风格接口、多数据源、自动搜索负载等。

4.1.4、Kibana

通过Kibana,可以对Elasticsearch进行可视化,还可以在Elasticsearch stack中进行导航,从而跟踪查询负载到理解请求如何流通整个应用,对汇总、分析、搜索重要日志数据都有着非常重要的作用。

作为数据的可视化平台,Kibana通过该web平台可以实时查看Elasticsearch中的相关数据,并提供丰富的图标统计功能。

4.2、为什么用ELK?

ELK对于应用系统可观察性有着非常重要的作用!

场景:随着业务量的增长,每天业务服务器都会产生大量日志,单个日志文件可能非常大,用Linux自带的工具如:cat、grep、awk分析会越来越力不从心;除了服务器日志,还有应用报错日志,这些日志分布在不同服务器,查阅烦琐。在规模较大的场景中,此方法效率低下,面临的问题包括日志量太大不好归档、文本搜索太慢、多维度查询等。因此,需要一套能集中化的日志管理系统,将所有服务器上的日志收集汇总。常见的解决思路是建立集中式日志收集系统,对所有节点上的日志进行统一收集、管理、访问。

问题举例如下:

  • 大量不同种类的日志成为运维人员的负担,难以管理。
  • 单个日志文件过大,难以使用常用的文本工具进行分析,检索困难。
  • 日志分布在不同服务器上,一旦业务出现故障,需要一台台机器查看日志。

一般大型系统是一个分布式部署的架构,不同的服务器模块部署在不同的服务器上,问题出现时,大部分情况需要根据问题暴露的关键信息,定位到具体的服务器和服务模块。构建一套集中式日志系统,可以提高定位问题的效率。

ELK提供了一整套解决方案,并且其中所用都是开源软件,各软件互相配合使用、无缝衔接,高效地满足了很多场合的需求,是主流的集中式日志解决方案。ELK主要支持如下功能:

  • 收集:能够采集多种来源的日志数据
  • 传输:能够稳定地把日志数据传输到中央系统
  • 存储:存储日志数据
  • 分析:支持UI分析
  • 警告:能够提供错误报告,拥有监控机制。

4.3、ELK基础架构

ELK基础架构如下图所示:

ELK基础架构

如上图所示,在个应用服务器分别部署一个Logstash组件,该组件作为日志收集器,将收集到的数据经过过滤、分析、格式化等处理后发送至Elasticsearch进行存储,最后使用Kibana进行可视化展示。

4.3.1、缺点

这种架构不足之处也很明显:

  1. Logstash在应用服务器上读取、过滤、输出log时,会占用服务器较多资源,增加服务器的负载压力,因此可能需要通过拆分来提升性能。
  2. 在高并发情况下,日志传输峰值较高。如直接写入Elasticsearch,可能会因Elasticsearch HTTP API无法处理过多请求,而导致在日志写入过于频繁的情况下出现写入超时、数据丢失等问题,所以需要一个缓冲中间件。
  3. 运维调优难度大。比如还未接入企业中所有业务时,就可能发生Elasticsearch索引数据量过大的情况,查询时报CPU负载过高等问题。
4.3.2、调优

这种基础架构调优的方向主要考虑以下几种思路:

  • 架构层面:增加Kafka代理缓存
  • Elasticsearch层面:很多卡死的原因是日志导致索引过大,故除了提升Elasticsearch服务器配置外,索引也需要进行调优(比如自定义templates、非必须字段不存储),模板中设置TTL过期时间(如30天,尽量不设置不过期)。
  • Kafka:在增加代理缓存的基础上,将partition分成不同的topic,不同业务日志写入不同的topic。

4.4、改良架构

改良后的另一种架构如下图所示,此架构引入Kafka或Redis的缓存机制,即使远端Logstash server因为故障停止运行,数据也不行丢失,因为数据已被持久化存储下来。

ELK改良架构

关于缓冲中间件的选择,早期的博客都是推荐使用Redis,因为Elastic stack建议使用Redis来做消息队列。但是很多大佬已经通过实践证明使用Kafka更加优秀,原因如下:

  1. Redis无法保证消息的可靠性(因为Redis是缓存服务器,数据临时存在内存中),但Kafka可以。
  2. Kafka吞吐量、集群模式都比Redis优秀。
  3. Redis受限于机器内存,当内存达到阈值,数据就会丢失。通过加Redis内存的方式治标不治本,因为Redis内存越大,触发持久化的操作阻塞主线程的时间越长。相比之下,Kafka的数据堆积在硬盘中不存在此问题。
4.4.1、缺点

此架构也存在一些问题:

  • 二级Logstash要分析处理大量数据,同时Elasticsearch要存储和索引大量数据,负载较高,往往要配置为集群模式,否则容易出现性能瓶颈。
  • Logstash shipper是JVM运行的,占用Java内存较多。有人做过测试,在8核8GB情况下,Logstash shipper常驻内存660MB。因此,这么一个巨无霸部署在应用服务器端就不太合适了,我们需要一个更加轻量级的日志采集组件。
  • 按上述架构部署集群,所有业务放在一个大集群中会出现相互影响的情况。一个业务系统出问题了,就可能拖垮整个日志系统。因此,需要降低系统的耦合度。

4.5、二次改良架构

基于改良架构进行二次改良,得到的新架构如下图所示:

ELK二次改良架构

这架构与上一个唯一不同的是日志收集器换成了filebeat。filebeat是轻量级别的,占用服务器资源较少,所以选择它来对应用服务器进行日志收集。一般filebeat会配合Logstash一起使用,小型企业多数采用这套架构。此套架构中Logstash和Elasticsearch都推荐采用集群模式部署,这样除了Beats节点以外,整个项目任何功能模块都能进行横向扩容收缩。相比之前的架构,二次改良的架构性能更好、采集速度更快、搜索速度更快、更稳定。

4.6、Tribe Node概念的架构

运维过ELK的朋友可能遇到过这样的场景,如果公司有3个独立的大型业务部门,那么可能会因为其中一个业务有问题而拖垮整个ELK集群从而导致整个日志系统宕机。针对这个问题可以考虑采用以下的架构方案:

Tribe Node概念的架构

这套架构主要是引入了Tribe Node的概念,中文翻译为部落节点、部落节点是一个特殊的客户端,可以连接多个集群,在所有连接的集群上执行搜索等操作。该节点还负责将请求路由到正确的后端Elasticsearch集群上。

这套架构的缺点在于没有对日志进行冷热分离。因为一般来说,对最近一周内的日志查询是最多的。以七天为界限,区分冷热数据,可以大大优化查询速度。

4.7、冷热分离的架构

在基于Tribe Node概念的架构基础上增加冷热分离的功能,如下图所示:

冷热分离的Tribe Node架构

这套架构中,每个业务准备两个Elasticsearch集群,可以把它们分别理解为冷、热集群。七天以内的数据,存入热集群,以SSD存储索引;超过七天的数据就放入冷集群,以SATA存储索引。

这个架构的隐患是,对敏感数据没有进行处理就直接写入日志了。可以通过Java现成的日志组件,比如log4j等提供日志过滤功能,对敏感信息进行脱敏后再存为日志。

五、fluentd和fluent bit项目简介

现在逐渐出现了从ELK到EFK的演进,也就是Logstash被逐渐替换成Fluentd/Flient Bit。

5.1、fluentd和fluent bit简述

2019年4月从CNCF毕业的用C和Ruby编写的fluentd作为通用日志收集器,以其高效、灵活、易用的特性逐渐取代了用Java编写的Logstash成为新的日志解决方案EFK中重要一员,并在云原生领域得到广泛的认可与应用。

Google的云端日志服务stackdriver也用修改后的fluentd作为agent。然而fluentd开发团队并没有停滞不前,之后又推出了更为轻量级别的完全用C编写的产品:fluent bit。

如下图所示:

fluent bit

fluent bit是一款开源的、多平台的日志处理器和转发器,它允许从不同的数据源收集数据和日志,并将它们统一发送到多个目的地。它完全兼容docker和kubernetes环境。fluent bit有一个可插拔的架构,支持大约30个扩展。它快速、轻量级,并通过TLS为网络操作提供所需的安全性。

5.2、fluentd和fluent bit区别

fluentd和fluent bit项目都是由Treasure data创建和赞助的,它们的目标是解决日志的收集、处理和交付等问题。这两个项目有很多相似之处,fluent bit是基于fluentd架构和一般的设计方法和经验,选择哪一个取决于最终的需求。从架构的角度来看,fluentd是一个日志收集器、处理器、聚合器。fluent bit占用资源更少,是一个日志收集器和处理器(无聚合特性)。二者的比较可以参考如下的官方文档:

fluentd和fluent bit比较

从上图可得知,fluent bit更轻量,更倾向于做数据采集;而fluentd插件更加成熟,更适合做日志的聚合器,也可以将二者结合起来使用,新的架构图如下所示:

fluentd和fluent bit结合架构图

在这个架构中,fluent bit负责数据采集,fluentd负责数据聚合处理,Elasticsearch/InfluxDB负责数据的存储,Kibana/Grafana则负责数据的分析及展示。采集日志的agent采用更轻量的fluent bit,而fluentd则作为fluent bit的接收者,用fluentd实现集中解析后再发到最终的存储器上,这样就不用每个节点都去部署fluentd了。

之前介绍的filebeat有自动加载配置的功能,解析日志也比较强大,如果需要频繁添加解析规则又不想频繁重启,并且想用一个比较轻量的agent,那么filebeat是一个不错的选择。

六、Operator模式现状与未来展望

云原生领域有一个很常见的词语:Operator。Operator=Controller+CRD(customer resource definition)。Operator概念由CentOS从kubernetes的CRD自定义资源衍生而来的。CRD的提出更是为开发者打开了创新的大门,从最开始的分布式应用部署,到更广阔的应用开发/发布场景,再到各类云服务场景,各类资源都可以接入kubernetes API中进行有效协同管理。Operator在这其中起到推波助澜的作用,我们可以从awesome-operator中看到,Operator实现的种类非常齐全。

Operator来自于kubernetes的理念,用户可以定义新的管理配置的API对象,然后通过编写controller来实现用户对云原生产品的管控需求,从而实现特定应用程序的常见操作以及运维自动化。这就是Operator的模式,它是一个概念,并不是一个开发框架、一种资源或者一个项目。

6.1、Prometheus Operator

为了应对无法通过kubernetes原生提供的应用管理概念实现自动化的运维管理和配置管理问题coreOS率先引入了Operator的概念,并且首先推出了在kubernetes下运行和管理etcd的etcd Operator,随后又推出了Prometheus Operator。

Prometheus Operator的架构示意图如下所示:

PrometheusOperator架构图

Prometheus Operator负责监听这些自定义资源的变化,并根据这些资源定义自动化地完成如Prometheus server自身以及配置的自动化管理工作。

Prometheus Operator目前主要提供4类资源:

  • Prometheus:声明式创建和管理Prometheus server实例。
  • ServiceMonitor:负责声明式管理告警配置
  • ALertmanager:声明式创建和管理ALertmanager实例。

在kubernetes集群中,可以简单地安装Prometheus Operator,并对Prometheus进行实例创建、管理监控配置甚至使用自定义配置等高级功能。

在kubernetes集群上,Rancher一般自带Prometheus Operator。

在上一节中介绍的fluent bit存在一个问题,即fluent bit虽然更加轻量高效,但配置文件变更后无法很好地自动重新加载新的配置。为解决这一问题,KubeSphere团队开发了fluentbit Operator并将其应用到KubeSphere中作为日志收集器。通过fluentbit operator,KubeSphere实现了控制台灵活地添加、删除、暂停、配置日志接收者。

6.2、TiDB Operator

国内技术型独角兽PingCAP也致力于通过Operator模式,将kubernetes打造成TiDB的一个最佳底座。通过TiDB Operator,一方面可以把TiDB在kubernetes上的运维门槛降低,让入门级用户也能轻松搞定水平伸缩和故障转移这些高级玩法(比如快速构建TiDB集群、local pv、故障转移、优雅升级等);另一方面,基于kubernetes的RESTful API提供了一套标准的集群管理API,用户可以依赖API把TiDB集成到自己的工具链或者PaaS平台中,真正赋能用户,让用户把TiDB玩好、玩精。

6.3、展望

在云原生时代,随着kubernetes越来越普及,operator模式在技术上也可以将kubernetes打造成中间件的底座,让中间件技术焕发出新的活力。operator在未来的规范化将体现在资源状态的规范化,功能、能力边界的规范化,协同方式的规范化这3方面。

operator模式未来的愿景会是技术下沉、体验上升。让所有的技术为业务服务,让复杂的技术实现下沉,让用户能够以最简单的方式体验到operator模式的能力,让广大开发者可以发挥创新能力创造出各种operator,然后将其共享出来,应用于实际业务之中。

七、关于灵活运用Prometheus的建议

写到这里,已经到了学习Prometheus的尾声。

Prometheus相关技术栈之多,是这十来篇文章难以道尽的,它就像是监控领域的独孤九剑。

举几个例子开拓一下思路:

7.1、压测可视化监控

JMete+InfluxDB+Grafana可以用于打造压测用的可视化实时监控系统。一些中小型公司的测试人员进行压测的时候,通过看命令行的实时数据进行系统瓶颈的定位,但如果有了全景可视化监控大盘,就可以同时观看CPU、load、I/O等一系列指标在压测过程中的数据变化,效果更好。

从JMete 3.2开始,Backend Listener中引入的InfluxDBBackendListenerClient允许使用UDP或HTTP协议将统计指标发送给InfluxDB。JMete引入了Backend Listener,用于在压测过程中实时发送统计指标数据给时序数据库InfluxDB,通过配置Grafana将数据源连接到InfluxDB,我们就可以创建炫酷的可视化看板,并可以实时获取测试指标数据。此功能提供实时的、美观的图标,能够对比两个以上的测试计划,能够向图标添加注释。

Grafana也提供很多与压测相关的可视化大盘模板,比如JMete Load Test(Grafana点击import输入1152即可),如下图所示:

image-20220623162849488

7.2、Prometheus协助发现问题

Prometheus在业务使用中也能协助发现问题。比如有家公司的业务由于广告及导流的影响,对搜索服务的稳定性剔除了苛刻的要求。为了应对可能的流量突增,他们内部进行了大规模、全方位的性能测试,目的是摸清线上服务的能力,定位瓶颈所在。搜索服务采用了Master-Slave模式的Redis来缓存用户的搜索结果,过期时间为5-10分钟,通过缓存来提升响应时间和并发。key是用户的请求实体JSON字符串,value是搜索结果JSON字符串,并没有使用set、hash等类型。

在他们的压测中,他们在Grafana发现最终瓶颈落在了Redis上面。最终发现是Redis的bigkey问题,主要是JSON字符串太大导致的。Key方面使用了commons-codec里面的md5加密摘要编码,value通过Jackson2序列化后再通过JDK自带的gzip进行压缩。执行完这个优化操作以后,再进行压测时,在同等压力下,Grafana在内存、I/O方面提升效果显著,顺利地解决了Redis的瓶颈问题。

不过序列化方案和压缩方案可以通过benchmark进一步对比,以追求更好的效果。

除了压测、业务领域,大数据领域同样可以使用Prometheus相关技术,比如Flink在1.9版的文档中就提供了接入Prometheus的方法,Hadoop、spark、presto、HBASE,乃至目前比较火的clickhouse,都可以使用Prometheus进行监控。

世间微尘里 独爱茶酒中