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

     1  // Copyright 2019-2022 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  	"context"
    19  	"fmt"
    20  	"math"
    21  	"strings"
    22  
    23  	log "github.com/sirupsen/logrus"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/fields"
    28  	"k8s.io/client-go/kubernetes"
    29  	"k8s.io/client-go/rest"
    30  
    31  	containerutils "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils"
    32  	runtimeclient "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/runtime-client"
    33  	containerutilsTypes "github.com/inspektor-gadget/inspektor-gadget/pkg/container-utils/types"
    34  	"github.com/inspektor-gadget/inspektor-gadget/pkg/types"
    35  )
    36  
    37  type K8sClient struct {
    38  	clientset     *kubernetes.Clientset
    39  	nodeName      string
    40  	fieldSelector string
    41  	runtimeClient runtimeclient.ContainerRuntimeClient
    42  }
    43  
    44  func NewK8sClient(nodeName string) (*K8sClient, error) {
    45  	config, err := rest.InClusterConfig()
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	clientset, err := kubernetes.NewForConfig(config)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	fieldSelector := fields.OneTermEqualSelector("spec.nodeName", nodeName).String()
    55  
    56  	node, err := clientset.CoreV1().Nodes().Get(context.TODO(), nodeName, metav1.GetOptions{})
    57  	if err != nil {
    58  		return nil, fmt.Errorf("getting node %w", err)
    59  	}
    60  
    61  	// Get a runtime client to talk to the container runtime handling pods in
    62  	// this node.
    63  	list := strings.SplitN(node.Status.NodeInfo.ContainerRuntimeVersion, "://", 2)
    64  	runtimeClient, err := containerutils.NewContainerRuntimeClient(
    65  		&containerutilsTypes.RuntimeConfig{
    66  			Name: types.String2RuntimeName(list[0]),
    67  		})
    68  	if err != nil {
    69  		return nil, err
    70  	}
    71  
    72  	return &K8sClient{
    73  		clientset:     clientset,
    74  		nodeName:      nodeName,
    75  		fieldSelector: fieldSelector,
    76  		runtimeClient: runtimeClient,
    77  	}, nil
    78  }
    79  
    80  func (k *K8sClient) Close() {
    81  	k.runtimeClient.Close()
    82  }
    83  
    84  // trimRuntimePrefix removes the runtime prefix from a container ID.
    85  func trimRuntimePrefix(id string) string {
    86  	parts := strings.SplitN(id, "//", 2)
    87  	if len(parts) != 2 {
    88  		return ""
    89  	}
    90  
    91  	return parts[1]
    92  }
    93  
    94  // GetNonRunningContainers returns the list of containers IDs that are not running.
    95  func (k *K8sClient) GetNonRunningContainers(pod *v1.Pod) []string {
    96  	ret := []string{}
    97  
    98  	containerStatuses := append([]v1.ContainerStatus{}, pod.Status.InitContainerStatuses...)
    99  	containerStatuses = append(containerStatuses, pod.Status.ContainerStatuses...)
   100  	containerStatuses = append(containerStatuses, pod.Status.EphemeralContainerStatuses...)
   101  
   102  	for _, s := range containerStatuses {
   103  		if s.ContainerID != "" && s.State.Running == nil {
   104  			id := trimRuntimePrefix(s.ContainerID)
   105  			if id == "" {
   106  				continue
   107  			}
   108  
   109  			ret = append(ret, id)
   110  		}
   111  	}
   112  
   113  	return ret
   114  }
   115  
   116  // GetRunningContainers returns a list of the containers of a given Pod that are running.
   117  func (k *K8sClient) GetRunningContainers(pod *v1.Pod) []Container {
   118  	containers := []Container{}
   119  
   120  	labels := map[string]string{}
   121  	for k, v := range pod.ObjectMeta.Labels {
   122  		labels[k] = v
   123  	}
   124  
   125  	containerStatuses := append([]v1.ContainerStatus{}, pod.Status.InitContainerStatuses...)
   126  	containerStatuses = append(containerStatuses, pod.Status.ContainerStatuses...)
   127  	containerStatuses = append(containerStatuses, pod.Status.EphemeralContainerStatuses...)
   128  
   129  	for _, s := range containerStatuses {
   130  		if s.ContainerID == "" || s.State.Running == nil {
   131  			continue
   132  		}
   133  
   134  		containerData, err := k.runtimeClient.GetContainerDetails(s.ContainerID)
   135  		if err != nil {
   136  			log.Warnf("Skip pod %s/%s: cannot find container (ID: %s): %v",
   137  				pod.GetNamespace(), pod.GetName(), s.ContainerID, err)
   138  			continue
   139  		}
   140  
   141  		pid := containerData.Pid
   142  		if pid > math.MaxUint32 {
   143  			log.Errorf("Container PID (%d) exceeds math.MaxUint32 (%d), skipping this container", pid, math.MaxUint32)
   144  			continue
   145  		}
   146  
   147  		containerDef := Container{
   148  			Runtime: RuntimeMetadata{
   149  				BasicRuntimeMetadata: containerData.Runtime.BasicRuntimeMetadata,
   150  			},
   151  			Pid: uint32(pid),
   152  			K8s: K8sMetadata{
   153  				BasicK8sMetadata: types.BasicK8sMetadata{
   154  					Namespace:     pod.GetNamespace(),
   155  					PodName:       pod.GetName(),
   156  					ContainerName: s.Name,
   157  					PodLabels:     labels,
   158  				},
   159  			},
   160  		}
   161  		containers = append(containers, containerDef)
   162  	}
   163  
   164  	return containers
   165  }
   166  
   167  // ListContainers return a list of the current containers that are
   168  // running in the node.
   169  func (k *K8sClient) ListContainers() (arr []Container, err error) {
   170  	// List pods
   171  	pods, err := k.clientset.CoreV1().Pods("").List(context.TODO(), metav1.ListOptions{
   172  		FieldSelector: k.fieldSelector,
   173  	})
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  
   178  	for _, pod := range pods.Items {
   179  		containers := k.GetRunningContainers(&pod)
   180  		arr = append(arr, containers...)
   181  	}
   182  	return arr, nil
   183  }