github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/scrape/discovery/kubernetes/kubernetes.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  	"reflect"
    22  	"regexp"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/prometheus/client_golang/prometheus"
    28  	"github.com/sirupsen/logrus"
    29  	apiv1 "k8s.io/api/core/v1"
    30  	disv1beta1 "k8s.io/api/discovery/v1beta1"
    31  	networkv1 "k8s.io/api/networking/v1"
    32  	"k8s.io/api/networking/v1beta1"
    33  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    34  	"k8s.io/apimachinery/pkg/fields"
    35  	"k8s.io/apimachinery/pkg/labels"
    36  	"k8s.io/apimachinery/pkg/runtime"
    37  	utilversion "k8s.io/apimachinery/pkg/util/version"
    38  	"k8s.io/apimachinery/pkg/watch"
    39  	k8s "k8s.io/client-go/kubernetes"
    40  	"k8s.io/client-go/rest"
    41  	"k8s.io/client-go/tools/cache"
    42  	"k8s.io/client-go/tools/clientcmd"
    43  
    44  	"github.com/pyroscope-io/pyroscope/pkg/build"
    45  	"github.com/pyroscope-io/pyroscope/pkg/scrape/config"
    46  	"github.com/pyroscope-io/pyroscope/pkg/scrape/discovery"
    47  	"github.com/pyroscope-io/pyroscope/pkg/scrape/discovery/targetgroup"
    48  	"github.com/pyroscope-io/pyroscope/pkg/scrape/model"
    49  )
    50  
    51  // revive:disable:max-public-structs complex domain
    52  // revive:disable:cognitive-complexity preserve original implementation
    53  
    54  const (
    55  	// kubernetesMetaLabelPrefix is the meta prefix used for all meta labels.
    56  	// in this discovery.
    57  	metaLabelPrefix  = model.MetaLabelPrefix + "kubernetes_"
    58  	namespaceLabel   = metaLabelPrefix + "namespace"
    59  	metricsNamespace = "pyroscope_sd_kubernetes"
    60  	presentValue     = model.LabelValue("true")
    61  )
    62  
    63  var (
    64  	// Http header
    65  	userAgent = fmt.Sprintf("Pyroscope/%s", build.Version)
    66  	// Custom events metric
    67  	eventCount = prometheus.NewCounterVec(
    68  		prometheus.CounterOpts{
    69  			Namespace: metricsNamespace,
    70  			Name:      "events_total",
    71  			Help:      "The number of Kubernetes events handled.",
    72  		},
    73  		[]string{"role", "event"},
    74  	)
    75  	// DefaultSDConfig is the default Kubernetes SD configuration
    76  	DefaultSDConfig = SDConfig{
    77  		HTTPClientConfig: config.DefaultHTTPClientConfig,
    78  	}
    79  )
    80  
    81  func init() {
    82  	discovery.RegisterConfig(&SDConfig{})
    83  	prometheus.MustRegister(eventCount)
    84  	// Initialize metric vectors.
    85  	for _, role := range []string{"endpointslice", "endpoints", "node", "pod", "service", "ingress"} {
    86  		for _, evt := range []string{"add", "delete", "update"} {
    87  			eventCount.WithLabelValues(role, evt)
    88  		}
    89  	}
    90  	(&clientGoRequestMetricAdapter{}).Register(prometheus.DefaultRegisterer)
    91  	(&clientGoWorkqueueMetricsProvider{}).Register(prometheus.DefaultRegisterer)
    92  }
    93  
    94  // Role is role of the service in Kubernetes.
    95  type Role string
    96  
    97  // The valid options for Role.
    98  const (
    99  	RoleNode          Role = "node"
   100  	RolePod           Role = "pod"
   101  	RoleService       Role = "service"
   102  	RoleEndpoint      Role = "endpoints"
   103  	RoleEndpointSlice Role = "endpointslice"
   104  	RoleIngress       Role = "ingress"
   105  )
   106  
   107  // UnmarshalYAML implements the yaml.Unmarshaler interface.
   108  func (c *Role) UnmarshalYAML(unmarshal func(interface{}) error) error {
   109  	if err := unmarshal((*string)(c)); err != nil {
   110  		return err
   111  	}
   112  	switch *c {
   113  	case RoleNode, RolePod, RoleService, RoleEndpoint, RoleEndpointSlice, RoleIngress:
   114  		return nil
   115  	default:
   116  		return fmt.Errorf("unknown Kubernetes SD role %q", *c)
   117  	}
   118  }
   119  
   120  // SDConfig is the configuration for Kubernetes service discovery.
   121  type SDConfig struct {
   122  	APIServer          config.URL              `yaml:"api-server,omitempty"`
   123  	Role               Role                    `yaml:"role"`
   124  	KubeConfig         string                  `yaml:"kubeconfig-file"`
   125  	HTTPClientConfig   config.HTTPClientConfig `yaml:",inline"`
   126  	NamespaceDiscovery NamespaceDiscovery      `yaml:"namespaces,omitempty"`
   127  	Selectors          []SelectorConfig        `yaml:"selectors,omitempty"`
   128  }
   129  
   130  // Name returns the name of the Config.
   131  func (*SDConfig) Name() string { return "kubernetes" }
   132  
   133  // NewDiscoverer returns a Discoverer for the Config.
   134  func (c *SDConfig) NewDiscoverer(opts discovery.DiscovererOptions) (discovery.Discoverer, error) {
   135  	return New(opts.Logger, c)
   136  }
   137  
   138  // SetDirectory joins any relative file paths with dir.
   139  func (c *SDConfig) SetDirectory(dir string) {
   140  	c.HTTPClientConfig.SetDirectory(dir)
   141  	c.KubeConfig = config.JoinDir(dir, c.KubeConfig)
   142  }
   143  
   144  type roleSelector struct {
   145  	node          resourceSelector
   146  	pod           resourceSelector
   147  	service       resourceSelector
   148  	endpoints     resourceSelector
   149  	endpointslice resourceSelector
   150  	ingress       resourceSelector
   151  }
   152  
   153  type SelectorConfig struct {
   154  	Role  Role   `yaml:"role,omitempty"`
   155  	Label string `yaml:"label,omitempty"`
   156  	Field string `yaml:"field,omitempty"`
   157  }
   158  
   159  type resourceSelector struct {
   160  	label string
   161  	field string
   162  }
   163  
   164  // UnmarshalYAML implements the yaml.Unmarshaler interface.
   165  func (c *SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
   166  	*c = DefaultSDConfig
   167  	type plain SDConfig
   168  	err := unmarshal((*plain)(c))
   169  	if err != nil {
   170  		return err
   171  	}
   172  	if c.Role == "" {
   173  		return fmt.Errorf("role missing (one of: pod, service, endpoints, endpointslice, node, ingress)")
   174  	}
   175  	err = c.HTTPClientConfig.Validate()
   176  	if err != nil {
   177  		return err
   178  	}
   179  	if c.APIServer.URL != nil && c.KubeConfig != "" {
   180  		// Api-server and kubeconfig-file are mutually exclusive
   181  		return fmt.Errorf("cannot use 'kubeconfig-file' and 'api-server' simultaneously")
   182  	}
   183  	if c.KubeConfig != "" && !reflect.DeepEqual(c.HTTPClientConfig, config.DefaultHTTPClientConfig) {
   184  		// Kubeconfig-file and custom http config are mutually exclusive
   185  		return fmt.Errorf("cannot use a custom HTTP client configuration together with 'kubeconfig-file'")
   186  	}
   187  	if c.APIServer.URL == nil && !reflect.DeepEqual(c.HTTPClientConfig, config.DefaultHTTPClientConfig) {
   188  		return fmt.Errorf("to use custom HTTP client configuration please provide the 'api-server' URL explicitly")
   189  	}
   190  
   191  	foundSelectorRoles := make(map[Role]struct{})
   192  	allowedSelectors := map[Role][]string{
   193  		RolePod:           {string(RolePod)},
   194  		RoleService:       {string(RoleService)},
   195  		RoleEndpointSlice: {string(RolePod), string(RoleService), string(RoleEndpointSlice)},
   196  		RoleEndpoint:      {string(RolePod), string(RoleService), string(RoleEndpoint)},
   197  		RoleNode:          {string(RoleNode)},
   198  		RoleIngress:       {string(RoleIngress)},
   199  	}
   200  
   201  	for _, selector := range c.Selectors {
   202  		if _, ok := foundSelectorRoles[selector.Role]; ok {
   203  			return fmt.Errorf("duplicated selector role: %s", selector.Role)
   204  		}
   205  		foundSelectorRoles[selector.Role] = struct{}{}
   206  
   207  		if _, ok := allowedSelectors[c.Role]; !ok {
   208  			return fmt.Errorf("invalid role: %q, expecting one of: pod, service, endpoints, endpointslice, node or ingress", c.Role)
   209  		}
   210  		var allowed bool
   211  		for _, role := range allowedSelectors[c.Role] {
   212  			if role == string(selector.Role) {
   213  				allowed = true
   214  				break
   215  			}
   216  		}
   217  
   218  		if !allowed {
   219  			return fmt.Errorf("%s role supports only %s selectors", c.Role, strings.Join(allowedSelectors[c.Role], ", "))
   220  		}
   221  
   222  		_, err := fields.ParseSelector(selector.Field)
   223  		if err != nil {
   224  			return err
   225  		}
   226  		_, err = labels.Parse(selector.Label)
   227  		if err != nil {
   228  			return err
   229  		}
   230  	}
   231  	return nil
   232  }
   233  
   234  // NamespaceDiscovery is the configuration for discovering
   235  // Kubernetes namespaces.
   236  type NamespaceDiscovery struct {
   237  	Names []string `yaml:"names"`
   238  }
   239  
   240  // UnmarshalYAML implements the yaml.Unmarshaler interface.
   241  func (c *NamespaceDiscovery) UnmarshalYAML(unmarshal func(interface{}) error) error {
   242  	*c = NamespaceDiscovery{}
   243  	type plain NamespaceDiscovery
   244  	return unmarshal((*plain)(c))
   245  }
   246  
   247  // Discovery implements the discoverer interface for discovering
   248  // targets from Kubernetes.
   249  type Discovery struct {
   250  	sync.RWMutex
   251  	client             k8s.Interface
   252  	role               Role
   253  	logger             logrus.FieldLogger
   254  	namespaceDiscovery *NamespaceDiscovery
   255  	discoverers        []discovery.Discoverer
   256  	selectors          roleSelector
   257  }
   258  
   259  func (d *Discovery) getNamespaces() []string {
   260  	namespaces := d.namespaceDiscovery.Names
   261  	if len(namespaces) == 0 {
   262  		namespaces = []string{apiv1.NamespaceAll}
   263  	}
   264  	return namespaces
   265  }
   266  
   267  // New creates a new Kubernetes discovery for the given role.
   268  func New(l logrus.FieldLogger, conf *SDConfig) (*Discovery, error) {
   269  	var (
   270  		kcfg *rest.Config
   271  		err  error
   272  	)
   273  	if conf.KubeConfig != "" {
   274  		kcfg, err = clientcmd.BuildConfigFromFlags("", conf.KubeConfig)
   275  		if err != nil {
   276  			return nil, err
   277  		}
   278  	} else if conf.APIServer.URL == nil {
   279  		// Use the Kubernetes provided pod service account
   280  		// as described in https://kubernetes.io/docs/admin/service-accounts-admin/
   281  		kcfg, err = rest.InClusterConfig()
   282  		if err != nil {
   283  			return nil, err
   284  		}
   285  		l.Debug("using pod service account via in-cluster config")
   286  	} else {
   287  		rt, err := config.NewRoundTripperFromConfig(conf.HTTPClientConfig, "kubernetes-sd")
   288  		if err != nil {
   289  			return nil, err
   290  		}
   291  		kcfg = &rest.Config{
   292  			Host:      conf.APIServer.String(),
   293  			Transport: rt,
   294  		}
   295  	}
   296  
   297  	kcfg.UserAgent = userAgent
   298  
   299  	c, err := k8s.NewForConfig(kcfg)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  	return &Discovery{
   304  		client:             c,
   305  		logger:             l,
   306  		role:               conf.Role,
   307  		namespaceDiscovery: &conf.NamespaceDiscovery,
   308  		discoverers:        make([]discovery.Discoverer, 0),
   309  		selectors:          mapSelector(conf.Selectors),
   310  	}, nil
   311  }
   312  
   313  func mapSelector(rawSelector []SelectorConfig) roleSelector {
   314  	rs := roleSelector{}
   315  	for _, resourceSelectorRaw := range rawSelector {
   316  		switch resourceSelectorRaw.Role {
   317  		case RoleEndpointSlice:
   318  			rs.endpointslice.field = resourceSelectorRaw.Field
   319  			rs.endpointslice.label = resourceSelectorRaw.Label
   320  		case RoleEndpoint:
   321  			rs.endpoints.field = resourceSelectorRaw.Field
   322  			rs.endpoints.label = resourceSelectorRaw.Label
   323  		case RoleIngress:
   324  			rs.ingress.field = resourceSelectorRaw.Field
   325  			rs.ingress.label = resourceSelectorRaw.Label
   326  		case RoleNode:
   327  			rs.node.field = resourceSelectorRaw.Field
   328  			rs.node.label = resourceSelectorRaw.Label
   329  		case RolePod:
   330  			rs.pod.field = resourceSelectorRaw.Field
   331  			rs.pod.label = resourceSelectorRaw.Label
   332  		case RoleService:
   333  			rs.service.field = resourceSelectorRaw.Field
   334  			rs.service.label = resourceSelectorRaw.Label
   335  		}
   336  	}
   337  	return rs
   338  }
   339  
   340  const resyncPeriod = 10 * time.Minute
   341  
   342  // Run implements the discoverer interface.
   343  func (d *Discovery) Run(ctx context.Context, ch chan<- []*targetgroup.Group) {
   344  	d.Lock()
   345  	namespaces := d.getNamespaces()
   346  
   347  	switch d.role {
   348  	case RoleEndpointSlice:
   349  		for _, namespace := range namespaces {
   350  			e := d.client.DiscoveryV1beta1().EndpointSlices(namespace)
   351  			elw := &cache.ListWatch{
   352  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   353  					options.FieldSelector = d.selectors.endpointslice.field
   354  					options.LabelSelector = d.selectors.endpointslice.label
   355  					return e.List(ctx, options)
   356  				},
   357  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   358  					options.FieldSelector = d.selectors.endpointslice.field
   359  					options.LabelSelector = d.selectors.endpointslice.label
   360  					return e.Watch(ctx, options)
   361  				},
   362  			}
   363  			s := d.client.CoreV1().Services(namespace)
   364  			slw := &cache.ListWatch{
   365  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   366  					options.FieldSelector = d.selectors.service.field
   367  					options.LabelSelector = d.selectors.service.label
   368  					return s.List(ctx, options)
   369  				},
   370  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   371  					options.FieldSelector = d.selectors.service.field
   372  					options.LabelSelector = d.selectors.service.label
   373  					return s.Watch(ctx, options)
   374  				},
   375  			}
   376  			p := d.client.CoreV1().Pods(namespace)
   377  			plw := &cache.ListWatch{
   378  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   379  					options.FieldSelector = d.selectors.pod.field
   380  					options.LabelSelector = d.selectors.pod.label
   381  					return p.List(ctx, options)
   382  				},
   383  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   384  					options.FieldSelector = d.selectors.pod.field
   385  					options.LabelSelector = d.selectors.pod.label
   386  					return p.Watch(ctx, options)
   387  				},
   388  			}
   389  			eps := NewEndpointSlice(
   390  				d.logger.WithField("role", "endpointslice"),
   391  				cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
   392  				cache.NewSharedInformer(elw, &disv1beta1.EndpointSlice{}, resyncPeriod),
   393  				cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
   394  			)
   395  			d.discoverers = append(d.discoverers, eps)
   396  			go eps.endpointSliceInf.Run(ctx.Done())
   397  			go eps.serviceInf.Run(ctx.Done())
   398  			go eps.podInf.Run(ctx.Done())
   399  		}
   400  	case RoleEndpoint:
   401  		for _, namespace := range namespaces {
   402  			e := d.client.CoreV1().Endpoints(namespace)
   403  			elw := &cache.ListWatch{
   404  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   405  					options.FieldSelector = d.selectors.endpoints.field
   406  					options.LabelSelector = d.selectors.endpoints.label
   407  					return e.List(ctx, options)
   408  				},
   409  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   410  					options.FieldSelector = d.selectors.endpoints.field
   411  					options.LabelSelector = d.selectors.endpoints.label
   412  					return e.Watch(ctx, options)
   413  				},
   414  			}
   415  			s := d.client.CoreV1().Services(namespace)
   416  			slw := &cache.ListWatch{
   417  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   418  					options.FieldSelector = d.selectors.service.field
   419  					options.LabelSelector = d.selectors.service.label
   420  					return s.List(ctx, options)
   421  				},
   422  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   423  					options.FieldSelector = d.selectors.service.field
   424  					options.LabelSelector = d.selectors.service.label
   425  					return s.Watch(ctx, options)
   426  				},
   427  			}
   428  			p := d.client.CoreV1().Pods(namespace)
   429  			plw := &cache.ListWatch{
   430  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   431  					options.FieldSelector = d.selectors.pod.field
   432  					options.LabelSelector = d.selectors.pod.label
   433  					return p.List(ctx, options)
   434  				},
   435  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   436  					options.FieldSelector = d.selectors.pod.field
   437  					options.LabelSelector = d.selectors.pod.label
   438  					return p.Watch(ctx, options)
   439  				},
   440  			}
   441  			eps := NewEndpoints(
   442  				d.logger.WithField("role", "endpoint"),
   443  				cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
   444  				cache.NewSharedInformer(elw, &apiv1.Endpoints{}, resyncPeriod),
   445  				cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
   446  			)
   447  			d.discoverers = append(d.discoverers, eps)
   448  			go eps.endpointsInf.Run(ctx.Done())
   449  			go eps.serviceInf.Run(ctx.Done())
   450  			go eps.podInf.Run(ctx.Done())
   451  		}
   452  	case RolePod:
   453  		for _, namespace := range namespaces {
   454  			p := d.client.CoreV1().Pods(namespace)
   455  			plw := &cache.ListWatch{
   456  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   457  					options.FieldSelector = d.selectors.pod.field
   458  					options.LabelSelector = d.selectors.pod.label
   459  					return p.List(ctx, options)
   460  				},
   461  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   462  					options.FieldSelector = d.selectors.pod.field
   463  					options.LabelSelector = d.selectors.pod.label
   464  					return p.Watch(ctx, options)
   465  				},
   466  			}
   467  			pod := NewPod(
   468  				d.logger.WithField("role", "pod"),
   469  				cache.NewSharedInformer(plw, &apiv1.Pod{}, resyncPeriod),
   470  			)
   471  			d.discoverers = append(d.discoverers, pod)
   472  			go pod.informer.Run(ctx.Done())
   473  		}
   474  	case RoleService:
   475  		for _, namespace := range namespaces {
   476  			s := d.client.CoreV1().Services(namespace)
   477  			slw := &cache.ListWatch{
   478  				ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   479  					options.FieldSelector = d.selectors.service.field
   480  					options.LabelSelector = d.selectors.service.label
   481  					return s.List(ctx, options)
   482  				},
   483  				WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   484  					options.FieldSelector = d.selectors.service.field
   485  					options.LabelSelector = d.selectors.service.label
   486  					return s.Watch(ctx, options)
   487  				},
   488  			}
   489  			svc := NewService(
   490  				d.logger.WithField("role", "service"),
   491  				cache.NewSharedInformer(slw, &apiv1.Service{}, resyncPeriod),
   492  			)
   493  			d.discoverers = append(d.discoverers, svc)
   494  			go svc.informer.Run(ctx.Done())
   495  		}
   496  	case RoleIngress:
   497  		// Check "networking.k8s.io/v1" availability with retries.
   498  		// If "v1" is not avaiable, use "networking.k8s.io/v1beta1" for backward compatibility
   499  		var v1Supported bool
   500  		if retryOnError(ctx, 10*time.Second,
   501  			func() (err error) {
   502  				v1Supported, err = checkNetworkingV1Supported(d.client)
   503  				if err != nil {
   504  					d.logger.WithError(err).Error("failed to check networking.k8s.io/v1 availability")
   505  				}
   506  				return err
   507  			},
   508  		) {
   509  			d.Unlock()
   510  			return
   511  		}
   512  
   513  		for _, namespace := range namespaces {
   514  			var informer cache.SharedInformer
   515  			if v1Supported {
   516  				i := d.client.NetworkingV1().Ingresses(namespace)
   517  				ilw := &cache.ListWatch{
   518  					ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   519  						options.FieldSelector = d.selectors.ingress.field
   520  						options.LabelSelector = d.selectors.ingress.label
   521  						return i.List(ctx, options)
   522  					},
   523  					WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   524  						options.FieldSelector = d.selectors.ingress.field
   525  						options.LabelSelector = d.selectors.ingress.label
   526  						return i.Watch(ctx, options)
   527  					},
   528  				}
   529  				informer = cache.NewSharedInformer(ilw, &networkv1.Ingress{}, resyncPeriod)
   530  			} else {
   531  				i := d.client.NetworkingV1beta1().Ingresses(namespace)
   532  				ilw := &cache.ListWatch{
   533  					ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   534  						options.FieldSelector = d.selectors.ingress.field
   535  						options.LabelSelector = d.selectors.ingress.label
   536  						return i.List(ctx, options)
   537  					},
   538  					WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   539  						options.FieldSelector = d.selectors.ingress.field
   540  						options.LabelSelector = d.selectors.ingress.label
   541  						return i.Watch(ctx, options)
   542  					},
   543  				}
   544  				informer = cache.NewSharedInformer(ilw, &v1beta1.Ingress{}, resyncPeriod)
   545  			}
   546  			ingress := NewIngress(
   547  				d.logger.WithField("role", "ingress"),
   548  				informer,
   549  			)
   550  			d.discoverers = append(d.discoverers, ingress)
   551  			go ingress.informer.Run(ctx.Done())
   552  		}
   553  	case RoleNode:
   554  		nlw := &cache.ListWatch{
   555  			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   556  				options.FieldSelector = d.selectors.node.field
   557  				options.LabelSelector = d.selectors.node.label
   558  				return d.client.CoreV1().Nodes().List(ctx, options)
   559  			},
   560  			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   561  				options.FieldSelector = d.selectors.node.field
   562  				options.LabelSelector = d.selectors.node.label
   563  				return d.client.CoreV1().Nodes().Watch(ctx, options)
   564  			},
   565  		}
   566  		node := NewNode(
   567  			d.logger.WithField("role", "node"),
   568  			cache.NewSharedInformer(nlw, &apiv1.Node{}, resyncPeriod),
   569  		)
   570  		d.discoverers = append(d.discoverers, node)
   571  		go node.informer.Run(ctx.Done())
   572  	default:
   573  		d.logger.WithField("role", d.role).Error("unknown Kubernetes discovery kind")
   574  	}
   575  
   576  	var wg sync.WaitGroup
   577  	for _, dd := range d.discoverers {
   578  		wg.Add(1)
   579  		go func(d discovery.Discoverer) {
   580  			defer wg.Done()
   581  			d.Run(ctx, ch)
   582  		}(dd)
   583  	}
   584  
   585  	d.Unlock()
   586  
   587  	wg.Wait()
   588  	<-ctx.Done()
   589  }
   590  
   591  func lv(s string) model.LabelValue {
   592  	return model.LabelValue(s)
   593  }
   594  
   595  func send(ctx context.Context, ch chan<- []*targetgroup.Group, tg *targetgroup.Group) {
   596  	if tg == nil {
   597  		return
   598  	}
   599  	select {
   600  	case <-ctx.Done():
   601  	case ch <- []*targetgroup.Group{tg}:
   602  	}
   603  }
   604  
   605  func retryOnError(ctx context.Context, interval time.Duration, f func() error) (canceled bool) {
   606  	var err error
   607  	err = f()
   608  	for {
   609  		if err == nil {
   610  			return false
   611  		}
   612  		select {
   613  		case <-ctx.Done():
   614  			return true
   615  		case <-time.After(interval):
   616  			err = f()
   617  		}
   618  	}
   619  }
   620  
   621  func checkNetworkingV1Supported(client k8s.Interface) (bool, error) {
   622  	k8sVer, err := client.Discovery().ServerVersion()
   623  	if err != nil {
   624  		return false, err
   625  	}
   626  	semVer, err := utilversion.ParseSemantic(k8sVer.String())
   627  	if err != nil {
   628  		return false, err
   629  	}
   630  	// networking.k8s.io/v1 is available since Kubernetes v1.19
   631  	// https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.19.md
   632  	return semVer.Major() >= 1 && semVer.Minor() >= 19, nil
   633  }
   634  
   635  var invalidLabelCharRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
   636  
   637  // sanitizeLabelName replaces anything that doesn't match
   638  // client_label.LabelNameRE with an underscore.
   639  func sanitizeLabelName(name string) string {
   640  	return invalidLabelCharRE.ReplaceAllString(name, "_")
   641  }