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 }