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  }