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  }