github.com/google/cadvisor@v0.49.1/validate/validate.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 /validate content. 16 // Validates cadvisor dependencies - kernel, os, docker setup. 17 18 package validate 19 20 import ( 21 "fmt" 22 "log" 23 "net/http" 24 "os" 25 "path" 26 "strings" 27 28 "github.com/google/cadvisor/container/docker" 29 "github.com/google/cadvisor/manager" 30 "github.com/google/cadvisor/utils" 31 32 "github.com/opencontainers/runc/libcontainer/cgroups" 33 ) 34 35 const ( 36 ValidatePage = "/validate/" 37 Supported = "[Supported, but not recommended]" 38 Unsupported = "[Unsupported]" 39 Recommended = "[Supported and recommended]" 40 Unknown = "[Unknown]" 41 VersionFormat = "%d.%d%s" 42 OutputFormat = "%s: %s\n\t%s\n\n" 43 ) 44 45 func getMajorMinor(version string) (int, int, error) { 46 var major, minor int 47 var ign string 48 n, err := fmt.Sscanf(version, VersionFormat, &major, &minor, &ign) 49 if n != 3 || err != nil { 50 log.Printf("Failed to parse version for %s", version) 51 return -1, -1, err 52 } 53 return major, minor, nil 54 } 55 56 func validateKernelVersion(version string) (string, string) { 57 desc := fmt.Sprintf("Kernel version is %s. Versions >= 2.6 are supported. 3.0+ are recommended.\n", version) 58 major, minor, err := getMajorMinor(version) 59 if err != nil { 60 desc = fmt.Sprintf("Could not parse kernel version. %s", desc) 61 return Unknown, desc 62 } 63 64 if major < 2 { 65 return Unsupported, desc 66 } 67 68 if major == 2 && minor < 6 { 69 return Unsupported, desc 70 } 71 72 if major >= 3 { 73 return Recommended, desc 74 } 75 76 return Supported, desc 77 } 78 79 func validateDockerVersion(version string) (string, string) { 80 desc := fmt.Sprintf("Docker version is %s. Versions >= 1.0 are supported. 1.2+ are recommended.\n", version) 81 major, minor, err := getMajorMinor(version) 82 if err != nil { 83 desc = fmt.Sprintf("Could not parse docker version. %s\n\t", desc) 84 return Unknown, desc 85 } 86 if major < 1 { 87 return Unsupported, desc 88 } 89 90 if major == 1 && minor < 2 { 91 return Supported, desc 92 } 93 94 return Recommended, desc 95 } 96 97 func getEnabledCgroups() (map[string]int, error) { 98 out, err := os.ReadFile("/proc/cgroups") 99 if err != nil { 100 return nil, err 101 } 102 cgroups := make(map[string]int) 103 for i, line := range strings.Split(string(out), "\n") { 104 var cgroup string 105 var ign, enabled int 106 if i == 0 || line == "" { 107 continue 108 } 109 n, err := fmt.Sscanf(line, "%s %d %d %d", &cgroup, &ign, &ign, &enabled) 110 if n != 4 || err != nil { 111 if err == nil { 112 err = fmt.Errorf("failed to parse /proc/cgroup entry %s", line) 113 } 114 return nil, err 115 } 116 cgroups[cgroup] = enabled 117 } 118 return cgroups, nil 119 } 120 121 func areCgroupsPresent(available map[string]int, desired []string) (bool, string) { 122 for _, cgroup := range desired { 123 enabled, ok := available[cgroup] 124 if !ok { 125 reason := fmt.Sprintf("Missing cgroup %s. Available cgroups: %v\n", cgroup, available) 126 return false, reason 127 } 128 if enabled != 1 { 129 reason := fmt.Sprintf("Cgroup %s not enabled. Available cgroups: %v\n", cgroup, available) 130 return false, reason 131 } 132 } 133 return true, "" 134 } 135 136 func validateCPUCFSBandwidth(availableCgroups map[string]int) string { 137 ok, _ := areCgroupsPresent(availableCgroups, []string{"cpu"}) 138 if !ok { 139 return "\tCpu cfs bandwidth status unknown: cpu cgroup not enabled.\n" 140 } 141 mnt, err := cgroups.FindCgroupMountpoint("/", "cpu") 142 if err != nil { 143 return "\tCpu cfs bandwidth status unknown: cpu cgroup not mounted.\n" 144 } 145 _, err = os.Stat(path.Join(mnt, "cpu.cfs_period_us")) 146 if os.IsNotExist(err) { 147 return "\tCpu cfs bandwidth is disabled. Recompile kernel with \"CONFIG_CFS_BANDWIDTH\" enabled.\n" 148 } 149 150 return "\tCpu cfs bandwidth is enabled.\n" 151 } 152 153 func validateMemoryAccounting(availableCgroups map[string]int) string { 154 ok, _ := areCgroupsPresent(availableCgroups, []string{"memory"}) 155 if !ok { 156 return "\tHierarchical memory accounting status unknown: memory cgroup not enabled.\n" 157 } 158 var enabled int 159 if cgroups.IsCgroup2UnifiedMode() { 160 enabled = 1 161 } else { 162 mnt, err := cgroups.FindCgroupMountpoint("/", "memory") 163 if err != nil { 164 return "\tHierarchical memory accounting status unknown: memory cgroup not mounted.\n" 165 } 166 hier, err := os.ReadFile(path.Join(mnt, "memory.use_hierarchy")) 167 if err != nil { 168 return "\tHierarchical memory accounting status unknown: hierarchy interface unavailable.\n" 169 } 170 n, err := fmt.Sscanf(string(hier), "%d", &enabled) 171 if err != nil || n != 1 { 172 return "\tHierarchical memory accounting status unknown: hierarchy interface unreadable.\n" 173 } 174 } 175 if enabled == 1 { 176 return "\tHierarchical memory accounting enabled. Reported memory usage includes memory used by child containers.\n" 177 } 178 return "\tHierarchical memory accounting disabled. Memory usage does not include usage from child containers.\n" 179 180 } 181 182 func validateCgroups() (string, string) { 183 requiredCgroups := []string{"cpu", "cpuacct"} 184 recommendedCgroups := []string{"memory", "blkio", "cpuset", "devices", "freezer"} 185 availableCgroups, err := getEnabledCgroups() 186 desc := fmt.Sprintf("\tFollowing cgroups are required: %v\n\tFollowing other cgroups are recommended: %v\n", requiredCgroups, recommendedCgroups) 187 if err != nil { 188 desc = fmt.Sprintf("Could not parse /proc/cgroups.\n%s", desc) 189 return Unknown, desc 190 } 191 ok, out := areCgroupsPresent(availableCgroups, requiredCgroups) 192 if !ok { 193 out += desc 194 return Unsupported, out 195 } 196 ok, out = areCgroupsPresent(availableCgroups, recommendedCgroups) 197 if !ok { 198 // supported, but not recommended. 199 out += desc 200 return Supported, out 201 } 202 out = fmt.Sprintf("Available cgroups: %v\n", availableCgroups) 203 out += desc 204 out += validateMemoryAccounting(availableCgroups) 205 out += validateCPUCFSBandwidth(availableCgroups) 206 return Recommended, out 207 } 208 209 func validateDockerInfo() (string, string) { 210 info, err := docker.ValidateInfo(docker.Info, docker.VersionString) 211 if err != nil { 212 return Unsupported, fmt.Sprintf("Docker setup is invalid: %v", err) 213 } 214 215 desc := fmt.Sprintf("Storage driver is %s.\n", info.Driver) 216 return Recommended, desc 217 } 218 219 func validateCgroupMounts() (string, string) { 220 const recommendedMount = "/sys/fs/cgroup" 221 desc := fmt.Sprintf("\tAny cgroup mount point that is detectible and accessible is supported. %s is recommended as a standard location.\n", recommendedMount) 222 mnt, err := cgroups.FindCgroupMountpoint("/", "cpu") 223 if err != nil { 224 out := "Could not locate cgroup mount point.\n" 225 out += desc 226 return Unknown, out 227 } 228 mnt = path.Dir(mnt) 229 if !utils.FileExists(mnt) { 230 out := fmt.Sprintf("Cgroup mount directory %s inaccessible.\n", mnt) 231 out += desc 232 return Unsupported, out 233 } 234 mounts, err := os.ReadDir(mnt) 235 if err != nil { 236 out := fmt.Sprintf("Could not read cgroup mount directory %s.\n", mnt) 237 out += desc 238 return Unsupported, out 239 } 240 mountNames := "\tCgroup mount directories: " 241 for _, mount := range mounts { 242 mountNames += mount.Name() + " " 243 } 244 mountNames += "\n" 245 out := fmt.Sprintf("Cgroups are mounted at %s.\n", mnt) 246 out += mountNames 247 out += desc 248 info, err := os.ReadFile("/proc/mounts") 249 if err != nil { 250 out := "Could not read /proc/mounts.\n" 251 out += desc 252 return Unsupported, out 253 } 254 out += "\tCgroup mounts:\n" 255 for _, line := range strings.Split(string(info), "\n") { 256 if strings.Contains(line, " cgroup ") { 257 out += "\t" + line + "\n" 258 } 259 } 260 if mnt == recommendedMount { 261 return Recommended, out 262 } 263 return Supported, out 264 } 265 266 func validateIoScheduler(containerManager manager.Manager) (string, string) { 267 var desc string 268 mi, err := containerManager.GetMachineInfo() 269 if err != nil { 270 return Unknown, "Machine info not available\n\t" 271 } 272 cfq := false 273 for _, disk := range mi.DiskMap { 274 desc += fmt.Sprintf("\t Disk %q Scheduler type %q.\n", disk.Name, disk.Scheduler) 275 if disk.Scheduler == "cfq" { 276 cfq = true 277 } 278 } 279 // Since we get lot of random block devices, report recommended if 280 // at least one of them is on cfq. Report Supported otherwise. 281 if cfq { 282 desc = "At least one device supports 'cfq' I/O scheduler. Some disk stats can be reported.\n" + desc 283 return Recommended, desc 284 } 285 desc = "None of the devices support 'cfq' I/O scheduler. No disk stats can be reported.\n" + desc 286 return Supported, desc 287 } 288 289 func HandleRequest(w http.ResponseWriter, containerManager manager.Manager) error { 290 // Get cAdvisor version Info. 291 versionInfo, err := containerManager.GetVersionInfo() 292 if err != nil { 293 return err 294 } 295 296 out := fmt.Sprintf("cAdvisor version: %s\n\n", versionInfo.CadvisorVersion) 297 298 // No OS is preferred or unsupported as of now. 299 out += fmt.Sprintf("OS version: %s\n\n", versionInfo.ContainerOsVersion) 300 301 kernelValidation, desc := validateKernelVersion(versionInfo.KernelVersion) 302 out += fmt.Sprintf(OutputFormat, "Kernel version", kernelValidation, desc) 303 304 cgroupValidation, desc := validateCgroups() 305 out += fmt.Sprintf(OutputFormat, "Cgroup setup", cgroupValidation, desc) 306 307 mountsValidation, desc := validateCgroupMounts() 308 out += fmt.Sprintf(OutputFormat, "Cgroup mount setup", mountsValidation, desc) 309 310 dockerValidation, desc := validateDockerVersion(versionInfo.DockerVersion) 311 out += fmt.Sprintf(OutputFormat, "Docker version", dockerValidation, desc) 312 313 dockerInfoValidation, desc := validateDockerInfo() 314 out += fmt.Sprintf(OutputFormat, "Docker driver setup", dockerInfoValidation, desc) 315 316 ioSchedulerValidation, desc := validateIoScheduler(containerManager) 317 out += fmt.Sprintf(OutputFormat, "Block device setup", ioSchedulerValidation, desc) 318 319 // Output debug info. 320 debugInfo := containerManager.DebugInfo() 321 for category, lines := range debugInfo { 322 out += fmt.Sprintf(OutputFormat, category, "", strings.Join(lines, "\n\t")) 323 } 324 325 _, err = w.Write([]byte(out)) 326 return err 327 }