github.com/google/cadvisor@v0.49.1/container/containerd/handler.go (about)

     1  // Copyright 2017 Google Inc. All Rights Reserved.
     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  // Handler for containerd containers.
    16  package containerd
    17  
    18  import (
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/google/cadvisor/container/containerd/errdefs"
    26  	"github.com/opencontainers/runc/libcontainer/cgroups"
    27  	"golang.org/x/net/context"
    28  
    29  	specs "github.com/opencontainers/runtime-spec/specs-go"
    30  
    31  	"github.com/google/cadvisor/container"
    32  	"github.com/google/cadvisor/container/common"
    33  	containerlibcontainer "github.com/google/cadvisor/container/libcontainer"
    34  	"github.com/google/cadvisor/fs"
    35  	info "github.com/google/cadvisor/info/v1"
    36  )
    37  
    38  type containerdContainerHandler struct {
    39  	machineInfoFactory info.MachineInfoFactory
    40  	// Absolute path to the cgroup hierarchies of this container.
    41  	// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
    42  	cgroupPaths map[string]string
    43  	fsInfo      fs.FsInfo
    44  	// Metadata associated with the container.
    45  	reference info.ContainerReference
    46  	envs      map[string]string
    47  	labels    map[string]string
    48  	// Image name used for this container.
    49  	image string
    50  	// Filesystem handler.
    51  	includedMetrics container.MetricSet
    52  
    53  	libcontainerHandler *containerlibcontainer.Handler
    54  }
    55  
    56  var _ container.ContainerHandler = &containerdContainerHandler{}
    57  
    58  // newContainerdContainerHandler returns a new container.ContainerHandler
    59  func newContainerdContainerHandler(
    60  	client ContainerdClient,
    61  	name string,
    62  	machineInfoFactory info.MachineInfoFactory,
    63  	fsInfo fs.FsInfo,
    64  	cgroupSubsystems map[string]string,
    65  	inHostNamespace bool,
    66  	metadataEnvAllowList []string,
    67  	includedMetrics container.MetricSet,
    68  ) (container.ContainerHandler, error) {
    69  	// Create the cgroup paths.
    70  	cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems, name)
    71  
    72  	// Generate the equivalent cgroup manager for this container.
    73  	cgroupManager, err := containerlibcontainer.NewCgroupManager(name, cgroupPaths)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	id := ContainerNameToContainerdID(name)
    79  	// We assume that if load fails then the container is not known to containerd.
    80  	ctx := context.Background()
    81  	cntr, err := client.LoadContainer(ctx, id)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	var spec specs.Spec
    87  	if err := json.Unmarshal(cntr.Spec.Value, &spec); err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	// Cgroup is created during task creation. When cadvisor sees the cgroup,
    92  	// task may not be fully created yet. Use a retry+backoff to tolerant the
    93  	// race condition.
    94  	// TODO(random-liu): Use cri-containerd client to talk with cri-containerd
    95  	// instead. cri-containerd has some internal synchronization to make sure
    96  	// `ContainerStatus` only returns result after `StartContainer` finishes.
    97  	var taskPid uint32
    98  	backoff := 100 * time.Millisecond
    99  	retry := 5
   100  	for {
   101  		taskPid, err = client.TaskPid(ctx, id)
   102  		if err == nil {
   103  			break
   104  		}
   105  
   106  		// Retry when task is not created yet or task is in unknown state (likely in process of initializing)
   107  		isRetriableError := errdefs.IsNotFound(err) || errors.Is(err, ErrTaskIsInUnknownState)
   108  		if !isRetriableError || retry == 0 {
   109  			return nil, err
   110  		}
   111  
   112  		retry--
   113  		time.Sleep(backoff)
   114  		backoff *= 2
   115  	}
   116  
   117  	rootfs := "/"
   118  	if !inHostNamespace {
   119  		rootfs = "/rootfs"
   120  	}
   121  
   122  	containerReference := info.ContainerReference{
   123  		Id:        id,
   124  		Name:      name,
   125  		Namespace: k8sContainerdNamespace,
   126  		Aliases:   []string{id, name},
   127  	}
   128  
   129  	// Containers that don't have their own network -- this includes
   130  	// containers running in Kubernetes pods that use the network of the
   131  	// infrastructure container -- does not need their stats to be
   132  	// reported. This stops metrics being reported multiple times for each
   133  	// container in a pod.
   134  	metrics := common.RemoveNetMetrics(includedMetrics, cntr.Labels["io.cri-containerd.kind"] != "sandbox")
   135  
   136  	libcontainerHandler := containerlibcontainer.NewHandler(cgroupManager, rootfs, int(taskPid), metrics)
   137  
   138  	handler := &containerdContainerHandler{
   139  		machineInfoFactory:  machineInfoFactory,
   140  		cgroupPaths:         cgroupPaths,
   141  		fsInfo:              fsInfo,
   142  		envs:                make(map[string]string),
   143  		labels:              cntr.Labels,
   144  		includedMetrics:     metrics,
   145  		reference:           containerReference,
   146  		libcontainerHandler: libcontainerHandler,
   147  	}
   148  	// Add the name and bare ID as aliases of the container.
   149  	handler.image = cntr.Image
   150  
   151  	for _, exposedEnv := range metadataEnvAllowList {
   152  		if exposedEnv == "" {
   153  			// if no containerdEnvWhitelist provided, len(metadataEnvAllowList) == 1, metadataEnvAllowList[0] == ""
   154  			continue
   155  		}
   156  
   157  		for _, envVar := range spec.Process.Env {
   158  			if envVar != "" {
   159  				splits := strings.SplitN(envVar, "=", 2)
   160  				if len(splits) == 2 && strings.HasPrefix(splits[0], exposedEnv) {
   161  					handler.envs[splits[0]] = splits[1]
   162  				}
   163  			}
   164  		}
   165  	}
   166  
   167  	return handler, nil
   168  }
   169  
   170  func (h *containerdContainerHandler) ContainerReference() (info.ContainerReference, error) {
   171  	return h.reference, nil
   172  }
   173  
   174  func (h *containerdContainerHandler) GetSpec() (info.ContainerSpec, error) {
   175  	// TODO: Since we dont collect disk usage stats for containerd, we set hasFilesystem
   176  	// to false. Revisit when we support disk usage stats for containerd
   177  	hasFilesystem := false
   178  	hasNet := h.includedMetrics.Has(container.NetworkUsageMetrics)
   179  	spec, err := common.GetSpec(h.cgroupPaths, h.machineInfoFactory, hasNet, hasFilesystem)
   180  	spec.Labels = h.labels
   181  	spec.Envs = h.envs
   182  	spec.Image = h.image
   183  
   184  	return spec, err
   185  }
   186  
   187  func (h *containerdContainerHandler) getFsStats(stats *info.ContainerStats) error {
   188  	mi, err := h.machineInfoFactory.GetMachineInfo()
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	if h.includedMetrics.Has(container.DiskIOMetrics) {
   194  		common.AssignDeviceNamesToDiskStats((*common.MachineInfoNamer)(mi), &stats.DiskIo)
   195  	}
   196  	return nil
   197  }
   198  
   199  func (h *containerdContainerHandler) GetStats() (*info.ContainerStats, error) {
   200  	stats, err := h.libcontainerHandler.GetStats()
   201  	if err != nil {
   202  		return stats, err
   203  	}
   204  
   205  	// Get filesystem stats.
   206  	err = h.getFsStats(stats)
   207  	return stats, err
   208  }
   209  
   210  func (h *containerdContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
   211  	return []info.ContainerReference{}, nil
   212  }
   213  
   214  func (h *containerdContainerHandler) GetCgroupPath(resource string) (string, error) {
   215  	var res string
   216  	if !cgroups.IsCgroup2UnifiedMode() {
   217  		res = resource
   218  	}
   219  	path, ok := h.cgroupPaths[res]
   220  	if !ok {
   221  		return "", fmt.Errorf("could not find path for resource %q for container %q", resource, h.reference.Name)
   222  	}
   223  	return path, nil
   224  }
   225  
   226  func (h *containerdContainerHandler) GetContainerLabels() map[string]string {
   227  	return h.labels
   228  }
   229  
   230  func (h *containerdContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
   231  	return h.libcontainerHandler.GetProcesses()
   232  }
   233  
   234  func (h *containerdContainerHandler) Exists() bool {
   235  	return common.CgroupExists(h.cgroupPaths)
   236  }
   237  
   238  func (h *containerdContainerHandler) Type() container.ContainerType {
   239  	return container.ContainerTypeContainerd
   240  }
   241  
   242  func (h *containerdContainerHandler) Start() {
   243  }
   244  
   245  func (h *containerdContainerHandler) Cleanup() {
   246  }
   247  
   248  func (h *containerdContainerHandler) GetContainerIPAddress() string {
   249  	// containerd doesnt take care of networking.So it doesnt maintain networking states
   250  	return ""
   251  }