github.com/castai/kvisor@v1.7.1-0.20240516114728-b3572a2607b5/cmd/controller/kube/client.go (about)

     1  package kube
     2  
     3  import (
     4  	"context"
     5  	"net/netip"
     6  	"os"
     7  	"reflect"
     8  	"slices"
     9  	"strings"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/castai/kvisor/pkg/logging"
    14  	appsv1 "k8s.io/api/apps/v1"
    15  	batchv1 "k8s.io/api/batch/v1"
    16  	corev1 "k8s.io/api/core/v1"
    17  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    18  	"k8s.io/apimachinery/pkg/runtime"
    19  	"k8s.io/apimachinery/pkg/types"
    20  	"k8s.io/client-go/informers"
    21  	"k8s.io/client-go/kubernetes"
    22  	"k8s.io/client-go/kubernetes/scheme"
    23  	"k8s.io/client-go/tools/cache"
    24  )
    25  
    26  type EventType string
    27  
    28  const (
    29  	EventAdd    EventType = "add"
    30  	EventUpdate EventType = "update"
    31  	EventDelete EventType = "delete"
    32  )
    33  
    34  type Object interface {
    35  	runtime.Object
    36  	metav1.Object
    37  }
    38  
    39  type AddListener interface {
    40  	OnAdd(obj Object)
    41  }
    42  
    43  type UpdateListener interface {
    44  	OnUpdate(obj Object)
    45  }
    46  
    47  type DeleteListener interface {
    48  	OnDelete(obj Object)
    49  }
    50  
    51  type KubernetesChangeEventListener interface {
    52  	RequiredTypes() []reflect.Type
    53  }
    54  
    55  type Client struct {
    56  	log                           *logging.Logger
    57  	kvisorNamespace               string
    58  	podName                       string
    59  	kvisorControllerContainerName string
    60  	client                        kubernetes.Interface
    61  
    62  	mu                      sync.RWMutex
    63  	kvisorControllerPodSpec *corev1.PodSpec
    64  
    65  	index *Index
    66  
    67  	clusterInfo *ClusterInfo
    68  
    69  	changeListenersMu sync.RWMutex
    70  	changeListeners   []*eventListener
    71  	version           Version
    72  }
    73  
    74  func NewClient(
    75  	log *logging.Logger,
    76  	podName, kvisorNamespace string,
    77  	version Version,
    78  	client kubernetes.Interface,
    79  ) *Client {
    80  	return &Client{
    81  		log:                           log.WithField("component", "kube_watcher"),
    82  		kvisorNamespace:               kvisorNamespace,
    83  		podName:                       podName,
    84  		kvisorControllerContainerName: "controller",
    85  		client:                        client,
    86  		index:                         NewIndex(),
    87  		version:                       version,
    88  	}
    89  }
    90  
    91  func (c *Client) RegisterHandlers(factory informers.SharedInformerFactory) {
    92  	informersList := []cache.SharedInformer{
    93  		factory.Core().V1().Nodes().Informer(),
    94  		factory.Core().V1().Services().Informer(),
    95  		factory.Core().V1().Endpoints().Informer(),
    96  		factory.Core().V1().Namespaces().Informer(),
    97  		factory.Apps().V1().Deployments().Informer(),
    98  		factory.Apps().V1().StatefulSets().Informer(),
    99  		factory.Apps().V1().DaemonSets().Informer(),
   100  		factory.Apps().V1().ReplicaSets().Informer(),
   101  		factory.Batch().V1().CronJobs().Informer(),
   102  		factory.Batch().V1().Jobs().Informer(),
   103  		factory.Rbac().V1().ClusterRoles().Informer(),
   104  		factory.Rbac().V1().Roles().Informer(),
   105  		factory.Rbac().V1().ClusterRoleBindings().Informer(),
   106  		factory.Rbac().V1().RoleBindings().Informer(),
   107  		factory.Networking().V1().NetworkPolicies().Informer(),
   108  		factory.Networking().V1().Ingresses().Informer(),
   109  	}
   110  
   111  	if c.version.MinorInt >= 21 {
   112  		informersList = append(informersList, factory.Batch().V1().CronJobs().Informer())
   113  	} else {
   114  		informersList = append(informersList, factory.Batch().V1beta1().CronJobs().Informer())
   115  	}
   116  
   117  	for _, informer := range informersList {
   118  		if err := informer.SetTransform(c.transformFunc); err != nil {
   119  			panic(err)
   120  		}
   121  		if _, err := informer.AddEventHandler(c.eventHandler()); err != nil {
   122  			panic(err)
   123  		}
   124  	}
   125  }
   126  
   127  func (c *Client) RegisterPodsHandlers(factory informers.SharedInformerFactory) {
   128  	podsInformer := factory.Core().V1().Pods().Informer()
   129  	if err := podsInformer.SetTransform(c.transformFunc); err != nil {
   130  		panic(err)
   131  	}
   132  	if _, err := podsInformer.AddEventHandler(c.eventHandler()); err != nil {
   133  		panic(err)
   134  	}
   135  }
   136  
   137  func (c *Client) Run(ctx context.Context) error {
   138  	select {
   139  	case <-ctx.Done():
   140  		return ctx.Err()
   141  	}
   142  }
   143  
   144  func (c *Client) eventHandler() cache.ResourceEventHandler {
   145  	return cache.ResourceEventHandlerFuncs{
   146  		AddFunc: func(obj any) {
   147  			c.mu.Lock()
   148  			defer c.mu.Unlock()
   149  
   150  			switch t := obj.(type) {
   151  			case *corev1.Pod:
   152  				c.index.addFromPod(t)
   153  			case *corev1.Service:
   154  				c.index.addFromService(t)
   155  			case *corev1.Endpoints:
   156  				c.index.addFromEndpoints(t)
   157  			case *corev1.Node:
   158  				c.index.addFromNode(t)
   159  			case *batchv1.Job:
   160  				c.index.jobs[t.UID] = t.ObjectMeta
   161  			case *appsv1.ReplicaSet:
   162  				c.index.replicaSets[t.UID] = t.ObjectMeta
   163  			case *appsv1.Deployment:
   164  				c.index.deployments[t.UID] = t
   165  			}
   166  
   167  			if kubeObj, ok := obj.(Object); ok {
   168  				c.fireKubernetesAddEvent(kubeObj)
   169  			}
   170  		},
   171  		UpdateFunc: func(oldObj, newObj any) {
   172  			c.mu.Lock()
   173  			defer c.mu.Unlock()
   174  
   175  			switch t := newObj.(type) {
   176  			case *corev1.Pod:
   177  				c.index.addFromPod(t)
   178  			case *corev1.Service:
   179  				c.index.addFromService(t)
   180  			case *corev1.Endpoints:
   181  				c.index.addFromEndpoints(t)
   182  			case *corev1.Node:
   183  				c.index.addFromNode(t)
   184  			case *batchv1.Job:
   185  				c.index.jobs[t.UID] = t.ObjectMeta
   186  			case *appsv1.ReplicaSet:
   187  				c.index.replicaSets[t.UID] = t.ObjectMeta
   188  			case *appsv1.Deployment:
   189  				c.index.deployments[t.UID] = t
   190  			}
   191  
   192  			if kubeObj, ok := newObj.(Object); ok {
   193  				c.fireKubernetesUpdateEvent(kubeObj)
   194  			}
   195  		},
   196  		DeleteFunc: func(obj interface{}) {
   197  			c.mu.Lock()
   198  			defer c.mu.Unlock()
   199  
   200  			switch t := obj.(type) {
   201  			case *corev1.Pod:
   202  				c.index.deleteFromPod(t)
   203  			case *corev1.Service:
   204  				c.index.deleteFromService(t)
   205  			case *corev1.Endpoints:
   206  				c.index.deleteFromEndpoints(t)
   207  			case *corev1.Node:
   208  				c.index.deleteByNode(t)
   209  			case *batchv1.Job:
   210  				delete(c.index.jobs, t.UID)
   211  			case *appsv1.ReplicaSet:
   212  				delete(c.index.replicaSets, t.UID)
   213  			case *appsv1.Deployment:
   214  				delete(c.index.deployments, t.UID)
   215  			}
   216  
   217  			if kubeObj, ok := obj.(Object); ok {
   218  				c.fireKubernetesDeleteEvent(kubeObj)
   219  			}
   220  		},
   221  	}
   222  }
   223  
   224  func (c *Client) GetIPInfo(ip string) (IPInfo, bool) {
   225  	c.mu.RLock()
   226  	defer c.mu.RUnlock()
   227  
   228  	val, found := c.index.podsInfoByIP[ip]
   229  	return val, found
   230  }
   231  
   232  func (c *Client) GetPod(uid string) (*corev1.Pod, bool) {
   233  	c.mu.RLock()
   234  	defer c.mu.RUnlock()
   235  
   236  	val, found := c.index.pods[types.UID(uid)]
   237  	return val, found
   238  }
   239  
   240  func (c *Client) GetOwnerUID(obj Object) string {
   241  	c.mu.RLock()
   242  	defer c.mu.RUnlock()
   243  
   244  	switch v := obj.(type) {
   245  	case *corev1.Pod:
   246  		return string(c.index.getPodOwner(v).UID)
   247  	}
   248  
   249  	if len(obj.GetOwnerReferences()) == 0 {
   250  		return ""
   251  	}
   252  	return string(obj.GetOwnerReferences()[0].UID)
   253  }
   254  
   255  func (c *Client) GetPodOwner(podUID string) (metav1.OwnerReference, bool) {
   256  	c.mu.RLock()
   257  	defer c.mu.RUnlock()
   258  
   259  	pod, found := c.index.pods[types.UID(podUID)]
   260  	if !found {
   261  		return metav1.OwnerReference{}, false
   262  	}
   263  	res := c.index.getPodOwner(pod)
   264  	if res.UID == types.UID(podUID) {
   265  		return metav1.OwnerReference{}, false
   266  	}
   267  	return res, true
   268  }
   269  
   270  type ClusterInfo struct {
   271  	PodCidr     string
   272  	ServiceCidr string
   273  }
   274  
   275  func (c *Client) GetClusterInfo() (*ClusterInfo, bool) {
   276  	c.mu.Lock()
   277  	defer c.mu.Unlock()
   278  	if c.clusterInfo != nil {
   279  		return c.clusterInfo, true
   280  	}
   281  
   282  	var res ClusterInfo
   283  	for _, node := range c.index.nodesByName {
   284  		subnet, err := netip.ParsePrefix(node.Spec.PodCIDR)
   285  		if err != nil {
   286  			return nil, false
   287  		}
   288  		res.PodCidr = netip.PrefixFrom(subnet.Addr(), 16).String()
   289  		break
   290  	}
   291  
   292  	for _, info := range c.index.podsInfoByIP {
   293  		if svc := info.Service; svc != nil && svc.Spec.Type == corev1.ServiceTypeClusterIP {
   294  			addr, err := netip.ParseAddr(svc.Spec.ClusterIP)
   295  			if err != nil {
   296  				return nil, false
   297  			}
   298  			res.ServiceCidr = netip.PrefixFrom(addr, 16).String()
   299  		}
   300  	}
   301  	c.clusterInfo = &res
   302  	return &res, false
   303  }
   304  
   305  type ImageDetails struct {
   306  	ScannerImageName string
   307  	ImagePullSecrets []corev1.LocalObjectReference
   308  }
   309  
   310  // GetKvisorAgentImageDetails returns kvisor agent image details.
   311  // This is used for image analyzer and kube-bench dynamic jobs to schedule using the same image.
   312  func (c *Client) GetKvisorAgentImageDetails() (ImageDetails, bool) {
   313  	spec, found := c.getKvisorControllerPodSpec()
   314  	if !found {
   315  		c.log.Warn("kvisor controller pod spec not found")
   316  		return ImageDetails{}, false
   317  	}
   318  
   319  	imageName := os.Getenv("SCANNERS_IMAGE")
   320  	if imageName == "" {
   321  		for _, container := range spec.Containers {
   322  			if container.Name == c.kvisorControllerContainerName {
   323  				imageName = container.Image
   324  				break
   325  			}
   326  		}
   327  
   328  		imageName = strings.Replace(imageName, "-controller", "-scanners", 1)
   329  	}
   330  
   331  	if imageName == "" {
   332  		c.log.Warn("kvisor container image not found")
   333  		return ImageDetails{}, false
   334  	}
   335  
   336  	return ImageDetails{
   337  		ScannerImageName: imageName,
   338  		ImagePullSecrets: spec.ImagePullSecrets,
   339  	}, true
   340  }
   341  
   342  func (c *Client) getKvisorControllerPodSpec() (*corev1.PodSpec, bool) {
   343  	c.mu.RLock()
   344  	spec := c.kvisorControllerPodSpec
   345  	c.mu.RUnlock()
   346  	if spec != nil {
   347  		return spec, true
   348  	}
   349  
   350  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   351  	defer cancel()
   352  	pod, err := c.client.CoreV1().Pods(c.kvisorNamespace).Get(ctx, c.podName, metav1.GetOptions{})
   353  	if err != nil {
   354  		return nil, false
   355  	}
   356  
   357  	c.mu.Lock()
   358  	c.kvisorControllerPodSpec = &pod.Spec
   359  	c.mu.Unlock()
   360  
   361  	return &pod.Spec, true
   362  }
   363  
   364  func (c *Client) RegisterKubernetesChangeListener(l KubernetesChangeEventListener) {
   365  	c.changeListenersMu.Lock()
   366  	defer c.changeListenersMu.Unlock()
   367  
   368  	requiredTypes := l.RequiredTypes()
   369  	internalListener := &eventListener{
   370  		lis:           l,
   371  		requiredTypes: make(map[reflect.Type]struct{}, len(requiredTypes)),
   372  	}
   373  	for _, t := range requiredTypes {
   374  		internalListener.requiredTypes[t] = struct{}{}
   375  	}
   376  	c.changeListeners = append(c.changeListeners, internalListener)
   377  }
   378  
   379  func (c *Client) UnregisterKubernetesChangeListener(l KubernetesChangeEventListener) {
   380  	c.changeListenersMu.Lock()
   381  	defer c.changeListenersMu.Unlock()
   382  
   383  	for i, lis := range c.changeListeners {
   384  		if lis.lis == l {
   385  			c.changeListeners = slices.Delete(c.changeListeners, i, i+1)
   386  			return
   387  		}
   388  	}
   389  }
   390  
   391  func (c *Client) fireKubernetesAddEvent(obj Object) {
   392  	c.changeListenersMu.RLock()
   393  	listeners := c.changeListeners
   394  	c.changeListenersMu.RUnlock()
   395  
   396  	for _, l := range listeners {
   397  		actionListener, ok := l.lis.(AddListener)
   398  		if ok && l.required(obj) {
   399  			go actionListener.OnAdd(obj)
   400  		}
   401  	}
   402  }
   403  
   404  func (c *Client) fireKubernetesUpdateEvent(obj Object) {
   405  	c.changeListenersMu.RLock()
   406  	listeners := c.changeListeners
   407  	c.changeListenersMu.RUnlock()
   408  
   409  	for _, l := range listeners {
   410  		actionListener, ok := l.lis.(UpdateListener)
   411  		if ok && l.required(obj) {
   412  			go actionListener.OnUpdate(obj)
   413  		}
   414  	}
   415  }
   416  
   417  func (c *Client) fireKubernetesDeleteEvent(obj Object) {
   418  	c.changeListenersMu.RLock()
   419  	listeners := c.changeListeners
   420  	c.changeListenersMu.RUnlock()
   421  
   422  	for _, l := range listeners {
   423  		actionListener, ok := l.lis.(DeleteListener)
   424  		if ok && l.required(obj) {
   425  			go actionListener.OnDelete(obj)
   426  		}
   427  	}
   428  }
   429  
   430  func (c *Client) transformFunc(i any) (any, error) {
   431  	obj := i.(Object)
   432  	// Add missing metadata which is removed by k8s.
   433  	addObjectMeta(obj)
   434  	// Remove managed fields since we don't need them. This should decrease memory usage.
   435  	obj.SetManagedFields(nil)
   436  	return obj, nil
   437  }
   438  
   439  // addObjectMeta adds missing metadata since kubernetes client removes object kind and api version information.
   440  // See one of many issues related to this https://github.com/kubernetes/kubernetes/issues/80609
   441  func addObjectMeta(o Object) {
   442  	gvks, _, _ := scheme.Scheme.ObjectKinds(o)
   443  	if len(gvks) > 0 {
   444  		o.GetObjectKind().SetGroupVersionKind(gvks[0])
   445  	}
   446  }
   447  
   448  type eventListener struct {
   449  	lis           KubernetesChangeEventListener
   450  	requiredTypes map[reflect.Type]struct{}
   451  }
   452  
   453  func (e *eventListener) required(obj Object) bool {
   454  	_, found := e.requiredTypes[reflect.TypeOf(obj)]
   455  	return found
   456  }