Skip to content

Commit 94a8d81

Browse files
author
Kubernetes Submit Queue
authored
Merge pull request kubernetes#55447 from jingxu97/Nov/podmetric
Automatic merge from submit-queue (batch tested with PRs 55812, 55752, 55447, 55848, 50984). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Add Pod-level local ephemeral storage metric in Summary API This PR adds pod-level ephemeral storage metric into Summary API. Pod-level ephemeral storage usage is the sum of all containers and local ephemeral volume including EmptyDir (if not backed up by memory or hugepages), configueMap, and downwardAPI. Address issue kubernetes#55978 **Release note**: ```release-note Add pod-level local ephemeral storage metric in Summary API. Pod-level ephemeral storage reports the total filesystem usage for the containers and emptyDir volumes in the measured Pod. ```
2 parents 2f2ab91 + 75ef18c commit 94a8d81

9 files changed

+233
-33
lines changed

pkg/kubelet/apis/stats/v1alpha1/types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ type PodStats struct {
9595
// +patchMergeKey=name
9696
// +patchStrategy=merge
9797
VolumeStats []VolumeStats `json:"volume,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
98+
// EphemeralStorage reports the total filesystem usage for the containers and emptyDir-backed volumes in the measured Pod.
99+
// +optional
100+
EphemeralStorage *FsStats `json:"ephemeral-storage,omitempty"`
98101
}
99102

100103
// ContainerStats holds container-level unprocessed sample stats.

pkg/kubelet/server/stats/volume_stat_calculator.go

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ type volumeStatCalculator struct {
4242
latest atomic.Value
4343
}
4444

45-
// PodVolumeStats encapsulates all VolumeStats for a pod
45+
// PodVolumeStats encapsulates the VolumeStats for a pod.
46+
// It consists of two lists, for local ephemeral volumes, and for persistent volumes respectively.
4647
type PodVolumeStats struct {
47-
Volumes []stats.VolumeStats
48+
EphemeralVolumes []stats.VolumeStats
49+
PersistentVolumes []stats.VolumeStats
4850
}
4951

5052
// newVolumeStatCalculator creates a new VolumeStatCalculator
@@ -101,7 +103,8 @@ func (s *volumeStatCalculator) calcAndStoreStats() {
101103
}
102104

103105
// Call GetMetrics on each Volume and copy the result to a new VolumeStats.FsStats
104-
fsStats := make([]stats.VolumeStats, 0, len(volumes))
106+
ephemeralStats := []stats.VolumeStats{}
107+
persistentStats := []stats.VolumeStats{}
105108
for name, v := range volumes {
106109
metric, err := v.GetMetrics()
107110
if err != nil {
@@ -113,31 +116,38 @@ func (s *volumeStatCalculator) calcAndStoreStats() {
113116
}
114117
// Lookup the volume spec and add a 'PVCReference' for volumes that reference a PVC
115118
volSpec := volumesSpec[name]
119+
var pvcRef *stats.PVCReference
116120
if pvcSource := volSpec.PersistentVolumeClaim; pvcSource != nil {
117-
pvcRef := stats.PVCReference{
121+
pvcRef = &stats.PVCReference{
118122
Name: pvcSource.ClaimName,
119123
Namespace: s.pod.GetNamespace(),
120124
}
121-
fsStats = append(fsStats, s.parsePodVolumeStats(name, &pvcRef, metric))
122125
// Set the PVC's prometheus metrics
123-
s.setPVCMetrics(&pvcRef, metric)
126+
s.setPVCMetrics(pvcRef, metric)
127+
}
128+
volumeStats := s.parsePodVolumeStats(name, pvcRef, metric, volSpec)
129+
if isVolumeEphemeral(volSpec) {
130+
ephemeralStats = append(ephemeralStats, volumeStats)
124131
} else {
125-
fsStats = append(fsStats, s.parsePodVolumeStats(name, nil, metric))
132+
persistentStats = append(persistentStats, volumeStats)
126133
}
134+
127135
}
128136

129137
// Store the new stats
130-
s.latest.Store(PodVolumeStats{Volumes: fsStats})
138+
s.latest.Store(PodVolumeStats{EphemeralVolumes: ephemeralStats,
139+
PersistentVolumes: persistentStats})
131140
}
132141

133142
// parsePodVolumeStats converts (internal) volume.Metrics to (external) stats.VolumeStats structures
134-
func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats.PVCReference, metric *volume.Metrics) stats.VolumeStats {
143+
func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats.PVCReference, metric *volume.Metrics, volSpec v1.Volume) stats.VolumeStats {
135144
available := uint64(metric.Available.Value())
136145
capacity := uint64(metric.Capacity.Value())
137146
used := uint64(metric.Used.Value())
138147
inodes := uint64(metric.Inodes.Value())
139148
inodesFree := uint64(metric.InodesFree.Value())
140149
inodesUsed := uint64(metric.InodesUsed.Value())
150+
141151
return stats.VolumeStats{
142152
Name: podName,
143153
PVCRef: pvcRef,
@@ -146,6 +156,14 @@ func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats
146156
}
147157
}
148158

159+
func isVolumeEphemeral(volume v1.Volume) bool {
160+
if (volume.EmptyDir != nil && volume.EmptyDir.Medium == v1.StorageMediumDefault) ||
161+
volume.ConfigMap != nil || volume.GitRepo != nil {
162+
return true
163+
}
164+
return false
165+
}
166+
149167
// setPVCMetrics sets the given PVC's prometheus metrics to match the given volume.Metrics
150168
func (s *volumeStatCalculator) setPVCMetrics(pvcRef *stats.PVCReference, metric *volume.Metrics) {
151169
metrics.VolumeStatsAvailableBytes.WithLabelValues(pvcRef.Namespace, pvcRef.Name).Set(float64(metric.Available.Value()))

pkg/kubelet/server/stats/volume_stat_calculator_test.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ limitations under the License.
1717
package stats
1818

1919
import (
20-
"github.com/stretchr/testify/assert"
2120
"testing"
2221
"time"
2322

23+
"github.com/stretchr/testify/assert"
24+
2425
k8sv1 "k8s.io/api/core/v1"
2526
"k8s.io/apimachinery/pkg/api/resource"
2627
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -84,14 +85,14 @@ func TestPVCRef(t *testing.T) {
8485
statsCalculator.calcAndStoreStats()
8586
vs, _ := statsCalculator.GetLatest()
8687

87-
assert.Len(t, vs.Volumes, 2)
88+
assert.Len(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), 2)
8889
// Verify 'vol0' doesn't have a PVC reference
89-
assert.Contains(t, vs.Volumes, kubestats.VolumeStats{
90+
assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
9091
Name: vol0,
9192
FsStats: expectedFSStats(),
9293
})
9394
// Verify 'vol1' has a PVC reference
94-
assert.Contains(t, vs.Volumes, kubestats.VolumeStats{
95+
assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
9596
Name: vol1,
9697
PVCRef: &kubestats.PVCReference{
9798
Name: pvcClaimName,

pkg/kubelet/stats/cadvisor_stats_provider.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,66 @@ func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
134134
for _, podStats := range podToStats {
135135
// Lookup the volume stats for each pod.
136136
podUID := types.UID(podStats.PodRef.UID)
137+
var ephemeralStats []statsapi.VolumeStats
137138
if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found {
138-
podStats.VolumeStats = vstats.Volumes
139+
ephemeralStats = make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes))
140+
copy(ephemeralStats, vstats.EphemeralVolumes)
141+
podStats.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...)
139142
}
143+
podStats.EphemeralStorage = calcEphemeralStorage(podStats.Containers, ephemeralStats, &rootFsInfo)
140144
result = append(result, *podStats)
141145
}
142146

143147
return result, nil
144148
}
145149

150+
func calcEphemeralStorage(containers []statsapi.ContainerStats, volumes []statsapi.VolumeStats, rootFsInfo *cadvisorapiv2.FsInfo) *statsapi.FsStats {
151+
result := &statsapi.FsStats{
152+
Time: metav1.NewTime(rootFsInfo.Timestamp),
153+
AvailableBytes: &rootFsInfo.Available,
154+
CapacityBytes: &rootFsInfo.Capacity,
155+
InodesFree: rootFsInfo.InodesFree,
156+
Inodes: rootFsInfo.Inodes,
157+
}
158+
for _, container := range containers {
159+
addContainerUsage(result, &container)
160+
}
161+
for _, volume := range volumes {
162+
result.UsedBytes = addUsage(result.UsedBytes, volume.FsStats.UsedBytes)
163+
result.InodesUsed = addUsage(result.InodesUsed, volume.InodesUsed)
164+
result.Time = maxUpdateTime(&result.Time, &volume.FsStats.Time)
165+
}
166+
return result
167+
}
168+
169+
func addContainerUsage(stat *statsapi.FsStats, container *statsapi.ContainerStats) {
170+
if rootFs := container.Rootfs; rootFs != nil {
171+
stat.Time = maxUpdateTime(&stat.Time, &rootFs.Time)
172+
stat.InodesUsed = addUsage(stat.InodesUsed, rootFs.InodesUsed)
173+
stat.UsedBytes = addUsage(stat.UsedBytes, rootFs.UsedBytes)
174+
if logs := container.Logs; logs != nil {
175+
stat.UsedBytes = addUsage(stat.UsedBytes, logs.UsedBytes)
176+
stat.Time = maxUpdateTime(&stat.Time, &logs.Time)
177+
}
178+
}
179+
}
180+
181+
func maxUpdateTime(first, second *metav1.Time) metav1.Time {
182+
if first.Before(second) {
183+
return *second
184+
}
185+
return *first
186+
}
187+
func addUsage(first, second *uint64) *uint64 {
188+
if first == nil {
189+
return second
190+
} else if second == nil {
191+
return first
192+
}
193+
total := *first + *second
194+
return &total
195+
}
196+
146197
// ImageFsStats returns the stats of the filesystem for storing images.
147198
func (p *cadvisorStatsProvider) ImageFsStats() (*statsapi.FsStats, error) {
148199
imageFsInfo, err := p.cadvisor.ImagesFsInfo()

pkg/kubelet/stats/cadvisor_stats_provider_test.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
2828
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
2929
"k8s.io/kubernetes/pkg/kubelet/leaky"
30+
serverstats "k8s.io/kubernetes/pkg/kubelet/server/stats"
3031
)
3132

3233
func TestRemoveTerminatedContainerInfo(t *testing.T) {
@@ -79,17 +80,21 @@ func TestCadvisorListPodStats(t *testing.T) {
7980
namespace2 = "test2"
8081
)
8182
const (
82-
seedRoot = 0
83-
seedRuntime = 100
84-
seedKubelet = 200
85-
seedMisc = 300
86-
seedPod0Infra = 1000
87-
seedPod0Container0 = 2000
88-
seedPod0Container1 = 2001
89-
seedPod1Infra = 3000
90-
seedPod1Container = 4000
91-
seedPod2Infra = 5000
92-
seedPod2Container = 6000
83+
seedRoot = 0
84+
seedRuntime = 100
85+
seedKubelet = 200
86+
seedMisc = 300
87+
seedPod0Infra = 1000
88+
seedPod0Container0 = 2000
89+
seedPod0Container1 = 2001
90+
seedPod1Infra = 3000
91+
seedPod1Container = 4000
92+
seedPod2Infra = 5000
93+
seedPod2Container = 6000
94+
seedEphemeralVolume1 = 10000
95+
seedEphemeralVolume2 = 10001
96+
seedPersistentVolume1 = 20000
97+
seedPersistentVolume2 = 20001
9398
)
9499
const (
95100
pName0 = "pod0"
@@ -181,7 +186,16 @@ func TestCadvisorListPodStats(t *testing.T) {
181186
mockRuntime.
182187
On("ImageStats").Return(&kubecontainer.ImageStats{TotalStorageBytes: 123}, nil)
183188

184-
resourceAnalyzer := &fakeResourceAnalyzer{}
189+
ephemeralVolumes := []statsapi.VolumeStats{getPodVolumeStats(seedEphemeralVolume1, "ephemeralVolume1"),
190+
getPodVolumeStats(seedEphemeralVolume2, "ephemeralVolume2")}
191+
persistentVolumes := []statsapi.VolumeStats{getPodVolumeStats(seedPersistentVolume1, "persistentVolume1"),
192+
getPodVolumeStats(seedPersistentVolume2, "persistentVolume2")}
193+
volumeStats := serverstats.PodVolumeStats{
194+
EphemeralVolumes: ephemeralVolumes,
195+
PersistentVolumes: persistentVolumes,
196+
}
197+
198+
resourceAnalyzer := &fakeResourceAnalyzer{podVolumeStats: volumeStats}
185199

186200
p := NewCadvisorStatsProvider(mockCadvisor, resourceAnalyzer, nil, nil, mockRuntime)
187201
pods, err := p.ListPodStats()
@@ -213,6 +227,7 @@ func TestCadvisorListPodStats(t *testing.T) {
213227

214228
assert.EqualValues(t, testTime(creationTime, seedPod0Infra).Unix(), ps.StartTime.Time.Unix())
215229
checkNetworkStats(t, "Pod0", seedPod0Infra, ps.Network)
230+
checkEphemeralStats(t, "Pod0", []int{seedPod0Container0, seedPod0Container1}, []int{seedEphemeralVolume1, seedEphemeralVolume2}, ps.EphemeralStorage)
216231

217232
// Validate Pod1 Results
218233
ps, found = indexPods[prf1]

pkg/kubelet/stats/cri_stats_provider.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,16 @@ func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
131131
// container belongs to.
132132
ps, found := sandboxIDToPodStats[podSandboxID]
133133
if !found {
134-
ps = p.makePodStats(podSandbox)
134+
ps = buildPodStats(podSandbox)
135135
sandboxIDToPodStats[podSandboxID] = ps
136136
}
137-
ps.Containers = append(ps.Containers, *p.makeContainerStats(stats, container, &rootFsInfo, uuidToFsInfo))
137+
containerStats := p.makeContainerStats(stats, container, &rootFsInfo, uuidToFsInfo)
138+
ps.Containers = append(ps.Containers, *containerStats)
138139
}
139140

140141
result := make([]statsapi.PodStats, 0, len(sandboxIDToPodStats))
141142
for _, s := range sandboxIDToPodStats {
143+
p.makePodStorageStats(s, &rootFsInfo)
142144
result = append(result, *s)
143145
}
144146
return result, nil
@@ -199,8 +201,9 @@ func (p *criStatsProvider) getFsInfo(storageID *runtimeapi.StorageIdentifier) *c
199201
return &fsInfo
200202
}
201203

202-
func (p *criStatsProvider) makePodStats(podSandbox *runtimeapi.PodSandbox) *statsapi.PodStats {
203-
s := &statsapi.PodStats{
204+
// buildPodRef returns a PodStats that identifies the Pod managing cinfo
205+
func buildPodStats(podSandbox *runtimeapi.PodSandbox) *statsapi.PodStats {
206+
return &statsapi.PodStats{
204207
PodRef: statsapi.PodReference{
205208
Name: podSandbox.Metadata.Name,
206209
UID: podSandbox.Metadata.Uid,
@@ -210,9 +213,15 @@ func (p *criStatsProvider) makePodStats(podSandbox *runtimeapi.PodSandbox) *stat
210213
StartTime: metav1.NewTime(time.Unix(0, podSandbox.CreatedAt)),
211214
// Network stats are not supported by CRI.
212215
}
216+
}
217+
218+
func (p *criStatsProvider) makePodStorageStats(s *statsapi.PodStats, rootFsInfo *cadvisorapiv2.FsInfo) *statsapi.PodStats {
213219
podUID := types.UID(s.PodRef.UID)
214220
if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found {
215-
s.VolumeStats = vstats.Volumes
221+
ephemeralStats := make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes))
222+
copy(ephemeralStats, vstats.EphemeralVolumes)
223+
s.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...)
224+
s.EphemeralStorage = calcEphemeralStorage(s.Containers, ephemeralStats, rootFsInfo)
216225
}
217226
return s
218227
}

0 commit comments

Comments
 (0)