Menci's Blog
念念不忘,必有回响
使用 Thanos 低成本构建存储于 Homelab 上的多服务 Prometheus 监控
  1. 1. 介绍
  2. 2. 部署
    1. 2.1. MinIO
    2. 2.2. Prometheus
    3. 2.3. Thanos
      1. 2.3.1. Sidecar
      2. 2.3.2. Store
      3. 2.3.3. Query
  3. 3. 后记

Prometheus 是很常用的服务指标监控系统。通过 Prometheus,我们将线上服务暴露的指标收集存储到时序数据库中,通过 PromQL 来查询,并在 Grafana 中展示。但是,使用小型 VPS 部署的个人项目而言,要在运行业务的同时运行这样一整服务监控系统,无论是长期储存时序数据所占用的磁盘空间,还是执行 PromQL 查询所占用的 CPU 资源,都太为难一台小型 VPS 了。

而相对的,家里 Homelab 服务器上有成本低廉的存储空间和计算资源,只是难以保证在线率,对 Metrics 这种需要不间断记录数据的场景不太适合 —— 如何才能将二者相结合,在 VPS 上实时收集自身服务的指标数据,同步到 Homelab 上进行储存和查询呢?

介绍

Thanos 是一套基于 Prometheus 的服务监控方案,它为 Prometheus 扩展了中心化查询与长期存储的能力。通过 Thanos,我们可以将一个或多个 Prometheus 收集到的指标数据同步到对象存储(如 S3 兼容的对象存储)中,并通过连接到对象储存进行全局的 PromQL 查询。Thanos 有多个微服务组件,这里我们用到 Thanos Sidecar、Thanos Store 和 Thanos Query:

  • Thanos Sidecar: 在运行 Prometheus 的主机上运行,连接对象存储,将 Prometheus 的时序数据库产生的每个块自动上传到对象存储中;
  • Thanos Store: 连接对象存储,对对象存储中的数据进行查询;
  • Thanos Query: 将多个 Thanos 组件作为数据源组合起来,对整体数据进行查询。

每个 Thanos 组件都会同时暴露 PromQL HTTP 接口和 Thanos StoreAPI gRPC 接口,所以既可以将 Thanos 组件用 Thanos Query 进行级联,也可以将任意一个 Thanos 组件实例接入 Grafana。

部署

为了降低成本,我们使用自家 Homelab 上部署的 MinIO 代替 S3 / OSS 服务作为对象存储,并同时在 Homelab 上部署 Thanos Store 和 Thanos Query 来进行查询,而将 Prometheus 和 Thanos Sidecar 部署在每个业务服务器上。为了安全,将各个服务器之间用 VPN 组成内网,将这些服务全部监听在 VPN 内网上。

MinIO

部署 MinIO 的方式不再赘述,这里只给出最简单的 Docker 部署的命令。有条件的也可以考虑把 MinIO 部署在家用 NAS 上。

# 建立 MinIO 数据目录
mkdir -p ~/minio-data
# 假设整个监控服务的 VPN 网段是 10.0.0.0/24,部署 MinIO 的主机是 10.0.0.1
docker run -d \
    -p 10.0.0.1:9000:9000 \
    --restart=unless-stopped \
    --name minio \
    -v ~/minio-data:/data \
    -e "MINIO_ROOT_USER=<MinIO AK>" \
    -e "MINIO_ROOT_PASSWORD=<MinIO SK>" \
    quay.io/minio/minio server /data --console-address :9001

Prometheus

在业务服务器上安装 Prometheus,一般直接使用包管理器安装即可。安装后修改其启动参数(Ubuntu / Debian 通过 APT 安装后在 /etc/default/prometheus):

# 在 ARGS="" 中加入以下参数:

# 因为只需要暴露给本机的 Thanos Sidecar,所以监听 127.0.0.1 即可。
--web.listen-address=127.0.0.1:9090
# 设置一个稍长一点的存储时间,保证在 Homelab 暂时离线时不会丢失数据。
--storage.tsdb.retention.time=7d
# 将块大小设置为 2h(Thanos Sidecar 会在每个块生成后将其上传)。
--storage.tsdb.max-block-duration=2h
--storage.tsdb.min-block-duration=2h
# 启用一些 Thanos Sidecar 会用到的接口。
--web.enable-admin-api
--web.enable-lifecycle

# 如果安装了一些 Prometheus Exporter(如 prometheus-node-exporter),
# 建议也将它们改为仅监听本机。

在 Prometheus 配置中加入 external_labels,对来自该主机的所有数据添加标签:

global:
  external_labels:
    # 用标签标明这些数据来自于哪里,注意不要和业务标签冲突。
    monitor: my-vps

Thanos

Thanos 不在 Ubuntu 和 Debian 的官方仓库中,但在 Cloud Posse 仓库中有所提供。安装 Cloud Posse 的 DEB 仓库(对于其他发行版可以参考链接中的 README 进行安装):

curl -1sLf 'https://dl.cloudsmith.io/public/cloudposse/packages/cfg/setup/bash.deb.sh' | bash

然后 apt install thanos 即可安装 Thanos。但 Cloud Posse 提供的 Thanos 只有程序本身,没有 systemd 服务配置,需要自行编写。创建 /etc/systemd/system/[email protected](修改自 thanos-io/thanos#6865):

[Unit]
Description=Thanos (%i)
After=network.target
RequiresMountsFor=/etc/thanos
AssertPathExists=/etc/thanos/%i.conf

[Service]
Type=exec
# The following reads words from `/etc/thanos/%i.conf`, unescapes any `\`-
# escape-sequences for `printf`’s `b`-conversion-specifier in each word and uses
# the resulting words as arguments to Thanos.
ExecStart=sh -c 'name="$$1"; set --; for word in $$(grep -E -v "^[[:space:]]*(#.*)?$$" "/etc/thanos/$${name}.conf"); do set -- "$$@" "$$(printf "%%b" "$${word}")"; done; exec /usr/bin/thanos "$$@"' '' %i

User=prometheus
Group=prometheus

SyslogIdentifier=thanos@%i
Restart=on-failure
RestartPreventExitStatus=SIGINT SIGTERM

[Install]
WantedBy=multi-user.target

注意,为了方便读取 Prometheus 的数据文件,这里直接使用了 prometheus 用户和组。如果没有安装 Prometheus 请注意修改。

Sidecar

在运行 Prometheus 的主机上启动 Thanos Sidecar。创建 /etc/thanos/sidecar.conf

sidecar
# 限制只能查询最近 3h 的数据,以免 Thanos Query 将过多的查询任务
# 分配给 Thanos Sidecar。考虑到 Thanos Sidecar 每 2h 同步一个块
# 到 MinIO,3h 足以填充最近的时间窗口。
--min-time=-3h
# 在 VPN 内网上暴露 Thanos StoreAPI gRPC 服务。
--grpc-address=10.0.0.2:10901
--http-address=10.0.0.2:10902
# 指定本机的 Prometheus 端口,Thanos Store 会将查询下游的查询请求
# 对应转发到 Prometheus 上。
--prometheus.url=http://127.0.0.1:9090
# 指定 Prometheus 的时序数据库路径,以下为 Debian / Ubuntu APT 安装
# 后的默认路径。
--tsdb.path=/var/lib/prometheus/metrics2/
# 指定要上传到的对象存储,在单独的文件中配置。
--objstore.config-file=/etc/thanos/objstore.yaml

并创建对象存储配置 /etc/thanos/objstore.yaml

type: S3
config:
  bucket: thanos
  # 通过 VPN 连接到 Homelab 上的 MinIO。
  endpoint: 10.0.0.1:9000
  # `insecure` 表示使用 HTTP 而非 HTTPS。
  insecure: true
  access_key: <MinIO AK>
  secret_key: <MinIO SK>

启动 Thanos Sidecar(systemctl enable --now thanos@sidecar)后,如果一切正常,Thanos Sidecar 应当会输出:

ts=2024-02-14T19:06:34.414308781Z caller=sidecar.go:195 level=info msg="successfully loaded prometheus version"
ts=2024-02-14T19:06:34.415053542Z caller=sidecar.go:217 level=info msg="successfully loaded prometheus external labels" external_labels="{monitor=\"my-vps\"}"
ts=2024-02-14T19:06:36.412919439Z caller=shipper.go:263 level=warn msg="reading meta file failed, will override it" err="failed to read /var/lib/prometheus/metrics2/thanos.shipper.json: open /var/lib/prometheus/metrics2/thanos.shipper.json: no such file or directory"
ts=2024-02-14T19:06:36.504494277Z caller=shipper.go:361 level=info msg="upload new block" id=01HPM5YGK7IUD9PFK3FYSUJIN2
ts=2024-02-14T19:06:39.282504533Z caller=shipper.go:361 level=info msg="upload new block" id=01HPM9DF4JODUT9L2PA8NG4UT4

其中 reading meta file failed, will override it 表示第一次启动,没有读取到上传进度文件,将会自动创建。如果又看到上传错误相关的信息,请检查对象存储是否配置正确。

Store

在 Homelab 上启动 Thanos Store。为了方便这里直接将 Thanos Store 与 Thanos Query 部署在一起。

创建 /etc/thanos/store.conf

store
# 仅在本机暴露 Thanos StoreAPI gRPC 服务,供 Thanos Query 访问。
--grpc-address=localhost:10901
--http-address=localhost:10902
# 需要注意的是,Thanos Store 本身不参与时序数据的存储,它只会连接
# 对象存储服务进行查询,而磁盘上仅存储少量元数据。
# 需要手动创建该目录,注意权限。
--data-dir=/var/lib/thanos
# 指定要上传到的对象存储,在单独的文件中配置。
--objstore.config-file=/etc/thanos/objstore.yaml

并且同样创建 /etc/thanos/objstore.yaml 配置文件,连接 Homelab 上的 MinIO。

启动 Thanos Sidecar(systemctl enable --now thanos@store)后,如果一切正常,Thanos Sidecar 应当会输出:

ts=2024-02-14T18:16:46.892043238Z caller=fetcher.go:557 level=info component=block.BaseFetcher msg="successfully synchronized block metadata" duration=7.928513ms duration_ms=7 cached=2 returned=2 partial=0
ts=2024-02-14T18:16:46.904327891Z caller=bucket.go:757 level=info msg="loaded new block" elapsed=12.215001ms id=01HPM5YGK7IUD9PFK3FYSUJIN2
ts=2024-02-14T18:16:46.904555976Z caller=bucket.go:757 level=info msg="loaded new block" elapsed=12.367995ms id=01HPM9DF4JODUT9L2PA8NG4UT4
ts=2024-02-14T18:16:46.904609028Z caller=store.go:451 level=info msg="bucket store ready" init_duration=20.501403ms
ts=2024-02-14T18:16:46.904732128Z caller=intrumentation.go:56 level=info msg="changing probe status" status=ready
ts=2024-02-14T18:16:46.904780023Z caller=grpc.go:131 level=info service=gRPC/server component=store msg="listening for serving gRPC" address=127.0.0.1:10901

Query

在 Homelab 上启动 Thanos Query。创建 /etc/thanos/query.conf

query
# 仅在本机暴露 Thanos StoreAPI gRPC 服务,供 Grafana 访问。
# 如果需要在其他主机访问,建议使用 Nginx 进行 HTTPS 反代。
--http-address=localhost:19090
--grpc-address=localhost:10911
# 如果需要在其他主机访问,启用 HTTP Basic Auth。
--http.config=/etc/thanos/query-http.yaml

# 指定数据源,每个 `--store` 参数是一个数据源,与各个服务的
# `--grpc-address` 相对应。

# Thanos Store 包含除最新一个块(2h)之外的所有历史数据。
--store=localhost:10901

# 加入各个 VPS 上的 Thanos Sidecar,以查询最新数据。
# 因为有 `--min-time` 的限制,所以 Thanos Sidecar 和对应
# 的 Prometheus 不会有过大的压力。

# my-vps
--store=10.0.0.2:10901

# my-vps-2
--store=10.0.0.3:10901

# my-vps-3
--store=10.0.0.4:10901

如果需要启用 HTTP Basic Auth,创建 /etc/thanos/query-http.yaml(密码使用 bcrypt 哈希后存储):

basic_auth_users:
  my-grafana: $2y$04$wSTX2UC9pGdLB.1Dl7t1eOxXuIcerYgH/qJRf2id.m60hnuKbxKHm

最后在 Grafana 或者其他 PromQL 客户端上连接到 Thanos Query 的 HTTP 端口即可。Thanos Query 会自动根据各个 --store 数据源的元数据来得知他们提供的数据范围,来决定去哪些数据源查询。

后记

除了使用 Thanos Sidecar 将 Prometheus 接入到 Thanos 中之外,还可以使用 Thanos Receiver 进行接入。Thanos Receiver 通过配置 Prometheus 的远程写入功能,来让 Prometheus 主动将数据推送给它,来取代主动读取 Prometheus 的时序数据库目录,这主要适用于当 Prometheus 运行在云上的托管环境,无法访问其数据目录的情况。除此之外,一般来说更推荐使用 Thanos Sidecar 进行接入。

此外,Thanos 还有一些其他组件:

  • Thanos Compactor: 对上传到对象存储中的数据进行压缩和降采样,减少空间占用;
  • Thanos Query Frontend: 置于 Thanos Query 之前,对查询进行拆分和缓存,并能够对慢查询进行记录;
  • Thanos Ruler: 通过预定义监控规则,自动监视 Thanos 数据源中的指标数据,当条件满足时触发告警。