github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/discovery/kubernetes/endpoints.go (about) 1 // Copyright 2016 The Prometheus Authors 2 // Copyright 2021 The Pyroscope Authors 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 16 package kubernetes 17 18 import ( 19 "context" 20 "fmt" 21 "net" 22 "strconv" 23 24 "github.com/sirupsen/logrus" 25 apiv1 "k8s.io/api/core/v1" 26 "k8s.io/client-go/tools/cache" 27 "k8s.io/client-go/util/workqueue" 28 29 "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/targetgroup" 30 "github.com/pyroscope-io/pyroscope/pkg/scrape/model" 31 ) 32 33 // revive:disable:cognitive-complexity preserve original implementation 34 35 var ( 36 epAddCount = eventCount.WithLabelValues("endpoints", "add") 37 epUpdateCount = eventCount.WithLabelValues("endpoints", "update") 38 epDeleteCount = eventCount.WithLabelValues("endpoints", "delete") 39 ) 40 41 // Endpoints discovers new endpoint targets. 42 type Endpoints struct { 43 logger logrus.FieldLogger 44 45 endpointsInf cache.SharedInformer 46 serviceInf cache.SharedInformer 47 podInf cache.SharedInformer 48 49 podStore cache.Store 50 endpointsStore cache.Store 51 serviceStore cache.Store 52 53 queue *workqueue.Type 54 } 55 56 // NewEndpoints returns a new endpoints discovery. 57 func NewEndpoints(l logrus.FieldLogger, svc, eps, pod cache.SharedInformer) *Endpoints { 58 e := &Endpoints{ 59 logger: l, 60 endpointsInf: eps, 61 endpointsStore: eps.GetStore(), 62 serviceInf: svc, 63 serviceStore: svc.GetStore(), 64 podInf: pod, 65 podStore: pod.GetStore(), 66 queue: workqueue.NewNamed("endpoints"), 67 } 68 69 e.endpointsInf.AddEventHandler(cache.ResourceEventHandlerFuncs{ 70 AddFunc: func(o interface{}) { 71 epAddCount.Inc() 72 e.enqueue(o) 73 }, 74 UpdateFunc: func(_, o interface{}) { 75 epUpdateCount.Inc() 76 e.enqueue(o) 77 }, 78 DeleteFunc: func(o interface{}) { 79 epDeleteCount.Inc() 80 e.enqueue(o) 81 }, 82 }) 83 84 serviceUpdate := func(o interface{}) { 85 svc, err := convertToService(o) 86 if err != nil { 87 e.logger.WithError(err).Error("converting to Service object failed") 88 return 89 } 90 91 ep := &apiv1.Endpoints{} 92 ep.Namespace = svc.Namespace 93 ep.Name = svc.Name 94 obj, exists, err := e.endpointsStore.Get(ep) 95 if exists && err == nil { 96 e.enqueue(obj.(*apiv1.Endpoints)) 97 } 98 99 if err != nil { 100 e.logger.WithError(err).Error("retrieving endpoints failed") 101 } 102 } 103 e.serviceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{ 104 // TODO(fabxc): potentially remove add and delete event handlers. Those should 105 // be triggered via the endpoint handlers already. 106 AddFunc: func(o interface{}) { 107 svcAddCount.Inc() 108 serviceUpdate(o) 109 }, 110 UpdateFunc: func(_, o interface{}) { 111 svcUpdateCount.Inc() 112 serviceUpdate(o) 113 }, 114 DeleteFunc: func(o interface{}) { 115 svcDeleteCount.Inc() 116 serviceUpdate(o) 117 }, 118 }) 119 120 return e 121 } 122 123 func (e *Endpoints) enqueue(obj interface{}) { 124 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) 125 if err != nil { 126 return 127 } 128 129 e.queue.Add(key) 130 } 131 132 // Run implements the Discoverer interface. 133 func (e *Endpoints) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { 134 defer e.queue.ShutDown() 135 136 if !cache.WaitForCacheSync(ctx.Done(), e.endpointsInf.HasSynced, e.serviceInf.HasSynced, e.podInf.HasSynced) { 137 if ctx.Err() != context.Canceled { 138 e.logger.Error("endpoints informer unable to sync cache") 139 } 140 return 141 } 142 143 go func() { 144 for { 145 if !e.process(ctx, ch) { 146 return 147 } 148 } 149 }() 150 151 // Block until the target provider is explicitly canceled. 152 <-ctx.Done() 153 } 154 155 func (e *Endpoints) process(ctx context.Context, ch chan<- []*targetgroup.Group) bool { 156 keyObj, quit := e.queue.Get() 157 if quit { 158 return false 159 } 160 defer e.queue.Done(keyObj) 161 key := keyObj.(string) 162 logger := e.logger.WithField("key", key) 163 namespace, name, err := cache.SplitMetaNamespaceKey(key) 164 if err != nil { 165 logger.WithError(err).Error("splitting key") 166 return true 167 } 168 169 o, exists, err := e.endpointsStore.GetByKey(key) 170 if err != nil { 171 logger.WithError(err).Error("getting object from store") 172 return true 173 } 174 if !exists { 175 send(ctx, ch, &targetgroup.Group{Source: endpointsSourceFromNamespaceAndName(namespace, name)}) 176 return true 177 } 178 eps, err := convertToEndpoints(o) 179 if err != nil { 180 logger.WithError(err).Error("converting to Endpoints object") 181 return true 182 } 183 send(ctx, ch, e.buildEndpoints(eps)) 184 return true 185 } 186 187 func convertToEndpoints(o interface{}) (*apiv1.Endpoints, error) { 188 endpoints, ok := o.(*apiv1.Endpoints) 189 if ok { 190 return endpoints, nil 191 } 192 193 return nil, fmt.Errorf("received unexpected object: %v", o) 194 } 195 196 func endpointsSource(ep *apiv1.Endpoints) string { 197 return endpointsSourceFromNamespaceAndName(ep.Namespace, ep.Name) 198 } 199 200 func endpointsSourceFromNamespaceAndName(namespace, name string) string { 201 return "endpoints/" + namespace + "/" + name 202 } 203 204 const ( 205 endpointsLabelPrefix = metaLabelPrefix + "endpoints_label_" 206 endpointsLabelPresentPrefix = metaLabelPrefix + "endpoints_labelpresent_" 207 endpointsNameLabel = metaLabelPrefix + "endpoints_name" 208 endpointNodeName = metaLabelPrefix + "endpoint_node_name" 209 endpointHostname = metaLabelPrefix + "endpoint_hostname" 210 endpointReadyLabel = metaLabelPrefix + "endpoint_ready" 211 endpointPortNameLabel = metaLabelPrefix + "endpoint_port_name" 212 endpointPortProtocolLabel = metaLabelPrefix + "endpoint_port_protocol" 213 endpointAddressTargetKindLabel = metaLabelPrefix + "endpoint_address_target_kind" 214 endpointAddressTargetNameLabel = metaLabelPrefix + "endpoint_address_target_name" 215 ) 216 217 func (e *Endpoints) buildEndpoints(eps *apiv1.Endpoints) *targetgroup.Group { 218 tg := &targetgroup.Group{ 219 Source: endpointsSource(eps), 220 } 221 tg.Labels = model.LabelSet{ 222 namespaceLabel: lv(eps.Namespace), 223 endpointsNameLabel: lv(eps.Name), 224 } 225 e.addServiceLabels(eps.Namespace, eps.Name, tg) 226 // Add endpoints labels metadata. 227 for k, v := range eps.Labels { 228 ln := sanitizeLabelName(k) 229 tg.Labels[model.LabelName(endpointsLabelPrefix+ln)] = lv(v) 230 tg.Labels[model.LabelName(endpointsLabelPresentPrefix+ln)] = presentValue 231 } 232 233 type podEntry struct { 234 pod *apiv1.Pod 235 servicePorts []apiv1.EndpointPort 236 } 237 seenPods := map[string]*podEntry{} 238 239 add := func(addr apiv1.EndpointAddress, port apiv1.EndpointPort, ready string) { 240 a := net.JoinHostPort(addr.IP, strconv.FormatUint(uint64(port.Port), 10)) 241 242 target := model.LabelSet{ 243 model.AddressLabel: lv(a), 244 endpointPortNameLabel: lv(port.Name), 245 endpointPortProtocolLabel: lv(string(port.Protocol)), 246 endpointReadyLabel: lv(ready), 247 } 248 249 if addr.TargetRef != nil { 250 target[model.LabelName(endpointAddressTargetKindLabel)] = lv(addr.TargetRef.Kind) 251 target[model.LabelName(endpointAddressTargetNameLabel)] = lv(addr.TargetRef.Name) 252 } 253 254 if addr.NodeName != nil { 255 target[model.LabelName(endpointNodeName)] = lv(*addr.NodeName) 256 } 257 if addr.Hostname != "" { 258 target[model.LabelName(endpointHostname)] = lv(addr.Hostname) 259 } 260 261 pod := e.resolvePodRef(addr.TargetRef) 262 if pod == nil { 263 // This target is not a Pod, so don't continue with Pod specific logic. 264 tg.Targets = append(tg.Targets, target) 265 return 266 } 267 s := pod.Namespace + "/" + pod.Name 268 269 sp, ok := seenPods[s] 270 if !ok { 271 sp = &podEntry{pod: pod} 272 seenPods[s] = sp 273 } 274 275 // Attach standard pod labels. 276 target = target.Merge(podLabels(pod)) 277 278 // Attach potential container port labels matching the endpoint port. 279 for _, c := range pod.Spec.Containers { 280 for _, cport := range c.Ports { 281 if port.Port == cport.ContainerPort { 282 ports := strconv.FormatUint(uint64(port.Port), 10) 283 284 target[podContainerNameLabel] = lv(c.Name) 285 target[podContainerPortNameLabel] = lv(cport.Name) 286 target[podContainerPortNumberLabel] = lv(ports) 287 target[podContainerPortProtocolLabel] = lv(string(port.Protocol)) 288 break 289 } 290 } 291 } 292 293 // Add service port so we know that we have already generated a target 294 // for it. 295 sp.servicePorts = append(sp.servicePorts, port) 296 tg.Targets = append(tg.Targets, target) 297 } 298 299 for _, ss := range eps.Subsets { 300 for _, port := range ss.Ports { 301 for _, addr := range ss.Addresses { 302 add(addr, port, "true") 303 } 304 // Although this generates the same target again, as it was generated in 305 // the loop above, it causes the ready meta label to be overridden. 306 for _, addr := range ss.NotReadyAddresses { 307 add(addr, port, "false") 308 } 309 } 310 } 311 312 v := eps.Labels[apiv1.EndpointsOverCapacity] 313 if v == "truncated" { 314 e.logger.WithField("endpoint", eps.Name). 315 Warn("number of endpoints in one Endpoints object exceeds 1000 and has been truncated, please use \"role: endpointslice\" instead") 316 } 317 if v == "warning" { 318 e.logger.WithField("endpoint", eps.Name). 319 Warn("number of endpoints in one Endpoints object exceeds 1000, please use \"role: endpointslice\" instead") 320 } 321 322 // For all seen pods, check all container ports. If they were not covered 323 // by one of the service endpoints, generate targets for them. 324 for _, pe := range seenPods { 325 for _, c := range pe.pod.Spec.Containers { 326 for _, cport := range c.Ports { 327 hasSeenPort := func() bool { 328 for _, eport := range pe.servicePorts { 329 if cport.ContainerPort == eport.Port { 330 return true 331 } 332 } 333 return false 334 } 335 if hasSeenPort() { 336 continue 337 } 338 339 a := net.JoinHostPort(pe.pod.Status.PodIP, strconv.FormatUint(uint64(cport.ContainerPort), 10)) 340 ports := strconv.FormatUint(uint64(cport.ContainerPort), 10) 341 342 target := model.LabelSet{ 343 model.AddressLabel: lv(a), 344 podContainerNameLabel: lv(c.Name), 345 podContainerPortNameLabel: lv(cport.Name), 346 podContainerPortNumberLabel: lv(ports), 347 podContainerPortProtocolLabel: lv(string(cport.Protocol)), 348 } 349 tg.Targets = append(tg.Targets, target.Merge(podLabels(pe.pod))) 350 } 351 } 352 } 353 354 return tg 355 } 356 357 func (e *Endpoints) resolvePodRef(ref *apiv1.ObjectReference) *apiv1.Pod { 358 if ref == nil || ref.Kind != "Pod" { 359 return nil 360 } 361 p := &apiv1.Pod{} 362 p.Namespace = ref.Namespace 363 p.Name = ref.Name 364 365 obj, exists, err := e.podStore.Get(p) 366 if err != nil { 367 e.logger.WithError(err).Error("resolving pod") 368 return nil 369 } 370 if !exists { 371 return nil 372 } 373 return obj.(*apiv1.Pod) 374 } 375 376 func (e *Endpoints) addServiceLabels(ns, name string, tg *targetgroup.Group) { 377 svc := &apiv1.Service{} 378 svc.Namespace = ns 379 svc.Name = name 380 381 obj, exists, err := e.serviceStore.Get(svc) 382 if err != nil { 383 e.logger.WithError(err).Error("retrieving service") 384 return 385 } 386 if !exists { 387 return 388 } 389 svc = obj.(*apiv1.Service) 390 391 tg.Labels = tg.Labels.Merge(serviceLabels(svc)) 392 }