package manifests import ( "crypto/sha1" "fmt" "strings" lokiv1 "github.com/grafana/loki/operator/apis/loki/v1" "github.com/grafana/loki/operator/internal/manifests/internal/config" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // LokiConfigMap creates the single configmap containing the loki configuration for the whole cluster func LokiConfigMap(opt Options) (*corev1.ConfigMap, string, error) { cfg := ConfigOptions(opt) if opt.Stack.Tenants != nil { if err := ConfigureOptionsForMode(&cfg, opt); err != nil { return nil, "", err } } c, rc, err := config.Build(cfg) if err != nil { return nil, "", err } s := sha1.New() _, err = s.Write(c) if err != nil { return nil, "", err } sha1C := fmt.Sprintf("%x", s.Sum(nil)) return &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", APIVersion: corev1.SchemeGroupVersion.String(), }, ObjectMeta: metav1.ObjectMeta{ Name: lokiConfigMapName(opt.Name), Labels: commonLabels(opt.Name), }, BinaryData: map[string][]byte{ config.LokiConfigFileName: c, config.LokiRuntimeConfigFileName: rc, }, }, sha1C, nil } // ConfigOptions converts Options to config.Options func ConfigOptions(opt Options) config.Options { rulerEnabled := opt.Stack.Rules != nil && opt.Stack.Rules.Enabled stackLimitsEnabled := opt.Stack.Limits != nil && len(opt.Stack.Limits.Tenants) > 0 rulerLimitsEnabled := rulerEnabled && opt.Ruler.Spec != nil && len(opt.Ruler.Spec.Overrides) > 0 var ( evalInterval, pollInterval string amConfig *config.AlertManagerConfig rwConfig *config.RemoteWriteConfig overrides map[string]config.LokiOverrides ) if rulerEnabled { // Map alertmanager config from CRD to config options if opt.Ruler.Spec != nil { evalInterval = string(opt.Ruler.Spec.EvalutionInterval) pollInterval = string(opt.Ruler.Spec.PollInterval) amConfig = alertManagerConfig(opt.Ruler.Spec.AlertManagerSpec) } // Map remote write config from CRD to config options if opt.Ruler.Spec != nil && opt.Ruler.Secret != nil { rwConfig = remoteWriteConfig(opt.Ruler.Spec.RemoteWriteSpec, opt.Ruler.Secret) } } if stackLimitsEnabled || rulerLimitsEnabled { overrides = map[string]config.LokiOverrides{} } if stackLimitsEnabled { for tenant, limits := range opt.Stack.Limits.Tenants { so := overrides[tenant] so.Limits = limits overrides[tenant] = so } } if rulerLimitsEnabled { for tenant, override := range opt.Ruler.Spec.Overrides { so := overrides[tenant] so.Ruler = config.RulerOverrides{ AlertManager: alertManagerConfig(override.AlertManagerOverrides), } overrides[tenant] = so } } protocol := "http" if opt.Gates.HTTPEncryption { protocol = "https" } return config.Options{ Stack: opt.Stack, Gates: opt.Gates, TLS: config.TLSOptions{ Ciphers: opt.TLSProfile.Ciphers, MinTLSVersion: opt.TLSProfile.MinTLSVersion, Paths: config.TLSFilePaths{ CA: signingCAPath(), GRPC: config.TLSCertPath{ Certificate: lokiServerGRPCTLSCert(), Key: lokiServerGRPCTLSKey(), }, HTTP: config.TLSCertPath{ Certificate: lokiServerHTTPTLSCert(), Key: lokiServerHTTPTLSKey(), }, }, ServerNames: config.TLSServerNames{ GRPC: config.GRPCServerNames{ Compactor: fqdn(serviceNameCompactorGRPC(opt.Name), opt.Namespace), IndexGateway: fqdn(serviceNameIndexGatewayGRPC(opt.Name), opt.Namespace), Ingester: fqdn(serviceNameIngesterGRPC(opt.Name), opt.Namespace), QueryFrontend: fqdn(serviceNameQueryFrontendGRPC(opt.Name), opt.Namespace), Ruler: fqdn(serviceNameRulerGRPC(opt.Name), opt.Namespace), }, HTTP: config.HTTPServerNames{ Querier: fqdn(serviceNameQuerierHTTP(opt.Name), opt.Namespace), }, }, }, Namespace: opt.Namespace, Name: opt.Name, Compactor: config.Address{ FQDN: fqdn(NewCompactorGRPCService(opt).GetName(), opt.Namespace), Port: grpcPort, }, FrontendWorker: config.Address{ FQDN: fqdn(NewQueryFrontendGRPCService(opt).GetName(), opt.Namespace), Port: grpcPort, }, GossipRing: gossipRingConfig(opt.Name, opt.Namespace, opt.Stack.HashRing), Querier: config.Address{ Protocol: protocol, FQDN: fqdn(NewQuerierHTTPService(opt).GetName(), opt.Namespace), Port: httpPort, }, IndexGateway: config.Address{ FQDN: fqdn(NewIndexGatewayGRPCService(opt).GetName(), opt.Namespace), Port: grpcPort, }, StorageDirectory: dataDirectory, MaxConcurrent: config.MaxConcurrent{ AvailableQuerierCPUCores: int32(opt.ResourceRequirements.Querier.Requests.Cpu().Value()), }, WriteAheadLog: config.WriteAheadLog{ Directory: walDirectory, IngesterMemoryRequest: opt.ResourceRequirements.Ingester.Requests.Memory().Value(), }, ObjectStorage: opt.ObjectStorage, EnableRemoteReporting: opt.Gates.GrafanaLabsUsageReport, Ruler: config.Ruler{ Enabled: rulerEnabled, RulesStorageDirectory: rulesStorageDirectory, EvaluationInterval: evalInterval, PollInterval: pollInterval, AlertManager: amConfig, RemoteWrite: rwConfig, }, Retention: retentionConfig(&opt.Stack), Overrides: overrides, } } func alertManagerConfig(spec *lokiv1.AlertManagerSpec) *config.AlertManagerConfig { if spec == nil { return nil } conf := &config.AlertManagerConfig{ ExternalURL: spec.ExternalURL, ExternalLabels: spec.ExternalLabels, Hosts: strings.Join(spec.Endpoints, ","), EnableV2: spec.EnableV2, } if d := spec.DiscoverySpec; d != nil { conf.EnableDiscovery = d.EnableSRV conf.RefreshInterval = string(d.RefreshInterval) } if n := spec.NotificationQueueSpec; n != nil { conf.QueueCapacity = n.Capacity conf.Timeout = string(n.Timeout) conf.ForOutageTolerance = string(n.ForOutageTolerance) conf.ForGracePeriod = string(n.ForGracePeriod) conf.ResendDelay = string(n.ResendDelay) } for _, cfg := range spec.RelabelConfigs { conf.RelabelConfigs = append(conf.RelabelConfigs, config.RelabelConfig{ SourceLabels: cfg.SourceLabels, Separator: cfg.Separator, TargetLabel: cfg.TargetLabel, Regex: cfg.Regex, Modulus: cfg.Modulus, Replacement: cfg.Replacement, Action: string(cfg.Action), }) } if clt := spec.Client; clt != nil { conf.Notifier = &config.NotifierConfig{} if tls := clt.TLS; tls != nil { conf.Notifier.TLS = config.TLSConfig{ CAPath: tls.CAPath, ServerName: tls.ServerName, CertPath: tls.CertPath, KeyPath: tls.KeyPath, } } if ha := clt.HeaderAuth; ha != nil { conf.Notifier.HeaderAuth = config.HeaderAuth{ Type: ha.Type, Credentials: ha.Credentials, CredentialsFile: ha.CredentialsFile, } } if ba := clt.BasicAuth; ba != nil { conf.Notifier.BasicAuth = config.BasicAuth{ Username: ba.Username, Password: ba.Password, } } } return conf } func gossipRingConfig(stackName, stackNs string, spec *lokiv1.HashRingSpec) config.GossipRing { var instanceAddr string if spec != nil && spec.Type == lokiv1.HashRingMemberList && spec.MemberList != nil { switch spec.MemberList.InstanceAddrType { case lokiv1.InstanceAddrPodIP: instanceAddr = fmt.Sprintf("${%s}", gossipInstanceAddrEnvVarName) case lokiv1.InstanceAddrDefault: // Do nothing use loki defaults default: // Do nothing use loki defaults } } return config.GossipRing{ InstanceAddr: instanceAddr, InstancePort: grpcPort, BindPort: gossipPort, MembersDiscoveryAddr: fqdn(BuildLokiGossipRingService(stackName).GetName(), stackNs), } } func remoteWriteConfig(s *lokiv1.RemoteWriteSpec, rs *RulerSecret) *config.RemoteWriteConfig { if s == nil || rs == nil { return nil } c := &config.RemoteWriteConfig{ Enabled: s.Enabled, RefreshPeriod: string(s.RefreshPeriod), } if cls := s.ClientSpec; cls != nil { c.Client = &config.RemoteWriteClientConfig{ Name: cls.Name, URL: cls.URL, RemoteTimeout: string(cls.Timeout), ProxyURL: cls.ProxyURL, Headers: cls.AdditionalHeaders, FollowRedirects: cls.FollowRedirects, } switch cls.AuthorizationType { case lokiv1.BasicAuthorization: c.Client.BasicAuthUsername = rs.Username c.Client.BasicAuthPassword = rs.Password case lokiv1.BearerAuthorization: c.Client.BearerToken = rs.BearerToken } for _, cfg := range cls.RelabelConfigs { c.RelabelConfigs = append(c.RelabelConfigs, config.RelabelConfig{ SourceLabels: cfg.SourceLabels, Separator: cfg.Separator, TargetLabel: cfg.TargetLabel, Regex: cfg.Regex, Modulus: cfg.Modulus, Replacement: cfg.Replacement, Action: string(cfg.Action), }) } } if q := s.QueueSpec; q != nil { c.Queue = &config.RemoteWriteQueueConfig{ Capacity: q.Capacity, MaxShards: q.MaxShards, MinShards: q.MinShards, MaxSamplesPerSend: q.MaxSamplesPerSend, BatchSendDeadline: string(q.BatchSendDeadline), MinBackOffPeriod: string(q.MinBackOffPeriod), MaxBackOffPeriod: string(q.MaxBackOffPeriod), } } return c } var deleteWorkerCountMap = map[lokiv1.LokiStackSizeType]uint{ lokiv1.SizeOneXDemo: 10, lokiv1.SizeOneXExtraSmall: 10, lokiv1.SizeOneXSmall: 150, lokiv1.SizeOneXMedium: 150, } func retentionConfig(ls *lokiv1.LokiStackSpec) config.RetentionOptions { if ls.Limits == nil { return config.RetentionOptions{} } globalRetention := ls.Limits.Global != nil && ls.Limits.Global.Retention != nil tenantRetention := false for _, t := range ls.Limits.Tenants { if t.Retention != nil { tenantRetention = true break } } if !globalRetention && !tenantRetention { return config.RetentionOptions{} } return config.RetentionOptions{ Enabled: true, DeleteWorkerCount: deleteWorkerCountMap[ls.Size], } }