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 }