Thanos源码专题【左扬精讲】——Thanos Sidecar 组件(release-0.26)源码阅读和分析(第三章—— cmd/sidecar.go 源代码注释)
Thanos Sidecar 组件(release-0.26)源码阅读和分析(第三章—— cmd/sidecar.go 源代码注释)
https://github.com/thanos-io/thanos/blob/v0.26.0/cmd/thanos/sidecar.go
// Copyright (c) The Thanos Authors.
// Licensed under the Apache License 2.0.
package main
import (
"context"
"math"
"net/url"
"sync"
"time"
extflag "github.com/efficientgo/tools/extkingpin"
"github.com/go-kit/log"
"github.com/go-kit/log/level"
grpc_logging "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging"
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/tags"
"github.com/oklog/run"
"github.com/opentracing/opentracing-go"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
"github.com/thanos-io/thanos/pkg/block/metadata"
"github.com/thanos-io/thanos/pkg/component"
"github.com/thanos-io/thanos/pkg/exemplars"
"github.com/thanos-io/thanos/pkg/extkingpin"
"github.com/thanos-io/thanos/pkg/extprom"
"github.com/thanos-io/thanos/pkg/httpconfig"
"github.com/thanos-io/thanos/pkg/info"
"github.com/thanos-io/thanos/pkg/info/infopb"
"github.com/thanos-io/thanos/pkg/logging"
meta "github.com/thanos-io/thanos/pkg/metadata"
thanosmodel "github.com/thanos-io/thanos/pkg/model"
"github.com/thanos-io/thanos/pkg/objstore/client"
"github.com/thanos-io/thanos/pkg/prober"
"github.com/thanos-io/thanos/pkg/promclient"
"github.com/thanos-io/thanos/pkg/reloader"
"github.com/thanos-io/thanos/pkg/rules"
"github.com/thanos-io/thanos/pkg/runutil"
grpcserver "github.com/thanos-io/thanos/pkg/server/grpc"
httpserver "github.com/thanos-io/thanos/pkg/server/http"
"github.com/thanos-io/thanos/pkg/shipper"
"github.com/thanos-io/thanos/pkg/store"
"github.com/thanos-io/thanos/pkg/store/labelpb"
"github.com/thanos-io/thanos/pkg/targets"
"github.com/thanos-io/thanos/pkg/tls"
)
// 【1、Sidecar 注册与初始化】
// registerSidecar 注册 sidecar 命令到 CLI 应用
func registerSidecar(app *extkingpin.App) {
// 注册命令,并设置帮助信息
// 通过extkingpin.App类型的app参数注册一个新的命令。命令的名称由component.Sidecar.String()提供,描述为"Sidecar for Prometheus server."。extkingpin是对kingpin库的扩展,用于构建命令行接口。
cmd := app.Command(component.Sidecar.String(), "Sidecar for Prometheus server.")
// 创建一个sidecarConfig类型的实例conf,用于存储sidecar进程的配置信息。sidecarConfig结构体定义了一些字段来存储配置数据。
conf := &sidecarConfig{}
// 调用conf的registerFlag方法,注册所有命令行参数到 sidecarConfig。
conf.registerFlag(cmd)
// 设置命令的执行逻辑,即在执行sidecar命令时调用的函数。这里通过Setup方法注册了一个匿名函数,该函数的参数包括一个 run.Group实例、日志记录器logger、Prometheus注册表reg、OpenTracing 追踪器、一个信号通道和一个布尔值。
// 这个匿名函数主要负责配置 reloader 的创建和sidecar服务的启动。
cmd.Setup(func(g *run.Group, logger log.Logger, reg *prometheus.Registry, tracer opentracing.Tracer, _ <-chan struct{}, _ bool) error {
// 解析请求日志配置
// 【问】 为什么要解析 gRPC 日志配置?
// 【答】 为了定制 gRPC 请求的日志格式(如添加跟踪标签),用于调试和监控 gRPC 调用。
tagOpts, grpcLogOpts, err := logging.ParsegRPCOptions("", conf.reqLogConfig)
if err != nil {
return errors.Wrap(err, "error while parsing config for request logging")
}
// 【关键逻辑】创建 reloader 实例,【目的】:监控 Prometheus 配置文件变化,触发动态重载。
// 参数说明:
// - ReloadURL: Prometheus 的 /-/reload 端点
// - CfgFile: 监控的 Prometheus 配置文件路径
// - WatchedDirs: 需要监控的规则文件目录
rl := reloader.New(log.With(logger, "component", "reloader"),
extprom.WrapRegistererWithPrefix("thanos_sidecar_", reg),
&reloader.Options{
// 配置重载URL
ReloadURL: reloader.ReloadURLFromBase(conf.prometheus.url),
// 配置文件路径
CfgFile: conf.reloader.confFile,
// 配置输出文件环境变量
CfgOutputFile: conf.reloader.envVarConfFile,
// 监控目录
WatchedDirs: conf.reloader.ruleDirectories,
// 监控间隔
WatchInterval: conf.reloader.watchInterval,
// 重试间隔
RetryInterval: conf.reloader.retryInterval,
})
//【启动 thanos-sidecar 核心逻辑】调用runSidecar函数来启动sidecar进程。这个函数负责根据提供的配置和资源启动sidecar的实际工作。
return runSidecar(g, logger, reg, tracer, rl, component.Sidecar, *conf, grpcLogOpts, tagOpts)
})
}
// 【2、thanos-sidecar 核心运行逻辑】
// runSidecar 函数用于启动 sidecar 服务,与 Prometheus 进行交互,并处理数据上传。
func runSidecar(
g *run.Group, // g: *run.Group 类型的组对象,用于管理并发执行的任务;
logger log.Logger, // logger: log.Logger 类型的日志记录器;
reg *prometheus.Registry, // reg: *prometheus.Registry 类型的 Prometheus 注册中心。
tracer opentracing.Tracer, // tracer: opentracing.Tracer 类型的追踪器。
reloader *reloader.Reloader, // reloader: *reloader.Reloader 类型的重载器,用于动态加载配置。
comp component.Component, // comp: component.Component 类型的组件对象。
conf sidecarConfig, // conf: sidecarConfig 类型的 sidecar 配置对象。
grpcLogOpts []grpc_logging.Option, // grpcLogOpts: []grpc_logging.Option 类型的 gRPC 日志选项。
tagOpts []tags.Option, // tagOpts: []tags.Option 类型的标签选项。
) error {
// 获取 Prometheus 的 http 客户端配置
// 链式调用方式在 Go 语言中很常见:获取 Prometheus 的 http 客户端配置,并解析为 httpconfig.ClientConfig 结构体。
httpConfContentYaml, err := conf.prometheus.httpClient.Content()
if err != nil {
return errors.Wrap(err, "getting http client config") // 错误处理,如果获取 http 客户端配置失败则返回错误。
}
httpClientConfig, err := httpconfig.NewClientConfigFromYAML(httpConfContentYaml) // 解析 Prometheus 的 http 客户端配置,并返回一个 httpconfig.ClientConfig 结构体。
if err != nil {
return errors.Wrap(err, "parsing http config YAML") // 错误处理,如果解析 http 配置 YAML 文件失败则返回错误。
}
// 【问】 为什么要单独创建 HTTP Client?
// 【答】 使用与 Prometheus 相同的 TLS/认证配置,确保 Sidecar 能安全访问 Prometheus API。
httpClient, err := httpconfig.NewHTTPClient(*httpClientConfig, "thanos-sidecar")
if err != nil {
return errors.Wrap(err, "Improper http client config")
}
// 设置 reloader 的 http 客户端
// 问1:thanos sidecar 动态加载配置文件的必要性是什么?
// 答1:1、允许在不重启 sidecar 的情况下更新配置文件,例如修改对象存储的访问凭证调整数据上传策略等;2、通过重新加载配置文件,可以动态调整 sidecar 的行为和性能参数,例如增加并发上传数、修改数据压缩级别等。
// 问2:thanos sidecar 如何与 Prometheus 进行交互?
// 答2:thanos sidecar 通过 reloader.Reloader 对象与 Prometheus 进行交互,通过 reloader.Reloader 对象的 Watch 方法, continuously watch the Prometheus configuration file for changes, and reload the configuration when changes are detected.
// 问3:为什么需要 reloader.SetHttpClient 方法?
// 答3:reloader组件负责监控配置文件的变化,并在检测到变化时重新加载配置。为了实现这一点,reloader需要能够通知Prometheus(或其他需要知道配置变化的组件)配置已更新。
// 在Thanos中,reloader通过HTTP请求通知Prometheus配置已更改。这是Prometheus重新加载配置的标准机制之一(Prometheus支持通过HTTP请求触发配置重载)。因此,reloader.SetHttpClient 方法允许 reloader 使用 Prometheus 的 http 客户端来发送这些 HTTP 请求。因此,reloader需要一个HTTP客户端来发送这些通知请求。通过reloader.SetHttpClient(*httpClient),我们为reloader提供了一个配置好的HTTP客户端,它可以使用与Sidecar相同的认证和TLS设置来与Prometheus通信。
reloader.SetHttpClient(*httpClient)
// 【关键结构体】 promMetadata 定义
var m = &promMetadata{
promURL: conf.prometheus.url, // 连接的 Prometheus 实例地址
// Start out with the full time range. The shipper will constrain it later.
// TODO(fabxc): minimum timestamp is never adjusted if shipping is disabled.
// 暴露给查询层(如 Thanos Querier),告知当前可查询的时间范围。通过 UpdateTimestamps 方法,从 Shipper 模块获取已上传块的时间范围,并更新 mint 和 maxt。
mint: conf.limitMinTime.PrometheusTimestamp(), // (最小时间戳),初始化为 --min-time 配置
maxt: math.MaxInt64, // (最大时间戳):初始化为 math.MaxInt64(表示无上限)。
limitMinTime: conf.limitMinTime, // 查询时过滤早于此时间的数据,在 UpdateTimestamps 中强制 mint = max(实际mint, limitMinTime),但 不删除已上传的旧数据。
client: promclient.NewWithTracingClient(logger, httpClient, "thanos-sidecar"), // 创建 Prometheus 客户端,用于与 Prometheus 进行交互,并启用追踪
}
confContentYaml, err := conf.objStore.Content() // 这里的 objStore.Content() 方法用于获取对象存储的配置内容,并将其转换为 YAML 格式,通常包括访问凭证、桶名称等关键参数
if err != nil {
return errors.Wrap(err, "getting object store config") // 错误处理,如果获取对象存储配置失败则返回错误。
}
var uploads = true // 默认启用上传功能
if len(confContentYaml) == 0 {
level.Info(logger).Log("msg", "no supported bucket was configured, uploads will be disabled") // 如果对象存储配置为空,则禁用上传功能。日志记录相关信息,并设置 uploads 变量为 false。
uploads = false // 设置 uploads 变量为 false,表示禁用上传功能。
}
// 【3、探针与服务启动逻辑】
// 初始化双模式探针系统,用于检查服务的健康状态
grpcProbe := prober.NewGRPC() // 创建 GRPC 探针,用于检查 gRPC 服务是否健康 (即 gRPC 服务是否正常运行)
httpProbe := prober.NewHTTP() // 创建 HTTP 探针,用于检查 HTTP 服务是否健康 (即 HTTP 服务是否正常运行)
statusProber := prober.Combine( // 合并多个探针为一个,以便同时检查 gRPC 和 HTTP 服务是否健康 (即同时检查 gRPC 和 HTTP 服务是否正常运行)
httpProbe,
grpcProbe,
prober.NewInstrumentation(comp, logger, extprom.WrapRegistererWithPrefix("thanos_", reg)), // 添加额外的监控指标,以便跟踪服务的健康状态和性能表现S
)
// 启动 HTTP 服务,用于提供健康检查和指标收集等功能
// 【问】 为什么需要 GracePeriod?
// 【答】 优雅关闭等待时间,确保正在处理的请求完成,防止数据丢失或中断。
srv := httpserver.New(logger, reg, comp, httpProbe,
httpserver.WithListen(conf.http.bindAddress), // 设置 HTTP 服务的监听地址
httpserver.WithGracePeriod(time.Duration(conf.http.gracePeriod)), // 设置优雅关闭的等待时间,以便在服务停止时允许正在处理的请求完成
httpserver.WithTLSConfig(conf.http.tlsConfig), // 设置 HTTP 服务的 TLS 配置,以便启用 HTTPS
)
// 启动 gRPC 服务,用于提供数据上传等功能
// Add 方法添加了一个新的并发任务,该任务会在服务启动时执行。
g.Add(func() error {
statusProber.Healthy() // 通过调用statusProber.Healthy()方法将服务标记为健康状态。这通常用于监控和健康检查,表明服务已准备好接收请求。
return srv.ListenAndServe() // srv.ListenAndServe()是gRPC服务的核心启动方法。它会使服务监听指定的端口,并处理传入的请求。如果服务成功启动,该方法将阻塞当前goroutine,直到服务停止。如果启动过程中发生错误,它将返回一个错误。
}, func(err error) { // 如果srv.ListenAndServe()返回错误,将执行这个错误处理函数。
statusProber.NotReady(err) // 首先将服务标记为 NotReady 状态,这通常意味着服务遇到了问题;
defer statusProber.NotHealthy(err) // 用defer关键字确保在函数返回之前,将服务标记为不健康状态。这是为了确保无论后续操作如何,服务的健康状态都能被正确更新。
srv.Shutdown(err) // 最后,调用srv.Shutdown(err)方法来优雅地关闭gRPC服务。这个方法会停止监听新的连接,并等待当前正在处理的请求完成。
})
// Setup all the concurrent groups.
// 启动并发组,以便同时执行多个任务。
{
promUp := promauto.With(reg).NewGauge(prometheus.GaugeOpts{ // 创建一个新的指标,用于跟踪 sidecar 是否能够成功连接到 Prometheus。
Name: "thanos_sidecar_prometheus_up", // 指标名称,用于标识该指标。
Help: "Boolean indicator whether the sidecar can reach its Prometheus peer.", // 指标的帮助信息,用于描述该指标的作用。
})
ctx, cancel := context.WithCancel(context.Background()) // 创建一个新的上下文,并返回一个取消函数。这个上下文将在服务关闭时被取消。
// 【关键流程】 Prometheus元数据校验环节
// 【问】为什么要在服务启动时校验 Prometheus 的配置?
// 【答】在服务启动时校验 Prometheus 的配置是为了确保 sidecar 能够正确连接到 Prometheus,并且和thanos数据兼容。这样可以避免由于配置不一致导致的数据上传失败或数据异常
g.Add(func() error {
// Only check Prometheus's flags when upload is enabled.
// 如果上传功能被禁用,则跳过 Prometheus 的配置校验。
if uploads {
// Check prometheus's flags to ensure same sidecar flags.
if err := validatePrometheus(ctx, m.client, logger, conf.shipper.ignoreBlockSize, m); err != nil { // 校验 Prometheus 的配置,确保它与 sidecar 的配置一致。如果存在不一致,则返回错误。
return errors.Wrap(err, "validate Prometheus flags") // 如果校验失败,则返回错误。
}
}
// We retry infinitely until we reach and fetch BuildVersion from our Prometheus.
// 无限重试,直到成功从 Prometheus 获取构建版本。这确保了 sidecar 能够与正在运行的 Prometheus 实例通信,并获取其版本信息。
err := runutil.Retry(2*time.Second, ctx.Done(), func() error {
if err := m.BuildVersion(ctx); err != nil {
level.Warn(logger).Log(
"msg", "failed to fetch prometheus version. Is Prometheus running? Retrying",
"err", err,
)
return err
}
level.Info(logger).Log(
"msg", "successfully loaded prometheus version",
)
return nil
})
if err != nil {
return errors.Wrap(err, "failed to get prometheus version")
}
// Blocking query of external labels before joining as a Source Peer into gossip.
// We retry infinitely until we reach and fetch labels from our Prometheus.
// 无限重试,直到成功从 Prometheus 获取外部标签。这确保了 sidecar 能够获取到正确的外部标签信息,以便正确地处理和路由数据。
err = runutil.Retry(2*time.Second, ctx.Done(), func() error {
if err := m.UpdateLabels(ctx); err != nil {
level.Warn(logger).Log(
"msg", "failed to fetch initial external labels. Is Prometheus running? Retrying",
"err", err,
)
promUp.Set(0)
statusProber.NotReady(err)
return err
}
level.Info(logger).Log(
"msg", "successfully loaded prometheus external labels",
"external_labels", m.Labels().String(),
)
promUp.Set(1)
statusProber.Ready()
return nil
})
if err != nil {
return errors.Wrap(err, "initial external labels query") // 如果获取外部标签失败,则返回错误。
}
if len(m.Labels()) == 0 {
return errors.New("no external labels configured on Prometheus server, uniquely identifying external labels must be configured; see https://thanos.io/tip/thanos/storage.md#external-labels for details.") // 如果没有配置外部标签,则返回错误。这通常是因为 Prometheus 服务器没有正确配置外部标签,这些标签对于唯一标识和路由数据至关重要。
}
// Periodically query the Prometheus config. We use this as a heartbeat as well as for updating
// the external labels we apply.
// 定期查询 Prometheus 的配置。我们使用这个作为心跳,以及更新我们应用的外部标签。
return runutil.Repeat(30*time.Second, ctx.Done(), func() error {
iterCtx, iterCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer iterCancel()
if err := m.UpdateLabels(iterCtx); err != nil {
level.Warn(logger).Log("msg", "heartbeat failed", "err", err)
promUp.Set(0)
statusProber.NotReady(err)
} else {
promUp.Set(1)
statusProber.Ready()
}
return nil
})
}, func(error) {
cancel()
})
}
{
ctx, cancel := context.WithCancel(context.Background())
g.Add(func() error {
return reloader.Watch(ctx)
}, func(error) {
cancel()
})
}
{
// 【问】 为什么要创建一个新的 Prometheus 客户端?
// 【答】 创建一个新的 Prometheus 客户端,用于与主 Prometheus 服务进行通信。
// 这个客户端将使用特定的用户代理字符串和 HTTP 配置来确保正确的请求头被发送到服务器。这个客户端将用于执行各种查询和操作,例如获取 Prometheus 的版本信息、更新外部标签等。
c := promclient.NewWithTracingClient(logger, httpClient, httpconfig.ThanosUserAgent)
// 【关键服务】暴露多种 gRPC 端点,查询端点。
promStore, err := store.NewPrometheusStore(logger, reg, c, conf.prometheus.url, component.Sidecar, m.Labels, m.Timestamps, m.Version)
if err != nil {
return errors.Wrap(err, "create Prometheus store") // 如果创建 Prometheus 数据存储失败,则返回错误。
}
tlsCfg, err := tls.NewServerConfig(log.With(logger, "protocol", "gRPC"), // 配置 gRPC 服务器使用的 TLS 配置。这将确保在客户端和服务器之间建立安全的连接。
conf.grpc.tlsSrvCert, conf.grpc.tlsSrvKey, conf.grpc.tlsSrvClientCA)
if err != nil {
return errors.Wrap(err, "setup gRPC server") // 如果配置 gRPC 服务器失败,则返回错误。
}
// 【关键服务】暴露多种 gRPC 端点,示例数据端点。
exemplarSrv := exemplars.NewPrometheus(conf.prometheus.url, c, m.Labels)
// 【关键服务】暴露多种 gRPC 端点,元数据端点。
infoSrv := info.NewInfoServer(
component.Sidecar.String(), // 组件名称,用于标识服务。
info.WithLabelSetFunc(func() []labelpb.ZLabelSet { // 返回 Prometheus 数据存储的标签集。这将允许客户端了解存储的标签集,这对于路由和查询数据至关重要。
return promStore.LabelSet() // 获取 Prometheus 数据存储的标签集。这将允许客户端了解存储的标签集,这对于路由和查询数据至关重要。
}),
info.WithStoreInfoFunc(func() *infopb.StoreInfo {
if httpProbe.IsReady() {
mint, maxt := promStore.Timestamps()
return &infopb.StoreInfo{
MinTime: mint,
MaxTime: maxt,
}
}
return nil
}),
info.WithExemplarsInfoFunc(), // 配置 exemplarSrv,使其能够提供有关 Prometheus 实例的示例数据信息。这将允许客户端查询特定时间范围内的样本值和标签集。
info.WithRulesInfoFunc(), // 配置规则端点,使其能够提供有关 Prometheus 实例的记录规则信息。这将允许客户端查询和评估记录规则的状态和结果。
info.WithTargetsInfoFunc(), // 配置目标端点,使其能够提供有关 Prometheus 实例的目标信息。这将允许客户端查询和监控目标的健康状态、标签集等。
info.WithMetricMetadataInfoFunc(), // 配置元数据端点,使其能够提供有关 Prometheus 实例的指标元数据信息。这将允许客户端查询和了解特定指标的定义、标签集等。
)
// 【问】 为什么需要 TLS 配置?
// 【答】 保护 gRPC 通信安全,防止中间人攻击,确保数据机密性和完整性。
// 这将确保在客户端和服务器之间建立安全的连接,防止中间人攻击,并保护数据的机密性和完整性。
s := grpcserver.New(logger, reg, tracer, grpcLogOpts, tagOpts, comp, grpcProbe,
grpcserver.WithServer(store.RegisterStoreServer(promStore)),
grpcserver.WithServer(rules.RegisterRulesServer(rules.NewPrometheus(conf.prometheus.url, c, m.Labels))),
grpcserver.WithServer(targets.RegisterTargetsServer(targets.NewPrometheus(conf.prometheus.url, c, m.Labels))),
grpcserver.WithServer(meta.RegisterMetadataServer(meta.NewPrometheus(conf.prometheus.url, c))),
grpcserver.WithServer(exemplars.RegisterExemplarsServer(exemplarSrv)),
grpcserver.WithServer(info.RegisterInfoServer(infoSrv)),
grpcserver.WithListen(conf.grpc.bindAddress),
grpcserver.WithGracePeriod(time.Duration(conf.grpc.gracePeriod)),
grpcserver.WithTLSConfig(tlsCfg),
)
g.Add(func() error {
statusProber.Ready()
return s.ListenAndServe()
}, func(err error) {
statusProber.NotReady(err)
s.Shutdown(err)
})
}
// 【5、数据上传逻辑(Shipper)】
if uploads {
// The background shipper continuously scans the data directory and uploads
// new blocks to Google Cloud Storage or an S3-compatible storage service.
// 初始化对象存储客户端,用于上传数据块到对象存储服务。这包括配置客户端以连接到对象存储服务,并初始化一个用于上传数据块的桶(bucket)。
bkt, err := client.NewBucket(logger, confContentYaml, reg, component.Sidecar.String())
if err != nil {
return err
}
// Ensure we close up everything properly.
// 确保在出现错误时正确关闭所有资源。这包括对象存储客户端和任何其他相关资源,以避免潜在的资源泄露或悬挂连接等问题。
defer func() {
if err != nil {
runutil.CloseWithLogOnErr(logger, bkt, "bucket client") // 关闭对象存储客户端。
}
}()
// 检查 wal directory is accessible.
if err := promclient.IsWALDirAccessible(conf.tsdb.path); err != nil {
level.Error(logger).Log("err", err)
}
ctx, cancel := context.WithCancel(context.Background())
// 【关键模块】定时查询 Prometheus 的配置。我们使用这个作为心跳,以及更新我们应用的外部标签。
g.Add(func() error {
defer runutil.CloseWithLogOnErr(logger, bkt, "bucket client")
promReadyTimeout := conf.prometheus.readyTimeout // Prometheus 准备超时时间。这用于等待 Prometheus 服务变得可用,以便我们可以获取外部标签并开始上传数据块。
extLabelsCtx, cancel := context.WithTimeout(ctx, promReadyTimeout) // 创建上下文,用于等待 Prometheus 服务变得可用。这将确保在尝试获取外部标签之前,Prometheus 服务已经准备好并可访问。
defer cancel() // 取消等待 Prometheus 服务变得可用的上下文。这将确保在退出函数时不会出现悬挂的 goroutine 或资源泄露等问题。
if err := runutil.Retry(2*time.Second, extLabelsCtx.Done(), func() error {
if len(m.Labels()) == 0 {
return errors.New("not uploading as no external labels are configured yet - is Prometheus healthy/reachable?") // 如果 Prometheus 服务不可用,则返回错误,并提示用户检查 Prometheus 服务是否可用。这个错误在 sidecar 启动时候,是不是很常见?
}
return nil
}); err != nil {
return errors.Wrapf(err, "aborting as no external labels found after waiting %s", promReadyTimeout) // 如果等待 Prometheus 服务变得可用超时,则返回错误。这将确保在尝试获取外部标签之前,Prometheus 服务已经准备好并可访问。如果在指定的时间内无法找到外部标签,这通常意味着配置有问题或服务不可用。在这种情况下,我们记录一条警告日志,并取消上传操作。
}
// 【关键模块】Shipper 定时同步本地到对象存储。该模块定期检查本地数据目录中的新块,并将它们上传到配置的对象存储服务。
s := shipper.New(
logger, // 日志记录器
reg, // Prometheus 注册器
conf.tsdb.path, // 本地 TSDB 数据路径
bkt, // 对象存储客户端,用于上传数据块到配置的对象存储服务。这将允许我们将本地 TSDB 数据目录中的新块上传到远程存储中,以便进行长期保存和查询。这将允许我们将本地 TSDB 数据目录中的新块上传到远程存储中,以便进行长期保存和查询。
m.Labels, // 外部标签函数,用于获取 Prometheus 的外部标签。这将允许我们为上传的数据块添加额外的元数据,以便在查询时能够正确地路由和过滤数据。
metadata.SidecarSource, // 标识数据来源
conf.shipper.uploadCompacted, // 是否上传压缩块
conf.shipper.allowOutOfOrderUpload, // 是否允许无序上传
metadata.HashFunc(conf.shipper.hashFunc) // 哈希函数,用于计算数据块的哈希值。这将允许我们验证上传的数据块是否完整且未被篡改。
)
return runutil.Repeat(30*time.Second, ctx.Done(), func() error {
if uploaded, err := s.Sync(ctx); err != nil {
level.Warn(logger).Log("err", err, "uploaded", uploaded)
}
minTime, _, err := s.Timestamps()
if err != nil {
level.Warn(logger).Log("msg", "reading timestamps failed", "err", err)
return nil
}
m.UpdateTimestamps(minTime, math.MaxInt64)
return nil
})
}, func(error) {
cancel()
})
}
level.Info(logger).Log("msg", "starting sidecar") // 记录一条信息日志,表示 sidecar 已启动。这将允许用户知道 sidecar 服务已经成功启动并正在运行中。
return nil
}
// validatePrometheus 验证Prometheus配置是否正确
//
// 参数:
// ctx: 上下文对象
// client: Prometheus客户端
// logger: 日志记录器
// ignoreBlockSize: 是否忽略块大小配置
// m: Prometheus元数据
//
// 返回值:
// 如果验证失败,返回错误对象;否则返回nil
// validatePrometheus的函数,其目的是验证Prometheus服务器的配置,特别是与TSDB(Time Series Database)相关的配置。这个函数主要用于确保Prometheus的某些设置符合特定的要求,这对于数据的一致性和完整性非常重要,特别是在与Thanos等系统集成时。
func validatePrometheus(
ctx context.Context, // 用于控制函数执行的上下文,允许取消操作或设置超时。
client *promclient.Client, // Prometheus客户端,用于与Prometheus服务器通信。
logger log.Logger, // 日志记录器,用于记录警告或错误信息。
ignoreBlockSize bool, // 一个标志,指示是否忽略块大小(即TSDB最小和最大块持续时间)的不同。
m *promMetadata // 包含Prometheus相关元数据的结构体指针,这里主要用到了promURL字段。
) error { // 如果验证失败,返回一个错误。
var (
flagErr error
flags promclient.Flags
)
// 使用runutil.Retry函数尝试两次(每次间隔2秒)从Prometheus获取配置标志。如果因为网络问题或Prometheus暂时不可用导致失败,这可以提供一定的容错能力。
if err := runutil.Retry(2*time.Second, ctx.Done(), func() error {
// 获取Prometheus的配置标志,例如TSDB的最小和最大块持续时间。如果找不到这些标志(可能是因为Prometheus版本较旧),则记录警告并返回错误。
if flags, flagErr = client.ConfiguredFlags(ctx, m.promURL); flagErr != nil && flagErr != promclient.ErrFlagEndpointNotFound {
level.Warn(logger).Log("msg", "failed to get Prometheus flags. Is Prometheus running? Retrying", "err", flagErr)
return errors.Wrapf(flagErr, "fetch Prometheus flags")
}
return nil
}); err != nil { // 如果最终失败(即重试后仍然失败),返回错误。
return errors.Wrapf(err, "fetch Prometheus flags")
}
// 如果flagErr不为nil但等于ErrFlagEndpointNotFound(意味着Prometheus版本较旧,不支持获取这些标志),记录警告信息但不返回错误。
if flagErr != nil {
level.Warn(logger).Log("msg", "failed to check Prometheus flags, due to potentially older Prometheus. No extra validation is done.", "err", flagErr)
return nil
}
// Check if compaction is disabled.
// 检查TSDB的MinTime和MaxTime:
// 如果TSDBMinTime不等于TSDBMaxTime,表示禁用了压缩(因为最小和最大块持续时间不同)。如果ignoreBlockSize为false,则返回错误,提示用户需要禁用压缩(即设置最小和最大块持续时间相等)。
// 如果使用了ignoreBlockSize标志,记录警告信息,提醒用户如果上传2小时的块失败且Prometheus发生压缩,则该块可能会从Thanos存储桶中丢失。
if flags.TSDBMinTime != flags.TSDBMaxTime {
if !ignoreBlockSize {
return errors.Errorf("found that TSDB Max time is %s and Min time is %s. "+
"Compaction needs to be disabled (storage.tsdb.min-block-duration = storage.tsdb.max-block-duration)", flags.TSDBMaxTime, flags.TSDBMinTime)
}
level.Warn(logger).Log("msg", "flag to ignore Prometheus min/max block duration flags differing is being used. If the upload of a 2h block fails and a Prometheus compaction happens that block may be missing from your Thanos bucket storage.")
}
// Check if block time is 2h.
// 检查块时间是否为2小时:如果TSDB的MinTime(即块时间)不是2小时,记录警告信息,建议用户只使用2小时的块时间。
if flags.TSDBMinTime != model.Duration(2*time.Hour) {
level.Warn(logger).Log("msg", "found that TSDB block time is not 2h. Only 2h block time is recommended.", "block-time", flags.TSDBMinTime)
}
return nil
}
type promMetadata struct {
promURL *url.URL
mtx sync.Mutex // 用于同步的互斥锁
mint int64 // 最小时间戳
maxt int64 // 最大时间戳
labels labels.Labels // 外部标签集合,用于标识 Prometheus 的实例和配置
promVersion string // Prometheus 版本信息
limitMinTime thanosmodel.TimeOrDurationValue // 限制最小时间戳,用于确保上传的块不会太旧
client *promclient.Client // Prometheus 客户端,用于与 Prometheus API 进行交互
}
// promMetadata 结构体方法
func (s *promMetadata) UpdateLabels(ctx context.Context) error {
// 从 Prometheus 获取最新 external_labels
elset, err := s.client.ExternalLabels(ctx, s.promURL)
if err != nil {
return err
}
// 加锁确保线程安全更新
s.mtx.Lock()
defer s.mtx.Unlock() // 解锁,允许其他线程访问
// 线程安全更新
s.labels = elset
return nil
}
func (s *promMetadata) UpdateTimestamps(mint, maxt int64) {
s.mtx.Lock() // 加锁确保线程安全更新
defer s.mtx.Unlock() // 解锁,允许其他线程访问
// [关键逻辑] 强制 mint 不低于 limitMinTime
if mint < s.limitMinTime.PrometheusTimestamp() {
mint = s.limitMinTime.PrometheusTimestamp()
}
s.mint = mint
s.maxt = maxt
}
func (s *promMetadata) Labels() labels.Labels {
// 加锁
s.mtx.Lock()
defer s.mtx.Unlock() // 延迟解锁
// 返回标签
return s.labels
}
func (s *promMetadata) Timestamps() (mint, maxt int64) {
// 加锁
s.mtx.Lock()
defer s.mtx.Unlock()
// 返回最小和最大时间戳
return s.mint, s.maxt
}
func (s *promMetadata) BuildVersion(ctx context.Context) error {
// 调用客户端的BuildVersion方法获取版本信息
ver, err := s.client.BuildVersion(ctx, s.promURL)
if err != nil {
// 如果发生错误,返回错误
return err
}
// 加锁,确保线程安全
s.mtx.Lock()
defer s.mtx.Unlock()
// 更新Prometheus版本信息
s.promVersion = ver
return nil
}
func (s *promMetadata) Version() string {
// 加锁
s.mtx.Lock()
defer s.mtx.Unlock() // 解锁
// 返回版本信息
return s.promVersion
}
type sidecarConfig struct {
http httpConfig // HTTP 配置结构体,包含HTTP服务器和客户端的配置信息
grpc grpcConfig // gRPC 配置结构体,包含gRPC服务器和客户端的配置信息
prometheus prometheusConfig // Prometheus 配置结构体,包含与Prometheus交互的配置信息
tsdb tsdbConfig // TSDB 配置结构体,包含TSDB的配置信息
reloader reloaderConfig // Reloader 配置结构体,包含Reloader的配置信息
reqLogConfig *extflag.PathOrContent // 请求日志配置结构体,包含请求日志的配置信息
objStore extflag.PathOrContent // 对象存储配置结构体,包含与对象存储交互的配置信息
shipper shipperConfig // Shipper 配置结构体,包含与Shipper交互的配置信息
limitMinTime thanosmodel.TimeOrDurationValue // 限制最小时间戳
}
func (sc *sidecarConfig) registerFlag(cmd extkingpin.FlagClause) {
// 注册HTTP相关标志
sc.http.registerFlag(cmd)
// 注册gRPC相关标志
sc.grpc.registerFlag(cmd)
// 注册Prometheus相关标志
sc.prometheus.registerFlag(cmd)
// 注册TSDB相关标志
sc.tsdb.registerFlag(cmd)
// 注册Reloader相关标志
sc.reloader.registerFlag(cmd)
// 注册请求日志记录标志
sc.reqLogConfig = extkingpin.RegisterRequestLoggingFlags(cmd)
// 注册通用对象存储标志
sc.objStore = *extkingpin.RegisterCommonObjStoreFlags(cmd, "", false)
// 注册Shipper相关标志
sc.shipper.registerFlag(cmd)
// 注册时间范围限制标志
cmd.Flag("min-time", "Start of time range limit to serve. Thanos sidecar will serve only metrics, which happened later than this value. Option can be a constant time in RFC3339 format or time duration relative to current time, such as -1d or 2h45m. Valid duration units are ms, s, m, h, d, w, y.").
Default("0000-01-01T00:00:00Z").SetValue(&sc.limitMinTime)
}

浙公网安备 33010602011771号