github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/operators/common/kubeinventorycache.go (about) 1 // Copyright 2023-2024 The Inspektor Gadget authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package common 16 17 import ( 18 "fmt" 19 "sync" 20 "time" 21 22 log "github.com/sirupsen/logrus" 23 v1 "k8s.io/api/core/v1" 24 "k8s.io/client-go/informers" 25 "k8s.io/client-go/kubernetes" 26 k8sCache "k8s.io/client-go/tools/cache" 27 28 "github.com/inspektor-gadget/inspektor-gadget/pkg/cachedmap" 29 "github.com/inspektor-gadget/inspektor-gadget/pkg/k8sutil" 30 ) 31 32 // K8sInventoryCache is a cache of Kubernetes resources such as pods and services 33 // that can be used by operators to enrich events. 34 type K8sInventoryCache interface { 35 Start() 36 Stop() 37 38 GetPods() []*v1.Pod 39 GetPodByName(namespace string, name string) *v1.Pod 40 GetPodByIp(ip string) *v1.Pod 41 42 GetSvcs() []*v1.Service 43 GetSvcByName(namespace string, name string) *v1.Service 44 GetSvcByIp(ip string) *v1.Service 45 } 46 47 type inventoryCache struct { 48 clientset *kubernetes.Clientset 49 50 factory informers.SharedInformerFactory 51 52 pods cachedmap.CachedMap[string, *v1.Pod] 53 svcs cachedmap.CachedMap[string, *v1.Service] 54 55 exit chan struct{} 56 57 useCount int 58 useCountMutex sync.Mutex 59 } 60 61 const ( 62 informerResync = 10 * time.Minute 63 ) 64 65 var ( 66 k8sInventorySingleton *inventoryCache 67 k8sInventoryErr error 68 k8sInventoryOnce sync.Once 69 ) 70 71 func GetK8sInventoryCache() (K8sInventoryCache, error) { 72 k8sInventoryOnce.Do(func() { 73 k8sInventorySingleton, k8sInventoryErr = newCache() 74 }) 75 return k8sInventorySingleton, k8sInventoryErr 76 } 77 78 func newCache() (*inventoryCache, error) { 79 clientset, err := k8sutil.NewClientset("") 80 if err != nil { 81 return nil, fmt.Errorf("creating new k8s clientset: %w", err) 82 } 83 84 return &inventoryCache{ 85 clientset: clientset, 86 pods: cachedmap.NewCachedMap[string, *v1.Pod](2 * time.Second), 87 svcs: cachedmap.NewCachedMap[string, *v1.Service](2 * time.Second), 88 }, nil 89 } 90 91 func (cache *inventoryCache) Close() { 92 if cache.exit != nil { 93 close(cache.exit) 94 cache.exit = nil 95 } 96 if cache.factory != nil { 97 cache.factory.Shutdown() 98 cache.factory = nil 99 } 100 } 101 102 func (cache *inventoryCache) Start() { 103 cache.useCountMutex.Lock() 104 defer cache.useCountMutex.Unlock() 105 106 // No uses before us, we are the first one 107 if cache.useCount == 0 { 108 cache.factory = informers.NewSharedInformerFactory(cache.clientset, informerResync) 109 cache.factory.Core().V1().Pods().Informer().AddEventHandler(cache) 110 cache.factory.Core().V1().Services().Informer().AddEventHandler(cache) 111 112 cache.exit = make(chan struct{}) 113 cache.factory.Start(cache.exit) 114 cache.factory.WaitForCacheSync(cache.exit) 115 } 116 cache.useCount++ 117 } 118 119 func (cache *inventoryCache) Stop() { 120 cache.useCountMutex.Lock() 121 defer cache.useCountMutex.Unlock() 122 123 // We are the last user, stop everything 124 if cache.useCount == 1 { 125 cache.Close() 126 } 127 cache.useCount-- 128 } 129 130 func (cache *inventoryCache) GetPods() []*v1.Pod { 131 return cache.pods.Values() 132 } 133 134 func (cache *inventoryCache) GetPodByName(namespace string, name string) *v1.Pod { 135 pod, found := cache.pods.Get(namespace + "/" + name) 136 if !found { 137 return nil 138 } 139 return pod 140 } 141 142 func (cache *inventoryCache) GetPodByIp(ip string) *v1.Pod { 143 pod, found := cache.pods.GetCmp(func(pod *v1.Pod) bool { 144 return pod.Status.PodIP == ip 145 }) 146 if !found { 147 return nil 148 } 149 return pod 150 } 151 152 func (cache *inventoryCache) GetSvcs() []*v1.Service { 153 return cache.svcs.Values() 154 } 155 156 func (cache *inventoryCache) GetSvcByName(namespace string, name string) *v1.Service { 157 svc, found := cache.svcs.Get(namespace + "/" + name) 158 if !found { 159 return nil 160 } 161 return svc 162 } 163 164 func (cache *inventoryCache) GetSvcByIp(ip string) *v1.Service { 165 svc, found := cache.svcs.GetCmp(func(svc *v1.Service) bool { 166 return svc.Spec.ClusterIP == ip 167 }) 168 if !found { 169 return nil 170 } 171 return svc 172 } 173 174 func (cache *inventoryCache) OnAdd(obj any, _ bool) { 175 switch o := obj.(type) { 176 case *v1.Pod: 177 key, err := k8sCache.MetaNamespaceKeyFunc(o) 178 if err != nil { 179 log.Warnf("OnAdd: error getting key for pod: %v", err) 180 return 181 } 182 cache.pods.Add(key, o) 183 case *v1.Service: 184 key, err := k8sCache.MetaNamespaceKeyFunc(o) 185 if err != nil { 186 log.Warnf("OnAdd: error getting key for service: %v", err) 187 return 188 } 189 cache.svcs.Add(key, o) 190 default: 191 log.Warnf("OnAdd: unknown object type: %T", o) 192 } 193 } 194 195 func (cache *inventoryCache) OnUpdate(_, newObj any) { 196 switch o := newObj.(type) { 197 case *v1.Pod: 198 key, err := k8sCache.MetaNamespaceKeyFunc(o) 199 if err != nil { 200 log.Warnf("OnUpdate: error getting key for pod: %v", err) 201 return 202 } 203 cache.pods.Add(key, o) 204 case *v1.Service: 205 key, err := k8sCache.MetaNamespaceKeyFunc(o) 206 if err != nil { 207 log.Warnf("OnUpdate: error getting key for service: %v", err) 208 return 209 } 210 cache.svcs.Add(key, o) 211 default: 212 log.Warnf("OnUpdate: unknown object type: %T", o) 213 } 214 } 215 216 func (cache *inventoryCache) OnDelete(obj any) { 217 switch o := obj.(type) { 218 case *v1.Pod: 219 key, err := k8sCache.MetaNamespaceKeyFunc(o) 220 if err != nil { 221 log.Warnf("OnDelete: error getting key for pod: %v", err) 222 return 223 } 224 cache.pods.Remove(key) 225 case *v1.Service: 226 key, err := k8sCache.MetaNamespaceKeyFunc(o) 227 if err != nil { 228 log.Warnf("OnDelete: error getting key for service: %v", err) 229 return 230 } 231 cache.svcs.Remove(key) 232 case k8sCache.DeletedFinalStateUnknown: 233 cache.OnDelete(o.Obj) 234 default: 235 log.Warnf("OnDelete: unknown object type: %T", o) 236 } 237 }