mirror of https://github.com/k3s-io/k3s
282 lines
9.7 KiB
Go
282 lines
9.7 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package stats
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/golang/glog"
|
|
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
|
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
|
|
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
|
"k8s.io/kubernetes/pkg/kubelet/leaky"
|
|
"k8s.io/kubernetes/pkg/kubelet/server/stats"
|
|
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
|
)
|
|
|
|
// cadvisorStatsProvider implements the containerStatsProvider interface by
|
|
// getting the container stats from cAdvisor. This is needed by docker and rkt
|
|
// integrations since they do not provide stats from CRI.
|
|
type cadvisorStatsProvider struct {
|
|
// cadvisor is used to get the stats of the cgroup for the containers that
|
|
// are managed by pods.
|
|
cadvisor cadvisor.Interface
|
|
// resourceAnalyzer is used to get the volume stats of the pods.
|
|
resourceAnalyzer stats.ResourceAnalyzer
|
|
// imageService is used to get the stats of the image filesystem.
|
|
imageService kubecontainer.ImageService
|
|
}
|
|
|
|
// newCadvisorStatsProvider returns a containerStatsProvider that provides
|
|
// container stats from cAdvisor.
|
|
func newCadvisorStatsProvider(
|
|
cadvisor cadvisor.Interface,
|
|
resourceAnalyzer stats.ResourceAnalyzer,
|
|
imageService kubecontainer.ImageService,
|
|
) containerStatsProvider {
|
|
return &cadvisorStatsProvider{
|
|
cadvisor: cadvisor,
|
|
resourceAnalyzer: resourceAnalyzer,
|
|
imageService: imageService,
|
|
}
|
|
}
|
|
|
|
// ListPodStats returns the stats of all the pod-managed containers.
|
|
func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
|
|
// Gets node root filesystem information and image filesystem stats, which
|
|
// will be used to populate the available and capacity bytes/inodes in
|
|
// container stats.
|
|
rootFsInfo, err := p.cadvisor.RootFsInfo()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get rootFs info: %v", err)
|
|
}
|
|
imageFsInfo, err := p.cadvisor.ImagesFsInfo()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get imageFs info: %v", err)
|
|
}
|
|
|
|
infos, err := p.cadvisor.ContainerInfoV2("/", cadvisorapiv2.RequestOptions{
|
|
IdType: cadvisorapiv2.TypeName,
|
|
Count: 2, // 2 samples are needed to compute "instantaneous" CPU
|
|
Recursive: true,
|
|
})
|
|
if err != nil {
|
|
if _, ok := infos["/"]; ok {
|
|
// If the failure is partial, log it and return a best-effort
|
|
// response.
|
|
glog.Errorf("Partial failure issuing cadvisor.ContainerInfoV2: %v", err)
|
|
} else {
|
|
return nil, fmt.Errorf("failed to get root cgroup stats: %v", err)
|
|
}
|
|
}
|
|
|
|
infos = removeTerminatedContainerInfo(infos)
|
|
|
|
// Map each container to a pod and update the PodStats with container data.
|
|
podToStats := map[statsapi.PodReference]*statsapi.PodStats{}
|
|
for key, cinfo := range infos {
|
|
// On systemd using devicemapper each mount into the container has an
|
|
// associated cgroup. We ignore them to ensure we do not get duplicate
|
|
// entries in our summary. For details on .mount units:
|
|
// http://man7.org/linux/man-pages/man5/systemd.mount.5.html
|
|
if strings.HasSuffix(key, ".mount") {
|
|
continue
|
|
}
|
|
// Build the Pod key if this container is managed by a Pod
|
|
if !isPodManagedContainer(&cinfo) {
|
|
continue
|
|
}
|
|
ref := buildPodRef(&cinfo)
|
|
|
|
// Lookup the PodStats for the pod using the PodRef. If none exists,
|
|
// initialize a new entry.
|
|
podStats, found := podToStats[ref]
|
|
if !found {
|
|
podStats = &statsapi.PodStats{PodRef: ref}
|
|
podToStats[ref] = podStats
|
|
}
|
|
|
|
// Update the PodStats entry with the stats from the container by
|
|
// adding it to podStats.Containers.
|
|
containerName := kubetypes.GetContainerName(cinfo.Spec.Labels)
|
|
if containerName == leaky.PodInfraContainerName {
|
|
// Special case for infrastructure container which is hidden from
|
|
// the user and has network stats.
|
|
podStats.Network = cadvisorInfoToNetworkStats("pod:"+ref.Namespace+"_"+ref.Name, &cinfo)
|
|
podStats.StartTime = metav1.NewTime(cinfo.Spec.CreationTime)
|
|
} else {
|
|
podStats.Containers = append(podStats.Containers, *cadvisorInfoToContainerStats(containerName, &cinfo, &rootFsInfo, &imageFsInfo))
|
|
}
|
|
}
|
|
|
|
// Add each PodStats to the result.
|
|
result := make([]statsapi.PodStats, 0, len(podToStats))
|
|
for _, podStats := range podToStats {
|
|
// Lookup the volume stats for each pod.
|
|
podUID := types.UID(podStats.PodRef.UID)
|
|
if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found {
|
|
podStats.VolumeStats = vstats.Volumes
|
|
}
|
|
result = append(result, *podStats)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// ImageFsStats returns the stats of the filesystem for storing images.
|
|
func (p *cadvisorStatsProvider) ImageFsStats() (*statsapi.FsStats, error) {
|
|
imageFsInfo, err := p.cadvisor.ImagesFsInfo()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get imageFs info: %v", err)
|
|
}
|
|
imageStats, err := p.imageService.ImageStats()
|
|
if err != nil || imageStats == nil {
|
|
return nil, fmt.Errorf("failed to get image stats: %v", err)
|
|
}
|
|
|
|
var imageFsInodesUsed *uint64
|
|
if imageFsInfo.Inodes != nil && imageFsInfo.InodesFree != nil {
|
|
imageFsIU := *imageFsInfo.Inodes - *imageFsInfo.InodesFree
|
|
imageFsInodesUsed = &imageFsIU
|
|
}
|
|
|
|
return &statsapi.FsStats{
|
|
Time: metav1.NewTime(imageFsInfo.Timestamp),
|
|
AvailableBytes: &imageFsInfo.Available,
|
|
CapacityBytes: &imageFsInfo.Capacity,
|
|
UsedBytes: &imageStats.TotalStorageBytes,
|
|
InodesFree: imageFsInfo.InodesFree,
|
|
Inodes: imageFsInfo.Inodes,
|
|
InodesUsed: imageFsInodesUsed,
|
|
}, nil
|
|
}
|
|
|
|
// buildPodRef returns a PodReference that identifies the Pod managing cinfo
|
|
func buildPodRef(cinfo *cadvisorapiv2.ContainerInfo) statsapi.PodReference {
|
|
podName := kubetypes.GetPodName(cinfo.Spec.Labels)
|
|
podNamespace := kubetypes.GetPodNamespace(cinfo.Spec.Labels)
|
|
podUID := kubetypes.GetPodUID(cinfo.Spec.Labels)
|
|
return statsapi.PodReference{Name: podName, Namespace: podNamespace, UID: podUID}
|
|
}
|
|
|
|
// isPodManagedContainer returns true if the cinfo container is managed by a Pod
|
|
func isPodManagedContainer(cinfo *cadvisorapiv2.ContainerInfo) bool {
|
|
podName := kubetypes.GetPodName(cinfo.Spec.Labels)
|
|
podNamespace := kubetypes.GetPodNamespace(cinfo.Spec.Labels)
|
|
managed := podName != "" && podNamespace != ""
|
|
if !managed && podName != podNamespace {
|
|
glog.Warningf(
|
|
"Expect container to have either both podName (%s) and podNamespace (%s) labels, or neither.",
|
|
podName, podNamespace)
|
|
}
|
|
return managed
|
|
}
|
|
|
|
// removeTerminatedContainerInfo returns the specified containerInfo but with
|
|
// the stats of the terminated containers removed.
|
|
//
|
|
// A ContainerInfo is considered to be of a terminated container if it has an
|
|
// older CreationTime and zero CPU instantaneous and memory RSS usage.
|
|
func removeTerminatedContainerInfo(containerInfo map[string]cadvisorapiv2.ContainerInfo) map[string]cadvisorapiv2.ContainerInfo {
|
|
cinfoMap := make(map[containerID][]containerInfoWithCgroup)
|
|
for key, cinfo := range containerInfo {
|
|
if !isPodManagedContainer(&cinfo) {
|
|
continue
|
|
}
|
|
cinfoID := containerID{
|
|
podRef: buildPodRef(&cinfo),
|
|
containerName: kubetypes.GetContainerName(cinfo.Spec.Labels),
|
|
}
|
|
cinfoMap[cinfoID] = append(cinfoMap[cinfoID], containerInfoWithCgroup{
|
|
cinfo: cinfo,
|
|
cgroup: key,
|
|
})
|
|
}
|
|
result := make(map[string]cadvisorapiv2.ContainerInfo)
|
|
for _, refs := range cinfoMap {
|
|
if len(refs) == 1 {
|
|
result[refs[0].cgroup] = refs[0].cinfo
|
|
continue
|
|
}
|
|
sort.Sort(ByCreationTime(refs))
|
|
i := 0
|
|
for ; i < len(refs); i++ {
|
|
if hasMemoryAndCPUInstUsage(&refs[i].cinfo) {
|
|
// Stops removing when we first see an info with non-zero
|
|
// CPU/Memory usage.
|
|
break
|
|
}
|
|
}
|
|
for ; i < len(refs); i++ {
|
|
result[refs[i].cgroup] = refs[i].cinfo
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// ByCreationTime implements sort.Interface for []containerInfoWithCgroup based
|
|
// on the cinfo.Spec.CreationTime field.
|
|
type ByCreationTime []containerInfoWithCgroup
|
|
|
|
func (a ByCreationTime) Len() int { return len(a) }
|
|
func (a ByCreationTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
func (a ByCreationTime) Less(i, j int) bool {
|
|
if a[i].cinfo.Spec.CreationTime.Equal(a[j].cinfo.Spec.CreationTime) {
|
|
// There shouldn't be two containers with the same name and/or the same
|
|
// creation time. However, to make the logic here robust, we break the
|
|
// tie by moving the one without CPU instantaneous or memory RSS usage
|
|
// to the beginning.
|
|
return hasMemoryAndCPUInstUsage(&a[j].cinfo)
|
|
}
|
|
return a[i].cinfo.Spec.CreationTime.Before(a[j].cinfo.Spec.CreationTime)
|
|
}
|
|
|
|
// containerID is the identity of a container in a pod.
|
|
type containerID struct {
|
|
podRef statsapi.PodReference
|
|
containerName string
|
|
}
|
|
|
|
// containerInfoWithCgroup contains the ContainerInfo and its cgroup name.
|
|
type containerInfoWithCgroup struct {
|
|
cinfo cadvisorapiv2.ContainerInfo
|
|
cgroup string
|
|
}
|
|
|
|
// hasMemoryAndCPUInstUsage returns true if the specified container info has
|
|
// both non-zero CPU instantaneous usage and non-zero memory RSS usage, and
|
|
// false otherwise.
|
|
func hasMemoryAndCPUInstUsage(info *cadvisorapiv2.ContainerInfo) bool {
|
|
if !info.Spec.HasCpu || !info.Spec.HasMemory {
|
|
return false
|
|
}
|
|
cstat, found := latestContainerStats(info)
|
|
if !found {
|
|
return false
|
|
}
|
|
if cstat.CpuInst == nil {
|
|
return false
|
|
}
|
|
return cstat.CpuInst.Usage.Total != 0 && cstat.Memory.RSS != 0
|
|
}
|