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 }