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 }