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 }