github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/discovery/kubernetes/ingress.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 "strings" 22 23 "github.com/sirupsen/logrus" 24 v1 "k8s.io/api/networking/v1" 25 "k8s.io/api/networking/v1beta1" 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 ingressAddCount = eventCount.WithLabelValues("ingress", "add") 35 ingressUpdateCount = eventCount.WithLabelValues("ingress", "update") 36 ingressDeleteCount = eventCount.WithLabelValues("ingress", "delete") 37 ) 38 39 // Ingress implements discovery of Kubernetes ingress. 40 type Ingress struct { 41 logger logrus.FieldLogger 42 informer cache.SharedInformer 43 store cache.Store 44 queue *workqueue.Type 45 } 46 47 // NewIngress returns a new ingress discovery. 48 func NewIngress(l logrus.FieldLogger, inf cache.SharedInformer) *Ingress { 49 s := &Ingress{logger: l, informer: inf, store: inf.GetStore(), queue: workqueue.NewNamed("ingress")} 50 s.informer.AddEventHandler(cache.ResourceEventHandlerFuncs{ 51 AddFunc: func(o interface{}) { 52 ingressAddCount.Inc() 53 s.enqueue(o) 54 }, 55 DeleteFunc: func(o interface{}) { 56 ingressDeleteCount.Inc() 57 s.enqueue(o) 58 }, 59 UpdateFunc: func(_, o interface{}) { 60 ingressUpdateCount.Inc() 61 s.enqueue(o) 62 }, 63 }) 64 return s 65 } 66 67 func (i *Ingress) enqueue(obj interface{}) { 68 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) 69 if err != nil { 70 return 71 } 72 73 i.queue.Add(key) 74 } 75 76 // Run implements the Discoverer interface. 77 func (i *Ingress) Run(ctx context.Context, ch chan<- []*targetgroup.Group) { 78 defer i.queue.ShutDown() 79 80 if !cache.WaitForCacheSync(ctx.Done(), i.informer.HasSynced) { 81 if ctx.Err() != context.Canceled { 82 i.logger.Error("ingress informer unable to sync cache") 83 } 84 return 85 } 86 87 go func() { 88 for { 89 if !i.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 (i *Ingress) process(ctx context.Context, ch chan<- []*targetgroup.Group) bool { 100 keyObj, quit := i.queue.Get() 101 if quit { 102 return false 103 } 104 defer i.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 := i.store.GetByKey(key) 113 if err != nil { 114 return true 115 } 116 if !exists { 117 send(ctx, ch, &targetgroup.Group{Source: ingressSourceFromNamespaceAndName(namespace, name)}) 118 return true 119 } 120 121 var ia ingressAdaptor 122 switch ingress := o.(type) { 123 case *v1.Ingress: 124 ia = newIngressAdaptorFromV1(ingress) 125 case *v1beta1.Ingress: 126 ia = newIngressAdaptorFromV1beta1(ingress) 127 default: 128 i.logger.WithError(fmt.Errorf("received unexpected object: %v", o)). 129 Error("converting to Ingress object") 130 return true 131 } 132 send(ctx, ch, i.buildIngress(ia)) 133 return true 134 } 135 136 func ingressSource(s ingressAdaptor) string { 137 return ingressSourceFromNamespaceAndName(s.namespace(), s.name()) 138 } 139 140 func ingressSourceFromNamespaceAndName(namespace, name string) string { 141 return "ingress/" + namespace + "/" + name 142 } 143 144 const ( 145 ingressNameLabel = metaLabelPrefix + "ingress_name" 146 ingressLabelPrefix = metaLabelPrefix + "ingress_label_" 147 ingressLabelPresentPrefix = metaLabelPrefix + "ingress_labelpresent_" 148 ingressAnnotationPrefix = metaLabelPrefix + "ingress_annotation_" 149 ingressAnnotationPresentPrefix = metaLabelPrefix + "ingress_annotationpresent_" 150 ingressSchemeLabel = metaLabelPrefix + "ingress_scheme" 151 ingressHostLabel = metaLabelPrefix + "ingress_host" 152 ingressPathLabel = metaLabelPrefix + "ingress_path" 153 ingressClassNameLabel = metaLabelPrefix + "ingress_class_name" 154 ) 155 156 func ingressLabels(ingress ingressAdaptor) model.LabelSet { 157 // Each label and annotation will create two key-value pairs in the map. 158 ls := make(model.LabelSet, 2*(len(ingress.labels())+len(ingress.annotations()))+2) 159 ls[ingressNameLabel] = lv(ingress.name()) 160 ls[namespaceLabel] = lv(ingress.namespace()) 161 if cls := ingress.ingressClassName(); cls != nil { 162 ls[ingressClassNameLabel] = lv(*cls) 163 } 164 165 for k, v := range ingress.labels() { 166 ln := sanitizeLabelName(k) 167 ls[model.LabelName(ingressLabelPrefix+ln)] = lv(v) 168 ls[model.LabelName(ingressLabelPresentPrefix+ln)] = presentValue 169 } 170 171 for k, v := range ingress.annotations() { 172 ln := sanitizeLabelName(k) 173 ls[model.LabelName(ingressAnnotationPrefix+ln)] = lv(v) 174 ls[model.LabelName(ingressAnnotationPresentPrefix+ln)] = presentValue 175 } 176 return ls 177 } 178 179 func pathsFromIngressPaths(ingressPaths []string) []string { 180 if ingressPaths == nil { 181 return []string{"/"} 182 } 183 paths := make([]string, len(ingressPaths)) 184 for n, p := range ingressPaths { 185 path := p 186 if p == "" { 187 path = "/" 188 } 189 paths[n] = path 190 } 191 return paths 192 } 193 194 func (*Ingress) buildIngress(ingress ingressAdaptor) *targetgroup.Group { 195 tg := &targetgroup.Group{ 196 Source: ingressSource(ingress), 197 } 198 tg.Labels = ingressLabels(ingress) 199 200 for _, rule := range ingress.rules() { 201 scheme := "http" 202 paths := pathsFromIngressPaths(rule.paths()) 203 204 out: 205 for _, pattern := range ingress.tlsHosts() { 206 if matchesHostnamePattern(pattern, rule.host()) { 207 scheme = "https" 208 break out 209 } 210 } 211 212 for _, path := range paths { 213 tg.Targets = append(tg.Targets, model.LabelSet{ 214 model.AddressLabel: lv(rule.host()), 215 ingressSchemeLabel: lv(scheme), 216 ingressHostLabel: lv(rule.host()), 217 ingressPathLabel: lv(path), 218 }) 219 } 220 } 221 222 return tg 223 } 224 225 // matchesHostnamePattern returns true if the host matches a wildcard DNS 226 // pattern or pattern and host are equal. 227 func matchesHostnamePattern(pattern, host string) bool { 228 if pattern == host { 229 return true 230 } 231 232 patternParts := strings.Split(pattern, ".") 233 hostParts := strings.Split(host, ".") 234 235 // If the first element of the pattern is not a wildcard, give up. 236 if len(patternParts) == 0 || patternParts[0] != "*" { 237 return false 238 } 239 240 // A wildcard match require the pattern to have the same length as the host 241 // path. 242 if len(patternParts) != len(hostParts) { 243 return false 244 } 245 246 for i := 1; i < len(patternParts); i++ { 247 if patternParts[i] != hostParts[i] { 248 return false 249 } 250 } 251 252 return true 253 }