Files
loki/pkg/loki/loki.go

793 lines
30 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package loki
import (
"bytes"
"context"
"flag"
"fmt"
"net/http"
"os"
rt "runtime"
"time"
"go.uber.org/atomic"
"github.com/fatih/color"
"github.com/felixge/fgprof"
"github.com/go-kit/log/level"
"github.com/grafana/dskit/flagext"
"github.com/grafana/dskit/grpcutil"
"github.com/grafana/dskit/kv/memberlist"
"github.com/grafana/dskit/middleware"
"github.com/grafana/dskit/modules"
"github.com/grafana/dskit/ring"
"github.com/grafana/dskit/runtimeconfig"
"github.com/grafana/dskit/server"
"github.com/grafana/dskit/services"
"github.com/grafana/dskit/signals"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc/health/grpc_health_v1"
"github.com/grafana/loki/pkg/analytics"
"github.com/grafana/loki/pkg/bloomcompactor"
"github.com/grafana/loki/pkg/bloomgateway"
"github.com/grafana/loki/pkg/compactor"
compactorclient "github.com/grafana/loki/pkg/compactor/client"
"github.com/grafana/loki/pkg/compactor/deletion"
"github.com/grafana/loki/pkg/distributor"
"github.com/grafana/loki/pkg/ingester"
ingester_client "github.com/grafana/loki/pkg/ingester/client"
"github.com/grafana/loki/pkg/loghttp/push"
"github.com/grafana/loki/pkg/loki/common"
"github.com/grafana/loki/pkg/lokifrontend"
"github.com/grafana/loki/pkg/lokifrontend/frontend/transport"
"github.com/grafana/loki/pkg/querier"
"github.com/grafana/loki/pkg/querier/queryrange"
"github.com/grafana/loki/pkg/querier/queryrange/queryrangebase"
"github.com/grafana/loki/pkg/querier/worker"
"github.com/grafana/loki/pkg/ruler"
base_ruler "github.com/grafana/loki/pkg/ruler/base"
"github.com/grafana/loki/pkg/ruler/rulestore"
"github.com/grafana/loki/pkg/runtime"
"github.com/grafana/loki/pkg/scheduler"
internalserver "github.com/grafana/loki/pkg/server"
"github.com/grafana/loki/pkg/storage"
"github.com/grafana/loki/pkg/storage/config"
"github.com/grafana/loki/pkg/storage/stores/series/index"
"github.com/grafana/loki/pkg/storage/stores/shipper/bloomshipper"
"github.com/grafana/loki/pkg/storage/stores/shipper/indexshipper/indexgateway"
"github.com/grafana/loki/pkg/tracing"
"github.com/grafana/loki/pkg/util"
"github.com/grafana/loki/pkg/util/constants"
"github.com/grafana/loki/pkg/util/fakeauth"
"github.com/grafana/loki/pkg/util/limiter"
util_log "github.com/grafana/loki/pkg/util/log"
lokiring "github.com/grafana/loki/pkg/util/ring"
serverutil "github.com/grafana/loki/pkg/util/server"
"github.com/grafana/loki/pkg/validation"
)
// Config is the root config for Loki.
type Config struct {
Target flagext.StringSliceCSV `yaml:"target,omitempty"`
AuthEnabled bool `yaml:"auth_enabled,omitempty"`
HTTPPrefix string `yaml:"http_prefix" doc:"hidden"`
BallastBytes int `yaml:"ballast_bytes"`
Server server.Config `yaml:"server,omitempty"`
InternalServer internalserver.Config `yaml:"internal_server,omitempty" doc:"hidden"`
Distributor distributor.Config `yaml:"distributor,omitempty"`
Querier querier.Config `yaml:"querier,omitempty"`
QueryScheduler scheduler.Config `yaml:"query_scheduler"`
Frontend lokifrontend.Config `yaml:"frontend,omitempty"`
QueryRange queryrange.Config `yaml:"query_range,omitempty"`
Ruler ruler.Config `yaml:"ruler,omitempty"`
IngesterClient ingester_client.Config `yaml:"ingester_client,omitempty"`
Ingester ingester.Config `yaml:"ingester,omitempty"`
IndexGateway indexgateway.Config `yaml:"index_gateway"`
BloomCompactor bloomcompactor.Config `yaml:"bloom_compactor"`
BloomGateway bloomgateway.Config `yaml:"bloom_gateway"`
StorageConfig storage.Config `yaml:"storage_config,omitempty"`
ChunkStoreConfig config.ChunkStoreConfig `yaml:"chunk_store_config,omitempty"`
SchemaConfig config.SchemaConfig `yaml:"schema_config,omitempty"`
CompactorConfig compactor.Config `yaml:"compactor,omitempty"`
CompactorHTTPClient compactorclient.HTTPConfig `yaml:"compactor_client,omitempty" doc:"hidden"`
CompactorGRPCClient compactorclient.GRPCConfig `yaml:"compactor_grpc_client,omitempty" doc:"hidden"`
LimitsConfig validation.Limits `yaml:"limits_config,omitempty"`
Worker worker.Config `yaml:"frontend_worker,omitempty"`
TableManager index.TableManagerConfig `yaml:"table_manager,omitempty"`
MemberlistKV memberlist.KVConfig `yaml:"memberlist"`
RuntimeConfig runtimeconfig.Config `yaml:"runtime_config,omitempty"`
Tracing tracing.Config `yaml:"tracing"`
Analytics analytics.Config `yaml:"analytics"`
LegacyReadTarget bool `yaml:"legacy_read_target,omitempty" doc:"hidden|deprecated"`
Common common.Config `yaml:"common,omitempty"`
ShutdownDelay time.Duration `yaml:"shutdown_delay" category:"experimental"`
MetricsNamespace string `yaml:"metrics_namespace"`
}
// RegisterFlags registers flag.
func (c *Config) RegisterFlags(f *flag.FlagSet) {
c.Server.MetricsNamespace = constants.Loki
c.Server.ExcludeRequestInLog = true
// Set the default module list to 'all'
c.Target = []string{All}
f.Var(&c.Target, "target",
"A comma-separated list of components to run. "+
"The default value 'all' runs Loki in single binary mode. "+
"The value 'read' is an alias to run only read-path related components such as the querier and query-frontend, but all in the same process. "+
"The value 'write' is an alias to run only write-path related components such as the distributor and compactor, but all in the same process. "+
"Supported values: all, compactor, distributor, ingester, querier, query-scheduler, ingester-querier, query-frontend, index-gateway, ruler, table-manager, read, write. "+
"A full list of available targets can be printed when running Loki with the '-list-targets' command line flag. ",
)
f.BoolVar(&c.AuthEnabled, "auth.enabled", true,
"Enables authentication through the X-Scope-OrgID header, which must be present if true. "+
"If false, the OrgID will always be set to 'fake'.",
)
f.IntVar(&c.BallastBytes, "config.ballast-bytes", 0,
"The amount of virtual memory in bytes to reserve as ballast in order to optimize garbage collection. "+
"Larger ballasts result in fewer garbage collection passes, reducing CPU overhead at the cost of heap size. "+
"The ballast will not consume physical memory, because it is never read from. "+
"It will, however, distort metrics, because it is counted as live memory. ",
)
f.BoolVar(&c.LegacyReadTarget, "legacy-read-mode", false, "Deprecated. Set to true to enable the legacy read mode which includes the components from the backend target. "+
"This setting is deprecated and will be removed in the next minor release.")
f.DurationVar(&c.ShutdownDelay, "shutdown-delay", 0, "How long to wait between SIGTERM and shutdown. After receiving SIGTERM, Loki will report 503 Service Unavailable status via /ready endpoint.")
f.StringVar(&c.MetricsNamespace, "metrics-namespace", constants.Loki, "Namespace of the metrics that in previous releases had cortex as namespace. This setting is deprecated and will be removed in the next minor release.")
c.registerServerFlagsWithChangedDefaultValues(f)
c.Common.RegisterFlags(f)
c.Distributor.RegisterFlags(f)
c.Querier.RegisterFlags(f)
c.CompactorHTTPClient.RegisterFlags(f)
c.CompactorGRPCClient.RegisterFlags(f)
c.IngesterClient.RegisterFlags(f)
c.Ingester.RegisterFlags(f)
c.StorageConfig.RegisterFlags(f)
c.IndexGateway.RegisterFlags(f)
c.BloomGateway.RegisterFlags(f)
c.ChunkStoreConfig.RegisterFlags(f)
c.SchemaConfig.RegisterFlags(f)
c.LimitsConfig.RegisterFlags(f)
c.TableManager.RegisterFlags(f)
c.Frontend.RegisterFlags(f)
c.Ruler.RegisterFlags(f)
c.Worker.RegisterFlags(f)
c.QueryRange.RegisterFlags(f)
c.RuntimeConfig.RegisterFlags(f)
c.MemberlistKV.RegisterFlags(f)
c.Tracing.RegisterFlags(f)
c.CompactorConfig.RegisterFlags(f)
c.BloomCompactor.RegisterFlags(f)
c.QueryScheduler.RegisterFlags(f)
c.Analytics.RegisterFlags(f)
}
func (c *Config) registerServerFlagsWithChangedDefaultValues(fs *flag.FlagSet) {
throwaway := flag.NewFlagSet("throwaway", flag.PanicOnError)
// Register to throwaway flags first. Default values are remembered during registration and cannot be changed,
// but we can take values from throwaway flag set and reregister into supplied flags with new default values.
c.Server.RegisterFlags(throwaway)
c.InternalServer.RegisterFlags(throwaway)
throwaway.VisitAll(func(f *flag.Flag) {
// Ignore errors when setting new values. We have a test to verify that it works.
switch f.Name {
case "server.grpc.keepalive.min-time-between-pings":
_ = f.Value.Set("10s")
case "server.grpc.keepalive.ping-without-stream-allowed":
_ = f.Value.Set("true")
case "server.http-listen-port":
_ = f.Value.Set("3100")
}
fs.Var(f.Value, f.Name, f.Usage)
})
c.Server.DisableRequestSuccessLog = true
}
// Clone takes advantage of pass-by-value semantics to return a distinct *Config.
// This is primarily used to parse a different flag set without mutating the original *Config.
func (c *Config) Clone() flagext.Registerer {
return func(c Config) *Config {
return &c
}(*c)
}
// Validate the config and returns an error if the validation
// doesn't pass
func (c *Config) Validate() error {
if err := c.SchemaConfig.Validate(); err != nil {
return errors.Wrap(err, "invalid schema config")
}
if err := c.StorageConfig.Validate(); err != nil {
return errors.Wrap(err, "invalid storage config")
}
if err := c.QueryRange.Validate(); err != nil {
return errors.Wrap(err, "invalid queryrange config")
}
if err := c.Querier.Validate(); err != nil {
return errors.Wrap(err, "invalid querier config")
}
if err := c.QueryScheduler.Validate(); err != nil {
return errors.Wrap(err, "invalid query_scheduler config")
}
if err := c.TableManager.Validate(); err != nil {
return errors.Wrap(err, "invalid tablemanager config")
}
if err := c.Ruler.Validate(); err != nil {
return errors.Wrap(err, "invalid ruler config")
}
if err := c.Ingester.Validate(); err != nil {
return errors.Wrap(err, "invalid ingester config")
}
if err := c.LimitsConfig.Validate(); err != nil {
return errors.Wrap(err, "invalid limits config")
}
if err := c.Worker.Validate(); err != nil {
return errors.Wrap(err, "invalid frontend-worker config")
}
if err := c.StorageConfig.BoltDBShipperConfig.Validate(); err != nil {
return errors.Wrap(err, "invalid boltdb-shipper config")
}
if err := c.IndexGateway.Validate(); err != nil {
return errors.Wrap(err, "invalid index_gateway config")
}
if err := c.CompactorConfig.Validate(); err != nil {
return errors.Wrap(err, "invalid compactor config")
}
if err := c.ChunkStoreConfig.Validate(); err != nil {
return errors.Wrap(err, "invalid chunk store config")
}
if err := c.QueryRange.Validate(); err != nil {
return errors.Wrap(err, "invalid query_range config")
}
if err := c.BloomCompactor.Validate(); err != nil {
return errors.Wrap(err, "invalid bloom_compactor config")
}
if err := ValidateConfigCompatibility(*c); err != nil {
return err
}
// Honor the legacy scalable deployment topology
if c.LegacyReadTarget {
if c.isModuleEnabled(Backend) {
return fmt.Errorf("invalid target, cannot run backend target with legacy read mode")
}
}
return nil
}
func (c *Config) isModuleEnabled(m string) bool {
return util.StringsContain(c.Target, m)
}
type Frontend interface {
services.Service
CheckReady(_ context.Context) error
}
// Codec defines methods to encode and decode requests from HTTP, httpgrpc and Protobuf.
type Codec interface {
transport.Codec
worker.RequestCodec
}
// Loki is the root datastructure for Loki.
type Loki struct {
Cfg Config
// set during initialization
ModuleManager *modules.Manager
serviceMap map[string]services.Service
deps map[string][]string
SignalHandler *signals.Handler
Server *server.Server
InternalServer *server.Server
ring *ring.Ring
Overrides limiter.CombinedLimits
tenantConfigs *runtime.TenantConfigs
TenantLimits validation.TenantLimits
distributor *distributor.Distributor
Ingester ingester.Interface
Querier querier.Querier
cacheGenerationLoader queryrangebase.CacheGenNumberLoader
querierAPI *querier.QuerierAPI
ingesterQuerier *querier.IngesterQuerier
Store storage.Store
BloomStore bloomshipper.Store
tableManager *index.TableManager
frontend Frontend
ruler *base_ruler.Ruler
ruleEvaluator ruler.Evaluator
RulerStorage rulestore.RuleStore
rulerAPI *base_ruler.API
stopper queryrange.Stopper
runtimeConfig *runtimeconfig.Manager
MemberlistKV *memberlist.KVInitService
compactor *compactor.Compactor
QueryFrontEndMiddleware queryrangebase.Middleware
queryScheduler *scheduler.Scheduler
querySchedulerRingManager *lokiring.RingManager
usageReport *analytics.Reporter
indexGatewayRingManager *lokiring.RingManager
bloomCompactorRingManager *lokiring.RingManager
bloomGatewayRingManager *lokiring.RingManager
ClientMetrics storage.ClientMetrics
deleteClientMetrics *deletion.DeleteRequestClientMetrics
Tee distributor.Tee
PushParserWrapper push.RequestParserWrapper
HTTPAuthMiddleware middleware.Interface
Codec Codec
Metrics *server.Metrics
UsageTracker push.UsageTracker
}
// New makes a new Loki.
func New(cfg Config) (*Loki, error) {
loki := &Loki{
Cfg: cfg,
ClientMetrics: storage.NewClientMetrics(),
deleteClientMetrics: deletion.NewDeleteRequestClientMetrics(prometheus.DefaultRegisterer),
Codec: queryrange.DefaultCodec,
}
analytics.Edition("oss")
loki.setupAuthMiddleware()
loki.setupGRPCRecoveryMiddleware()
if err := loki.setupModuleManager(); err != nil {
return nil, err
}
return loki, nil
}
func (t *Loki) setupAuthMiddleware() {
t.HTTPAuthMiddleware = fakeauth.SetupAuthMiddleware(&t.Cfg.Server, t.Cfg.AuthEnabled,
// Also don't check auth for these gRPC methods, since single call is used for multiple users (or no user like health check).
[]string{
"/grpc.health.v1.Health/Check",
"/logproto.StreamData/GetStreamRates",
"/frontend.Frontend/Process",
"/frontend.Frontend/NotifyClientShutdown",
"/schedulerpb.SchedulerForFrontend/FrontendLoop",
"/schedulerpb.SchedulerForQuerier/QuerierLoop",
"/schedulerpb.SchedulerForQuerier/NotifyQuerierShutdown",
})
}
func (t *Loki) setupGRPCRecoveryMiddleware() {
t.Cfg.Server.GRPCMiddleware = append(t.Cfg.Server.GRPCMiddleware, serverutil.RecoveryGRPCUnaryInterceptor)
t.Cfg.Server.GRPCStreamMiddleware = append(t.Cfg.Server.GRPCStreamMiddleware, serverutil.RecoveryGRPCStreamInterceptor)
}
func newDefaultConfig() *Config {
defaultConfig := &Config{}
defaultFS := flag.NewFlagSet("", flag.PanicOnError)
defaultConfig.RegisterFlags(defaultFS)
return defaultConfig
}
// RunOpts configures custom behavior for running Loki.
type RunOpts struct {
// CustomConfigEndpointHandlerFn is the handlerFunc to be used by the /config endpoint.
// If empty, default handlerFunc will be used.
CustomConfigEndpointHandlerFn func(http.ResponseWriter, *http.Request)
// StartTime is the time at which the main() function started executing.
// It is used to determine the startup time as well as the running time of the Loki process.
StartTime time.Time
}
func (t *Loki) bindConfigEndpoint(opts RunOpts) {
configEndpointHandlerFn := configHandler(t.Cfg, newDefaultConfig())
if opts.CustomConfigEndpointHandlerFn != nil {
configEndpointHandlerFn = opts.CustomConfigEndpointHandlerFn
}
t.Server.HTTP.Path("/config").Methods("GET").HandlerFunc(configEndpointHandlerFn)
}
// ListTargets prints a list of available user visible targets and their
// dependencies
func (t *Loki) ListTargets() {
green := color.New(color.FgGreen, color.Bold)
if rt.GOOS == "windows" {
green.DisableColor()
}
for _, m := range t.ModuleManager.UserVisibleModuleNames() {
fmt.Fprintln(os.Stdout, green.Sprint(m))
for _, n := range t.ModuleManager.DependenciesForModule(m) {
if t.ModuleManager.IsUserVisibleModule(n) {
fmt.Fprintln(os.Stdout, " ", n)
}
}
}
}
// Run starts Loki running, and blocks until a Loki stops.
func (t *Loki) Run(opts RunOpts) error {
startTime := time.Now()
serviceMap, err := t.ModuleManager.InitModuleServices(t.Cfg.Target...)
if err != nil {
return err
}
t.serviceMap = serviceMap
t.Server.HTTP.Path("/services").Methods("GET").Handler(http.HandlerFunc(t.servicesHandler))
// get all services, create service manager and tell it to start
var servs []services.Service
for _, s := range serviceMap {
servs = append(servs, s)
}
sm, err := services.NewManager(servs...)
if err != nil {
return err
}
shutdownRequested := atomic.NewBool(false)
// before starting servers, register /ready handler. It should reflect entire Loki.
if t.Cfg.InternalServer.Enable {
t.InternalServer.HTTP.Path("/ready").Methods("GET").Handler(t.readyHandler(sm, shutdownRequested))
}
t.Server.HTTP.Path("/ready").Methods("GET").Handler(t.readyHandler(sm, shutdownRequested))
t.Server.HTTP.Path("/log_level").Methods("GET", "POST").Handler(util_log.LevelHandler(&t.Cfg.Server.LogLevel))
grpc_health_v1.RegisterHealthServer(t.Server.GRPC, grpcutil.NewHealthCheck(sm))
// Config endpoint adds a way to see the config and the changes compared to the defaults.
t.bindConfigEndpoint(opts)
// Each component serves its version.
if t.Cfg.InternalServer.Enable {
t.InternalServer.HTTP.Path("/loki/api/v1/status/buildinfo").Methods("GET").HandlerFunc(versionHandler())
}
t.Server.HTTP.Path("/loki/api/v1/status/buildinfo").Methods("GET").HandlerFunc(versionHandler())
t.Server.HTTP.Path("/debug/fgprof").Methods("GET", "POST").Handler(fgprof.Handler())
t.Server.HTTP.Path("/loki/api/v1/format_query").Methods("GET", "POST").HandlerFunc(formatQueryHandler())
// Let's listen for events from this manager, and log them.
logHook := func(msg, key string) func() {
return func() {
started := startTime
if opts.StartTime.After(time.Time{}) {
started = opts.StartTime
}
level.Info(util_log.Logger).Log("msg", msg, key, time.Since(started))
_ = util_log.Flush()
}
}
healthy := logHook("Loki started", "startup_time")
stopped := logHook("Loki stopped", "running_time")
serviceFailed := func(service services.Service) {
// if any service fails, stop entire Loki
sm.StopAsync()
// let's find out which module failed
for m, s := range serviceMap {
if s == service {
if service.FailureCase() == modules.ErrStopProcess {
level.Info(util_log.Logger).Log("msg", "received stop signal via return error", "module", m, "error", service.FailureCase())
} else {
level.Error(util_log.Logger).Log("msg", "module failed", "module", m, "error", service.FailureCase())
}
return
}
}
level.Error(util_log.Logger).Log("msg", "module failed", "module", "unknown", "error", service.FailureCase())
}
sm.AddListener(services.NewManagerListener(healthy, stopped, serviceFailed))
// Setup signal handler. If signal arrives, we stop the manager, which stops all the services.
t.SignalHandler = signals.NewHandler(t.Server.Log)
go func() {
t.SignalHandler.Loop()
shutdownRequested.Store(true)
if t.Cfg.ShutdownDelay > 0 {
level.Info(util_log.Logger).Log("msg", fmt.Sprintf("waiting %v before shutting down services", t.Cfg.ShutdownDelay))
time.Sleep(t.Cfg.ShutdownDelay)
}
sm.StopAsync()
}()
// Start all services. This can really only fail if some service is already
// in other state than New, which should not be the case.
err = sm.StartAsync(context.Background())
if err == nil {
// Wait until service manager stops. It can stop in two ways:
// 1) Signal is received and manager is stopped.
// 2) Any service fails.
err = sm.AwaitStopped(context.Background())
}
// If there is no error yet (= service manager started and then stopped without problems),
// but any service failed, report that failure as an error to caller.
if err == nil {
if failed := sm.ServicesByState()[services.Failed]; len(failed) > 0 {
for _, f := range failed {
if f.FailureCase() != modules.ErrStopProcess {
// Details were reported via failure listener before
err = errors.New("failed services")
break
}
}
}
}
return err
}
func (t *Loki) readyHandler(sm *services.Manager, shutdownRequested *atomic.Bool) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if shutdownRequested.Load() {
level.Debug(util_log.Logger).Log("msg", "application is stopping")
http.Error(w, "Application is stopping", http.StatusServiceUnavailable)
return
}
if !sm.IsHealthy() {
msg := bytes.Buffer{}
msg.WriteString("Some services are not Running:\n")
byState := sm.ServicesByState()
for st, ls := range byState {
msg.WriteString(fmt.Sprintf("%v: %d\n", st, len(ls)))
}
http.Error(w, msg.String(), http.StatusServiceUnavailable)
return
}
// Ingester has a special check that makes sure that it was able to register into the ring,
// and that all other ring entries are OK too.
if t.Ingester != nil {
if err := t.Ingester.CheckReady(r.Context()); err != nil {
http.Error(w, "Ingester not ready: "+err.Error(), http.StatusServiceUnavailable)
return
}
}
// Query Frontend has a special check that makes sure that a querier is attached before it signals
// itself as ready
if t.frontend != nil {
if err := t.frontend.CheckReady(r.Context()); err != nil {
http.Error(w, "Query Frontend not ready: "+err.Error(), http.StatusServiceUnavailable)
return
}
}
http.Error(w, "ready", http.StatusOK)
}
}
func (t *Loki) setupModuleManager() error {
mm := modules.NewManager(util_log.Logger)
mm.RegisterModule(Server, t.initServer, modules.UserInvisibleModule)
if t.Cfg.InternalServer.Enable {
mm.RegisterModule(InternalServer, t.initInternalServer, modules.UserInvisibleModule)
}
mm.RegisterModule(RuntimeConfig, t.initRuntimeConfig, modules.UserInvisibleModule)
mm.RegisterModule(MemberlistKV, t.initMemberlistKV, modules.UserInvisibleModule)
mm.RegisterModule(Ring, t.initRing, modules.UserInvisibleModule)
mm.RegisterModule(Overrides, t.initOverrides, modules.UserInvisibleModule)
mm.RegisterModule(OverridesExporter, t.initOverridesExporter)
mm.RegisterModule(TenantConfigs, t.initTenantConfigs, modules.UserInvisibleModule)
mm.RegisterModule(Distributor, t.initDistributor)
mm.RegisterModule(Store, t.initStore, modules.UserInvisibleModule)
mm.RegisterModule(Querier, t.initQuerier)
mm.RegisterModule(Ingester, t.initIngester)
mm.RegisterModule(IngesterQuerier, t.initIngesterQuerier)
mm.RegisterModule(IngesterGRPCInterceptors, t.initIngesterGRPCInterceptors, modules.UserInvisibleModule)
mm.RegisterModule(QueryFrontendTripperware, t.initQueryFrontendMiddleware, modules.UserInvisibleModule)
mm.RegisterModule(QueryFrontend, t.initQueryFrontend)
mm.RegisterModule(RulerStorage, t.initRulerStorage, modules.UserInvisibleModule)
mm.RegisterModule(Ruler, t.initRuler)
mm.RegisterModule(RuleEvaluator, t.initRuleEvaluator, modules.UserInvisibleModule)
mm.RegisterModule(TableManager, t.initTableManager)
mm.RegisterModule(Compactor, t.initCompactor)
mm.RegisterModule(BloomStore, t.initBloomStore)
mm.RegisterModule(BloomCompactor, t.initBloomCompactor)
mm.RegisterModule(BloomCompactorRing, t.initBloomCompactorRing, modules.UserInvisibleModule)
mm.RegisterModule(IndexGateway, t.initIndexGateway)
mm.RegisterModule(IndexGatewayRing, t.initIndexGatewayRing, modules.UserInvisibleModule)
mm.RegisterModule(IndexGatewayInterceptors, t.initIndexGatewayInterceptors, modules.UserInvisibleModule)
mm.RegisterModule(BloomGateway, t.initBloomGateway)
mm.RegisterModule(BloomGatewayRing, t.initBloomGatewayRing, modules.UserInvisibleModule)
mm.RegisterModule(QueryScheduler, t.initQueryScheduler)
mm.RegisterModule(QuerySchedulerRing, t.initQuerySchedulerRing, modules.UserInvisibleModule)
mm.RegisterModule(Analytics, t.initAnalytics)
mm.RegisterModule(CacheGenerationLoader, t.initCacheGenerationLoader)
mm.RegisterModule(All, nil)
mm.RegisterModule(Read, nil)
mm.RegisterModule(Write, nil)
mm.RegisterModule(Backend, nil)
// Add dependencies
deps := map[string][]string{
Ring: {RuntimeConfig, Server, MemberlistKV},
Analytics: {},
Overrides: {RuntimeConfig},
OverridesExporter: {Overrides, Server},
TenantConfigs: {RuntimeConfig},
Distributor: {Ring, Server, Overrides, TenantConfigs, Analytics},
Store: {Overrides, IndexGatewayRing},
Ingester: {Store, Server, MemberlistKV, TenantConfigs, Analytics},
Querier: {Store, Ring, Server, IngesterQuerier, Overrides, Analytics, CacheGenerationLoader, QuerySchedulerRing},
QueryFrontendTripperware: {Server, Overrides, TenantConfigs},
QueryFrontend: {QueryFrontendTripperware, Analytics, CacheGenerationLoader, QuerySchedulerRing},
QueryScheduler: {Server, Overrides, MemberlistKV, Analytics, QuerySchedulerRing},
Ruler: {Ring, Server, RulerStorage, RuleEvaluator, Overrides, TenantConfigs, Analytics},
RuleEvaluator: {Ring, Server, Store, IngesterQuerier, Overrides, TenantConfigs, Analytics},
TableManager: {Server, Analytics},
Compactor: {Server, Overrides, MemberlistKV, Analytics},
IndexGateway: {Server, Store, IndexGatewayRing, IndexGatewayInterceptors, Analytics},
BloomGateway: {Server, BloomStore, BloomGatewayRing, Analytics},
BloomCompactor: {Server, BloomStore, BloomCompactorRing, Analytics, Store},
IngesterQuerier: {Ring},
QuerySchedulerRing: {Overrides, MemberlistKV},
IndexGatewayRing: {Overrides, MemberlistKV},
BloomGatewayRing: {Overrides, MemberlistKV},
BloomCompactorRing: {Overrides, MemberlistKV},
MemberlistKV: {Server},
Read: {QueryFrontend, Querier},
Write: {Ingester, Distributor},
Backend: {QueryScheduler, Ruler, Compactor, IndexGateway, BloomGateway, BloomCompactor},
// TODO(salvacorts): We added the BloomCompactor component to the `all` target to ease testing.
// We should remove it before releasing the feature since we dont think any user running
// the single binary will benefit from the blooms given their scale in terms of ingested data
All: {QueryScheduler, QueryFrontend, Querier, Ingester, Distributor, Ruler, Compactor, BloomCompactor},
}
if t.Cfg.Querier.PerRequestLimitsEnabled {
level.Debug(util_log.Logger).Log("msg", "per-query request limits support enabled")
mm.RegisterModule(QueryLimiter, t.initQueryLimiter, modules.UserInvisibleModule)
mm.RegisterModule(QueryLimitsInterceptors, t.initQueryLimitsInterceptors, modules.UserInvisibleModule)
// This module is defunct but the target remains for backwards compatibility.
mm.RegisterModule(QueryLimitsTripperware, func() (services.Service, error) { return nil, nil }, modules.UserInvisibleModule)
// Ensure query limiter embeds overrides after they've been
// created.
deps[QueryLimiter] = []string{Overrides}
deps[QueryLimitsInterceptors] = []string{}
deps[Querier] = append(deps[Querier], QueryLimiter)
// query frontend tripperware uses t.Overrides. Make sure it
// uses the one wrapped by query limiter.
deps[QueryFrontendTripperware] = append(deps[QueryFrontendTripperware], QueryLimiter)
if err := mm.AddDependency(Server, QueryLimitsInterceptors); err != nil {
return err
}
}
// Add IngesterQuerier as a dependency for store when target is either querier, ruler, read, or backend.
if t.Cfg.isModuleEnabled(Querier) || t.Cfg.isModuleEnabled(Ruler) || t.Cfg.isModuleEnabled(Read) || t.Cfg.isModuleEnabled(Backend) {
deps[Store] = append(deps[Store], IngesterQuerier)
}
// If the query scheduler and querier are running together, make sure the scheduler goes
// first to initialize the ring that will also be used by the querier
if (t.Cfg.isModuleEnabled(Querier) && t.Cfg.isModuleEnabled(QueryScheduler)) || t.Cfg.isModuleEnabled(All) {
deps[Querier] = append(deps[Querier], QueryScheduler)
}
// If the query scheduler and query frontend are running together, make sure the scheduler goes
// first to initialize the ring that will also be used by the query frontend
if (t.Cfg.isModuleEnabled(QueryFrontend) && t.Cfg.isModuleEnabled(QueryScheduler)) || t.Cfg.isModuleEnabled(All) {
deps[QueryFrontend] = append(deps[QueryFrontend], QueryScheduler)
}
// Initialise query tags interceptors on targets running ingester
if t.Cfg.isModuleEnabled(Ingester) || t.Cfg.isModuleEnabled(Write) || t.Cfg.isModuleEnabled(All) {
deps[Server] = append(deps[Server], IngesterGRPCInterceptors)
}
// Add bloom gateway ring in client mode to IndexGateway service dependencies if bloom filtering is enabled.
if t.Cfg.BloomGateway.Enabled {
deps[IndexGateway] = append(deps[IndexGateway], BloomGatewayRing)
}
if t.Cfg.LegacyReadTarget {
deps[Read] = append(deps[Read], deps[Backend]...)
}
if t.Cfg.InternalServer.Enable {
for key, ds := range deps {
idx := -1
for i, v := range ds {
if v == Server {
idx = i
break
}
}
if idx == -1 {
continue
}
a := append(ds[:idx+1], ds[idx:]...)
a[idx] = InternalServer
deps[key] = a
}
}
for mod, targets := range deps {
if err := mm.AddDependency(mod, targets...); err != nil {
return err
}
}
t.deps = deps
t.ModuleManager = mm
if t.isModuleActive(Ingester) {
if err := mm.AddDependency(Analytics, Ring); err != nil {
return err
}
}
return nil
}
func (t *Loki) isModuleActive(m string) bool {
for _, target := range t.Cfg.Target {
if target == m {
return true
}
if t.recursiveIsModuleActive(target, m) {
return true
}
}
return false
}
func (t *Loki) recursiveIsModuleActive(target, m string) bool {
if targetDeps, ok := t.deps[target]; ok {
for _, dep := range targetDeps {
if dep == m {
return true
}
if t.recursiveIsModuleActive(dep, m) {
return true
}
}
}
return false
}