github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/container-collection/container-collection.go (about)

     1  // Copyright 2019-2023 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 containercollection provides the ContainerCollection struct to keep
    16  // track of the set of running containers and primitives to query that set with
    17  // various criteria.
    18  //
    19  // It is used by the Gadget Tracer Manager to keep track of containers part of
    20  // Kubernetes pods and by IG Manager to keep track of containers on a
    21  // Linux system.
    22  package containercollection
    23  
    24  import (
    25  	"sync"
    26  	"time"
    27  
    28  	log "github.com/sirupsen/logrus"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  
    31  	eventtypes "github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    32  )
    33  
    34  // ContainerCollection holds a set of containers. It can be embedded as an
    35  // anonymous struct to help other structs implement the ContainerResolver
    36  // interface. For this reason, some methods are namespaced with 'Container' to
    37  // make this clear.
    38  type ContainerCollection struct {
    39  	mu sync.Mutex
    40  
    41  	// Keys:   containerID string
    42  	// Values: container   Container
    43  	containers sync.Map
    44  
    45  	// Keys:   MntNsID     string
    46  	// Values: container   Container
    47  	containersByMntNs sync.Map
    48  
    49  	// Keys:   NetNsID     string
    50  	// Values: container   Container
    51  	containersByNetNs sync.Map
    52  
    53  	// Saves containers for "cacheDelay" to be able to enrich events after the container is
    54  	// removed. This is enabled by using WithTracerCollection().
    55  	cachedContainers *sync.Map
    56  	cacheDelay       time.Duration
    57  
    58  	// subs contains a list of subscribers of container events
    59  	pubsub *GadgetPubSub
    60  
    61  	// containerEnrichers are functions that automatically add metadata
    62  	// upon AddContainer. The functions return true on success or false if
    63  	// the container is meant to be dropped.
    64  	containerEnrichers []func(container *Container) (ok bool)
    65  
    66  	// initialContainers is used during the initialization process to
    67  	// gather initial containers and then call the enrichers
    68  	initialContainers []*Container
    69  
    70  	// nodeName is used by the Enrich() function
    71  	nodeName string
    72  
    73  	// initialized tells if Initialize() has been called.
    74  	initialized bool
    75  
    76  	// closed tells if Close() has been called.
    77  	closed bool
    78  	done   chan struct{}
    79  
    80  	// functions to be called on Close()
    81  	cleanUpFuncs []func()
    82  
    83  	// disableContainerRuntimeWarnings is used to disable warnings about container runtimes.
    84  	disableContainerRuntimeWarnings bool
    85  }
    86  
    87  // ContainerCollectionOption are options to pass to
    88  // Initialize using the functional option code pattern.
    89  type ContainerCollectionOption func(*ContainerCollection) error
    90  
    91  // Initialize initializes a ContainerCollection. It is
    92  // useful when ContainerCollection is embedded as an anonymous struct because
    93  // we don't use a contructor in that case.
    94  func (cc *ContainerCollection) Initialize(options ...ContainerCollectionOption) error {
    95  	cc.done = make(chan struct{})
    96  
    97  	if cc.initialized {
    98  		panic("Initialize already called")
    99  	}
   100  
   101  	// Call functional options. This might fetch initial containers.
   102  	for _, o := range options {
   103  		err := o(cc)
   104  		if err != nil {
   105  			return err
   106  		}
   107  	}
   108  
   109  	// Consume initial containers that might have been fetched by
   110  	// functional options. This is done after all functional options have
   111  	// been called, so that cc.containerEnrichers is fully set up.
   112  	for _, container := range cc.initialContainers {
   113  		cc.AddContainer(container)
   114  	}
   115  	cc.initialContainers = nil
   116  
   117  	cc.initialized = true
   118  	return nil
   119  }
   120  
   121  // GetContainer looks up a container by the container id and return it if
   122  // found, or return nil if not found.
   123  func (cc *ContainerCollection) GetContainer(id string) *Container {
   124  	v, ok := cc.containers.Load(id)
   125  	if !ok {
   126  		return nil
   127  	}
   128  	container := v.(*Container)
   129  	return container
   130  }
   131  
   132  // RemoveContainer removes a container from the collection, but only after
   133  // notifying all the subscribers.
   134  func (cc *ContainerCollection) RemoveContainer(id string) {
   135  	v, loaded := cc.containers.Load(id)
   136  	if !loaded {
   137  		return
   138  	}
   139  
   140  	container := v.(*Container)
   141  
   142  	if cc.pubsub != nil {
   143  		cc.pubsub.Publish(EventTypeRemoveContainer, container)
   144  	}
   145  
   146  	// Save the container in the cache as enrichers might need the container some time after it
   147  	// has been removed.
   148  	if cc.cachedContainers != nil {
   149  		container.deletionTimestamp = time.Now()
   150  		cc.cachedContainers.Store(id, v)
   151  	}
   152  
   153  	// Remove the container from the collection after publishing the event as
   154  	// subscribers might need to use the different collection's lookups during
   155  	// the notification handler, and they expect the container to still be
   156  	// present.
   157  	cc.containers.Delete(id)
   158  
   159  	// Make this operation atomic, as RemoveContainer() could be called concurrently, which could result in
   160  	// dirty map contents
   161  	cc.mu.Lock()
   162  	defer cc.mu.Unlock()
   163  
   164  	// Remove from MntNs lookup
   165  	mntNsContainer, ok := cc.containersByMntNs.Load(container.Mntns)
   166  	if !ok || mntNsContainer.(*Container).Runtime.ContainerID != container.Runtime.ContainerID {
   167  		log.Warn("container not found or mismatch in mntns lookup map")
   168  		return
   169  	} else {
   170  		cc.containersByMntNs.Delete(container.Mntns)
   171  	}
   172  
   173  	// Remove from NetNs lookup; arrays should be immutable, so recreate them
   174  	netNsContainers, ok := cc.containersByNetNs.Load(container.Netns)
   175  	if !ok {
   176  		log.Warn("container netns not found in netns lookup map")
   177  		return
   178  	}
   179  
   180  	found := false
   181  	netNsContainersArr := netNsContainers.([]*Container)
   182  	newNetNsContainers := make([]*Container, 0, len(netNsContainersArr)-1)
   183  	for _, netNsContainer := range netNsContainersArr {
   184  		if netNsContainer.Runtime.ContainerID == container.Runtime.ContainerID {
   185  			found = true
   186  			continue
   187  		}
   188  		newNetNsContainers = append(newNetNsContainers, netNsContainer)
   189  	}
   190  	if !found {
   191  		log.Warn("container not found in netns lookup array")
   192  	}
   193  
   194  	if len(newNetNsContainers) > 0 {
   195  		cc.containersByNetNs.Store(container.Netns, newNetNsContainers)
   196  	} else {
   197  		// clean up empty entries
   198  		cc.containersByNetNs.Delete(container.Netns)
   199  	}
   200  }
   201  
   202  // AddContainer adds a container to the collection.
   203  func (cc *ContainerCollection) AddContainer(container *Container) {
   204  	for _, enricher := range cc.containerEnrichers {
   205  		ok := enricher(container)
   206  		// Enrichers can decide to drop a container
   207  		if !ok {
   208  			container.close()
   209  			return
   210  		}
   211  	}
   212  
   213  	_, loaded := cc.containers.LoadOrStore(container.Runtime.ContainerID, container)
   214  	if loaded {
   215  		return
   216  	}
   217  	cc.mu.Lock()
   218  	cc.containersByMntNs.Store(container.Mntns, container)
   219  	arr, ok := cc.containersByNetNs.Load(container.Netns)
   220  	var newContainerArr []*Container
   221  	if ok {
   222  		newContainerArr = append(newContainerArr, arr.([]*Container)...)
   223  	}
   224  	newContainerArr = append(newContainerArr, container)
   225  	cc.containersByNetNs.Store(container.Netns, newContainerArr)
   226  	cc.mu.Unlock()
   227  
   228  	if cc.pubsub != nil {
   229  		cc.pubsub.Publish(EventTypeAddContainer, container)
   230  	}
   231  }
   232  
   233  // LookupMntnsByContainer returns the mount namespace inode of the container
   234  // specified in arguments or zero if not found
   235  func (cc *ContainerCollection) LookupMntnsByContainer(namespace, pod, container string) (mntns uint64) {
   236  	cc.containers.Range(func(key, value interface{}) bool {
   237  		c := value.(*Container)
   238  		if namespace == c.K8s.Namespace && pod == c.K8s.PodName && container == c.K8s.ContainerName {
   239  			mntns = c.Mntns
   240  			// container found, stop iterating
   241  			return false
   242  		}
   243  		return true
   244  	})
   245  	return
   246  }
   247  
   248  func lookupContainerByMntns(m *sync.Map, mntnsid uint64) *Container {
   249  	var container *Container
   250  
   251  	m.Range(func(key, value interface{}) bool {
   252  		c := value.(*Container)
   253  		if c.Mntns == mntnsid {
   254  			container = c
   255  			// container found, stop iterating
   256  			return false
   257  		}
   258  		return true
   259  	})
   260  	return container
   261  }
   262  
   263  // LookupContainerByMntns returns a container by its mount namespace
   264  // inode id. If not found nil is returned.
   265  func (cc *ContainerCollection) LookupContainerByMntns(mntnsid uint64) *Container {
   266  	container, ok := cc.containersByMntNs.Load(mntnsid)
   267  	if !ok {
   268  		return nil
   269  	}
   270  	return container.(*Container)
   271  }
   272  
   273  // LookupContainersByNetns returns a slice of containers that run in a given
   274  // network namespace. Or an empty slice if there are no containers running in
   275  // that network namespace.
   276  func (cc *ContainerCollection) LookupContainersByNetns(netnsid uint64) []*Container {
   277  	containers, ok := cc.containersByNetNs.Load(netnsid)
   278  	if !ok {
   279  		return nil
   280  	}
   281  	return containers.([]*Container)
   282  }
   283  
   284  func lookupContainersByNetns(m *sync.Map, netnsid uint64) (containers []*Container) {
   285  	m.Range(func(key, value interface{}) bool {
   286  		c := value.(*Container)
   287  		if c.Netns == netnsid {
   288  			containers = append(containers, c)
   289  		}
   290  		return true
   291  	})
   292  	return containers
   293  }
   294  
   295  // LookupMntnsByPod returns the mount namespace inodes of all containers
   296  // belonging to the pod specified in arguments, indexed by the name of the
   297  // containers or an empty map if not found
   298  func (cc *ContainerCollection) LookupMntnsByPod(namespace, pod string) map[string]uint64 {
   299  	ret := make(map[string]uint64)
   300  	cc.containers.Range(func(key, value interface{}) bool {
   301  		c := value.(*Container)
   302  		if namespace == c.K8s.Namespace && pod == c.K8s.PodName {
   303  			ret[c.K8s.ContainerName] = c.Mntns
   304  		}
   305  		return true
   306  	})
   307  	return ret
   308  }
   309  
   310  // LookupPIDByContainer returns the PID of the container
   311  // specified in arguments or zero if not found
   312  func (cc *ContainerCollection) LookupPIDByContainer(namespace, pod, container string) (pid uint32) {
   313  	cc.containers.Range(func(key, value interface{}) bool {
   314  		c := value.(*Container)
   315  		if namespace == c.K8s.Namespace && pod == c.K8s.PodName && container == c.K8s.ContainerName {
   316  			pid = c.Pid
   317  			// container found, stop iterating
   318  			return false
   319  		}
   320  		return true
   321  	})
   322  	return
   323  }
   324  
   325  // LookupPIDByPod returns the PID of all containers belonging to
   326  // the pod specified in arguments, indexed by the name of the
   327  // containers or an empty map if not found
   328  func (cc *ContainerCollection) LookupPIDByPod(namespace, pod string) map[string]uint32 {
   329  	ret := make(map[string]uint32)
   330  	cc.containers.Range(func(key, value interface{}) bool {
   331  		c := value.(*Container)
   332  		if namespace == c.K8s.Namespace && pod == c.K8s.PodName {
   333  			ret[c.K8s.ContainerName] = c.Pid
   334  		}
   335  		return true
   336  	})
   337  	return ret
   338  }
   339  
   340  // LookupOwnerReferenceByMntns returns a pointer to the owner reference of the
   341  // container identified by the mount namespace, or nil if not found
   342  func (cc *ContainerCollection) LookupOwnerReferenceByMntns(mntns uint64) *metav1.OwnerReference {
   343  	var ownerRef *metav1.OwnerReference
   344  	var err error
   345  	cc.containers.Range(func(key, value interface{}) bool {
   346  		c := value.(*Container)
   347  		if mntns == c.Mntns {
   348  			ownerRef, err = c.GetOwnerReference()
   349  			if err != nil {
   350  				log.Warnf("Failed to get owner reference of %s/%s/%s: %s",
   351  					c.K8s.Namespace, c.K8s.PodName, c.K8s.ContainerName, err)
   352  			}
   353  			// container found, stop iterating
   354  			return false
   355  		}
   356  		return true
   357  	})
   358  	return ownerRef
   359  }
   360  
   361  // GetContainersBySelector returns a slice of containers that match
   362  // the selector or an empty slice if there are not matches
   363  func (cc *ContainerCollection) GetContainersBySelector(
   364  	containerSelector *ContainerSelector,
   365  ) []*Container {
   366  	selectedContainers := []*Container{}
   367  	cc.containers.Range(func(key, value interface{}) bool {
   368  		c := value.(*Container)
   369  		if ContainerSelectorMatches(containerSelector, c) {
   370  			selectedContainers = append(selectedContainers, c)
   371  		}
   372  		return true
   373  	})
   374  	return selectedContainers
   375  }
   376  
   377  // ContainerLen returns how many containers are stored in the collection.
   378  func (cc *ContainerCollection) ContainerLen() (count int) {
   379  	cc.containers.Range(func(key, value interface{}) bool {
   380  		count++
   381  		return true
   382  	})
   383  	return
   384  }
   385  
   386  // ContainerRange iterates over the containers of the collection and calls the
   387  // callback function for each of them.
   388  func (cc *ContainerCollection) ContainerRange(f func(*Container)) {
   389  	cc.containers.Range(func(key, value interface{}) bool {
   390  		c := value.(*Container)
   391  		f(c)
   392  		return true
   393  	})
   394  }
   395  
   396  // ContainerRangeWithSelector iterates over the containers of the collection
   397  // and calls the callback function for each of those that matches the container
   398  // selector.
   399  func (cc *ContainerCollection) ContainerRangeWithSelector(
   400  	containerSelector *ContainerSelector,
   401  	f func(*Container),
   402  ) {
   403  	cc.containers.Range(func(key, value interface{}) bool {
   404  		c := value.(*Container)
   405  		if ContainerSelectorMatches(containerSelector, c) {
   406  			f(c)
   407  		}
   408  		return true
   409  	})
   410  }
   411  
   412  func (cc *ContainerCollection) EnrichNode(event *eventtypes.CommonData) {
   413  	event.K8s.Node = cc.nodeName
   414  }
   415  
   416  func (cc *ContainerCollection) EnrichByMntNs(event *eventtypes.CommonData, mountnsid uint64) {
   417  	event.K8s.Node = cc.nodeName
   418  
   419  	container := cc.LookupContainerByMntns(mountnsid)
   420  	if container == nil && cc.cachedContainers != nil {
   421  		container = lookupContainerByMntns(cc.cachedContainers, mountnsid)
   422  	}
   423  
   424  	if container != nil {
   425  		event.K8s.ContainerName = container.K8s.ContainerName
   426  		event.K8s.PodName = container.K8s.PodName
   427  		event.K8s.PodLabels = container.K8s.PodLabels
   428  		event.K8s.Namespace = container.K8s.Namespace
   429  
   430  		event.Runtime.RuntimeName = container.Runtime.RuntimeName
   431  		event.Runtime.ContainerName = container.Runtime.ContainerName
   432  		event.Runtime.ContainerID = container.Runtime.ContainerID
   433  		event.Runtime.ContainerImageName = container.Runtime.ContainerImageName
   434  		event.Runtime.ContainerImageDigest = container.Runtime.ContainerImageDigest
   435  	}
   436  }
   437  
   438  func (cc *ContainerCollection) EnrichByNetNs(event *eventtypes.CommonData, netnsid uint64) {
   439  	event.K8s.Node = cc.nodeName
   440  
   441  	containers := cc.LookupContainersByNetns(netnsid)
   442  	if len(containers) == 0 && cc.cachedContainers != nil {
   443  		containers = lookupContainersByNetns(cc.cachedContainers, netnsid)
   444  	}
   445  	if len(containers) == 0 {
   446  		return
   447  	}
   448  	if containers[0].HostNetwork {
   449  		event.K8s.HostNetwork = true
   450  		return
   451  	}
   452  
   453  	if len(containers) == 1 {
   454  		event.K8s.ContainerName = containers[0].K8s.ContainerName
   455  		event.K8s.PodName = containers[0].K8s.PodName
   456  		event.K8s.PodLabels = containers[0].K8s.PodLabels
   457  		event.K8s.Namespace = containers[0].K8s.Namespace
   458  
   459  		event.Runtime.RuntimeName = containers[0].Runtime.RuntimeName
   460  		event.Runtime.ContainerName = containers[0].Runtime.ContainerName
   461  		event.Runtime.ContainerID = containers[0].Runtime.ContainerID
   462  		event.Runtime.ContainerImageName = containers[0].Runtime.ContainerImageName
   463  		event.Runtime.ContainerImageDigest = containers[0].Runtime.ContainerImageDigest
   464  		return
   465  	}
   466  	if containers[0].K8s.PodName != "" && containers[0].K8s.Namespace != "" {
   467  		// Kubernetes containers within the same pod.
   468  		event.K8s.PodName = containers[0].K8s.PodName
   469  		event.K8s.PodLabels = containers[0].K8s.PodLabels
   470  		event.K8s.Namespace = containers[0].K8s.Namespace
   471  
   472  		// All containers in the same pod share the same container runtime
   473  		event.Runtime.RuntimeName = containers[0].Runtime.RuntimeName
   474  	}
   475  	// else {
   476  	// 	TODO: Non-Kubernetes containers sharing the same network namespace.
   477  	// 	What should we do here?
   478  	// }
   479  }
   480  
   481  // Subscribe returns the list of existing containers and registers a callback
   482  // for notifications about additions and deletions of containers
   483  func (cc *ContainerCollection) Subscribe(key interface{}, selector ContainerSelector, f FuncNotify) []*Container {
   484  	if cc.pubsub == nil {
   485  		panic("ContainerCollection's pubsub uninitialized")
   486  	}
   487  	ret := []*Container{}
   488  	cc.pubsub.Subscribe(key, func(event PubSubEvent) {
   489  		if ContainerSelectorMatches(&selector, event.Container) {
   490  			f(event)
   491  		}
   492  	}, func() {
   493  		// Fetch the list of containers inside pubsub.Subscribe() to
   494  		// guarantee that no new container event will be published at
   495  		// the same time.
   496  		cc.ContainerRangeWithSelector(&selector, func(c *Container) {
   497  			ret = append(ret, c)
   498  		})
   499  	})
   500  	return ret
   501  }
   502  
   503  // Unsubscribe undoes a previous call to Subscribe
   504  func (cc *ContainerCollection) Unsubscribe(key interface{}) {
   505  	if cc.pubsub == nil {
   506  		panic("ContainerCollection's pubsub uninitialized")
   507  	}
   508  	cc.pubsub.Unsubscribe(key)
   509  }
   510  
   511  func (cc *ContainerCollection) Close() {
   512  	cc.mu.Lock()
   513  	defer cc.mu.Unlock()
   514  
   515  	close(cc.done)
   516  
   517  	if !cc.initialized || cc.closed {
   518  		panic("ContainerCollection is not initialized or has been closed")
   519  	}
   520  
   521  	// TODO: it's not clear if we want/can allow to re-initialize
   522  	// this instance yet, so we don't set cc.initialized = false.
   523  	cc.closed = true
   524  
   525  	for _, f := range cc.cleanUpFuncs {
   526  		f()
   527  	}
   528  
   529  	// Similar to RemoveContainer() on all containers but without publishing
   530  	// events.
   531  	cc.containers.Range(func(key, value interface{}) bool {
   532  		c := value.(*Container)
   533  		c.close()
   534  		cc.containers.Delete(c)
   535  		return true
   536  	})
   537  }