pkg/controller/kibana/stackmon/sidecar.go (127 lines of code) (raw):

// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one // or more contributor license agreements. Licensed under the Elastic License 2.0; // you may not use this file except in compliance with the Elastic License 2.0. package stackmon import ( "context" "errors" "fmt" "hash/fnv" corev1 "k8s.io/api/core/v1" commonv1 "github.com/elastic/cloud-on-k8s/v3/pkg/apis/common/v1" kbv1 "github.com/elastic/cloud-on-k8s/v3/pkg/apis/kibana/v1" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/association" beatstackmon "github.com/elastic/cloud-on-k8s/v3/pkg/controller/beat/common/stackmon" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/defaults" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/stackmon" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/stackmon/monitoring" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/stackmon/validations" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/version" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/common/volume" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/elasticsearch/user" "github.com/elastic/cloud-on-k8s/v3/pkg/controller/kibana/network" "github.com/elastic/cloud-on-k8s/v3/pkg/utils/k8s" ) const ( // cfgHashAnnotation is used to store a hash of the Metricbeat and Filebeat configurations. cfgHashAnnotation = "kibana.k8s.elastic.co/monitoring-config-hash" kibanaLogsVolumeName = "kibana-logs" kibanaLogsMountPath = "/usr/share/kibana/logs" ) func Metricbeat(ctx context.Context, client k8s.Client, kb kbv1.Kibana, basePath string) (stackmon.BeatSidecar, error) { if !kb.Spec.ElasticsearchRef.IsDefined() { // should never happen because of the pre-creation validation return stackmon.BeatSidecar{}, errors.New(validations.InvalidKibanaElasticsearchRefForStackMonitoringMsg) } associatedEsNsn := kb.Spec.ElasticsearchRef.NamespacedName() if associatedEsNsn.Namespace == "" { associatedEsNsn.Namespace = kb.Namespace } var username, password string if esAssoc := kb.EsAssociation(); esAssoc.AssociationRef().IsExternal() { info, err := association.GetUnmanagedAssociationConnectionInfoFromSecret(client, esAssoc) if err != nil { return stackmon.BeatSidecar{}, err } username, password = info.Username, info.Password } else { var err error username = user.MonitoringUserName password, err = user.GetMonitoringUserPassword(client, associatedEsNsn) if err != nil { return stackmon.BeatSidecar{}, err } } v, err := version.Parse(kb.Spec.Version) if err != nil { return stackmon.BeatSidecar{}, err // error unlikely and should have been caught during validation } caVol, err := stackmon.CAVolume(client, k8s.ExtractNamespacedName(&kb), kbv1.KBNamer, commonv1.KbMonitoringAssociationType, kb.Spec.HTTP.TLS.Enabled()) if err != nil { return stackmon.BeatSidecar{}, err } type inputConfigData struct { stackmon.TemplateParams BasePath string } configData := inputConfigData{ TemplateParams: stackmon.TemplateParams{ Username: username, Password: password, URL: fmt.Sprintf("%s://localhost:%d", kb.Spec.HTTP.Protocol(), network.HTTPPort), // Metricbeat in the sidecar connects to the monitored resource using `localhost` IsSSL: kb.Spec.HTTP.TLS.Enabled(), // enable SSL configuration based on whether the monitored resource has TLS enabled CAVolume: caVol, }, BasePath: basePath, } cfg, err := stackmon.RenderTemplate(v, metricbeatConfigTemplate, configData) if err != nil { return stackmon.BeatSidecar{}, err } metricbeat, err := stackmon.NewMetricBeatSidecar(ctx, client, &kb, v, caVol, cfg) if err != nil { return stackmon.BeatSidecar{}, err } return metricbeat, nil } func Filebeat(ctx context.Context, client k8s.Client, kb kbv1.Kibana) (stackmon.BeatSidecar, error) { return stackmon.NewFileBeatSidecar(ctx, client, &kb, kb.Spec.Version, filebeatConfig, nil) } // WithMonitoring updates the Kibana Pod template builder to deploy Metricbeat and Filebeat in sidecar containers // in the Kibana pod and injects the volumes for the beat configurations and the ES CA certificates. func WithMonitoring(ctx context.Context, client k8s.Client, builder *defaults.PodTemplateBuilder, kb kbv1.Kibana, basePath string) (*defaults.PodTemplateBuilder, error) { isMonitoringReconcilable, err := monitoring.IsReconcilable(&kb) if err != nil { return nil, err } if !isMonitoringReconcilable { return builder, nil } configHash := fnv.New32a() volumes := make([]corev1.Volume, 0) if monitoring.IsMetricsDefined(&kb) { b, err := Metricbeat(ctx, client, kb, basePath) if err != nil { return nil, err } // Add metricbeat logs volume metricbeatLogsVolume := volume.NewEmptyDirVolume(beatstackmon.MetricbeatLogsVolumeName, beatstackmon.MetricbeatLogsVolumeMountPath) volumes = append(volumes, metricbeatLogsVolume.Volume()) b.Container.VolumeMounts = append(b.Container.VolumeMounts, metricbeatLogsVolume.VolumeMount()) volumes = append(volumes, b.Volumes...) builder.WithContainers(b.Container) configHash.Write(b.ConfigHash.Sum(nil)) } if monitoring.IsLogsDefined(&kb) { b, err := Filebeat(ctx, client, kb) if err != nil { return nil, err } // Add filebeat logs volume filebeatLogsVolume := volume.NewEmptyDirVolume("filebeat-logs", "/usr/share/filebeat/logs") volumes = append(volumes, filebeatLogsVolume.Volume()) b.Container.VolumeMounts = append(b.Container.VolumeMounts, filebeatLogsVolume.VolumeMount()) // create a logs volume shared between Kibana and Filebeat logsVolume := volume.NewEmptyDirVolume(kibanaLogsVolumeName, kibanaLogsMountPath) volumes = append(volumes, logsVolume.Volume()) filebeat := b.Container filebeat.VolumeMounts = append(filebeat.VolumeMounts, logsVolume.VolumeMount()) builder.WithVolumeMounts(logsVolume.VolumeMount()) volumes = append(volumes, b.Volumes...) builder.WithContainers(filebeat) configHash.Write(b.ConfigHash.Sum(nil)) } // add the config hash annotation to ensure pod rotation when an ES password or a CA are rotated builder.WithAnnotations(map[string]string{cfgHashAnnotation: fmt.Sprint(configHash.Sum32())}) // inject all volumes builder.WithVolumes(volumes...) return builder, nil }