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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 | // 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) } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2024-01-24 《PMBOK指南第六版》第5章 项目范围管理
2023-01-24 vuejs3.0 从入门到精通——Vue语法——插值绑定