github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/discovery/kubernetes/node.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  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"strconv"
    24  
    25  	"github.com/sirupsen/logrus"
    26  	apiv1 "k8s.io/api/core/v1"
    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  const (
    35  	NodeLegacyHostIP = "LegacyHostIP"
    36  )
    37  
    38  var (
    39  	nodeAddCount    = eventCount.WithLabelValues("node", "add")
    40  	nodeUpdateCount = eventCount.WithLabelValues("node", "update")
    41  	nodeDeleteCount = eventCount.WithLabelValues("node", "delete")
    42  )
    43  
    44  // Node discovers Kubernetes nodes.
    45  type Node struct {
    46  	logger   logrus.FieldLogger
    47  	informer cache.SharedInformer
    48  	store    cache.Store
    49  	queue    *workqueue.Type
    50  }
    51  
    52  // NewNode returns a new node discovery.
    53  func NewNode(l logrus.FieldLogger, inf cache.SharedInformer) *Node {
    54  	n := &Node{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("node")}
    55  	n.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    56  		AddFunc: func(o interface{}) {
    57  			nodeAddCount.Inc()
    58  			n.enqueue(o)
    59  		},
    60  		DeleteFunc: func(o interface{}) {
    61  			nodeDeleteCount.Inc()
    62  			n.enqueue(o)
    63  		},
    64  		UpdateFunc: func(_, o interface{}) {
    65  			nodeUpdateCount.Inc()
    66  			n.enqueue(o)
    67  		},
    68  	})
    69  	return n
    70  }
    71  
    72  func (n *Node) enqueue(obj interface{}) {
    73  	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
    74  	if err != nil {
    75  		return
    76  	}
    77  
    78  	n.queue.Add(key)
    79  }
    80  
    81  // Run implements the Discoverer interface.
    82  func (n *Node) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
    83  	defer n.queue.ShutDown()
    84  
    85  	if !cache.WaitForCacheSync(ctx.Done(), n.informer.HasSynced) {
    86  		if ctx.Err() != context.Canceled {
    87  			n.logger.Error("node informer unable to sync cache")
    88  		}
    89  		return
    90  	}
    91  
    92  	go func() {
    93  		for {
    94  			if !n.process(ctx, ch) {
    95  				return
    96  			}
    97  		}
    98  	}()
    99  
   100  	// Block until the target provider is explicitly canceled.
   101  	<-ctx.Done()
   102  }
   103  
   104  func (n *Node) process(ctx context.Context, ch chan<- []*targetgroup.Group) bool {
   105  	keyObj, quit := n.queue.Get()
   106  	if quit {
   107  		return false
   108  	}
   109  	defer n.queue.Done(keyObj)
   110  	key := keyObj.(string)
   111  
   112  	_, name, err := cache.SplitMetaNamespaceKey(key)
   113  	if err != nil {
   114  		return true
   115  	}
   116  
   117  	o, exists, err := n.store.GetByKey(key)
   118  	if err != nil {
   119  		return true
   120  	}
   121  	if !exists {
   122  		send(ctx, ch, &targetgroup.Group{Source: nodeSourceFromName(name)})
   123  		return true
   124  	}
   125  	node, err := convertToNode(o)
   126  	if err != nil {
   127  		n.logger.WithError(err).Error("converting to Node object")
   128  		return true
   129  	}
   130  	send(ctx, ch, n.buildNode(node))
   131  	return true
   132  }
   133  
   134  func convertToNode(o interface{}) (*apiv1.Node, error) {
   135  	node, ok := o.(*apiv1.Node)
   136  	if ok {
   137  		return node, nil
   138  	}
   139  
   140  	return nil, fmt.Errorf("received unexpected object: %v", o)
   141  }
   142  
   143  func nodeSource(n *apiv1.Node) string {
   144  	return nodeSourceFromName(n.Name)
   145  }
   146  
   147  func nodeSourceFromName(name string) string {
   148  	return "node/" + name
   149  }
   150  
   151  const (
   152  	nodeNameLabel               = metaLabelPrefix + "node_name"
   153  	nodeLabelPrefix             = metaLabelPrefix + "node_label_"
   154  	nodeLabelPresentPrefix      = metaLabelPrefix + "node_labelpresent_"
   155  	nodeAnnotationPrefix        = metaLabelPrefix + "node_annotation_"
   156  	nodeAnnotationPresentPrefix = metaLabelPrefix + "node_annotationpresent_"
   157  	nodeAddressPrefix           = metaLabelPrefix + "node_address_"
   158  )
   159  
   160  func nodeLabels(n *apiv1.Node) model.LabelSet {
   161  	// Each label and annotation will create two key-value pairs in the map.
   162  	ls := make(model.LabelSet, 2*(len(n.Labels)+len(n.Annotations))+1)
   163  
   164  	ls[nodeNameLabel] = lv(n.Name)
   165  
   166  	for k, v := range n.Labels {
   167  		ln := sanitizeLabelName(k)
   168  		ls[model.LabelName(nodeLabelPrefix+ln)] = lv(v)
   169  		ls[model.LabelName(nodeLabelPresentPrefix+ln)] = presentValue
   170  	}
   171  
   172  	for k, v := range n.Annotations {
   173  		ln := sanitizeLabelName(k)
   174  		ls[model.LabelName(nodeAnnotationPrefix+ln)] = lv(v)
   175  		ls[model.LabelName(nodeAnnotationPresentPrefix+ln)] = presentValue
   176  	}
   177  	return ls
   178  }
   179  
   180  func (n *Node) buildNode(node *apiv1.Node) *targetgroup.Group {
   181  	tg := &targetgroup.Group{
   182  		Source: nodeSource(node),
   183  	}
   184  	tg.Labels = nodeLabels(node)
   185  
   186  	addr, addrMap, err := nodeAddress(node)
   187  	if err != nil {
   188  		n.logger.WithError(err).Warn("no node address found")
   189  		return nil
   190  	}
   191  	addr = net.JoinHostPort(addr, strconv.FormatInt(int64(node.Status.DaemonEndpoints.KubeletEndpoint.Port), 10))
   192  
   193  	t := model.LabelSet{
   194  		model.AddressLabel:  lv(addr),
   195  		model.InstanceLabel: lv(node.Name),
   196  	}
   197  
   198  	for ty, a := range addrMap {
   199  		ln := sanitizeLabelName(nodeAddressPrefix + string(ty))
   200  		t[model.LabelName(ln)] = lv(a[0])
   201  	}
   202  	tg.Targets = append(tg.Targets, t)
   203  
   204  	return tg
   205  }
   206  
   207  // nodeAddresses returns the provided node's address, based on the priority:
   208  // 1. NodeInternalIP
   209  // 2. NodeInternalDNS
   210  // 3. NodeExternalIP
   211  // 4. NodeExternalDNS
   212  // 5. NodeLegacyHostIP
   213  // 6. NodeHostName
   214  //
   215  // Derived from k8s.io/kubernetes/pkg/util/node/node.go
   216  func nodeAddress(node *apiv1.Node) (string, map[apiv1.NodeAddressType][]string, error) {
   217  	m := map[apiv1.NodeAddressType][]string{}
   218  	for _, a := range node.Status.Addresses {
   219  		m[a.Type] = append(m[a.Type], a.Address)
   220  	}
   221  
   222  	if addresses, ok := m[apiv1.NodeInternalIP]; ok {
   223  		return addresses[0], m, nil
   224  	}
   225  	if addresses, ok := m[apiv1.NodeInternalDNS]; ok {
   226  		return addresses[0], m, nil
   227  	}
   228  	if addresses, ok := m[apiv1.NodeExternalIP]; ok {
   229  		return addresses[0], m, nil
   230  	}
   231  	if addresses, ok := m[apiv1.NodeExternalDNS]; ok {
   232  		return addresses[0], m, nil
   233  	}
   234  	if addresses, ok := m[apiv1.NodeAddressType(NodeLegacyHostIP)]; ok {
   235  		return addresses[0], m, nil
   236  	}
   237  	if addresses, ok := m[apiv1.NodeHostName]; ok {
   238  		return addresses[0], m, nil
   239  	}
   240  	return "", m, errors.New("host address unknown")
   241  }