Skip to content

Commit 5578a59

Browse files
committed
federated targets functionality (#1375)
Signed-off-by: Alexander Tunik <2braven@gmail.com>
1 parent 03c7747 commit 5578a59

25 files changed

+3023
-17
lines changed

cmd/thanos/query.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import (
4646
grpcserver "github.com/thanos-io/thanos/pkg/server/grpc"
4747
httpserver "github.com/thanos-io/thanos/pkg/server/http"
4848
"github.com/thanos-io/thanos/pkg/store"
49+
"github.com/thanos-io/thanos/pkg/targets"
4950
"github.com/thanos-io/thanos/pkg/tls"
5051
"github.com/thanos-io/thanos/pkg/ui"
5152
)
@@ -103,6 +104,10 @@ func registerQuery(app *extkingpin.App) {
103104
metadataEndpoints := cmd.Flag("metadata", "Experimental: Addresses of statically configured metadata API servers (repeatable). The scheme may be prefixed with 'dns+' or 'dnssrv+' to detect metadata API servers through respective DNS lookups.").
104105
Hidden().PlaceHolder("<metadata>").Strings()
105106

107+
// TODO(atunik): Hidden because we plan to extract discovery to separate API: https://github.com/thanos-io/thanos/issues/2600.
108+
targetEndpoints := cmd.Flag("target", "Experimental: Addresses of statically configured target API servers (repeatable). The scheme may be prefixed with 'dns+' or 'dnssrv+' to detect target API servers through respective DNS lookups.").
109+
Hidden().PlaceHolder("<target>").Strings()
110+
106111
strictStores := cmd.Flag("store-strict", "Addresses of only statically configured store API servers that are always used, even if the health check fails. Useful if you have a caching layer on top.").
107112
PlaceHolder("<staticstore>").Strings()
108113

@@ -169,6 +174,10 @@ func registerQuery(app *extkingpin.App) {
169174
return errors.Wrap(err, "error while parsing config for request logging")
170175
}
171176

177+
if dup := firstDuplicate(*targetEndpoints); dup != "" {
178+
return errors.Errorf("Address %s is duplicated for --target flag.", dup)
179+
}
180+
172181
var fileSD *file.Discovery
173182
if len(*fileSDFiles) > 0 {
174183
conf := &file.SDConfig{
@@ -222,6 +231,7 @@ func registerQuery(app *extkingpin.App) {
222231
getFlagsMap(cmd.Flags()),
223232
*stores,
224233
*ruleEndpoints,
234+
*targetEndpoints,
225235
*metadataEndpoints,
226236
*enableAutodownsampling,
227237
*enableQueryPartialResponse,
@@ -278,6 +288,7 @@ func runQuery(
278288
flagsMap map[string]string,
279289
storeAddrs []string,
280290
ruleAddrs []string,
291+
targetAddrs []string,
281292
metadataAddrs []string,
282293
enableAutodownsampling bool,
283294
enableQueryPartialResponse bool,
@@ -323,6 +334,12 @@ func runQuery(
323334
dns.ResolverType(dnsSDResolver),
324335
)
325336

337+
dnsTargetProvider := dns.NewProvider(
338+
logger,
339+
extprom.WrapRegistererWithPrefix("thanos_query_target_apis_", reg),
340+
dns.ResolverType(dnsSDResolver),
341+
)
342+
326343
dnsMetadataProvider := dns.NewProvider(
327344
logger,
328345
extprom.WrapRegistererWithPrefix("thanos_query_metadata_apis_", reg),
@@ -355,6 +372,13 @@ func runQuery(
355372

356373
return specs
357374
},
375+
func() (specs []query.TargetSpec) {
376+
for _, addr := range dnsTargetProvider.Addresses() {
377+
specs = append(specs, query.NewGRPCStoreSpec(addr, false))
378+
}
379+
380+
return specs
381+
},
358382
func() (specs []query.MetadataSpec) {
359383
for _, addr := range dnsMetadataProvider.Addresses() {
360384
specs = append(specs, query.NewGRPCStoreSpec(addr, false))
@@ -367,6 +391,7 @@ func runQuery(
367391
)
368392
proxy = store.NewProxyStore(logger, reg, stores.Get, component.Query, selectorLset, storeResponseTimeout)
369393
rulesProxy = rules.NewProxy(logger, stores.GetRulesClients)
394+
targetsProxy = targets.NewProxy(logger, stores.GetTargetsClients)
370395
metadataProxy = metadata.NewProxy(logger, stores.GetMetadataClients)
371396
queryableCreator = query.NewQueryableCreator(
372397
logger,
@@ -454,6 +479,9 @@ func runQuery(
454479
if err := dnsRuleProvider.Resolve(resolveCtx, ruleAddrs); err != nil {
455480
level.Error(logger).Log("msg", "failed to resolve addresses for rulesAPIs", "err", err)
456481
}
482+
if err := dnsTargetProvider.Resolve(ctx, targetAddrs); err != nil {
483+
level.Error(logger).Log("msg", "failed to resolve addresses for targetsAPIs", "err", err)
484+
}
457485
if err := dnsMetadataProvider.Resolve(resolveCtx, metadataAddrs); err != nil {
458486
level.Error(logger).Log("msg", "failed to resolve addresses for metadataAPIs", "err", err)
459487
}
@@ -504,6 +532,7 @@ func runQuery(
504532
queryableCreator,
505533
// NOTE: Will share the same replica label as the query for now.
506534
rules.NewGRPCClientWithDedup(rulesProxy, queryReplicaLabels),
535+
targets.NewGRPCClientWithDedup(targetsProxy, queryReplicaLabels),
507536
metadata.NewGRPCClient(metadataProxy),
508537
enableAutodownsampling,
509538
enableQueryPartialResponse,
@@ -550,6 +579,7 @@ func runQuery(
550579
s := grpcserver.New(logger, reg, tracer, grpcLogOpts, tagOpts, comp, grpcProbe,
551580
grpcserver.WithServer(store.RegisterStoreServer(proxy)),
552581
grpcserver.WithServer(rules.RegisterRulesServer(rulesProxy)),
582+
grpcserver.WithServer(targets.RegisterTargetsServer(targetsProxy)),
553583
grpcserver.WithServer(metadata.RegisterMetadataServer(metadataProxy)),
554584
grpcserver.WithListen(grpcBindAddr),
555585
grpcserver.WithGracePeriod(grpcGracePeriod),

cmd/thanos/sidecar.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import (
4242
httpserver "github.com/thanos-io/thanos/pkg/server/http"
4343
"github.com/thanos-io/thanos/pkg/shipper"
4444
"github.com/thanos-io/thanos/pkg/store"
45+
"github.com/thanos-io/thanos/pkg/targets"
4546
"github.com/thanos-io/thanos/pkg/tls"
4647
"github.com/thanos-io/thanos/pkg/tracing"
4748
)
@@ -229,6 +230,7 @@ func runSidecar(
229230
s := grpcserver.New(logger, reg, tracer, grpcLogOpts, tagOpts, comp, grpcProbe,
230231
grpcserver.WithServer(store.RegisterStoreServer(promStore)),
231232
grpcserver.WithServer(rules.RegisterRulesServer(rules.NewPrometheus(conf.prometheus.url, c, m.Labels))),
233+
grpcserver.WithServer(targets.RegisterTargetsServer(targets.NewPrometheus(conf.prometheus.url, c, m.Labels))),
232234
grpcserver.WithServer(meta.RegisterMetadataServer(meta.NewPrometheus(conf.prometheus.url, c))),
233235
grpcserver.WithListen(conf.grpc.bindAddress),
234236
grpcserver.WithGracePeriod(time.Duration(conf.grpc.gracePeriod)),

pkg/api/query/v1.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ import (
5050
"github.com/thanos-io/thanos/pkg/rules/rulespb"
5151
"github.com/thanos-io/thanos/pkg/runutil"
5252
"github.com/thanos-io/thanos/pkg/store/storepb"
53+
"github.com/thanos-io/thanos/pkg/targets"
54+
"github.com/thanos-io/thanos/pkg/targets/targetspb"
5355
"github.com/thanos-io/thanos/pkg/tracing"
5456
)
5557

@@ -72,6 +74,7 @@ type QueryAPI struct {
7274
// queryEngine returns appropriate promql.Engine for a query with a given step.
7375
queryEngine func(int64) *promql.Engine
7476
ruleGroups rules.UnaryClient
77+
targets targets.UnaryClient
7578
metadatas metadata.UnaryClient
7679

7780
enableAutodownsampling bool
@@ -95,6 +98,7 @@ func NewQueryAPI(
9598
qe func(int64) *promql.Engine,
9699
c query.QueryableCreator,
97100
ruleGroups rules.UnaryClient,
101+
targets targets.UnaryClient,
98102
metadatas metadata.UnaryClient,
99103
enableAutodownsampling bool,
100104
enableQueryPartialResponse bool,
@@ -115,6 +119,7 @@ func NewQueryAPI(
115119
queryableCreate: c,
116120
gate: gate,
117121
ruleGroups: ruleGroups,
122+
targets: targets,
118123
metadatas: metadatas,
119124

120125
enableAutodownsampling: enableAutodownsampling,
@@ -154,6 +159,8 @@ func (qapi *QueryAPI) Register(r *route.Router, tracer opentracing.Tracer, logge
154159

155160
r.Get("/rules", instr("rules", NewRulesHandler(qapi.ruleGroups, qapi.enableRulePartialResponse)))
156161

162+
r.Get("/targets", instr("targets", NewTargetsHandler(qapi.targets, true)))
163+
157164
r.Get("/metadata", instr("metadata", NewMetricMetadataHandler(qapi.metadatas, qapi.enableMetricMetadataPartialResponse)))
158165
}
159166

@@ -652,6 +659,36 @@ func (qapi *QueryAPI) stores(_ *http.Request) (interface{}, []error, *api.ApiErr
652659
return statuses, nil, nil
653660
}
654661

662+
func NewTargetsHandler(client targets.UnaryClient, enablePartialResponse bool) func(*http.Request) (interface{}, []error, *api.ApiError) {
663+
ps := storepb.PartialResponseStrategy_ABORT
664+
if enablePartialResponse {
665+
ps = storepb.PartialResponseStrategy_WARN
666+
}
667+
668+
return func(r *http.Request) (interface{}, []error, *api.ApiError) {
669+
stateParam := r.URL.Query().Get("state")
670+
state, ok := targetspb.TargetsRequest_State_value[strings.ToUpper(stateParam)]
671+
if !ok {
672+
if stateParam != "" {
673+
return nil, nil, &api.ApiError{Typ: api.ErrorBadData, Err: errors.Errorf("invalid targets parameter state='%v'", stateParam)}
674+
}
675+
state = int32(targetspb.TargetsRequest_ANY)
676+
}
677+
678+
req := &targetspb.TargetsRequest{
679+
State: targetspb.TargetsRequest_State(state),
680+
PartialResponseStrategy: ps,
681+
}
682+
683+
t, warnings, err := client.Targets(r.Context(), req)
684+
if err != nil {
685+
return nil, nil, &api.ApiError{Typ: api.ErrorInternal, Err: errors.Wrap(err, "retrieving targets")}
686+
}
687+
688+
return t, warnings, nil
689+
}
690+
}
691+
655692
// NewRulesHandler created handler compatible with HTTP /api/v1/rules https://prometheus.io/docs/prometheus/latest/querying/api/#rules
656693
// which uses gRPC Unary Rules API.
657694
func NewRulesHandler(client rules.UnaryClient, enablePartialResponse bool) func(*http.Request) (interface{}, []error, *api.ApiError) {

pkg/promclient/promclient.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/thanos-io/thanos/pkg/rules/rulespb"
3636
"github.com/thanos-io/thanos/pkg/runutil"
3737
"github.com/thanos-io/thanos/pkg/store/storepb"
38+
"github.com/thanos-io/thanos/pkg/targets/targetspb"
3839
"github.com/thanos-io/thanos/pkg/tracing"
3940
"google.golang.org/grpc/codes"
4041
yaml "gopkg.in/yaml.v2"
@@ -758,3 +759,19 @@ func (c *Client) MetadataInGRPC(ctx context.Context, base *url.URL, metric strin
758759
}
759760
return v.Data, c.get2xxResultWithGRPCErrors(ctx, "/metadata HTTP[client]", &u, &v)
760761
}
762+
763+
func (c *Client) TargetsInGRPC(ctx context.Context, base *url.URL, stateTargets string) (*targetspb.TargetDiscovery, error) {
764+
u := *base
765+
u.Path = path.Join(u.Path, "/api/v1/targets")
766+
767+
if stateTargets != "" {
768+
q := u.Query()
769+
q.Add("state", stateTargets)
770+
u.RawQuery = q.Encode()
771+
}
772+
773+
var v struct {
774+
Data *targetspb.TargetDiscovery `json:"data"`
775+
}
776+
return v.Data, c.get2xxResultWithGRPCErrors(ctx, "/targets HTTP[client]", &u, &v)
777+
}

pkg/query/storeset.go

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/thanos-io/thanos/pkg/store"
2626
"github.com/thanos-io/thanos/pkg/store/labelpb"
2727
"github.com/thanos-io/thanos/pkg/store/storepb"
28+
"github.com/thanos-io/thanos/pkg/targets/targetspb"
2829
)
2930

3031
const (
@@ -50,6 +51,11 @@ type RuleSpec interface {
5051
Addr() string
5152
}
5253

54+
type TargetSpec interface {
55+
// Addr returns TargetsAPI Address for the targets spec. It is used as its ID.
56+
Addr() string
57+
}
58+
5359
type MetadataSpec interface {
5460
// Addr returns MetadataAPI Address for the metadata spec. It is used as its ID.
5561
Addr() string
@@ -187,6 +193,7 @@ type StoreSet struct {
187193
// accessible and we close gRPC client for it.
188194
storeSpecs func() []StoreSpec
189195
ruleSpecs func() []RuleSpec
196+
targetSpecs func() []TargetSpec
190197
metadataSpecs func() []MetadataSpec
191198
dialOpts []grpc.DialOption
192199
gRPCInfoCallTimeout time.Duration
@@ -210,6 +217,7 @@ func NewStoreSet(
210217
reg *prometheus.Registry,
211218
storeSpecs func() []StoreSpec,
212219
ruleSpecs func() []RuleSpec,
220+
targetSpecs func() []TargetSpec,
213221
metadataSpecs func() []MetadataSpec,
214222
dialOpts []grpc.DialOption,
215223
unhealthyStoreTimeout time.Duration,
@@ -228,6 +236,9 @@ func NewStoreSet(
228236
if ruleSpecs == nil {
229237
ruleSpecs = func() []RuleSpec { return nil }
230238
}
239+
if targetSpecs == nil {
240+
targetSpecs = func() []TargetSpec { return nil }
241+
}
231242
if metadataSpecs == nil {
232243
metadataSpecs = func() []MetadataSpec { return nil }
233244
}
@@ -236,6 +247,7 @@ func NewStoreSet(
236247
logger: log.With(logger, "component", "storeset"),
237248
storeSpecs: storeSpecs,
238249
ruleSpecs: ruleSpecs,
250+
targetSpecs: targetSpecs,
239251
metadataSpecs: metadataSpecs,
240252
dialOpts: dialOpts,
241253
storesMetric: storesMetric,
@@ -258,6 +270,9 @@ type storeRef struct {
258270
rule rulespb.RulesClient
259271
metadata metadatapb.MetadataClient
260272

273+
// If target is not nil, then this store also supports targets API.
274+
target targetspb.TargetsClient
275+
261276
// Meta (can change during runtime).
262277
labelSets []labels.Labels
263278
storeType component.StoreAPI
@@ -267,7 +282,7 @@ type storeRef struct {
267282
logger log.Logger
268283
}
269284

270-
func (s *storeRef) Update(labelSets []labels.Labels, minTime int64, maxTime int64, storeType component.StoreAPI, rule rulespb.RulesClient, metadata metadatapb.MetadataClient) {
285+
func (s *storeRef) Update(labelSets []labels.Labels, minTime int64, maxTime int64, storeType component.StoreAPI, rule rulespb.RulesClient, target targetspb.TargetsClient, metadata metadatapb.MetadataClient) {
271286
s.mtx.Lock()
272287
defer s.mtx.Unlock()
273288

@@ -276,6 +291,7 @@ func (s *storeRef) Update(labelSets []labels.Labels, minTime int64, maxTime int6
276291
s.minTime = minTime
277292
s.maxTime = maxTime
278293
s.rule = rule
294+
s.target = target
279295
s.metadata = metadata
280296
}
281297

@@ -293,6 +309,13 @@ func (s *storeRef) HasRulesAPI() bool {
293309
return s.rule != nil
294310
}
295311

312+
func (s *storeRef) HasTargetsAPI() bool {
313+
s.mtx.RLock()
314+
defer s.mtx.RUnlock()
315+
316+
return s.target != nil
317+
}
318+
296319
func (s *storeRef) HasMetadataAPI() bool {
297320
s.mtx.RLock()
298321
defer s.mtx.RUnlock()
@@ -405,6 +428,10 @@ func (s *StoreSet) Update(ctx context.Context) {
405428
level.Info(s.logger).Log("msg", "adding new rulesAPI to query storeset", "address", addr)
406429
}
407430

431+
if st.HasTargetsAPI() {
432+
level.Info(s.logger).Log("msg", "adding new targetsAPI to query storeset", "address", addr)
433+
}
434+
408435
level.Info(s.logger).Log("msg", "adding new storeAPI to query storeset", "address", addr, "extLset", extLset)
409436
}
410437

@@ -425,6 +452,7 @@ func (s *StoreSet) getActiveStores(ctx context.Context, stores map[string]*store
425452

426453
storeAddrSet = make(map[string]struct{})
427454
ruleAddrSet = make(map[string]struct{})
455+
targetAddrSet = make(map[string]struct{})
428456
metadataAddrSet = make(map[string]struct{})
429457
)
430458

@@ -433,6 +461,11 @@ func (s *StoreSet) getActiveStores(ctx context.Context, stores map[string]*store
433461
ruleAddrSet[ruleSpec.Addr()] = struct{}{}
434462
}
435463

464+
// Gather active targets map concurrently. Add a new target if it does not exist already.
465+
for _, targetSpec := range s.targetSpecs() {
466+
targetAddrSet[targetSpec.Addr()] = struct{}{}
467+
}
468+
436469
// Gather active stores map concurrently. Build new store if does not exist already.
437470
for _, metadataSpec := range s.metadataSpecs() {
438471
metadataAddrSet[metadataSpec.Addr()] = struct{}{}
@@ -473,6 +506,11 @@ func (s *StoreSet) getActiveStores(ctx context.Context, stores map[string]*store
473506
rule = rulespb.NewRulesClient(st.cc)
474507
}
475508

509+
var target targetspb.TargetsClient
510+
if _, ok := targetAddrSet[addr]; ok {
511+
target = targetspb.NewTargetsClient(st.cc)
512+
}
513+
476514
var metadata metadatapb.MetadataClient
477515
if _, ok := metadataAddrSet[addr]; ok {
478516
metadata = metadatapb.NewMetadataClient(st.cc)
@@ -502,7 +540,7 @@ func (s *StoreSet) getActiveStores(ctx context.Context, stores map[string]*store
502540
}
503541

504542
s.updateStoreStatus(st, nil)
505-
st.Update(labelSets, minTime, maxTime, storeType, rule, metadata)
543+
st.Update(labelSets, minTime, maxTime, storeType, rule, target, metadata)
506544

507545
mtx.Lock()
508546
defer mtx.Unlock()
@@ -586,6 +624,20 @@ func (s *StoreSet) GetRulesClients() []rulespb.RulesClient {
586624
return rules
587625
}
588626

627+
// GetTargetsClients returns a list of all active targets clients.
628+
func (s *StoreSet) GetTargetsClients() []targetspb.TargetsClient {
629+
s.storesMtx.RLock()
630+
defer s.storesMtx.RUnlock()
631+
632+
targets := make([]targetspb.TargetsClient, 0, len(s.stores))
633+
for _, st := range s.stores {
634+
if st.HasTargetsAPI() {
635+
targets = append(targets, st.target)
636+
}
637+
}
638+
return targets
639+
}
640+
589641
// GetMetadataClients returns a list of all active metadata clients.
590642
func (s *StoreSet) GetMetadataClients() []metadatapb.MetadataClient {
591643
s.storesMtx.RLock()

0 commit comments

Comments
 (0)