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)
}

 

  

 

posted @   左扬  阅读(8)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源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语法——插值绑定
levels of contents
点击右上角即可分享
微信分享提示