github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/discovery/kubernetes/pod.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  	"strings"
    24  
    25  	"github.com/sirupsen/logrus"
    26  	apiv1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/client-go/tools/cache"
    29  	"k8s.io/client-go/util/workqueue"
    30  
    31  	"github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/targetgroup"
    32  	"github.com/pyroscope-io/pyroscope/pkg/scrape/model"
    33  )
    34  
    35  var (
    36  	podAddCount    = eventCount.WithLabelValues("pod", "add")
    37  	podUpdateCount = eventCount.WithLabelValues("pod", "update")
    38  	podDeleteCount = eventCount.WithLabelValues("pod", "delete")
    39  )
    40  
    41  // Pod discovers new pod targets.
    42  type Pod struct {
    43  	informer cache.SharedInformer
    44  	store    cache.Store
    45  	logger   logrus.FieldLogger
    46  	queue    *workqueue.Type
    47  }
    48  
    49  // NewPod creates a new pod discovery.
    50  func NewPod(l logrus.FieldLogger, pods cache.SharedInformer) *Pod {
    51  	p := &Pod{
    52  		informer: pods,
    53  		store:    pods.GetStore(),
    54  		logger:   l,
    55  		queue:    workqueue.NewNamed("pod"),
    56  	}
    57  	p.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
    58  		AddFunc: func(o interface{}) {
    59  			podAddCount.Inc()
    60  			p.enqueue(o)
    61  		},
    62  		DeleteFunc: func(o interface{}) {
    63  			podDeleteCount.Inc()
    64  			p.enqueue(o)
    65  		},
    66  		UpdateFunc: func(_, o interface{}) {
    67  			podUpdateCount.Inc()
    68  			p.enqueue(o)
    69  		},
    70  	})
    71  	return p
    72  }
    73  
    74  func (p *Pod) enqueue(obj interface{}) {
    75  	key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
    76  	if err != nil {
    77  		return
    78  	}
    79  
    80  	p.queue.Add(key)
    81  }
    82  
    83  // Run implements the Discoverer interface.
    84  func (p *Pod) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
    85  	defer p.queue.ShutDown()
    86  
    87  	if !cache.WaitForCacheSync(ctx.Done(), p.informer.HasSynced) {
    88  		if ctx.Err() != context.Canceled {
    89  			p.logger.Error("pod informer unable to sync cache")
    90  		}
    91  		return
    92  	}
    93  
    94  	go func() {
    95  		for {
    96  			if !p.process(ctx, ch) {
    97  				return
    98  			}
    99  		}
   100  	}()
   101  
   102  	// Block until the target provider is explicitly canceled.
   103  	<-ctx.Done()
   104  }
   105  
   106  func (p *Pod) process(ctx context.Context, ch chan<- []*targetgroup.Group) bool {
   107  	keyObj, quit := p.queue.Get()
   108  	if quit {
   109  		return false
   110  	}
   111  	defer p.queue.Done(keyObj)
   112  	key := keyObj.(string)
   113  
   114  	namespace, name, err := cache.SplitMetaNamespaceKey(key)
   115  	if err != nil {
   116  		return true
   117  	}
   118  
   119  	o, exists, err := p.store.GetByKey(key)
   120  	if err != nil {
   121  		return true
   122  	}
   123  	if !exists {
   124  		send(ctx, ch, &targetgroup.Group{Source: podSourceFromNamespaceAndName(namespace, name)})
   125  		return true
   126  	}
   127  	pod, err := convertToPod(o)
   128  	if err != nil {
   129  		p.logger.WithError(err).Error("converting to Pod object failed")
   130  		return true
   131  	}
   132  	send(ctx, ch, p.buildPod(pod))
   133  	return true
   134  }
   135  
   136  func convertToPod(o interface{}) (*apiv1.Pod, error) {
   137  	pod, ok := o.(*apiv1.Pod)
   138  	if ok {
   139  		return pod, nil
   140  	}
   141  
   142  	return nil, fmt.Errorf("received unexpected object: %v", o)
   143  }
   144  
   145  const (
   146  	podNameLabel                  = metaLabelPrefix + "pod_name"
   147  	podIPLabel                    = metaLabelPrefix + "pod_ip"
   148  	podContainerNameLabel         = metaLabelPrefix + "pod_container_name"
   149  	podContainerPortNameLabel     = metaLabelPrefix + "pod_container_port_name"
   150  	podContainerPortNumberLabel   = metaLabelPrefix + "pod_container_port_number"
   151  	podContainerPortProtocolLabel = metaLabelPrefix + "pod_container_port_protocol"
   152  	podContainerIsInit            = metaLabelPrefix + "pod_container_init"
   153  	podReadyLabel                 = metaLabelPrefix + "pod_ready"
   154  	podPhaseLabel                 = metaLabelPrefix + "pod_phase"
   155  	podLabelPrefix                = metaLabelPrefix + "pod_label_"
   156  	podLabelPresentPrefix         = metaLabelPrefix + "pod_labelpresent_"
   157  	podAnnotationPrefix           = metaLabelPrefix + "pod_annotation_"
   158  	podAnnotationPresentPrefix    = metaLabelPrefix + "pod_annotationpresent_"
   159  	podNodeNameLabel              = metaLabelPrefix + "pod_node_name"
   160  	podHostIPLabel                = metaLabelPrefix + "pod_host_ip"
   161  	podUID                        = metaLabelPrefix + "pod_uid"
   162  	podControllerKind             = metaLabelPrefix + "pod_controller_kind"
   163  	podControllerName             = metaLabelPrefix + "pod_controller_name"
   164  )
   165  
   166  // GetControllerOf returns a pointer to a copy of the controllerRef if controllee has a controller
   167  // https://github.com/kubernetes/apimachinery/blob/cd2cae2b39fa57e8063fa1f5f13cfe9862db3d41/pkg/apis/meta/v1/controller_ref.go
   168  func GetControllerOf(controllee metav1.Object) *metav1.OwnerReference {
   169  	for _, ref := range controllee.GetOwnerReferences() {
   170  		if ref.Controller != nil && *ref.Controller {
   171  			return &ref
   172  		}
   173  	}
   174  	return nil
   175  }
   176  
   177  func podLabels(pod *apiv1.Pod) model.LabelSet {
   178  	ls := model.LabelSet{
   179  		podNameLabel:     lv(pod.ObjectMeta.Name),
   180  		podIPLabel:       lv(pod.Status.PodIP),
   181  		podReadyLabel:    podReady(pod),
   182  		podPhaseLabel:    lv(string(pod.Status.Phase)),
   183  		podNodeNameLabel: lv(pod.Spec.NodeName),
   184  		podHostIPLabel:   lv(pod.Status.HostIP),
   185  		podUID:           lv(string(pod.ObjectMeta.UID)),
   186  	}
   187  
   188  	createdBy := GetControllerOf(pod)
   189  	if createdBy != nil {
   190  		if createdBy.Kind != "" {
   191  			ls[podControllerKind] = lv(createdBy.Kind)
   192  		}
   193  		if createdBy.Name != "" {
   194  			ls[podControllerName] = lv(createdBy.Name)
   195  		}
   196  	}
   197  
   198  	for k, v := range pod.Labels {
   199  		ln := sanitizeLabelName(k)
   200  		ls[model.LabelName(podLabelPrefix+ln)] = lv(v)
   201  		ls[model.LabelName(podLabelPresentPrefix+ln)] = presentValue
   202  	}
   203  
   204  	for k, v := range pod.Annotations {
   205  		ln := sanitizeLabelName(k)
   206  		ls[model.LabelName(podAnnotationPrefix+ln)] = lv(v)
   207  		ls[model.LabelName(podAnnotationPresentPrefix+ln)] = presentValue
   208  	}
   209  
   210  	return ls
   211  }
   212  
   213  func (*Pod) buildPod(pod *apiv1.Pod) *targetgroup.Group {
   214  	tg := &targetgroup.Group{
   215  		Source: podSource(pod),
   216  	}
   217  	// PodIP can be empty when a pod is starting or has been evicted.
   218  	if len(pod.Status.PodIP) == 0 {
   219  		return tg
   220  	}
   221  
   222  	tg.Labels = podLabels(pod)
   223  	tg.Labels[namespaceLabel] = lv(pod.Namespace)
   224  
   225  	containers := append(pod.Spec.Containers, pod.Spec.InitContainers...)
   226  	for i, c := range containers {
   227  		isInit := i >= len(pod.Spec.Containers)
   228  
   229  		// If no ports are defined for the container, create an anonymous
   230  		// target per container.
   231  		if len(c.Ports) == 0 {
   232  			// We don't have a port so we just set the address label to the pod IP.
   233  			// The user has to add a port manually.
   234  			tg.Targets = append(tg.Targets, model.LabelSet{
   235  				model.AddressLabel:    lv(pod.Status.PodIP),
   236  				podContainerNameLabel: lv(c.Name),
   237  				podContainerIsInit:    lv(strconv.FormatBool(isInit)),
   238  			})
   239  			continue
   240  		}
   241  		// Otherwise create one target for each container/port combination.
   242  		for _, port := range c.Ports {
   243  			ports := strconv.FormatUint(uint64(port.ContainerPort), 10)
   244  			addr := net.JoinHostPort(pod.Status.PodIP, ports)
   245  
   246  			tg.Targets = append(tg.Targets, model.LabelSet{
   247  				model.AddressLabel:            lv(addr),
   248  				podContainerNameLabel:         lv(c.Name),
   249  				podContainerPortNumberLabel:   lv(ports),
   250  				podContainerPortNameLabel:     lv(port.Name),
   251  				podContainerPortProtocolLabel: lv(string(port.Protocol)),
   252  				podContainerIsInit:            lv(strconv.FormatBool(isInit)),
   253  			})
   254  		}
   255  	}
   256  
   257  	return tg
   258  }
   259  
   260  func podSource(pod *apiv1.Pod) string {
   261  	return podSourceFromNamespaceAndName(pod.Namespace, pod.Name)
   262  }
   263  
   264  func podSourceFromNamespaceAndName(namespace, name string) string {
   265  	return "pod/" + namespace + "/" + name
   266  }
   267  
   268  func podReady(pod *apiv1.Pod) model.LabelValue {
   269  	for _, cond := range pod.Status.Conditions {
   270  		if cond.Type == apiv1.PodReady {
   271  			return lv(strings.ToLower(string(cond.Status)))
   272  		}
   273  	}
   274  	return lv(strings.ToLower(string(apiv1.ConditionUnknown)))
   275  }