github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/discovery/kubernetes/endpointslice.go (about) 1 // Copyright 2020 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 disv1beta1 "k8s.io/api/discovery/v1beta1" 27 "k8s.io/client-go/tools/cache" 28 "k8s.io/client-go/util/workqueue" 29 30 "github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/targetgroup" 31 "github.com/pyroscope-io/pyroscope/pkg/scrape/model" 32 ) 33 34 var ( 35 epslAddCount = eventCount.WithLabelValues("endpointslice", "add") 36 epslUpdateCount = eventCount.WithLabelValues("endpointslice", "update") 37 epslDeleteCount = eventCount.WithLabelValues("endpointslice", "delete") 38 ) 39 40 // revive:disable:cognitive-complexity preserve original implementation 41 42 // EndpointSlice discovers new endpoint targets. 43 type EndpointSlice struct { 44 logger logrus.FieldLogger 45 46 endpointSliceInf cache.SharedInformer 47 serviceInf cache.SharedInformer 48 podInf cache.SharedInformer 49 50 podStore cache.Store 51 endpointSliceStore cache.Store 52 serviceStore cache.Store 53 54 queue *workqueue.Type 55 } 56 57 // NewEndpointSlice returns a new endpointslice discovery. 58 func NewEndpointSlice(l logrus.FieldLogger, svc, eps, pod cache.SharedInformer) *EndpointSlice { 59 e := &EndpointSlice{ 60 logger: l, 61 endpointSliceInf: eps, 62 endpointSliceStore: eps.GetStore(), 63 serviceInf: svc, 64 serviceStore: svc.GetStore(), 65 podInf: pod, 66 podStore: pod.GetStore(), 67 queue: workqueue.NewNamed("endpointSlice"), 68 } 69 70 e.endpointSliceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{ 71 AddFunc: func(o interface{}) { 72 epslAddCount.Inc() 73 e.enqueue(o) 74 }, 75 UpdateFunc: func(_, o interface{}) { 76 epslUpdateCount.Inc() 77 e.enqueue(o) 78 }, 79 DeleteFunc: func(o interface{}) { 80 epslDeleteCount.Inc() 81 e.enqueue(o) 82 }, 83 }) 84 85 serviceUpdate := func(o interface{}) { 86 svc, err := convertToService(o) 87 if err != nil { 88 e.logger.WithError(err).Error("converting to Service object") 89 return 90 } 91 92 // TODO(brancz): use cache.Indexer to index endpoints by 93 // disv1beta1.LabelServiceName so this operation doesn't have to 94 // iterate over all endpoint objects. 95 for _, obj := range e.endpointSliceStore.List() { 96 ep := obj.(*disv1beta1.EndpointSlice) 97 if lv, exists := ep.Labels[disv1beta1.LabelServiceName]; exists && lv == svc.Name { 98 e.enqueue(ep) 99 } 100 } 101 } 102 e.serviceInf.AddEventHandler(cache.ResourceEventHandlerFuncs{ 103 AddFunc: func(o interface{}) { 104 svcAddCount.Inc() 105 serviceUpdate(o) 106 }, 107 UpdateFunc: func(_, o interface{}) { 108 svcUpdateCount.Inc() 109 serviceUpdate(o) 110 }, 111 DeleteFunc: func(o interface{}) { 112 svcDeleteCount.Inc() 113 serviceUpdate(o) 114 }, 115 }) 116 117 return e 118 } 119 120 func (e *EndpointSlice) enqueue(obj interface{}) { 121 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) 122 if err != nil { 123 return 124 } 125 126 e.queue.Add(key) 127 } 128 129 // Run implements the Discoverer interface. 130 func (e *EndpointSlice) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { 131 defer e.queue.ShutDown() 132 133 if !cache.WaitForCacheSync(ctx.Done(), e.endpointSliceInf.HasSynced, e.serviceInf.HasSynced, e.podInf.HasSynced) { 134 if ctx.Err() != context.Canceled { 135 e.logger.Error("endpointslice informer unable to sync cache") 136 } 137 return 138 } 139 140 go func() { 141 for { 142 if !e.process(ctx, ch) { 143 return 144 } 145 } 146 }() 147 148 // Block until the target provider is explicitly canceled. 149 <-ctx.Done() 150 } 151 152 func (e *EndpointSlice) process(ctx context.Context, ch chan<- []*targetgroup.Group) bool { 153 keyObj, quit := e.queue.Get() 154 if quit { 155 return false 156 } 157 defer e.queue.Done(keyObj) 158 key := keyObj.(string) 159 logger := e.logger.WithField("key", key) 160 161 namespace, name, err := cache.SplitMetaNamespaceKey(key) 162 if err != nil { 163 logger.WithError(err).Error("splitting key") 164 return true 165 } 166 167 o, exists, err := e.endpointSliceStore.GetByKey(key) 168 if err != nil { 169 logger.WithError(err).Error("getting object from store") 170 return true 171 } 172 if !exists { 173 send(ctx, ch, &targetgroup.Group{Source: endpointSliceSourceFromNamespaceAndName(namespace, name)}) 174 return true 175 } 176 eps, err := convertToEndpointSlice(o) 177 if err != nil { 178 logger.WithError(err).Error("converting to EndpointSlice object") 179 return true 180 } 181 send(ctx, ch, e.buildEndpointSlice(eps)) 182 return true 183 } 184 185 func convertToEndpointSlice(o interface{}) (*disv1beta1.EndpointSlice, error) { 186 endpoints, ok := o.(*disv1beta1.EndpointSlice) 187 if ok { 188 return endpoints, nil 189 } 190 191 return nil, fmt.Errorf("received unexpected object: %v", o) 192 } 193 194 func endpointSliceSource(ep *disv1beta1.EndpointSlice) string { 195 return endpointSliceSourceFromNamespaceAndName(ep.Namespace, ep.Name) 196 } 197 198 func endpointSliceSourceFromNamespaceAndName(namespace, name string) string { 199 return "endpointslice/" + namespace + "/" + name 200 } 201 202 const ( 203 endpointSliceNameLabel = metaLabelPrefix + "endpointslice_name" 204 endpointSliceAddressTypeLabel = metaLabelPrefix + "endpointslice_address_type" 205 endpointSlicePortNameLabel = metaLabelPrefix + "endpointslice_port_name" 206 endpointSlicePortProtocolLabel = metaLabelPrefix + "endpointslice_port_protocol" 207 endpointSlicePortLabel = metaLabelPrefix + "endpointslice_port" 208 endpointSlicePortAppProtocol = metaLabelPrefix + "endpointslice_port_app_protocol" 209 endpointSliceEndpointConditionsReadyLabel = metaLabelPrefix + "endpointslice_endpoint_conditions_ready" 210 endpointSliceEndpointHostnameLabel = metaLabelPrefix + "endpointslice_endpoint_hostname" 211 endpointSliceAddressTargetKindLabel = metaLabelPrefix + "endpointslice_address_target_kind" 212 endpointSliceAddressTargetNameLabel = metaLabelPrefix + "endpointslice_address_target_name" 213 endpointSliceEndpointTopologyLabelPrefix = metaLabelPrefix + "endpointslice_endpoint_topology_" 214 endpointSliceEndpointTopologyLabelPresentPrefix = metaLabelPrefix + "endpointslice_endpoint_topology_present_" 215 ) 216 217 func (e *EndpointSlice) buildEndpointSlice(eps *disv1beta1.EndpointSlice) *targetgroup.Group { 218 tg := &targetgroup.Group{ 219 Source: endpointSliceSource(eps), 220 } 221 tg.Labels = model.LabelSet{ 222 namespaceLabel: lv(eps.Namespace), 223 endpointSliceNameLabel: lv(eps.Name), 224 endpointSliceAddressTypeLabel: lv(string(eps.AddressType)), 225 } 226 e.addServiceLabels(eps, tg) 227 228 type podEntry struct { 229 pod *apiv1.Pod 230 servicePorts []disv1beta1.EndpointPort 231 } 232 seenPods := map[string]*podEntry{} 233 234 add := func(addr string, ep disv1beta1.Endpoint, port disv1beta1.EndpointPort) { 235 a := addr 236 if port.Port != nil { 237 a = net.JoinHostPort(addr, strconv.FormatUint(uint64(*port.Port), 10)) 238 } 239 240 target := model.LabelSet{ 241 model.AddressLabel: lv(a), 242 } 243 244 if port.Name != nil { 245 target[endpointSlicePortNameLabel] = lv(*port.Name) 246 } 247 248 if port.Protocol != nil { 249 target[endpointSlicePortProtocolLabel] = lv(string(*port.Protocol)) 250 } 251 252 if port.Port != nil { 253 target[endpointSlicePortLabel] = lv(strconv.FormatUint(uint64(*port.Port), 10)) 254 } 255 256 if port.AppProtocol != nil { 257 target[endpointSlicePortAppProtocol] = lv(*port.AppProtocol) 258 } 259 260 if ep.Conditions.Ready != nil { 261 target[endpointSliceEndpointConditionsReadyLabel] = lv(strconv.FormatBool(*ep.Conditions.Ready)) 262 } 263 264 if ep.Hostname != nil { 265 target[endpointSliceEndpointHostnameLabel] = lv(*ep.Hostname) 266 } 267 268 if ep.TargetRef != nil { 269 target[model.LabelName(endpointSliceAddressTargetKindLabel)] = lv(ep.TargetRef.Kind) 270 target[model.LabelName(endpointSliceAddressTargetNameLabel)] = lv(ep.TargetRef.Name) 271 } 272 273 for k, v := range ep.Topology { 274 ln := sanitizeLabelName(k) 275 target[model.LabelName(endpointSliceEndpointTopologyLabelPrefix+ln)] = lv(v) 276 target[model.LabelName(endpointSliceEndpointTopologyLabelPresentPrefix+ln)] = presentValue 277 } 278 279 pod := e.resolvePodRef(ep.TargetRef) 280 if pod == nil { 281 // This target is not a Pod, so don't continue with Pod specific logic. 282 tg.Targets = append(tg.Targets, target) 283 return 284 } 285 s := pod.Namespace + "/" + pod.Name 286 287 sp, ok := seenPods[s] 288 if !ok { 289 sp = &podEntry{pod: pod} 290 seenPods[s] = sp 291 } 292 293 // Attach standard pod labels. 294 target = target.Merge(podLabels(pod)) 295 296 // Attach potential container port labels matching the endpoint port. 297 for _, c := range pod.Spec.Containers { 298 for _, cport := range c.Ports { 299 if port.Port == nil { 300 continue 301 } 302 if *port.Port == cport.ContainerPort { 303 ports := strconv.FormatUint(uint64(*port.Port), 10) 304 305 target[podContainerNameLabel] = lv(c.Name) 306 target[podContainerPortNameLabel] = lv(cport.Name) 307 target[podContainerPortNumberLabel] = lv(ports) 308 target[podContainerPortProtocolLabel] = lv(string(cport.Protocol)) 309 break 310 } 311 } 312 } 313 314 // Add service port so we know that we have already generated a target 315 // for it. 316 sp.servicePorts = append(sp.servicePorts, port) 317 tg.Targets = append(tg.Targets, target) 318 } 319 320 for _, ep := range eps.Endpoints { 321 for _, port := range eps.Ports { 322 for _, addr := range ep.Addresses { 323 add(addr, ep, port) 324 } 325 } 326 } 327 328 // For all seen pods, check all container ports. If they were not covered 329 // by one of the service endpoints, generate targets for them. 330 for _, pe := range seenPods { 331 for _, c := range pe.pod.Spec.Containers { 332 for _, cport := range c.Ports { 333 hasSeenPort := func() bool { 334 for _, eport := range pe.servicePorts { 335 if eport.Port == nil { 336 continue 337 } 338 if cport.ContainerPort == *eport.Port { 339 return true 340 } 341 } 342 return false 343 } 344 if hasSeenPort() { 345 continue 346 } 347 348 a := net.JoinHostPort(pe.pod.Status.PodIP, strconv.FormatUint(uint64(cport.ContainerPort), 10)) 349 ports := strconv.FormatUint(uint64(cport.ContainerPort), 10) 350 351 target := model.LabelSet{ 352 model.AddressLabel: lv(a), 353 podContainerNameLabel: lv(c.Name), 354 podContainerPortNameLabel: lv(cport.Name), 355 podContainerPortNumberLabel: lv(ports), 356 podContainerPortProtocolLabel: lv(string(cport.Protocol)), 357 } 358 tg.Targets = append(tg.Targets, target.Merge(podLabels(pe.pod))) 359 } 360 } 361 } 362 363 return tg 364 } 365 366 func (e *EndpointSlice) resolvePodRef(ref *apiv1.ObjectReference) *apiv1.Pod { 367 if ref == nil || ref.Kind != "Pod" { 368 return nil 369 } 370 p := &apiv1.Pod{} 371 p.Namespace = ref.Namespace 372 p.Name = ref.Name 373 374 obj, exists, err := e.podStore.Get(p) 375 if err != nil { 376 e.logger.WithError(err).Error("resolving pod ref") 377 return nil 378 } 379 if !exists { 380 return nil 381 } 382 return obj.(*apiv1.Pod) 383 } 384 385 func (e *EndpointSlice) addServiceLabels(eps *disv1beta1.EndpointSlice, tg *targetgroup.Group) { 386 var ( 387 svc = &apiv1.Service{} 388 found bool 389 ) 390 svc.Namespace = eps.Namespace 391 392 // Every EndpointSlice object has the Service they belong to in the 393 // kubernetes.io/service-name label. 394 svc.Name, found = eps.Labels[disv1beta1.LabelServiceName] 395 if !found { 396 return 397 } 398 399 obj, exists, err := e.serviceStore.Get(svc) 400 if err != nil { 401 e.logger.WithError(err).Error("retrieving service") 402 return 403 } 404 if !exists { 405 return 406 } 407 svc = obj.(*apiv1.Service) 408 409 tg.Labels = tg.Labels.Merge(serviceLabels(svc)) 410 }