github.com/google/cadvisor@v0.49.1/container/docker/factory.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  package docker
    16  
    17  import (
    18  	"flag"
    19  	"fmt"
    20  	"regexp"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/blang/semver/v4"
    27  	dockertypes "github.com/docker/docker/api/types"
    28  
    29  	"github.com/google/cadvisor/container"
    30  	dockerutil "github.com/google/cadvisor/container/docker/utils"
    31  	"github.com/google/cadvisor/container/libcontainer"
    32  	"github.com/google/cadvisor/devicemapper"
    33  	"github.com/google/cadvisor/fs"
    34  	info "github.com/google/cadvisor/info/v1"
    35  	"github.com/google/cadvisor/machine"
    36  	"github.com/google/cadvisor/watcher"
    37  	"github.com/google/cadvisor/zfs"
    38  
    39  	docker "github.com/docker/docker/client"
    40  	"golang.org/x/net/context"
    41  	"k8s.io/klog/v2"
    42  )
    43  
    44  var ArgDockerEndpoint = flag.String("docker", "unix:///var/run/docker.sock", "docker endpoint")
    45  var ArgDockerTLS = flag.Bool("docker-tls", false, "use TLS to connect to docker")
    46  var ArgDockerCert = flag.String("docker-tls-cert", "cert.pem", "path to client certificate")
    47  var ArgDockerKey = flag.String("docker-tls-key", "key.pem", "path to private key")
    48  var ArgDockerCA = flag.String("docker-tls-ca", "ca.pem", "path to trusted CA")
    49  
    50  var dockerEnvMetadataWhiteList = flag.String("docker_env_metadata_whitelist", "", "DEPRECATED: this flag will be removed, please use `env_metadata_whitelist`. A comma-separated list of environment variable keys matched with specified prefix that needs to be collected for docker containers")
    51  
    52  // The namespace under which Docker aliases are unique.
    53  const DockerNamespace = "docker"
    54  
    55  // The retry times for getting docker root dir
    56  const rootDirRetries = 5
    57  
    58  // The retry period for getting docker root dir, Millisecond
    59  const rootDirRetryPeriod time.Duration = 1000 * time.Millisecond
    60  
    61  var (
    62  	// Basepath to all container specific information that libcontainer stores.
    63  	dockerRootDir string
    64  
    65  	dockerRootDirFlag = flag.String("docker_root", "/var/lib/docker", "DEPRECATED: docker root is read from docker info (this is a fallback, default: /var/lib/docker)")
    66  
    67  	dockerRootDirOnce sync.Once
    68  
    69  	// flag that controls globally disabling thin_ls pending future enhancements.
    70  	// in production, it has been found that thin_ls makes excessive use of iops.
    71  	// in an iops restricted environment, usage of thin_ls must be controlled via blkio.
    72  	// pending that enhancement, disable its usage.
    73  	disableThinLs = true
    74  )
    75  
    76  func RootDir() string {
    77  	dockerRootDirOnce.Do(func() {
    78  		for i := 0; i < rootDirRetries; i++ {
    79  			status, err := Status()
    80  			if err == nil && status.RootDir != "" {
    81  				dockerRootDir = status.RootDir
    82  				break
    83  			} else {
    84  				time.Sleep(rootDirRetryPeriod)
    85  			}
    86  		}
    87  		if dockerRootDir == "" {
    88  			dockerRootDir = *dockerRootDirFlag
    89  		}
    90  	})
    91  	return dockerRootDir
    92  }
    93  
    94  type StorageDriver string
    95  
    96  const (
    97  	DevicemapperStorageDriver StorageDriver = "devicemapper"
    98  	AufsStorageDriver         StorageDriver = "aufs"
    99  	OverlayStorageDriver      StorageDriver = "overlay"
   100  	Overlay2StorageDriver     StorageDriver = "overlay2"
   101  	ZfsStorageDriver          StorageDriver = "zfs"
   102  	VfsStorageDriver          StorageDriver = "vfs"
   103  )
   104  
   105  type dockerFactory struct {
   106  	machineInfoFactory info.MachineInfoFactory
   107  
   108  	storageDriver StorageDriver
   109  	storageDir    string
   110  
   111  	client *docker.Client
   112  
   113  	// Information about the mounted cgroup subsystems.
   114  	cgroupSubsystems map[string]string
   115  
   116  	// Information about mounted filesystems.
   117  	fsInfo fs.FsInfo
   118  
   119  	dockerVersion []int
   120  
   121  	dockerAPIVersion []int
   122  
   123  	includedMetrics container.MetricSet
   124  
   125  	thinPoolName    string
   126  	thinPoolWatcher *devicemapper.ThinPoolWatcher
   127  
   128  	zfsWatcher *zfs.ZfsWatcher
   129  }
   130  
   131  func (f *dockerFactory) String() string {
   132  	return DockerNamespace
   133  }
   134  
   135  func (f *dockerFactory) NewContainerHandler(name string, metadataEnvAllowList []string, inHostNamespace bool) (handler container.ContainerHandler, err error) {
   136  	client, err := Client()
   137  	if err != nil {
   138  		return
   139  	}
   140  
   141  	dockerMetadataEnvAllowList := strings.Split(*dockerEnvMetadataWhiteList, ",")
   142  
   143  	// prefer using the unified metadataEnvAllowList
   144  	if len(metadataEnvAllowList) != 0 {
   145  		dockerMetadataEnvAllowList = metadataEnvAllowList
   146  	}
   147  
   148  	handler, err = newDockerContainerHandler(
   149  		client,
   150  		name,
   151  		f.machineInfoFactory,
   152  		f.fsInfo,
   153  		f.storageDriver,
   154  		f.storageDir,
   155  		f.cgroupSubsystems,
   156  		inHostNamespace,
   157  		dockerMetadataEnvAllowList,
   158  		f.dockerVersion,
   159  		f.includedMetrics,
   160  		f.thinPoolName,
   161  		f.thinPoolWatcher,
   162  		f.zfsWatcher,
   163  	)
   164  	return
   165  }
   166  
   167  // Docker handles all containers under /docker
   168  func (f *dockerFactory) CanHandleAndAccept(name string) (bool, bool, error) {
   169  	// if the container is not associated with docker, we can't handle it or accept it.
   170  	if !dockerutil.IsContainerName(name) {
   171  		return false, false, nil
   172  	}
   173  
   174  	// Check if the container is known to docker and it is active.
   175  	id := dockerutil.ContainerNameToId(name)
   176  
   177  	// We assume that if Inspect fails then the container is not known to docker.
   178  	ctnr, err := f.client.ContainerInspect(context.Background(), id)
   179  	if err != nil || !ctnr.State.Running {
   180  		return false, true, fmt.Errorf("error inspecting container: %v", err)
   181  	}
   182  
   183  	return true, true, nil
   184  }
   185  
   186  func (f *dockerFactory) DebugInfo() map[string][]string {
   187  	return map[string][]string{}
   188  }
   189  
   190  var (
   191  	versionRegexpString    = `(\d+)\.(\d+)\.(\d+)`
   192  	VersionRe              = regexp.MustCompile(versionRegexpString)
   193  	apiVersionRegexpString = `(\d+)\.(\d+)`
   194  	apiVersionRe           = regexp.MustCompile(apiVersionRegexpString)
   195  )
   196  
   197  func StartThinPoolWatcher(dockerInfo *dockertypes.Info) (*devicemapper.ThinPoolWatcher, error) {
   198  	_, err := devicemapper.ThinLsBinaryPresent()
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	if err := ensureThinLsKernelVersion(machine.KernelVersion()); err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	if disableThinLs {
   208  		return nil, fmt.Errorf("usage of thin_ls is disabled to preserve iops")
   209  	}
   210  
   211  	dockerThinPoolName, err := dockerutil.DockerThinPoolName(*dockerInfo)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  
   216  	dockerMetadataDevice, err := dockerutil.DockerMetadataDevice(*dockerInfo)
   217  	if err != nil {
   218  		return nil, err
   219  	}
   220  
   221  	thinPoolWatcher, err := devicemapper.NewThinPoolWatcher(dockerThinPoolName, dockerMetadataDevice)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	go thinPoolWatcher.Start()
   227  	return thinPoolWatcher, nil
   228  }
   229  
   230  func StartZfsWatcher(dockerInfo *dockertypes.Info) (*zfs.ZfsWatcher, error) {
   231  	filesystem, err := dockerutil.DockerZfsFilesystem(*dockerInfo)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	zfsWatcher, err := zfs.NewZfsWatcher(filesystem)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	go zfsWatcher.Start()
   242  	return zfsWatcher, nil
   243  }
   244  
   245  func ensureThinLsKernelVersion(kernelVersion string) error {
   246  	// kernel 4.4.0 has the proper bug fixes to allow thin_ls to work without corrupting the thin pool
   247  	minKernelVersion := semver.MustParse("4.4.0")
   248  	// RHEL 7 kernel 3.10.0 release >= 366 has the proper bug fixes backported from 4.4.0 to allow
   249  	// thin_ls to work without corrupting the thin pool
   250  	minRhel7KernelVersion := semver.MustParse("3.10.0")
   251  
   252  	matches := VersionRe.FindStringSubmatch(kernelVersion)
   253  	if len(matches) < 4 {
   254  		return fmt.Errorf("error parsing kernel version: %q is not a semver", kernelVersion)
   255  	}
   256  
   257  	sem, err := semver.Make(matches[0])
   258  	if err != nil {
   259  		return err
   260  	}
   261  
   262  	if sem.GTE(minKernelVersion) {
   263  		// kernel 4.4+ - good
   264  		return nil
   265  	}
   266  
   267  	// Certain RHEL/Centos 7.x kernels have a backport to fix the corruption bug
   268  	if !strings.Contains(kernelVersion, ".el7") {
   269  		// not a RHEL 7.x kernel - won't work
   270  		return fmt.Errorf("kernel version 4.4.0 or later is required to use thin_ls - you have %q", kernelVersion)
   271  	}
   272  
   273  	// RHEL/Centos 7.x from here on
   274  	if sem.Major != 3 {
   275  		// only 3.x kernels *may* work correctly
   276  		return fmt.Errorf("RHEL/Centos 7.x kernel version 3.10.0-366 or later is required to use thin_ls - you have %q", kernelVersion)
   277  	}
   278  
   279  	if sem.GT(minRhel7KernelVersion) {
   280  		// 3.10.1+ - good
   281  		return nil
   282  	}
   283  
   284  	if sem.EQ(minRhel7KernelVersion) {
   285  		// need to check release
   286  		releaseRE := regexp.MustCompile(`^[^-]+-([0-9]+)\.`)
   287  		releaseMatches := releaseRE.FindStringSubmatch(kernelVersion)
   288  		if len(releaseMatches) != 2 {
   289  			return fmt.Errorf("unable to determine RHEL/Centos 7.x kernel release from %q", kernelVersion)
   290  		}
   291  
   292  		release, err := strconv.Atoi(releaseMatches[1])
   293  		if err != nil {
   294  			return fmt.Errorf("error parsing release %q: %v", releaseMatches[1], err)
   295  		}
   296  
   297  		if release >= 366 {
   298  			return nil
   299  		}
   300  	}
   301  
   302  	return fmt.Errorf("RHEL/Centos 7.x kernel version 3.10.0-366 or later is required to use thin_ls - you have %q", kernelVersion)
   303  }
   304  
   305  // Register root container before running this function!
   306  func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) error {
   307  	client, err := Client()
   308  	if err != nil {
   309  		return fmt.Errorf("unable to communicate with docker daemon: %v", err)
   310  	}
   311  
   312  	dockerInfo, err := ValidateInfo(Info, VersionString)
   313  	if err != nil {
   314  		return fmt.Errorf("failed to validate Docker info: %v", err)
   315  	}
   316  
   317  	// Version already validated above, assume no error here.
   318  	dockerVersion, _ := ParseVersion(dockerInfo.ServerVersion, VersionRe, 3)
   319  
   320  	dockerAPIVersion, _ := APIVersion()
   321  
   322  	cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
   323  	if err != nil {
   324  		return fmt.Errorf("failed to get cgroup subsystems: %v", err)
   325  	}
   326  
   327  	var (
   328  		thinPoolWatcher *devicemapper.ThinPoolWatcher
   329  		thinPoolName    string
   330  		zfsWatcher      *zfs.ZfsWatcher
   331  	)
   332  	if includedMetrics.Has(container.DiskUsageMetrics) {
   333  		if StorageDriver(dockerInfo.Driver) == DevicemapperStorageDriver {
   334  			thinPoolWatcher, err = StartThinPoolWatcher(dockerInfo)
   335  			if err != nil {
   336  				klog.Errorf("devicemapper filesystem stats will not be reported: %v", err)
   337  			}
   338  
   339  			// Safe to ignore error - driver status should always be populated.
   340  			status, _ := StatusFromDockerInfo(*dockerInfo)
   341  			thinPoolName = status.DriverStatus[dockerutil.DriverStatusPoolName]
   342  		}
   343  
   344  		if StorageDriver(dockerInfo.Driver) == ZfsStorageDriver {
   345  			zfsWatcher, err = StartZfsWatcher(dockerInfo)
   346  			if err != nil {
   347  				klog.Errorf("zfs filesystem stats will not be reported: %v", err)
   348  			}
   349  		}
   350  	}
   351  
   352  	klog.V(1).Infof("Registering Docker factory")
   353  	f := &dockerFactory{
   354  		cgroupSubsystems:   cgroupSubsystems,
   355  		client:             client,
   356  		dockerVersion:      dockerVersion,
   357  		dockerAPIVersion:   dockerAPIVersion,
   358  		fsInfo:             fsInfo,
   359  		machineInfoFactory: factory,
   360  		storageDriver:      StorageDriver(dockerInfo.Driver),
   361  		storageDir:         RootDir(),
   362  		includedMetrics:    includedMetrics,
   363  		thinPoolName:       thinPoolName,
   364  		thinPoolWatcher:    thinPoolWatcher,
   365  		zfsWatcher:         zfsWatcher,
   366  	}
   367  
   368  	container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw})
   369  	return nil
   370  }