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

     1  // Copyright 2022-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 containercollection
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/moby/moby/pkg/stringid"
    23  	ocispec "github.com/opencontainers/runtime-spec/specs-go"
    24  	"golang.org/x/sys/unix"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  	"k8s.io/client-go/dynamic"
    27  	"k8s.io/client-go/rest"
    28  
    29  	"github.com/inspektor-gadget/inspektor-gadget/pkg/columns"
    30  	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    31  )
    32  
    33  // Container represents a container with its metadata.
    34  type Container struct {
    35  	// Runtime contains the metadata of the container runtime
    36  	Runtime RuntimeMetadata `json:"runtime,omitempty" column:"runtime" columnTags:"runtime"`
    37  
    38  	// K8s contains the Kubernetes metadata of the container.
    39  	K8s K8sMetadata `json:"k8s,omitempty" column:"k8s" columnTags:"kubernetes"`
    40  
    41  	// Pid is the process id of the container
    42  	Pid uint32 `json:"pid,omitempty" column:"pid,template:pid,hide"`
    43  
    44  	// Container's configuration is the config.json from the OCI runtime
    45  	// spec
    46  	OciConfig *ocispec.Spec `json:"ociConfig,omitempty"`
    47  
    48  	// Bundle is the directory containing the config.json from the OCI
    49  	// runtime spec
    50  	// See https://github.com/opencontainers/runtime-spec/blob/main/bundle.md
    51  	Bundle string `json:"bundle,omitempty"`
    52  
    53  	// SandboxId is the sandbox id for the corresponding pod
    54  	SandboxId string `json:"sandboxId,omitempty"`
    55  
    56  	// Linux metadata can be derived from the pid via /proc/$pid/...
    57  	Mntns       uint64 `json:"mntns,omitempty" column:"mntns,template:ns"`
    58  	Netns       uint64 `json:"netns,omitempty" column:"netns,template:ns"`
    59  	HostNetwork bool   `json:"hostNetwork,omitempty" column:"hostNetwork,width:11,fixed,hide"`
    60  	CgroupPath  string `json:"cgroupPath,omitempty"`
    61  	CgroupID    uint64 `json:"cgroupID,omitempty"`
    62  	// Data required to find the container to Pod association in the
    63  	// gadgettracermanager.
    64  	CgroupV1 string `json:"cgroupV1,omitempty"`
    65  	CgroupV2 string `json:"cgroupV2,omitempty"`
    66  
    67  	// We keep an open file descriptor of the containers mount and net namespaces to be sure the
    68  	// kernel doesn't reuse the inode id before we get rid of this container. This logic avoids
    69  	// a race condition when the ns inode id is reused by a new container and we erroneously
    70  	// pick events from it or enrich data using it.
    71  	// These are only used when cachedContainers are enabled through WithTracerCollection().
    72  	mntNsFd int
    73  	netNsFd int
    74  
    75  	// when the container was removed. Useful for running cached containers.
    76  	deletionTimestamp time.Time
    77  }
    78  
    79  // close releases any resources (like  file descriptors) the container is using.
    80  func (c *Container) close() {
    81  	if c.mntNsFd != 0 {
    82  		unix.Close(c.mntNsFd)
    83  		c.mntNsFd = 0
    84  	}
    85  	if c.netNsFd != 0 {
    86  		unix.Close(c.netNsFd)
    87  		c.netNsFd = 0
    88  	}
    89  }
    90  
    91  type RuntimeMetadata struct {
    92  	types.BasicRuntimeMetadata `json:",inline"`
    93  }
    94  
    95  type K8sMetadata struct {
    96  	types.BasicK8sMetadata `json:",inline"`
    97  	PodUID                 string `json:"podUID,omitempty"`
    98  
    99  	ownerReference *metav1.OwnerReference
   100  }
   101  
   102  type K8sSelector struct {
   103  	types.BasicK8sMetadata
   104  }
   105  
   106  type RuntimeSelector struct {
   107  	// TODO: Support filtering by all the fields in BasicRuntimeMetadata
   108  	ContainerName string
   109  }
   110  
   111  type ContainerSelector struct {
   112  	K8s     K8sSelector
   113  	Runtime RuntimeSelector
   114  }
   115  
   116  // GetOwnerReference returns the owner reference information of the
   117  // container. Currently it's added to the seccomp profile as annotations
   118  // to help users to identify the workflow of the profile. We "lazily
   119  // enrich" this information because this operation is expensive and this
   120  // information is only needed in some cases.
   121  func (c *Container) GetOwnerReference() (*metav1.OwnerReference, error) {
   122  	if c.K8s.ownerReference != nil {
   123  		return c.K8s.ownerReference, nil
   124  	}
   125  
   126  	kubeconfig, err := rest.InClusterConfig()
   127  	if err != nil {
   128  		return nil, fmt.Errorf("getting Kubernetes config: %w", err)
   129  	}
   130  
   131  	dynamicClient, err := dynamic.NewForConfig(kubeconfig)
   132  	if err != nil {
   133  		return nil, fmt.Errorf("getting get dynamic Kubernetes client: %w", err)
   134  	}
   135  
   136  	err = ownerReferenceEnrichment(dynamicClient, c, nil)
   137  	if err != nil {
   138  		return nil, fmt.Errorf("enriching owner reference: %w", err)
   139  	}
   140  
   141  	return c.K8s.ownerReference, nil
   142  }
   143  
   144  func ownerReferenceEnrichment(
   145  	dynamicClient dynamic.Interface,
   146  	container *Container,
   147  	ownerReferences []metav1.OwnerReference,
   148  ) error {
   149  	resGroupVersion := "v1"
   150  	resKind := "pods"
   151  	resName := container.K8s.PodName
   152  	resNamespace := container.K8s.Namespace
   153  
   154  	var highestOwnerRef *metav1.OwnerReference
   155  
   156  	// Iterate until we reach the highest level of reference with one of the
   157  	// expected resource kind. Take into account that if this logic is changed,
   158  	// the gadget cluster role needs to be updated accordingly.
   159  	for {
   160  		if len(ownerReferences) == 0 {
   161  			var err error
   162  			ownerReferences, err = getOwnerReferences(dynamicClient,
   163  				resNamespace, resKind, resGroupVersion, resName)
   164  			if err != nil {
   165  				return fmt.Errorf("getting %s/%s/%s/%s owner reference: %w",
   166  					resNamespace, resKind, resGroupVersion, resName, err)
   167  			}
   168  
   169  			// No owner reference found
   170  			if len(ownerReferences) == 0 {
   171  				break
   172  			}
   173  		}
   174  
   175  		ownerRef := getExpectedOwnerReference(ownerReferences)
   176  		if ownerRef == nil {
   177  			// None expected owner reference found
   178  			break
   179  		}
   180  
   181  		// Update parameters for next iteration (Namespace does not change)
   182  		highestOwnerRef = ownerRef
   183  		resGroupVersion = ownerRef.APIVersion
   184  		resKind = strings.ToLower(ownerRef.Kind) + "s"
   185  		resName = ownerRef.Name
   186  		ownerReferences = nil
   187  	}
   188  
   189  	// Update container's owner reference (If any)
   190  	if highestOwnerRef != nil {
   191  		container.K8s.ownerReference = &metav1.OwnerReference{
   192  			APIVersion: highestOwnerRef.APIVersion,
   193  			Kind:       highestOwnerRef.Kind,
   194  			Name:       highestOwnerRef.Name,
   195  			UID:        highestOwnerRef.UID,
   196  		}
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  func GetColumns() *columns.Columns[Container] {
   203  	cols := columns.MustCreateColumns[Container]()
   204  
   205  	cols.MustSetExtractor("runtime.containerImageName", func(container *Container) any {
   206  		if container == nil {
   207  			return ""
   208  		}
   209  		if strings.Contains(container.Runtime.ContainerImageName, "sha256") {
   210  			return stringid.TruncateID(container.Runtime.ContainerImageName)
   211  		}
   212  		return container.Runtime.ContainerImageName
   213  	})
   214  
   215  	return cols
   216  }
   217  
   218  func (c *Container) K8sMetadata() *types.BasicK8sMetadata {
   219  	return &c.K8s.BasicK8sMetadata
   220  }
   221  
   222  func (c *Container) RuntimeMetadata() *types.BasicRuntimeMetadata {
   223  	return &c.Runtime.BasicRuntimeMetadata
   224  }
   225  
   226  func (c *Container) UsesHostNetwork() bool {
   227  	return c.HostNetwork
   228  }