基于thanos搭建分布式prometheus

prometheus存在单点问题,具体痛点可以这么描述:

prometheus单机存储和抓取能力都有上限,容易单点故障。

虽然有一种方式是通过部署N个prometheus分别抓取不同的target来分摊压力的,但是grafana就要为不同的图表配置不同的prometheus地址,复杂程度比较棘手。

thanos+prometheus可以解决这个问题,它提供了一个核心能力:

1,thanos querier组件可以反向代理到N个prometheus,然后grafana直接指向thanos querier即可,thanos querier会从N个prometheus同时查询数据,返回满足Promql的数据结果;

2,如果N个prometheus抓取的数据存在重复的,那么thanos querier会根据label自动去重,确保返回给grafana的数据不会重复,这个特性很关键。

有了thanos querier组件,我们可以很简单的解决单点故障问题:

部署N个prometheus同时抓取数据,然后thanos querier代理它们,这样prometheus挂了一台也不影响服务,数据在其他prometheus上还有。

方案1:thanos sidecar + thanos querier

实际上,querier不能直接反向代理prometheus,而是需要在prometheus的POD内部署一个thanos sidecar组件,querier反向代理至sidecar,再由sidecar查询POD内的prometheus。

querier和sidecar之间走GRPC协议,POD内的sidecar和prometheus则走原生HTTP接口拿数据。

为了高可用,我们可以部署多个prometheus同时抓取。

为了打散采集/存储压力,我们可以部署多个prometheus各自抓取部分数据。

无论出于上述哪种目标或者混合目标,最终依靠Querier的正确配置都可以提供统一的Promql查询入口给Grafana,这一点是毋庸置疑的。

这里看到了图中有几个陌生的组件:

  • Store
  • Bucket
  • Compactor

Prometheus单机存储空间有限,SideCar和Prometheus在同POD内共享数据volume,然后sidecar可以自动将prometheus磁盘数据上传到Bucket里归档存储;后续Querier可以通过Store组件从Bucket拉取到历史数据,这样就实现了历史数据的永久存储和查询能力。

Compactor则是对Bucket里的历史数据进行采样或者清理,有具体需求后再自行研究即可。

上述组件均是可选的,没有历史数据永存需求的可以直接忽略。

方案2:thanos receive + thanos querier

thanos提供的另一种方案是计算与存储分离,利用thanos receive组件部署一个分布式存储集群,然后令prometheus通过remote write机制直接写入到thanos receive集群完成分布式存储,这样prometheus本身就无状态了。

再利用thanos querier反向代理到thanos receive集群,最终对外提供统一的promql查询入口,这个阶段就没有prometheus什么事了。

这套方案的工作原理如下:

thanos receive组件需要部署N个进程组成集群,prometheus可以remote write写入数据到任意receive节点,收到写入请求的receive节点根据metrics label做哈希后计算出负责存储该部分数据的receive节点,并将数据转发过去;

同时,receive可以配置replica数量,这样的话receive会根据哈希结果将数据复制给多个receive做冗余,应该说是非常简单粗暴的复制方案吧,因为监控场景没那么严谨所以基本可用即可,查询去重和归拢则全部依靠querier组件反向查询所有Receive组件搞定。

我们的选择

让prometheus直接配置remote write的方式看起来更好一些,我们只需要在启动一套thanos receive集群,然后让prometheus切过去就可以了,这比配置sidecar的方式感受起来要好很多,也是腾讯TKE团队选择的大规模集群方案。

我发布了2个YAML,不需要任何改动,直接生效到集群中可以马上体验到效果:https://github.com/owenliang/thanos-prometheus

先部署thanos receive存储集群:

细节大家可以自己慢慢看,我讲一下关键:

  1. 用statefulset部署集群,因为这是一个有状态的存储集群,不能随便漂移。
  2. 因为是分布式存储集群,要通过POD亲和性避免2个POD跑到1个Node上。
  3. 核心配置是声明receive集群的地址列表,这里利用headless service可以直接拿到每个POD的dns name,是有规则的。
  4. 每个thanos receive实例的启动参数中指定replica标签唯一标识它。

thanos-receive集群的19291端口是remote-write端口,后续Prometheus往集群的这个端口写入即可,具体数据在集群内再分发会由thanos receive自己搞定。

再部署thanos-querier即可:

  1. thanos-querier需要指定反向代理的列表,它支持基于DNS SRV记录的服务发现机制,因为thanos querier部署在K8S集群内,所以直接走coredns的DNS SRV记录发现thanos receive的所有POD地址即可,对应参数:–store=dnssrv+_grpc._tcp.thanos-receive.monitoring.svc.cluster.local。
  2. 注意让querier忽略掉receive实例返回数据中的replica标签,这样多个receive返回的相同label数据才能判定为重复并被去重。

让grafana指向querier的http 9090端口即可,下图是thanos querier界面,它发现了3个receive store节点,同时对外提供和prometheus原生一模一样的查询界面和API:

最后配置prometheus的remote-write将数据写入到receive集群,这个是prometheus原生配置:

remote_write部分的IP填写receive集群对外暴露的入口IP,端口是receive组件的remote-write专用端口19291,URI部分是固定的。

最后

thanos官方提供了一个sidecar方案的交互式教程,帮助大家理解thanos整个组件关系和配置过程,建议学习:https://katacoda.com/thanos/courses/thanos,实际生产中建议采用方案2。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~