github.com/netdata/go.d.plugin@v0.58.1/agent/discovery/sd/kubernetes/kubernetes.go (about) 1 // SPDX-License-Identifier: GPL-3.0-or-later 2 3 package kubernetes 4 5 import ( 6 "context" 7 "fmt" 8 "log/slog" 9 "os" 10 "strings" 11 "sync" 12 "time" 13 14 "github.com/netdata/go.d.plugin/agent/discovery/sd/model" 15 "github.com/netdata/go.d.plugin/logger" 16 "github.com/netdata/go.d.plugin/pkg/k8sclient" 17 18 "github.com/ilyam8/hashstructure" 19 corev1 "k8s.io/api/core/v1" 20 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 "k8s.io/apimachinery/pkg/runtime" 22 "k8s.io/apimachinery/pkg/watch" 23 "k8s.io/client-go/kubernetes" 24 "k8s.io/client-go/tools/cache" 25 "k8s.io/client-go/util/workqueue" 26 ) 27 28 const ( 29 envNodeName = "MY_NODE_NAME" 30 ) 31 32 var log = logger.New().With( 33 slog.String("component", "discovery sd k8s"), 34 ) 35 36 func NewKubeDiscoverer(cfg Config) (*KubeDiscoverer, error) { 37 if err := validateConfig(cfg); err != nil { 38 return nil, fmt.Errorf("config validation: %v", err) 39 } 40 41 client, err := k8sclient.New("Netdata/service-td") 42 if err != nil { 43 return nil, fmt.Errorf("create clientset: %v", err) 44 } 45 46 ns := cfg.Namespaces 47 if len(ns) == 0 { 48 ns = []string{corev1.NamespaceAll} 49 } 50 51 d := &KubeDiscoverer{ 52 Logger: log, 53 namespaces: ns, 54 podConf: cfg.Pod, 55 svcConf: cfg.Service, 56 client: client, 57 discoverers: make([]model.Discoverer, 0, len(ns)), 58 started: make(chan struct{}), 59 } 60 61 return d, nil 62 } 63 64 type KubeDiscoverer struct { 65 *logger.Logger 66 67 podConf *PodConfig 68 svcConf *ServiceConfig 69 70 namespaces []string 71 client kubernetes.Interface 72 discoverers []model.Discoverer 73 started chan struct{} 74 } 75 76 func (d *KubeDiscoverer) String() string { 77 return "k8s td manager" 78 } 79 80 const resyncPeriod = 10 * time.Minute 81 82 func (d *KubeDiscoverer) Discover(ctx context.Context, in chan<- []model.TargetGroup) { 83 d.Info("instance is started") 84 defer d.Info("instance is stopped") 85 86 for _, namespace := range d.namespaces { 87 if err := d.setupPodDiscoverer(ctx, d.podConf, namespace); err != nil { 88 d.Errorf("create pod discoverer: %v", err) 89 return 90 } 91 if err := d.setupServiceDiscoverer(ctx, d.svcConf, namespace); err != nil { 92 d.Errorf("create service discoverer: %v", err) 93 return 94 } 95 } 96 97 if len(d.discoverers) == 0 { 98 d.Warning("no discoverers registered") 99 return 100 } 101 102 d.Infof("registered: %v", d.discoverers) 103 104 var wg sync.WaitGroup 105 updates := make(chan []model.TargetGroup) 106 107 for _, disc := range d.discoverers { 108 wg.Add(1) 109 go func(disc model.Discoverer) { defer wg.Done(); disc.Discover(ctx, updates) }(disc) 110 } 111 112 done := make(chan struct{}) 113 go func() { defer close(done); wg.Wait() }() 114 115 close(d.started) 116 117 for { 118 select { 119 case <-ctx.Done(): 120 select { 121 case <-done: 122 d.Info("all discoverers exited") 123 case <-time.After(time.Second * 5): 124 d.Warning("not all discoverers exited") 125 } 126 return 127 case <-done: 128 d.Info("all discoverers exited") 129 return 130 case tggs := <-updates: 131 select { 132 case <-ctx.Done(): 133 case in <- tggs: 134 } 135 } 136 } 137 } 138 139 func (d *KubeDiscoverer) setupPodDiscoverer(ctx context.Context, conf *PodConfig, namespace string) error { 140 if conf == nil { 141 return nil 142 } 143 144 if conf.LocalMode { 145 name := os.Getenv(envNodeName) 146 if name == "" { 147 return fmt.Errorf("local_mode is enabled, but env '%s' not set", envNodeName) 148 } 149 conf.Selector.Field = joinSelectors(conf.Selector.Field, "spec.nodeName="+name) 150 } 151 152 tags, err := model.ParseTags(conf.Tags) 153 if err != nil { 154 return fmt.Errorf("parse tags: %v", err) 155 } 156 157 pod := d.client.CoreV1().Pods(namespace) 158 podLW := &cache.ListWatch{ 159 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 160 options.FieldSelector = conf.Selector.Field 161 options.LabelSelector = conf.Selector.Label 162 return pod.List(ctx, options) 163 }, 164 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 165 options.FieldSelector = conf.Selector.Field 166 options.LabelSelector = conf.Selector.Label 167 return pod.Watch(ctx, options) 168 }, 169 } 170 171 cmap := d.client.CoreV1().ConfigMaps(namespace) 172 cmapLW := &cache.ListWatch{ 173 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 174 return cmap.List(ctx, options) 175 }, 176 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 177 return cmap.Watch(ctx, options) 178 }, 179 } 180 181 secret := d.client.CoreV1().Secrets(namespace) 182 secretLW := &cache.ListWatch{ 183 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 184 return secret.List(ctx, options) 185 }, 186 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 187 return secret.Watch(ctx, options) 188 }, 189 } 190 191 td := newPodDiscoverer( 192 cache.NewSharedInformer(podLW, &corev1.Pod{}, resyncPeriod), 193 cache.NewSharedInformer(cmapLW, &corev1.ConfigMap{}, resyncPeriod), 194 cache.NewSharedInformer(secretLW, &corev1.Secret{}, resyncPeriod), 195 ) 196 td.Tags().Merge(tags) 197 198 d.discoverers = append(d.discoverers, td) 199 200 return nil 201 } 202 203 func (d *KubeDiscoverer) setupServiceDiscoverer(ctx context.Context, conf *ServiceConfig, namespace string) error { 204 if conf == nil { 205 return nil 206 } 207 208 tags, err := model.ParseTags(conf.Tags) 209 if err != nil { 210 return fmt.Errorf("parse tags: %v", err) 211 } 212 213 svc := d.client.CoreV1().Services(namespace) 214 215 svcLW := &cache.ListWatch{ 216 ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 217 options.FieldSelector = conf.Selector.Field 218 options.LabelSelector = conf.Selector.Label 219 return svc.List(ctx, options) 220 }, 221 WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 222 options.FieldSelector = conf.Selector.Field 223 options.LabelSelector = conf.Selector.Label 224 return svc.Watch(ctx, options) 225 }, 226 } 227 228 inf := cache.NewSharedInformer(svcLW, &corev1.Service{}, resyncPeriod) 229 230 td := newServiceDiscoverer(inf) 231 td.Tags().Merge(tags) 232 233 d.discoverers = append(d.discoverers, td) 234 235 return nil 236 } 237 238 func enqueue(queue *workqueue.Type, obj any) { 239 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) 240 if err != nil { 241 return 242 } 243 queue.Add(key) 244 } 245 246 func send(ctx context.Context, in chan<- []model.TargetGroup, tgg model.TargetGroup) { 247 if tgg == nil { 248 return 249 } 250 select { 251 case <-ctx.Done(): 252 case in <- []model.TargetGroup{tgg}: 253 } 254 } 255 256 func calcHash(obj any) (uint64, error) { 257 return hashstructure.Hash(obj, nil) 258 } 259 260 func joinSelectors(srs ...string) string { 261 var i int 262 for _, v := range srs { 263 if v != "" { 264 srs[i] = v 265 i++ 266 } 267 } 268 return strings.Join(srs[:i], ",") 269 }