背景描述
当采集指标过多时,超过单个 prometheus 的处理能力,我们通常会采用多个 prometheus 实例分别采集一定的指标分片,然后通过 thanos 做聚合,对外提供统一查询服务。
本文通过简单的实验,演示 thanos 的分片数据采集与聚合查询能力,架构如下:
实验假设存在两个 prometheus 实例,分别采集不同的监控指标分片,prometheus 实例均通过 remote write 方式将数据写入 thanos receiver。receiver 采用多副本方式运行,并通过 hashring 方式实现将不同租户的数据写入不同的 receiver 实例,即 receiver 01 和 receiver 02 存储来自 prometheus foo 的监控指标数据,而 receiver 03 和 receiver 04 存储来自 prometheus bar 的监控指标数据,最后通过 thanos query 聚合多个 receiver 的数据,提供统一查询接口。
实验验证
本文基于 docker 方式启动 prometheus 以及 thanos,需要在机器上预装 docker。 Ubuntu 或 CentOS 环境,可以通过如下命令一件安装:
curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun
1. 部署 Prometheus 实例
# cat << EOF > prometheus-foo-conf.yaml
global:
scrape_interval: 5s
external_labels:
cluster: foo
replica: 0
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['127.0.0.1:9090']
remote_write:
- url: 'http://127.0.0.1:10908/api/v1/receive'
headers:
THANOS-TENANT: foo
EOF
# docker run -d --net=host --rm \
-v $(pwd)/prometheus-foo-conf.yaml:/etc/prometheus/prometheus.yaml \
-u root \
--name prometheus-foo \
quay.io/prometheus/prometheus:v2.27.0 \
--config.file=/etc/prometheus/prometheus.yaml \
--storage.tsdb.path=/prometheus \
--web.listen-address=:9090 \
--web.enable-lifecycle
# cat << EOF > prometheus-bar-conf.yaml
global:
scrape_interval: 5s
external_labels:
cluster: bar
replica: 0
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['127.0.0.1:9090']
remote_write:
- url: 'http://127.0.0.1:10908/api/v1/receive'
headers:
THANOS-TENANT: bar
EOF
# docker run -d --net=host --rm \
-v $(pwd)/prometheus-bar-conf.yaml:/etc/prometheus/prometheus.yaml \
-u root \
--name prometheus-bar \
quay.io/prometheus/prometheus:v2.27.0 \
--config.file=/etc/prometheus/prometheus.yaml \
--storage.tsdb.path=/prometheus \
--web.listen-address=:9091 \
--web.enable-lifecycle
注意,我们在实验中通过给 prometheus 增加 cluster:foo 和 cluster:bar 的标签,来判断数据指标的来源,同时通过 remote_write 中的 headers 字段(THANOS-TENANT)区分不同的租户,以便 thanos receiver 分片存储。
2. 部署 Thanos Receiver 实例
# cat << EOF > hashring.json
[
{
"hashring": "foo",
"endpoints": ["127.0.0.1:10907","127.0.0.1:10910"],
"tenants": ["foo"]
},
{
"hashring": "bar",
"endpoints": ["127.0.0.1:10913","127.0.0.1:10916"],
"tenants": ["bar"]
}
]
EOF
# docker run -d --rm \
--net=host \
-v $(pwd)/hashring.json:/data/hashring.json \
--name receive-01 \
quay.io/thanos/thanos:v0.25.0 \
receive \
--tsdb.path "/receive/data" \
--grpc-address 127.0.0.1:10907 \
--receive.local-endpoint 127.0.0.1:10907 \
--receive.hashrings-file=/data/hashring.json \
--receive.replication-factor 2 \
--label "receive_cluster=\"thanos\"" \
--http-address 127.0.0.1:10909 \
--remote-write.address 127.0.0.1:10908
# docker run -d --rm \
--net=host \
-v $(pwd)/hashring.json:/data/hashring.json \
--name receive-02 \
quay.io/thanos/thanos:v0.25.0 \
receive \
--tsdb.path "/receive/data" \
--grpc-address 127.0.0.1:10910 \
--receive.local-endpoint 127.0.0.1:10910 \
--receive.hashrings-file=/data/hashring.json \
--receive.replication-factor 2 \
--label "receive_cluster=\"thanos\"" \
--http-address 127.0.0.1:10912 \
--remote-write.address 127.0.0.1:10911
# docker run -d --rm \
--net=host \
-v $(pwd)/hashring.json:/data/hashring.json \
--name receive-03 \
quay.io/thanos/thanos:v0.25.0 \
receive \
--tsdb.path "/receive/data" \
--grpc-address 127.0.0.1:10913 \
--receive.local-endpoint 127.0.0.1:10913 \
--receive.hashrings-file=/data/hashring.json \
--receive.replication-factor 2 \
--label "receive_cluster=\"thanos\"" \
--http-address 127.0.0.1:10915 \
--remote-write.address 127.0.0.1:10914
# docker run -d --rm \
--net=host \
-v $(pwd)/hashring.json:/data/hashring.json \
--name receive-04 \
quay.io/thanos/thanos:v0.25.0 \
receive \
--tsdb.path "/receive/data" \
--grpc-address 127.0.0.1:10916 \
--receive.local-endpoint 127.0.0.1:10916 \
--receive.hashrings-file=/data/hashring.json \
--receive.replication-factor 2 \
--label "receive_cluster=\"thanos\"" \
--http-address 127.0.0.1:10918 \
--remote-write.address 127.0.0.1:10917
注意:所有 thanos receiver 使用的是同一份 hashring 配置,因此能够互相发现对端地址,根据租户标识实现 request 转发。每个 receiver 必须配置 --receive.local-endpoint
参数,并保证与 hashring 内配置的地址一致,才能正确实现 peer 间的互相发现。假如忘记给某个 receiver 配置该参数,会发生 context deadline exceeded
异常:
caller=handler.go:351 component=receive component=receive-handler msg="failed to handle request" err="context deadline exceeded"
3. 部署 Thanos Query 实例
3.1 仅连接 thanos receiver 01
# docker run -d --rm \
--net=host \
--name query \
quay.io/thanos/thanos:v0.25.0 \
query \
--http-address "0.0.0.0:39090" \
--store "127.0.0.1:10907"
仅能查看到 foo 的指标,证明已经按照租户分片存储了。
注意,–store 也可以指定 receiver 02 的地址,效果不变,因为我们设置了 --receive.replication-factor 2
副本复制。
3.2 仅连接 thanos receiver 03
# docker rm -f query
# docker run -d --rm \
--net=host \
--name query \
quay.io/thanos/thanos:v0.25.0 \
query \
--http-address "0.0.0.0:39090" \
--store "127.0.0.1:10913"
同理,仅能查看到 bar 的指标
注意,–store 也可以指定 receiver 04 的地址,效果不变,因为我们设置了 --receive.replication-factor 2
副本复制。
3.3 同时连接所有 receiver
# docker rm -f query
# docker run -d --rm \
--net=host \
--name query \
quay.io/thanos/thanos:v0.25.0 \
query \
--http-address "0.0.0.0:39090" \
--store "127.0.0.1:10907" \
--store "127.0.0.1:10910" \
--store "127.0.0.1:10913" \
--store "127.0.0.1:10916"
可以看到,查询出的是最终聚合的指标(同时包含 foo 和 bar 两个 prometheus 实例采集的数据)
小结
当采集数据量过大时,我们可以将 prometheus 实例水平扩展,每个实例负责部分指标的采集工作(例如每个 prometheus 负责一个小集群的数据采集),然后使用 thanos 聚合来自不同 prometheus 的数据。thanos receiver 本身可以水平扩展,而且支持对 prometheus 数据写请求根据租户信息进行哈希,即负载均衡到不同的 receiver 上处理和存储,极大提升了系统的监控承载能力。而且 thanos query 支持聚合查询,提供了和 prometheus 原生的一致的查询接口,能够轻松实现 prometheus 的平滑扩展。