github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/discovery/kubernetes/service.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 var ( 34 svcAddCount = eventCount.WithLabelValues("service", "add") 35 svcUpdateCount = eventCount.WithLabelValues("service", "update") 36 svcDeleteCount = eventCount.WithLabelValues("service", "delete") 37 ) 38 39 // Service implements discovery of Kubernetes services. 40 type Service struct { 41 logger logrus.FieldLogger 42 informer cache.SharedInformer 43 store cache.Store 44 queue *workqueue.Type 45 } 46 47 // NewService returns a new service discovery. 48 func NewService(l logrus.FieldLogger, inf cache.SharedInformer) *Service { 49 s := &Service{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("service")} 50 s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 51 AddFunc: func(o interface{}) { 52 svcAddCount.Inc() 53 s.enqueue(o) 54 }, 55 DeleteFunc: func(o interface{}) { 56 svcDeleteCount.Inc() 57 s.enqueue(o) 58 }, 59 UpdateFunc: func(_, o interface{}) { 60 svcUpdateCount.Inc() 61 s.enqueue(o) 62 }, 63 }) 64 return s 65 } 66 67 func (s *Service) enqueue(obj interface{}) { 68 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) 69 if err != nil { 70 return 71 } 72 73 s.queue.Add(key) 74 } 75 76 // Run implements the Discoverer interface. 77 func (s *Service) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { 78 defer s.queue.ShutDown() 79 80 if !cache.WaitForCacheSync(ctx.Done(), s.informer.HasSynced) { 81 if ctx.Err() != context.Canceled { 82 s.logger.Error("service informer unable to sync cache") 83 } 84 return 85 } 86 87 go func() { 88 for { 89 if !s.process(ctx, ch) { 90 return 91 } 92 } 93 }() 94 95 // Block until the target provider is explicitly canceled. 96 <-ctx.Done() 97 } 98 99 func (s *Service) process(ctx context.Context, ch chan<- []*targetgroup.Group) bool { 100 keyObj, quit := s.queue.Get() 101 if quit { 102 return false 103 } 104 defer s.queue.Done(keyObj) 105 key := keyObj.(string) 106 107 namespace, name, err := cache.SplitMetaNamespaceKey(key) 108 if err != nil { 109 return true 110 } 111 112 o, exists, err := s.store.GetByKey(key) 113 if err != nil { 114 return true 115 } 116 if !exists { 117 send(ctx, ch, &targetgroup.Group{Source: serviceSourceFromNamespaceAndName(namespace, name)}) 118 return true 119 } 120 eps, err := convertToService(o) 121 if err != nil { 122 s.logger.WithError(err).Error("converting to Service object") 123 return true 124 } 125 send(ctx, ch, s.buildService(eps)) 126 return true 127 } 128 129 func convertToService(o interface{}) (*apiv1.Service, error) { 130 service, ok := o.(*apiv1.Service) 131 if ok { 132 return service, nil 133 } 134 return nil, fmt.Errorf("received unexpected object: %v", o) 135 } 136 137 func serviceSource(s *apiv1.Service) string { 138 return serviceSourceFromNamespaceAndName(s.Namespace, s.Name) 139 } 140 141 func serviceSourceFromNamespaceAndName(namespace, name string) string { 142 return "svc/" + namespace + "/" + name 143 } 144 145 const ( 146 serviceNameLabel = metaLabelPrefix + "service_name" 147 serviceLabelPrefix = metaLabelPrefix + "service_label_" 148 serviceLabelPresentPrefix = metaLabelPrefix + "service_labelpresent_" 149 serviceAnnotationPrefix = metaLabelPrefix + "service_annotation_" 150 serviceAnnotationPresentPrefix = metaLabelPrefix + "service_annotationpresent_" 151 servicePortNameLabel = metaLabelPrefix + "service_port_name" 152 servicePortProtocolLabel = metaLabelPrefix + "service_port_protocol" 153 serviceClusterIPLabel = metaLabelPrefix + "service_cluster_ip" 154 serviceExternalNameLabel = metaLabelPrefix + "service_external_name" 155 serviceType = metaLabelPrefix + "service_type" 156 ) 157 158 func serviceLabels(svc *apiv1.Service) model.LabelSet { 159 // Each label and annotation will create two key-value pairs in the map. 160 ls := make(model.LabelSet, 2*(len(svc.Labels)+len(svc.Annotations))+2) 161 162 ls[serviceNameLabel] = lv(svc.Name) 163 ls[namespaceLabel] = lv(svc.Namespace) 164 165 for k, v := range svc.Labels { 166 ln := sanitizeLabelName(k) 167 ls[model.LabelName(serviceLabelPrefix+ln)] = lv(v) 168 ls[model.LabelName(serviceLabelPresentPrefix+ln)] = presentValue 169 } 170 171 for k, v := range svc.Annotations { 172 ln := sanitizeLabelName(k) 173 ls[model.LabelName(serviceAnnotationPrefix+ln)] = lv(v) 174 ls[model.LabelName(serviceAnnotationPresentPrefix+ln)] = presentValue 175 } 176 return ls 177 } 178 179 func (*Service) buildService(svc *apiv1.Service) *targetgroup.Group { 180 tg := &targetgroup.Group{ 181 Source: serviceSource(svc), 182 } 183 tg.Labels = serviceLabels(svc) 184 185 for _, port := range svc.Spec.Ports { 186 addr := net.JoinHostPort(svc.Name+"."+svc.Namespace+".svc", strconv.FormatInt(int64(port.Port), 10)) 187 188 labelSet := model.LabelSet{ 189 model.AddressLabel: lv(addr), 190 servicePortNameLabel: lv(port.Name), 191 servicePortProtocolLabel: lv(string(port.Protocol)), 192 serviceType: lv(string(svc.Spec.Type)), 193 } 194 195 if svc.Spec.Type == apiv1.ServiceTypeExternalName { 196 labelSet[serviceExternalNameLabel] = lv(svc.Spec.ExternalName) 197 } else { 198 labelSet[serviceClusterIPLabel] = lv(svc.Spec.ClusterIP) 199 } 200 201 tg.Targets = append(tg.Targets, labelSet) 202 } 203 204 return tg 205 }