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

     1  // Copyright 2014 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 Docker containers.
    16  package docker
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  	"path"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/google/cadvisor/container"
    27  	"github.com/google/cadvisor/container/common"
    28  	dockerutil "github.com/google/cadvisor/container/docker/utils"
    29  	containerlibcontainer "github.com/google/cadvisor/container/libcontainer"
    30  	"github.com/google/cadvisor/devicemapper"
    31  	"github.com/google/cadvisor/fs"
    32  	info "github.com/google/cadvisor/info/v1"
    33  	"github.com/google/cadvisor/zfs"
    34  	"github.com/opencontainers/runc/libcontainer/cgroups"
    35  
    36  	docker "github.com/docker/docker/client"
    37  	"golang.org/x/net/context"
    38  )
    39  
    40  const (
    41  	// The read write layers exist here.
    42  	aufsRWLayer     = "diff"
    43  	overlayRWLayer  = "upper"
    44  	overlay2RWLayer = "diff"
    45  
    46  	// Path to the directory where docker stores log files if the json logging driver is enabled.
    47  	pathToContainersDir = "containers"
    48  )
    49  
    50  type dockerContainerHandler struct {
    51  	// machineInfoFactory provides info.MachineInfo
    52  	machineInfoFactory info.MachineInfoFactory
    53  
    54  	// Absolute path to the cgroup hierarchies of this container.
    55  	// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
    56  	cgroupPaths map[string]string
    57  
    58  	// the docker storage driver
    59  	storageDriver    StorageDriver
    60  	fsInfo           fs.FsInfo
    61  	rootfsStorageDir string
    62  
    63  	// Time at which this container was created.
    64  	creationTime time.Time
    65  
    66  	// Metadata associated with the container.
    67  	envs   map[string]string
    68  	labels map[string]string
    69  
    70  	// Image name used for this container.
    71  	image string
    72  
    73  	// Filesystem handler.
    74  	fsHandler common.FsHandler
    75  
    76  	// The IP address of the container
    77  	ipAddress string
    78  
    79  	includedMetrics container.MetricSet
    80  
    81  	// the devicemapper poolname
    82  	poolName string
    83  
    84  	// zfsParent is the parent for docker zfs
    85  	zfsParent string
    86  
    87  	// Reference to the container
    88  	reference info.ContainerReference
    89  
    90  	libcontainerHandler *containerlibcontainer.Handler
    91  }
    92  
    93  var _ container.ContainerHandler = &dockerContainerHandler{}
    94  
    95  func getRwLayerID(containerID, storageDir string, sd StorageDriver, dockerVersion []int) (string, error) {
    96  	const (
    97  		// Docker version >=1.10.0 have a randomized ID for the root fs of a container.
    98  		randomizedRWLayerMinorVersion = 10
    99  		rwLayerIDFile                 = "mount-id"
   100  	)
   101  	if (dockerVersion[0] <= 1) && (dockerVersion[1] < randomizedRWLayerMinorVersion) {
   102  		return containerID, nil
   103  	}
   104  
   105  	bytes, err := os.ReadFile(path.Join(storageDir, "image", string(sd), "layerdb", "mounts", containerID, rwLayerIDFile))
   106  	if err != nil {
   107  		return "", fmt.Errorf("failed to identify the read-write layer ID for container %q. - %v", containerID, err)
   108  	}
   109  	return string(bytes), err
   110  }
   111  
   112  // newDockerContainerHandler returns a new container.ContainerHandler
   113  func newDockerContainerHandler(
   114  	client *docker.Client,
   115  	name string,
   116  	machineInfoFactory info.MachineInfoFactory,
   117  	fsInfo fs.FsInfo,
   118  	storageDriver StorageDriver,
   119  	storageDir string,
   120  	cgroupSubsystems map[string]string,
   121  	inHostNamespace bool,
   122  	metadataEnvAllowList []string,
   123  	dockerVersion []int,
   124  	includedMetrics container.MetricSet,
   125  	thinPoolName string,
   126  	thinPoolWatcher *devicemapper.ThinPoolWatcher,
   127  	zfsWatcher *zfs.ZfsWatcher,
   128  ) (container.ContainerHandler, error) {
   129  	// Create the cgroup paths.
   130  	cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems, name)
   131  
   132  	// Generate the equivalent cgroup manager for this container.
   133  	cgroupManager, err := containerlibcontainer.NewCgroupManager(name, cgroupPaths)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	rootFs := "/"
   139  	if !inHostNamespace {
   140  		rootFs = "/rootfs"
   141  		storageDir = path.Join(rootFs, storageDir)
   142  	}
   143  
   144  	id := dockerutil.ContainerNameToId(name)
   145  
   146  	// Add the Containers dir where the log files are stored.
   147  	// FIXME: Give `otherStorageDir` a more descriptive name.
   148  	otherStorageDir := path.Join(storageDir, pathToContainersDir, id)
   149  
   150  	rwLayerID, err := getRwLayerID(id, storageDir, storageDriver, dockerVersion)
   151  	if err != nil {
   152  		return nil, err
   153  	}
   154  
   155  	// Determine the rootfs storage dir OR the pool name to determine the device.
   156  	// For devicemapper, we only need the thin pool name, and that is passed in to this call
   157  	rootfsStorageDir, zfsFilesystem, zfsParent, err := DetermineDeviceStorage(storageDriver, storageDir, rwLayerID)
   158  	if err != nil {
   159  		return nil, fmt.Errorf("unable to determine device storage: %v", err)
   160  	}
   161  
   162  	// We assume that if Inspect fails then the container is not known to docker.
   163  	ctnr, err := client.ContainerInspect(context.Background(), id)
   164  	if err != nil {
   165  		return nil, fmt.Errorf("failed to inspect container %q: %v", id, err)
   166  	}
   167  
   168  	// Do not report network metrics for containers that share netns with another container.
   169  	metrics := common.RemoveNetMetrics(includedMetrics, ctnr.HostConfig.NetworkMode.IsContainer())
   170  
   171  	// TODO: extract object mother method
   172  	handler := &dockerContainerHandler{
   173  		machineInfoFactory: machineInfoFactory,
   174  		cgroupPaths:        cgroupPaths,
   175  		fsInfo:             fsInfo,
   176  		storageDriver:      storageDriver,
   177  		poolName:           thinPoolName,
   178  		rootfsStorageDir:   rootfsStorageDir,
   179  		envs:               make(map[string]string),
   180  		labels:             ctnr.Config.Labels,
   181  		includedMetrics:    metrics,
   182  		zfsParent:          zfsParent,
   183  	}
   184  	// Timestamp returned by Docker is in time.RFC3339Nano format.
   185  	handler.creationTime, err = time.Parse(time.RFC3339Nano, ctnr.Created)
   186  	if err != nil {
   187  		// This should not happen, report the error just in case
   188  		return nil, fmt.Errorf("failed to parse the create timestamp %q for container %q: %v", ctnr.Created, id, err)
   189  	}
   190  	handler.libcontainerHandler = containerlibcontainer.NewHandler(cgroupManager, rootFs, ctnr.State.Pid, metrics)
   191  
   192  	// Add the name and bare ID as aliases of the container.
   193  	handler.reference = info.ContainerReference{
   194  		Id:        id,
   195  		Name:      name,
   196  		Aliases:   []string{strings.TrimPrefix(ctnr.Name, "/"), id},
   197  		Namespace: DockerNamespace,
   198  	}
   199  	handler.image = ctnr.Config.Image
   200  	// Only adds restartcount label if it's greater than 0
   201  	if ctnr.RestartCount > 0 {
   202  		handler.labels["restartcount"] = strconv.Itoa(ctnr.RestartCount)
   203  	}
   204  
   205  	// Obtain the IP address for the container.
   206  	// If the NetworkMode starts with 'container:' then we need to use the IP address of the container specified.
   207  	// This happens in cases such as kubernetes where the containers doesn't have an IP address itself and we need to use the pod's address
   208  	ipAddress := ctnr.NetworkSettings.IPAddress
   209  	networkMode := string(ctnr.HostConfig.NetworkMode)
   210  	if ipAddress == "" && strings.HasPrefix(networkMode, "container:") {
   211  		containerID := strings.TrimPrefix(networkMode, "container:")
   212  		c, err := client.ContainerInspect(context.Background(), containerID)
   213  		if err != nil {
   214  			return nil, fmt.Errorf("failed to inspect container %q: %v", id, err)
   215  		}
   216  		ipAddress = c.NetworkSettings.IPAddress
   217  	}
   218  
   219  	handler.ipAddress = ipAddress
   220  
   221  	if includedMetrics.Has(container.DiskUsageMetrics) {
   222  		handler.fsHandler = &FsHandler{
   223  			FsHandler:       common.NewFsHandler(common.DefaultPeriod, rootfsStorageDir, otherStorageDir, fsInfo),
   224  			ThinPoolWatcher: thinPoolWatcher,
   225  			ZfsWatcher:      zfsWatcher,
   226  			DeviceID:        ctnr.GraphDriver.Data["DeviceId"],
   227  			ZfsFilesystem:   zfsFilesystem,
   228  		}
   229  	}
   230  
   231  	// split env vars to get metadata map.
   232  	for _, exposedEnv := range metadataEnvAllowList {
   233  		if exposedEnv == "" {
   234  			// if no dockerEnvWhitelist provided, len(metadataEnvAllowList) == 1, metadataEnvAllowList[0] == ""
   235  			continue
   236  		}
   237  
   238  		for _, envVar := range ctnr.Config.Env {
   239  			if envVar != "" {
   240  				splits := strings.SplitN(envVar, "=", 2)
   241  				if len(splits) == 2 && strings.HasPrefix(splits[0], exposedEnv) {
   242  					handler.envs[strings.ToLower(splits[0])] = splits[1]
   243  				}
   244  			}
   245  		}
   246  	}
   247  
   248  	return handler, nil
   249  }
   250  
   251  func DetermineDeviceStorage(storageDriver StorageDriver, storageDir string, rwLayerID string) (
   252  	rootfsStorageDir string, zfsFilesystem string, zfsParent string, err error) {
   253  	switch storageDriver {
   254  	case AufsStorageDriver:
   255  		rootfsStorageDir = path.Join(storageDir, string(AufsStorageDriver), aufsRWLayer, rwLayerID)
   256  	case OverlayStorageDriver:
   257  		rootfsStorageDir = path.Join(storageDir, string(storageDriver), rwLayerID, overlayRWLayer)
   258  	case Overlay2StorageDriver:
   259  		rootfsStorageDir = path.Join(storageDir, string(storageDriver), rwLayerID, overlay2RWLayer)
   260  	case VfsStorageDriver:
   261  		rootfsStorageDir = path.Join(storageDir)
   262  	case ZfsStorageDriver:
   263  		var status info.DockerStatus
   264  		status, err = Status()
   265  		if err != nil {
   266  			return
   267  		}
   268  		zfsParent = status.DriverStatus[dockerutil.DriverStatusParentDataset]
   269  		zfsFilesystem = path.Join(zfsParent, rwLayerID)
   270  	}
   271  	return
   272  }
   273  
   274  func (h *dockerContainerHandler) Start() {
   275  	if h.fsHandler != nil {
   276  		h.fsHandler.Start()
   277  	}
   278  }
   279  
   280  func (h *dockerContainerHandler) Cleanup() {
   281  	if h.fsHandler != nil {
   282  		h.fsHandler.Stop()
   283  	}
   284  }
   285  
   286  func (h *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) {
   287  	return h.reference, nil
   288  }
   289  
   290  func (h *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) {
   291  	hasFilesystem := h.includedMetrics.Has(container.DiskUsageMetrics)
   292  	hasNetwork := h.includedMetrics.Has(container.NetworkUsageMetrics)
   293  	spec, err := common.GetSpec(h.cgroupPaths, h.machineInfoFactory, hasNetwork, hasFilesystem)
   294  
   295  	spec.Labels = h.labels
   296  	spec.Envs = h.envs
   297  	spec.Image = h.image
   298  	spec.CreationTime = h.creationTime
   299  
   300  	return spec, err
   301  }
   302  
   303  // TODO(vmarmol): Get from libcontainer API instead of cgroup manager when we don't have to support older Dockers.
   304  func (h *dockerContainerHandler) GetStats() (*info.ContainerStats, error) {
   305  	stats, err := h.libcontainerHandler.GetStats()
   306  	if err != nil {
   307  		return stats, err
   308  	}
   309  
   310  	// Get filesystem stats.
   311  	err = FsStats(stats, h.machineInfoFactory, h.includedMetrics, h.storageDriver,
   312  		h.fsHandler, h.fsInfo, h.poolName, h.rootfsStorageDir, h.zfsParent)
   313  	if err != nil {
   314  		return stats, err
   315  	}
   316  
   317  	return stats, nil
   318  }
   319  
   320  func (h *dockerContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
   321  	// No-op for Docker driver.
   322  	return []info.ContainerReference{}, nil
   323  }
   324  
   325  func (h *dockerContainerHandler) GetCgroupPath(resource string) (string, error) {
   326  	var res string
   327  	if !cgroups.IsCgroup2UnifiedMode() {
   328  		res = resource
   329  	}
   330  	path, ok := h.cgroupPaths[res]
   331  	if !ok {
   332  		return "", fmt.Errorf("could not find path for resource %q for container %q", resource, h.reference.Name)
   333  	}
   334  	return path, nil
   335  }
   336  
   337  func (h *dockerContainerHandler) GetContainerLabels() map[string]string {
   338  	return h.labels
   339  }
   340  
   341  func (h *dockerContainerHandler) GetContainerIPAddress() string {
   342  	return h.ipAddress
   343  }
   344  
   345  func (h *dockerContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
   346  	return h.libcontainerHandler.GetProcesses()
   347  }
   348  
   349  func (h *dockerContainerHandler) Exists() bool {
   350  	return common.CgroupExists(h.cgroupPaths)
   351  }
   352  
   353  func (h *dockerContainerHandler) Type() container.ContainerType {
   354  	return container.ContainerTypeDocker
   355  }