cadvisor_stats_provider.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. /*
  2. Copyright 2017 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package stats
  14. import (
  15. "fmt"
  16. "path"
  17. "sort"
  18. "strings"
  19. cadvisorapiv2 "github.com/google/cadvisor/info/v2"
  20. "k8s.io/klog"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/apimachinery/pkg/types"
  23. statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
  24. "k8s.io/kubernetes/pkg/kubelet/cadvisor"
  25. "k8s.io/kubernetes/pkg/kubelet/cm"
  26. kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
  27. "k8s.io/kubernetes/pkg/kubelet/leaky"
  28. "k8s.io/kubernetes/pkg/kubelet/server/stats"
  29. "k8s.io/kubernetes/pkg/kubelet/status"
  30. kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
  31. )
  32. // cadvisorStatsProvider implements the containerStatsProvider interface by
  33. // getting the container stats from cAdvisor. This is needed by docker and rkt
  34. // integrations since they do not provide stats from CRI.
  35. type cadvisorStatsProvider struct {
  36. // cadvisor is used to get the stats of the cgroup for the containers that
  37. // are managed by pods.
  38. cadvisor cadvisor.Interface
  39. // resourceAnalyzer is used to get the volume stats of the pods.
  40. resourceAnalyzer stats.ResourceAnalyzer
  41. // imageService is used to get the stats of the image filesystem.
  42. imageService kubecontainer.ImageService
  43. // statusProvider is used to get pod metadata
  44. statusProvider status.PodStatusProvider
  45. }
  46. // newCadvisorStatsProvider returns a containerStatsProvider that provides
  47. // container stats from cAdvisor.
  48. func newCadvisorStatsProvider(
  49. cadvisor cadvisor.Interface,
  50. resourceAnalyzer stats.ResourceAnalyzer,
  51. imageService kubecontainer.ImageService,
  52. statusProvider status.PodStatusProvider,
  53. ) containerStatsProvider {
  54. return &cadvisorStatsProvider{
  55. cadvisor: cadvisor,
  56. resourceAnalyzer: resourceAnalyzer,
  57. imageService: imageService,
  58. statusProvider: statusProvider,
  59. }
  60. }
  61. // ListPodStats returns the stats of all the pod-managed containers.
  62. func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
  63. // Gets node root filesystem information and image filesystem stats, which
  64. // will be used to populate the available and capacity bytes/inodes in
  65. // container stats.
  66. rootFsInfo, err := p.cadvisor.RootFsInfo()
  67. if err != nil {
  68. return nil, fmt.Errorf("failed to get rootFs info: %v", err)
  69. }
  70. imageFsInfo, err := p.cadvisor.ImagesFsInfo()
  71. if err != nil {
  72. return nil, fmt.Errorf("failed to get imageFs info: %v", err)
  73. }
  74. infos, err := getCadvisorContainerInfo(p.cadvisor)
  75. if err != nil {
  76. return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err)
  77. }
  78. // removeTerminatedContainerInfo will also remove pod level cgroups, so save the infos into allInfos first
  79. allInfos := infos
  80. infos = removeTerminatedContainerInfo(infos)
  81. // Map each container to a pod and update the PodStats with container data.
  82. podToStats := map[statsapi.PodReference]*statsapi.PodStats{}
  83. for key, cinfo := range infos {
  84. // On systemd using devicemapper each mount into the container has an
  85. // associated cgroup. We ignore them to ensure we do not get duplicate
  86. // entries in our summary. For details on .mount units:
  87. // http://man7.org/linux/man-pages/man5/systemd.mount.5.html
  88. if strings.HasSuffix(key, ".mount") {
  89. continue
  90. }
  91. // Build the Pod key if this container is managed by a Pod
  92. if !isPodManagedContainer(&cinfo) {
  93. continue
  94. }
  95. ref := buildPodRef(cinfo.Spec.Labels)
  96. // Lookup the PodStats for the pod using the PodRef. If none exists,
  97. // initialize a new entry.
  98. podStats, found := podToStats[ref]
  99. if !found {
  100. podStats = &statsapi.PodStats{PodRef: ref}
  101. podToStats[ref] = podStats
  102. }
  103. // Update the PodStats entry with the stats from the container by
  104. // adding it to podStats.Containers.
  105. containerName := kubetypes.GetContainerName(cinfo.Spec.Labels)
  106. if containerName == leaky.PodInfraContainerName {
  107. // Special case for infrastructure container which is hidden from
  108. // the user and has network stats.
  109. podStats.Network = cadvisorInfoToNetworkStats("pod:"+ref.Namespace+"_"+ref.Name, &cinfo)
  110. } else {
  111. podStats.Containers = append(podStats.Containers, *cadvisorInfoToContainerStats(containerName, &cinfo, &rootFsInfo, &imageFsInfo))
  112. }
  113. }
  114. // Add each PodStats to the result.
  115. result := make([]statsapi.PodStats, 0, len(podToStats))
  116. for _, podStats := range podToStats {
  117. // Lookup the volume stats for each pod.
  118. podUID := types.UID(podStats.PodRef.UID)
  119. var ephemeralStats []statsapi.VolumeStats
  120. if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found {
  121. ephemeralStats = make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes))
  122. copy(ephemeralStats, vstats.EphemeralVolumes)
  123. podStats.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...)
  124. }
  125. podStats.EphemeralStorage = calcEphemeralStorage(podStats.Containers, ephemeralStats, &rootFsInfo, nil, false)
  126. // Lookup the pod-level cgroup's CPU and memory stats
  127. podInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos)
  128. if podInfo != nil {
  129. cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo)
  130. podStats.CPU = cpu
  131. podStats.Memory = memory
  132. }
  133. status, found := p.statusProvider.GetPodStatus(podUID)
  134. if found && status.StartTime != nil && !status.StartTime.IsZero() {
  135. podStats.StartTime = *status.StartTime
  136. // only append stats if we were able to get the start time of the pod
  137. result = append(result, *podStats)
  138. }
  139. }
  140. return result, nil
  141. }
  142. // ListPodStatsAndUpdateCPUNanoCoreUsage updates the cpu nano core usage for
  143. // the containers and returns the stats for all the pod-managed containers.
  144. // For cadvisor, cpu nano core usages are pre-computed and cached, so this
  145. // function simply calls ListPodStats.
  146. func (p *cadvisorStatsProvider) ListPodStatsAndUpdateCPUNanoCoreUsage() ([]statsapi.PodStats, error) {
  147. return p.ListPodStats()
  148. }
  149. // ListPodCPUAndMemoryStats returns the cpu and memory stats of all the pod-managed containers.
  150. func (p *cadvisorStatsProvider) ListPodCPUAndMemoryStats() ([]statsapi.PodStats, error) {
  151. infos, err := getCadvisorContainerInfo(p.cadvisor)
  152. if err != nil {
  153. return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err)
  154. }
  155. // removeTerminatedContainerInfo will also remove pod level cgroups, so save the infos into allInfos first
  156. allInfos := infos
  157. infos = removeTerminatedContainerInfo(infos)
  158. // Map each container to a pod and update the PodStats with container data.
  159. podToStats := map[statsapi.PodReference]*statsapi.PodStats{}
  160. for key, cinfo := range infos {
  161. // On systemd using devicemapper each mount into the container has an
  162. // associated cgroup. We ignore them to ensure we do not get duplicate
  163. // entries in our summary. For details on .mount units:
  164. // http://man7.org/linux/man-pages/man5/systemd.mount.5.html
  165. if strings.HasSuffix(key, ".mount") {
  166. continue
  167. }
  168. // Build the Pod key if this container is managed by a Pod
  169. if !isPodManagedContainer(&cinfo) {
  170. continue
  171. }
  172. ref := buildPodRef(cinfo.Spec.Labels)
  173. // Lookup the PodStats for the pod using the PodRef. If none exists,
  174. // initialize a new entry.
  175. podStats, found := podToStats[ref]
  176. if !found {
  177. podStats = &statsapi.PodStats{PodRef: ref}
  178. podToStats[ref] = podStats
  179. }
  180. // Update the PodStats entry with the stats from the container by
  181. // adding it to podStats.Containers.
  182. containerName := kubetypes.GetContainerName(cinfo.Spec.Labels)
  183. if containerName == leaky.PodInfraContainerName {
  184. // Special case for infrastructure container which is hidden from
  185. // the user and has network stats.
  186. podStats.StartTime = metav1.NewTime(cinfo.Spec.CreationTime)
  187. } else {
  188. podStats.Containers = append(podStats.Containers, *cadvisorInfoToContainerCPUAndMemoryStats(containerName, &cinfo))
  189. }
  190. }
  191. // Add each PodStats to the result.
  192. result := make([]statsapi.PodStats, 0, len(podToStats))
  193. for _, podStats := range podToStats {
  194. podUID := types.UID(podStats.PodRef.UID)
  195. // Lookup the pod-level cgroup's CPU and memory stats
  196. podInfo := getCadvisorPodInfoFromPodUID(podUID, allInfos)
  197. if podInfo != nil {
  198. cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo)
  199. podStats.CPU = cpu
  200. podStats.Memory = memory
  201. }
  202. result = append(result, *podStats)
  203. }
  204. return result, nil
  205. }
  206. // ImageFsStats returns the stats of the filesystem for storing images.
  207. func (p *cadvisorStatsProvider) ImageFsStats() (*statsapi.FsStats, error) {
  208. imageFsInfo, err := p.cadvisor.ImagesFsInfo()
  209. if err != nil {
  210. return nil, fmt.Errorf("failed to get imageFs info: %v", err)
  211. }
  212. imageStats, err := p.imageService.ImageStats()
  213. if err != nil || imageStats == nil {
  214. return nil, fmt.Errorf("failed to get image stats: %v", err)
  215. }
  216. var imageFsInodesUsed *uint64
  217. if imageFsInfo.Inodes != nil && imageFsInfo.InodesFree != nil {
  218. imageFsIU := *imageFsInfo.Inodes - *imageFsInfo.InodesFree
  219. imageFsInodesUsed = &imageFsIU
  220. }
  221. return &statsapi.FsStats{
  222. Time: metav1.NewTime(imageFsInfo.Timestamp),
  223. AvailableBytes: &imageFsInfo.Available,
  224. CapacityBytes: &imageFsInfo.Capacity,
  225. UsedBytes: &imageStats.TotalStorageBytes,
  226. InodesFree: imageFsInfo.InodesFree,
  227. Inodes: imageFsInfo.Inodes,
  228. InodesUsed: imageFsInodesUsed,
  229. }, nil
  230. }
  231. // ImageFsDevice returns name of the device where the image filesystem locates,
  232. // e.g. /dev/sda1.
  233. func (p *cadvisorStatsProvider) ImageFsDevice() (string, error) {
  234. imageFsInfo, err := p.cadvisor.ImagesFsInfo()
  235. if err != nil {
  236. return "", err
  237. }
  238. return imageFsInfo.Device, nil
  239. }
  240. // buildPodRef returns a PodReference that identifies the Pod managing cinfo
  241. func buildPodRef(containerLabels map[string]string) statsapi.PodReference {
  242. podName := kubetypes.GetPodName(containerLabels)
  243. podNamespace := kubetypes.GetPodNamespace(containerLabels)
  244. podUID := kubetypes.GetPodUID(containerLabels)
  245. return statsapi.PodReference{Name: podName, Namespace: podNamespace, UID: podUID}
  246. }
  247. // isPodManagedContainer returns true if the cinfo container is managed by a Pod
  248. func isPodManagedContainer(cinfo *cadvisorapiv2.ContainerInfo) bool {
  249. podName := kubetypes.GetPodName(cinfo.Spec.Labels)
  250. podNamespace := kubetypes.GetPodNamespace(cinfo.Spec.Labels)
  251. managed := podName != "" && podNamespace != ""
  252. if !managed && podName != podNamespace {
  253. klog.Warningf(
  254. "Expect container to have either both podName (%s) and podNamespace (%s) labels, or neither.",
  255. podName, podNamespace)
  256. }
  257. return managed
  258. }
  259. // getCadvisorPodInfoFromPodUID returns a pod cgroup information by matching the podUID with its CgroupName identifier base name
  260. func getCadvisorPodInfoFromPodUID(podUID types.UID, infos map[string]cadvisorapiv2.ContainerInfo) *cadvisorapiv2.ContainerInfo {
  261. for key, info := range infos {
  262. if cm.IsSystemdStyleName(key) {
  263. // Convert to internal cgroup name and take the last component only.
  264. internalCgroupName := cm.ParseSystemdToCgroupName(key)
  265. key = internalCgroupName[len(internalCgroupName)-1]
  266. } else {
  267. // Take last component only.
  268. key = path.Base(key)
  269. }
  270. if cm.GetPodCgroupNameSuffix(podUID) == key {
  271. return &info
  272. }
  273. }
  274. return nil
  275. }
  276. // removeTerminatedContainerInfo returns the specified containerInfo but with
  277. // the stats of the terminated containers removed.
  278. //
  279. // A ContainerInfo is considered to be of a terminated container if it has an
  280. // older CreationTime and zero CPU instantaneous and memory RSS usage.
  281. func removeTerminatedContainerInfo(containerInfo map[string]cadvisorapiv2.ContainerInfo) map[string]cadvisorapiv2.ContainerInfo {
  282. cinfoMap := make(map[containerID][]containerInfoWithCgroup)
  283. for key, cinfo := range containerInfo {
  284. if !isPodManagedContainer(&cinfo) {
  285. continue
  286. }
  287. cinfoID := containerID{
  288. podRef: buildPodRef(cinfo.Spec.Labels),
  289. containerName: kubetypes.GetContainerName(cinfo.Spec.Labels),
  290. }
  291. cinfoMap[cinfoID] = append(cinfoMap[cinfoID], containerInfoWithCgroup{
  292. cinfo: cinfo,
  293. cgroup: key,
  294. })
  295. }
  296. result := make(map[string]cadvisorapiv2.ContainerInfo)
  297. for _, refs := range cinfoMap {
  298. if len(refs) == 1 {
  299. result[refs[0].cgroup] = refs[0].cinfo
  300. continue
  301. }
  302. sort.Sort(ByCreationTime(refs))
  303. i := 0
  304. for ; i < len(refs); i++ {
  305. if hasMemoryAndCPUInstUsage(&refs[i].cinfo) {
  306. // Stops removing when we first see an info with non-zero
  307. // CPU/Memory usage.
  308. break
  309. }
  310. }
  311. for ; i < len(refs); i++ {
  312. result[refs[i].cgroup] = refs[i].cinfo
  313. }
  314. }
  315. return result
  316. }
  317. // ByCreationTime implements sort.Interface for []containerInfoWithCgroup based
  318. // on the cinfo.Spec.CreationTime field.
  319. type ByCreationTime []containerInfoWithCgroup
  320. func (a ByCreationTime) Len() int { return len(a) }
  321. func (a ByCreationTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
  322. func (a ByCreationTime) Less(i, j int) bool {
  323. if a[i].cinfo.Spec.CreationTime.Equal(a[j].cinfo.Spec.CreationTime) {
  324. // There shouldn't be two containers with the same name and/or the same
  325. // creation time. However, to make the logic here robust, we break the
  326. // tie by moving the one without CPU instantaneous or memory RSS usage
  327. // to the beginning.
  328. return hasMemoryAndCPUInstUsage(&a[j].cinfo)
  329. }
  330. return a[i].cinfo.Spec.CreationTime.Before(a[j].cinfo.Spec.CreationTime)
  331. }
  332. // containerID is the identity of a container in a pod.
  333. type containerID struct {
  334. podRef statsapi.PodReference
  335. containerName string
  336. }
  337. // containerInfoWithCgroup contains the ContainerInfo and its cgroup name.
  338. type containerInfoWithCgroup struct {
  339. cinfo cadvisorapiv2.ContainerInfo
  340. cgroup string
  341. }
  342. // hasMemoryAndCPUInstUsage returns true if the specified container info has
  343. // both non-zero CPU instantaneous usage and non-zero memory RSS usage, and
  344. // false otherwise.
  345. func hasMemoryAndCPUInstUsage(info *cadvisorapiv2.ContainerInfo) bool {
  346. if !info.Spec.HasCpu || !info.Spec.HasMemory {
  347. return false
  348. }
  349. cstat, found := latestContainerStats(info)
  350. if !found {
  351. return false
  352. }
  353. if cstat.CpuInst == nil {
  354. return false
  355. }
  356. return cstat.CpuInst.Usage.Total != 0 && cstat.Memory.RSS != 0
  357. }
  358. func getCadvisorContainerInfo(ca cadvisor.Interface) (map[string]cadvisorapiv2.ContainerInfo, error) {
  359. infos, err := ca.ContainerInfoV2("/", cadvisorapiv2.RequestOptions{
  360. IdType: cadvisorapiv2.TypeName,
  361. Count: 2, // 2 samples are needed to compute "instantaneous" CPU
  362. Recursive: true,
  363. })
  364. if err != nil {
  365. if _, ok := infos["/"]; ok {
  366. // If the failure is partial, log it and return a best-effort
  367. // response.
  368. klog.Errorf("Partial failure issuing cadvisor.ContainerInfoV2: %v", err)
  369. } else {
  370. return nil, fmt.Errorf("failed to get root cgroup stats: %v", err)
  371. }
  372. }
  373. return infos, nil
  374. }