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 }