k8s.io/kubernetes@v1.29.3/pkg/kubelet/stats/cadvisor_stats_provider.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package stats 18 19 import ( 20 "context" 21 "fmt" 22 "path/filepath" 23 "sort" 24 "strings" 25 26 cadvisorapiv2 "github.com/google/cadvisor/info/v2" 27 "k8s.io/klog/v2" 28 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "k8s.io/apimachinery/pkg/types" 31 utilfeature "k8s.io/apiserver/pkg/util/feature" 32 statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" 33 kubetypes "k8s.io/kubelet/pkg/types" 34 "k8s.io/kubernetes/pkg/features" 35 "k8s.io/kubernetes/pkg/kubelet/cadvisor" 36 "k8s.io/kubernetes/pkg/kubelet/cm" 37 kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" 38 "k8s.io/kubernetes/pkg/kubelet/leaky" 39 "k8s.io/kubernetes/pkg/kubelet/server/stats" 40 "k8s.io/kubernetes/pkg/kubelet/status" 41 ) 42 43 // cadvisorStatsProvider implements the containerStatsProvider interface by 44 // getting the container stats from cAdvisor. This is needed by 45 // integrations which do not provide stats from CRI. See 46 // `pkg/kubelet/cadvisor/util.go#UsingLegacyCadvisorStats` for the logic for 47 // determining which integrations do not provide stats from CRI. 48 type cadvisorStatsProvider struct { 49 // cadvisor is used to get the stats of the cgroup for the containers that 50 // are managed by pods. 51 cadvisor cadvisor.Interface 52 // resourceAnalyzer is used to get the volume stats of the pods. 53 resourceAnalyzer stats.ResourceAnalyzer 54 // imageService is used to get the stats of the image filesystem. 55 imageService kubecontainer.ImageService 56 // statusProvider is used to get pod metadata 57 statusProvider status.PodStatusProvider 58 // hostStatsProvider is used to get pod host stat usage. 59 hostStatsProvider HostStatsProvider 60 } 61 62 // newCadvisorStatsProvider returns a containerStatsProvider that provides 63 // container stats from cAdvisor. 64 func newCadvisorStatsProvider( 65 cadvisor cadvisor.Interface, 66 resourceAnalyzer stats.ResourceAnalyzer, 67 imageService kubecontainer.ImageService, 68 statusProvider status.PodStatusProvider, 69 hostStatsProvider HostStatsProvider, 70 ) containerStatsProvider { 71 return &cadvisorStatsProvider{ 72 cadvisor: cadvisor, 73 resourceAnalyzer: resourceAnalyzer, 74 imageService: imageService, 75 statusProvider: statusProvider, 76 hostStatsProvider: hostStatsProvider, 77 } 78 } 79 80 // ListPodStats returns the stats of all the pod-managed containers. 81 func (p *cadvisorStatsProvider) ListPodStats(_ context.Context) ([]statsapi.PodStats, error) { 82 // Gets node root filesystem information and image filesystem stats, which 83 // will be used to populate the available and capacity bytes/inodes in 84 // container stats. 85 rootFsInfo, err := p.cadvisor.RootFsInfo() 86 if err != nil { 87 return nil, fmt.Errorf("failed to get rootFs info: %v", err) 88 } 89 imageFsInfo, err := p.cadvisor.ImagesFsInfo() 90 if err != nil { 91 return nil, fmt.Errorf("failed to get imageFs info: %v", err) 92 } 93 infos, err := getCadvisorContainerInfo(p.cadvisor) 94 if err != nil { 95 return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err) 96 } 97 98 filteredInfos, allInfos := filterTerminatedContainerInfoAndAssembleByPodCgroupKey(infos) 99 // Map each container to a pod and update the PodStats with container data. 100 podToStats := map[statsapi.PodReference]*statsapi.PodStats{} 101 for key, cinfo := range filteredInfos { 102 // On systemd using devicemapper each mount into the container has an 103 // associated cgroup. We ignore them to ensure we do not get duplicate 104 // entries in our summary. For details on .mount units: 105 // http://man7.org/linux/man-pages/man5/systemd.mount.5.html 106 if strings.HasSuffix(key, ".mount") { 107 continue 108 } 109 // Build the Pod key if this container is managed by a Pod 110 if !isPodManagedContainer(&cinfo) { 111 continue 112 } 113 ref := buildPodRef(cinfo.Spec.Labels) 114 115 // Lookup the PodStats for the pod using the PodRef. If none exists, 116 // initialize a new entry. 117 podStats, found := podToStats[ref] 118 if !found { 119 podStats = &statsapi.PodStats{PodRef: ref} 120 podToStats[ref] = podStats 121 } 122 123 // Update the PodStats entry with the stats from the container by 124 // adding it to podStats.Containers. 125 containerName := kubetypes.GetContainerName(cinfo.Spec.Labels) 126 if containerName == leaky.PodInfraContainerName { 127 // Special case for infrastructure container which is hidden from 128 // the user and has network stats. 129 podStats.Network = cadvisorInfoToNetworkStats(&cinfo) 130 } else { 131 containerStat := cadvisorInfoToContainerStats(containerName, &cinfo, &rootFsInfo, &imageFsInfo) 132 // NOTE: This doesn't support the old pod log path, `/var/log/pods/UID`. For containers 133 // using old log path, they will be populated by cadvisorInfoToContainerStats. 134 podUID := types.UID(podStats.PodRef.UID) 135 logs, err := p.hostStatsProvider.getPodContainerLogStats(podStats.PodRef.Namespace, podStats.PodRef.Name, podUID, containerName, &rootFsInfo) 136 if err != nil { 137 klog.ErrorS(err, "Unable to fetch container log stats", "containerName", containerName) 138 } else { 139 containerStat.Logs = logs 140 } 141 podStats.Containers = append(podStats.Containers, *containerStat) 142 } 143 } 144 145 // Add each PodStats to the result. 146 result := make([]statsapi.PodStats, 0, len(podToStats)) 147 for _, podStats := range podToStats { 148 makePodStorageStats(podStats, &rootFsInfo, p.resourceAnalyzer, p.hostStatsProvider, false) 149 150 podUID := types.UID(podStats.PodRef.UID) 151 // Lookup the pod-level cgroup's CPU and memory stats 152 podInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos) 153 if podInfo != nil { 154 cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo) 155 podStats.CPU = cpu 156 podStats.Memory = memory 157 podStats.Swap = cadvisorInfoToSwapStats(podInfo) 158 podStats.ProcessStats = cadvisorInfoToProcessStats(podInfo) 159 } 160 161 status, found := p.statusProvider.GetPodStatus(podUID) 162 if found && status.StartTime != nil && !status.StartTime.IsZero() { 163 podStats.StartTime = *status.StartTime 164 // only append stats if we were able to get the start time of the pod 165 result = append(result, *podStats) 166 } 167 } 168 169 return result, nil 170 } 171 172 // ListPodStatsAndUpdateCPUNanoCoreUsage updates the cpu nano core usage for 173 // the containers and returns the stats for all the pod-managed containers. 174 // For cadvisor, cpu nano core usages are pre-computed and cached, so this 175 // function simply calls ListPodStats. 176 func (p *cadvisorStatsProvider) ListPodStatsAndUpdateCPUNanoCoreUsage(ctx context.Context) ([]statsapi.PodStats, error) { 177 return p.ListPodStats(ctx) 178 } 179 180 // ListPodCPUAndMemoryStats returns the cpu and memory stats of all the pod-managed containers. 181 func (p *cadvisorStatsProvider) ListPodCPUAndMemoryStats(_ context.Context) ([]statsapi.PodStats, error) { 182 infos, err := getCadvisorContainerInfo(p.cadvisor) 183 if err != nil { 184 return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err) 185 } 186 filteredInfos, allInfos := filterTerminatedContainerInfoAndAssembleByPodCgroupKey(infos) 187 // Map each container to a pod and update the PodStats with container data. 188 podToStats := map[statsapi.PodReference]*statsapi.PodStats{} 189 for key, cinfo := range filteredInfos { 190 // On systemd using devicemapper each mount into the container has an 191 // associated cgroup. We ignore them to ensure we do not get duplicate 192 // entries in our summary. For details on .mount units: 193 // http://man7.org/linux/man-pages/man5/systemd.mount.5.html 194 if strings.HasSuffix(key, ".mount") { 195 continue 196 } 197 // Build the Pod key if this container is managed by a Pod 198 if !isPodManagedContainer(&cinfo) { 199 continue 200 } 201 ref := buildPodRef(cinfo.Spec.Labels) 202 203 // Lookup the PodStats for the pod using the PodRef. If none exists, 204 // initialize a new entry. 205 podStats, found := podToStats[ref] 206 if !found { 207 podStats = &statsapi.PodStats{PodRef: ref} 208 podToStats[ref] = podStats 209 } 210 211 // Update the PodStats entry with the stats from the container by 212 // adding it to podStats.Containers. 213 containerName := kubetypes.GetContainerName(cinfo.Spec.Labels) 214 if containerName == leaky.PodInfraContainerName { 215 // Special case for infrastructure container which is hidden from 216 // the user and has network stats. 217 podStats.StartTime = metav1.NewTime(cinfo.Spec.CreationTime) 218 } else { 219 podStats.Containers = append(podStats.Containers, *cadvisorInfoToContainerCPUAndMemoryStats(containerName, &cinfo)) 220 } 221 } 222 223 // Add each PodStats to the result. 224 result := make([]statsapi.PodStats, 0, len(podToStats)) 225 for _, podStats := range podToStats { 226 podUID := types.UID(podStats.PodRef.UID) 227 // Lookup the pod-level cgroup's CPU and memory stats 228 podInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos) 229 if podInfo != nil { 230 cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo) 231 podStats.CPU = cpu 232 podStats.Memory = memory 233 podStats.Swap = cadvisorInfoToSwapStats(podInfo) 234 } 235 result = append(result, *podStats) 236 } 237 238 return result, nil 239 } 240 241 // ImageFsStats returns the stats of the filesystem for storing images. 242 func (p *cadvisorStatsProvider) ImageFsStats(ctx context.Context) (imageFsRet *statsapi.FsStats, containerFsRet *statsapi.FsStats, errCall error) { 243 imageFsInfo, err := p.cadvisor.ImagesFsInfo() 244 if err != nil { 245 return nil, nil, fmt.Errorf("failed to get imageFs info: %v", err) 246 } 247 248 if !utilfeature.DefaultFeatureGate.Enabled(features.KubeletSeparateDiskGC) { 249 imageStats, err := p.imageService.ImageStats(ctx) 250 if err != nil || imageStats == nil { 251 return nil, nil, fmt.Errorf("failed to get image stats: %v", err) 252 } 253 254 var imageFsInodesUsed *uint64 255 if imageFsInfo.Inodes != nil && imageFsInfo.InodesFree != nil { 256 imageFsIU := *imageFsInfo.Inodes - *imageFsInfo.InodesFree 257 imageFsInodesUsed = &imageFsIU 258 } 259 260 imageFs := &statsapi.FsStats{ 261 Time: metav1.NewTime(imageFsInfo.Timestamp), 262 AvailableBytes: &imageFsInfo.Available, 263 CapacityBytes: &imageFsInfo.Capacity, 264 UsedBytes: &imageStats.TotalStorageBytes, 265 InodesFree: imageFsInfo.InodesFree, 266 Inodes: imageFsInfo.Inodes, 267 InodesUsed: imageFsInodesUsed, 268 } 269 return imageFs, imageFs, nil 270 } 271 containerFsInfo, err := p.cadvisor.ContainerFsInfo() 272 if err != nil { 273 return nil, nil, fmt.Errorf("failed to get container fs info: %v", err) 274 } 275 imageStats, err := p.imageService.ImageFsInfo(ctx) 276 if err != nil || imageStats == nil { 277 return nil, nil, fmt.Errorf("failed to get image stats: %v", err) 278 } 279 splitFileSystem := false 280 if imageStats.ImageFilesystems[0].FsId.Mountpoint != imageStats.ContainerFilesystems[0].FsId.Mountpoint { 281 klog.InfoS("Detect Split Filesystem", "ImageFilesystems", imageStats.ImageFilesystems[0], "ContainerFilesystems", imageStats.ContainerFilesystems[0]) 282 splitFileSystem = true 283 } 284 imageFs := imageStats.ImageFilesystems[0] 285 var imageFsInodesUsed *uint64 286 if imageFsInfo.Inodes != nil && imageFsInfo.InodesFree != nil { 287 imageFsIU := *imageFsInfo.Inodes - *imageFsInfo.InodesFree 288 imageFsInodesUsed = &imageFsIU 289 } 290 291 fsStats := &statsapi.FsStats{ 292 Time: metav1.NewTime(imageFsInfo.Timestamp), 293 AvailableBytes: &imageFsInfo.Available, 294 CapacityBytes: &imageFsInfo.Capacity, 295 UsedBytes: &imageFs.UsedBytes.Value, 296 InodesFree: imageFsInfo.InodesFree, 297 Inodes: imageFsInfo.Inodes, 298 InodesUsed: imageFsInodesUsed, 299 } 300 if !splitFileSystem { 301 return fsStats, fsStats, nil 302 } 303 304 containerFs := imageStats.ContainerFilesystems[0] 305 var containerFsInodesUsed *uint64 306 if containerFsInfo.Inodes != nil && containerFsInfo.InodesFree != nil { 307 containerFsIU := *containerFsInfo.Inodes - *containerFsInfo.InodesFree 308 containerFsInodesUsed = &containerFsIU 309 } 310 311 fsContainerStats := &statsapi.FsStats{ 312 Time: metav1.NewTime(containerFsInfo.Timestamp), 313 AvailableBytes: &containerFsInfo.Available, 314 CapacityBytes: &containerFsInfo.Capacity, 315 UsedBytes: &containerFs.UsedBytes.Value, 316 InodesFree: containerFsInfo.InodesFree, 317 Inodes: containerFsInfo.Inodes, 318 InodesUsed: containerFsInodesUsed, 319 } 320 321 return fsStats, fsContainerStats, nil 322 } 323 324 // ImageFsDevice returns name of the device where the image filesystem locates, 325 // e.g. /dev/sda1. 326 func (p *cadvisorStatsProvider) ImageFsDevice(_ context.Context) (string, error) { 327 imageFsInfo, err := p.cadvisor.ImagesFsInfo() 328 if err != nil { 329 return "", err 330 } 331 return imageFsInfo.Device, nil 332 } 333 334 // buildPodRef returns a PodReference that identifies the Pod managing cinfo 335 func buildPodRef(containerLabels map[string]string) statsapi.PodReference { 336 podName := kubetypes.GetPodName(containerLabels) 337 podNamespace := kubetypes.GetPodNamespace(containerLabels) 338 podUID := kubetypes.GetPodUID(containerLabels) 339 return statsapi.PodReference{Name: podName, Namespace: podNamespace, UID: podUID} 340 } 341 342 // isPodManagedContainer returns true if the cinfo container is managed by a Pod 343 func isPodManagedContainer(cinfo *cadvisorapiv2.ContainerInfo) bool { 344 podName := kubetypes.GetPodName(cinfo.Spec.Labels) 345 podNamespace := kubetypes.GetPodNamespace(cinfo.Spec.Labels) 346 managed := podName != "" && podNamespace != "" 347 if !managed && podName != podNamespace { 348 klog.InfoS( 349 "Expect container to have either both podName and podNamespace labels, or neither", 350 "podNameLabel", podName, "podNamespaceLabel", podNamespace) 351 } 352 return managed 353 } 354 355 // getCadvisorPodInfoFromPodUID returns a pod cgroup information by matching the podUID with its CgroupName identifier base name 356 func getCadvisorPodInfoFromPodUID(podUID types.UID, infos map[string]cadvisorapiv2.ContainerInfo) *cadvisorapiv2.ContainerInfo { 357 if info, found := infos[cm.GetPodCgroupNameSuffix(podUID)]; found { 358 return &info 359 } 360 return nil 361 } 362 363 // filterTerminatedContainerInfoAndAssembleByPodCgroupKey returns the specified containerInfo but with 364 // the stats of the terminated containers removed and all containerInfos assembled by pod cgroup key. 365 // the first return map is container cgroup name <-> ContainerInfo and 366 // the second return map is pod cgroup key <-> ContainerInfo. 367 // A ContainerInfo is considered to be of a terminated container if it has an 368 // older CreationTime and zero CPU instantaneous and memory RSS usage. 369 func filterTerminatedContainerInfoAndAssembleByPodCgroupKey(containerInfo map[string]cadvisorapiv2.ContainerInfo) (map[string]cadvisorapiv2.ContainerInfo, map[string]cadvisorapiv2.ContainerInfo) { 370 cinfoMap := make(map[containerID][]containerInfoWithCgroup) 371 cinfosByPodCgroupKey := make(map[string]cadvisorapiv2.ContainerInfo) 372 for key, cinfo := range containerInfo { 373 var podCgroupKey string 374 if cm.IsSystemdStyleName(key) { 375 // Convert to internal cgroup name and take the last component only. 376 internalCgroupName := cm.ParseSystemdToCgroupName(key) 377 podCgroupKey = internalCgroupName[len(internalCgroupName)-1] 378 } else { 379 // Take last component only. 380 podCgroupKey = filepath.Base(key) 381 } 382 cinfosByPodCgroupKey[podCgroupKey] = cinfo 383 if !isPodManagedContainer(&cinfo) { 384 continue 385 } 386 cinfoID := containerID{ 387 podRef: buildPodRef(cinfo.Spec.Labels), 388 containerName: kubetypes.GetContainerName(cinfo.Spec.Labels), 389 } 390 cinfoMap[cinfoID] = append(cinfoMap[cinfoID], containerInfoWithCgroup{ 391 cinfo: cinfo, 392 cgroup: key, 393 }) 394 } 395 result := make(map[string]cadvisorapiv2.ContainerInfo) 396 for _, refs := range cinfoMap { 397 if len(refs) == 1 { 398 // ContainerInfo with no CPU/memory/network usage for uncleaned cgroups of 399 // already terminated containers, which should not be shown in the results. 400 if !isContainerTerminated(&refs[0].cinfo) { 401 result[refs[0].cgroup] = refs[0].cinfo 402 } 403 continue 404 } 405 sort.Sort(ByCreationTime(refs)) 406 for i := len(refs) - 1; i >= 0; i-- { 407 if hasMemoryAndCPUInstUsage(&refs[i].cinfo) { 408 result[refs[i].cgroup] = refs[i].cinfo 409 break 410 } 411 } 412 } 413 return result, cinfosByPodCgroupKey 414 } 415 416 // ByCreationTime implements sort.Interface for []containerInfoWithCgroup based 417 // on the cinfo.Spec.CreationTime field. 418 type ByCreationTime []containerInfoWithCgroup 419 420 func (a ByCreationTime) Len() int { return len(a) } 421 func (a ByCreationTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] } 422 func (a ByCreationTime) Less(i, j int) bool { 423 if a[i].cinfo.Spec.CreationTime.Equal(a[j].cinfo.Spec.CreationTime) { 424 // There shouldn't be two containers with the same name and/or the same 425 // creation time. However, to make the logic here robust, we break the 426 // tie by moving the one without CPU instantaneous or memory RSS usage 427 // to the beginning. 428 return hasMemoryAndCPUInstUsage(&a[j].cinfo) 429 } 430 return a[i].cinfo.Spec.CreationTime.Before(a[j].cinfo.Spec.CreationTime) 431 } 432 433 // containerID is the identity of a container in a pod. 434 type containerID struct { 435 podRef statsapi.PodReference 436 containerName string 437 } 438 439 // containerInfoWithCgroup contains the ContainerInfo and its cgroup name. 440 type containerInfoWithCgroup struct { 441 cinfo cadvisorapiv2.ContainerInfo 442 cgroup string 443 } 444 445 // hasMemoryAndCPUInstUsage returns true if the specified container info has 446 // both non-zero CPU instantaneous usage and non-zero memory RSS usage, and 447 // false otherwise. 448 func hasMemoryAndCPUInstUsage(info *cadvisorapiv2.ContainerInfo) bool { 449 if !info.Spec.HasCpu || !info.Spec.HasMemory { 450 return false 451 } 452 cstat, found := latestContainerStats(info) 453 if !found { 454 return false 455 } 456 if cstat.CpuInst == nil { 457 return false 458 } 459 return cstat.CpuInst.Usage.Total != 0 && cstat.Memory.RSS != 0 460 } 461 462 // isContainerTerminated returns true if the specified container meet one of the following conditions 463 // 1. info.spec both cpu memory and network are false conditions 464 // 2. info.Stats both network and cpu or memory are nil 465 // 3. both zero CPU instantaneous usage zero memory RSS usage and zero network usage, 466 // and false otherwise. 467 func isContainerTerminated(info *cadvisorapiv2.ContainerInfo) bool { 468 if !info.Spec.HasCpu && !info.Spec.HasMemory && !info.Spec.HasNetwork { 469 return true 470 } 471 cstat, found := latestContainerStats(info) 472 if !found { 473 return true 474 } 475 if cstat.Network != nil { 476 iStats := cadvisorInfoToNetworkStats(info) 477 if iStats != nil { 478 for _, iStat := range iStats.Interfaces { 479 if *iStat.RxErrors != 0 || *iStat.TxErrors != 0 || *iStat.RxBytes != 0 || *iStat.TxBytes != 0 { 480 return false 481 } 482 } 483 } 484 } 485 if cstat.CpuInst == nil || cstat.Memory == nil { 486 return true 487 } 488 return cstat.CpuInst.Usage.Total == 0 && cstat.Memory.RSS == 0 489 } 490 491 func getCadvisorContainerInfo(ca cadvisor.Interface) (map[string]cadvisorapiv2.ContainerInfo, error) { 492 infos, err := ca.ContainerInfoV2("/", cadvisorapiv2.RequestOptions{ 493 IdType: cadvisorapiv2.TypeName, 494 Count: 2, // 2 samples are needed to compute "instantaneous" CPU 495 Recursive: true, 496 }) 497 if err != nil { 498 if _, ok := infos["/"]; ok { 499 // If the failure is partial, log it and return a best-effort 500 // response. 501 klog.ErrorS(err, "Partial failure issuing cadvisor.ContainerInfoV2") 502 } else { 503 return nil, fmt.Errorf("failed to get root cgroup stats: %v", err) 504 } 505 } 506 return infos, nil 507 }