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