github.com/verrazzano/verrazzano@v1.7.0/application-operator/internal/metrics/service_monitor.go (about)

     1  // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package metrics
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  
    10  	promoperapi "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
    11  	"github.com/verrazzano/verrazzano/application-operator/constants"
    12  	"github.com/verrazzano/verrazzano/pkg/log/vzlog"
    13  	corev1 "k8s.io/api/core/v1"
    14  )
    15  
    16  const (
    17  	prometheusClusterNameLabel = "verrazzano_cluster"
    18  )
    19  
    20  // ScrapeInfo captures the information needed to construct the service monitor for a generic workload
    21  type ScrapeInfo struct {
    22  	// The path by which Prometheus should scrape metrics
    23  	Path *string
    24  	// The number of ports located for the workload
    25  	Ports int
    26  	// The basic authentication secret required for the service monitor if applicable
    27  	BasicAuthSecret *corev1.Secret
    28  	// Determines whether to enable Istio for the generated service monitor
    29  	IstioEnabled *bool
    30  	// Verify if the scrape target uses the Verrazzano Prometheus Labels
    31  	VZPrometheusLabels *bool
    32  	// The map to generate keep labels
    33  	// This matches the expected pod labels to the scrape config
    34  	KeepLabels map[string]string
    35  	// The name of the cluster for the selected workload
    36  	ClusterName string
    37  }
    38  
    39  // PopulateServiceMonitor populates the Service Monitor to prepare for a create or update
    40  // the Service Monitor reflects the specifications defined in the ScrapeInfo object
    41  func PopulateServiceMonitor(info ScrapeInfo, serviceMonitor *promoperapi.ServiceMonitor, log vzlog.VerrazzanoLogger) error {
    42  	// Create the Service Monitor selector from the info label if it exists
    43  	if serviceMonitor.ObjectMeta.Labels == nil {
    44  		serviceMonitor.ObjectMeta.Labels = make(map[string]string)
    45  	}
    46  	serviceMonitor.Labels["release"] = "prometheus-operator"
    47  	serviceMonitor.Spec.NamespaceSelector = promoperapi.NamespaceSelector{
    48  		MatchNames: []string{serviceMonitor.Namespace},
    49  	}
    50  
    51  	// Clear the existing endpoints to avoid duplications
    52  	serviceMonitor.Spec.Endpoints = nil
    53  
    54  	// Loop through ports in the info and create scrape targets for each
    55  	for i := 0; i < info.Ports; i++ {
    56  		endpoint, err := createServiceMonitorEndpoint(info, i)
    57  		if err != nil {
    58  			return log.ErrorfNewErr("Failed to create an endpoint for the Service Monitor: %v", err)
    59  		}
    60  		serviceMonitor.Spec.Endpoints = append(serviceMonitor.Spec.Endpoints, endpoint)
    61  	}
    62  	return nil
    63  }
    64  
    65  // createServiceMonitorEndpoint creates an endpoint for a given port increment and info
    66  // this function effectively creates a scrape config for the workload target through the Service Monitor API
    67  func createServiceMonitorEndpoint(info ScrapeInfo, portIncrement int) (promoperapi.Endpoint, error) {
    68  	var endpoint promoperapi.Endpoint
    69  	enabledHTTP2 := false
    70  	// Add the secret username and password if basic auth is required for this endpoint
    71  	// The secret has to exist in the workload and namespace
    72  	if secret := info.BasicAuthSecret; secret != nil {
    73  		endpoint.BasicAuth = &promoperapi.BasicAuth{
    74  			Username: corev1.SecretKeySelector{
    75  				LocalObjectReference: corev1.LocalObjectReference{
    76  					Name: secret.Name,
    77  				},
    78  				Key: "username",
    79  			},
    80  			Password: corev1.SecretKeySelector{
    81  				LocalObjectReference: corev1.LocalObjectReference{
    82  					Name: secret.Name,
    83  				},
    84  				Key: "password",
    85  			},
    86  		}
    87  	}
    88  	endpoint.Scheme = "http"
    89  	endpoint.Path = "/metrics"
    90  	if info.Path != nil {
    91  		endpoint.Path = *info.Path
    92  	}
    93  
    94  	if info.IstioEnabled != nil && *info.IstioEnabled {
    95  		// The Prometheus Pod contains Istio certificates from the installation process
    96  		// These certs are generated by Istio and are mounted as a volume on the Prometheus pod
    97  		// ServiceMonitors are used to take advantage of these existing files because it allows us to reference the files in the volume
    98  		certPath := "/etc/istio-certs"
    99  		endpoint.EnableHttp2 = &enabledHTTP2
   100  		endpoint.Scheme = "https"
   101  		endpoint.TLSConfig = &promoperapi.TLSConfig{
   102  			CAFile:   fmt.Sprintf("%s/root-cert.pem", certPath),
   103  			CertFile: fmt.Sprintf("%s/cert-chain.pem", certPath),
   104  			KeyFile:  fmt.Sprintf("%s/key.pem", certPath),
   105  		}
   106  		endpoint.TLSConfig.InsecureSkipVerify = true
   107  	}
   108  
   109  	// Change the expected labels based on the workload type
   110  	enabledLabel := "__meta_kubernetes_pod_annotation_prometheus_io_scrape"
   111  	portLabel := "__meta_kubernetes_pod_annotation_prometheus_io_port"
   112  	pathLabel := "__meta_kubernetes_pod_annotation_prometheus_io_path"
   113  	if info.VZPrometheusLabels != nil && *info.VZPrometheusLabels {
   114  		var portString string
   115  		if portIncrement > 0 {
   116  			portString = strconv.Itoa(portIncrement)
   117  		}
   118  		enabledLabel = fmt.Sprintf("__meta_kubernetes_pod_annotation_verrazzano_io_metricsEnabled%s", portString)
   119  		portLabel = fmt.Sprintf("__meta_kubernetes_pod_annotation_verrazzano_io_metricsPort%s", portString)
   120  		pathLabel = fmt.Sprintf("__meta_kubernetes_pod_annotation_verrazzano_io_metricsPath%s", portString)
   121  	}
   122  
   123  	// Add default cluster name if not populated
   124  	if info.ClusterName == "" {
   125  		info.ClusterName = constants.DefaultClusterName
   126  	}
   127  
   128  	// Relabel the cluster name
   129  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   130  		Action:      "replace",
   131  		Replacement: info.ClusterName,
   132  		TargetLabel: prometheusClusterNameLabel,
   133  	})
   134  
   135  	// Relabel to match the expected labels
   136  	regexString := "true"
   137  	sourceLabels := []promoperapi.LabelName{promoperapi.LabelName(enabledLabel)}
   138  	for key, val := range info.KeepLabels {
   139  		sourceLabels = append(sourceLabels, promoperapi.LabelName(key))
   140  		regexString = fmt.Sprintf("%s;%s", regexString, val)
   141  	}
   142  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   143  		Action:       "keep",
   144  		Regex:        regexString,
   145  		SourceLabels: sourceLabels,
   146  	})
   147  
   148  	// Replace the metrics path if specified
   149  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   150  		Action: "replace",
   151  		Regex:  "(.+)",
   152  		SourceLabels: []promoperapi.LabelName{
   153  			promoperapi.LabelName(pathLabel),
   154  		},
   155  		TargetLabel: "__metrics_path__",
   156  	})
   157  
   158  	// Relabel the address of the metrics endpoint
   159  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   160  		Action:      "replace",
   161  		Regex:       `([^:]+)(?::\d+)?;(\d+)`,
   162  		Replacement: "$1:$2",
   163  		SourceLabels: []promoperapi.LabelName{
   164  			"__address__",
   165  			promoperapi.LabelName(portLabel),
   166  		},
   167  		TargetLabel: "__address__",
   168  	})
   169  
   170  	// Relabel the namespace label
   171  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   172  		Action:      "replace",
   173  		Regex:       `(.*)`,
   174  		Replacement: "$1",
   175  		SourceLabels: []promoperapi.LabelName{
   176  			"__meta_kubernetes_namespace",
   177  		},
   178  		TargetLabel: "namespace",
   179  	})
   180  
   181  	// Relabel the pod label
   182  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   183  		Action: "labelmap",
   184  		Regex:  `__meta_kubernetes_pod_label_(.+)`,
   185  	})
   186  
   187  	// Relabel the pod name label
   188  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   189  		Action: "replace",
   190  		SourceLabels: []promoperapi.LabelName{
   191  			"__meta_kubernetes_pod_name",
   192  		},
   193  		TargetLabel: "pod_name",
   194  	})
   195  
   196  	// Drop the controller revision hash label
   197  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   198  		Action: "labeldrop",
   199  		Regex:  `(controller_revision_hash)`,
   200  	})
   201  
   202  	// Relabel the webapp label
   203  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   204  		Action:      "replace",
   205  		Regex:       `.*/(.*)$`,
   206  		Replacement: "$1",
   207  		SourceLabels: []promoperapi.LabelName{
   208  			"name",
   209  		},
   210  		TargetLabel: "webapp",
   211  	})
   212  
   213  	// Add a relabel config that will copy the value of "app" to "application" if "application" is empty
   214  	endpoint.RelabelConfigs = append(endpoint.RelabelConfigs, &promoperapi.RelabelConfig{
   215  		Action:      "replace",
   216  		Regex:       `;(.*)`,
   217  		Replacement: "$1",
   218  		Separator:   ";",
   219  		SourceLabels: []promoperapi.LabelName{
   220  			"application",
   221  			"app",
   222  		},
   223  		TargetLabel: "application",
   224  	})
   225  
   226  	return endpoint, nil
   227  }