Skip to content

Commit d3fd594

Browse files
authored
Merge pull request kubernetes#128333 from AnishShah/eviction-manager
Revert kubernetes#126562 that is causing eviction tests to fail
2 parents eb10b9b + 9faab07 commit d3fd594

File tree

9 files changed

+146
-123
lines changed

9 files changed

+146
-123
lines changed

pkg/kubelet/container/container_gc.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ type GC interface {
4545
GarbageCollect(ctx context.Context) error
4646
// Deletes all unused containers, including containers belonging to pods that are terminated but not deleted
4747
DeleteAllUnusedContainers(ctx context.Context) error
48+
// IsContainerFsSeparateFromImageFs tells if writeable layer and read-only layer are separate.
49+
IsContainerFsSeparateFromImageFs(ctx context.Context) bool
4850
}
4951

5052
// SourcesReadyProvider knows how to determine if configuration sources are ready
@@ -86,3 +88,22 @@ func (cgc *realContainerGC) DeleteAllUnusedContainers(ctx context.Context) error
8688
klog.InfoS("Attempting to delete unused containers")
8789
return cgc.runtime.GarbageCollect(ctx, cgc.policy, cgc.sourcesReadyProvider.AllReady(), true)
8890
}
91+
92+
func (cgc *realContainerGC) IsContainerFsSeparateFromImageFs(ctx context.Context) bool {
93+
resp, err := cgc.runtime.ImageFsInfo(ctx)
94+
if err != nil {
95+
return false
96+
}
97+
// These fields can be empty if CRI implementation didn't populate.
98+
if resp.ContainerFilesystems == nil || resp.ImageFilesystems == nil || len(resp.ContainerFilesystems) == 0 || len(resp.ImageFilesystems) == 0 {
99+
return false
100+
}
101+
// KEP 4191 explains that multiple filesystems for images and containers is not
102+
// supported at the moment.
103+
// See https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/4191-split-image-filesystem#comment-on-future-extensions
104+
// for work needed to support multiple filesystems.
105+
if resp.ContainerFilesystems[0].FsId != nil && resp.ImageFilesystems[0].FsId != nil {
106+
return resp.ContainerFilesystems[0].FsId.Mountpoint != resp.ImageFilesystems[0].FsId.Mountpoint
107+
}
108+
return false
109+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
Copyright 2023 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 container_test
18+
19+
import (
20+
"context"
21+
"reflect"
22+
"testing"
23+
24+
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
25+
. "k8s.io/kubernetes/pkg/kubelet/container"
26+
ctest "k8s.io/kubernetes/pkg/kubelet/container/testing"
27+
)
28+
29+
func TestIsContainerFsSeparateFromImageFs(t *testing.T) {
30+
runtime := &ctest.FakeRuntime{}
31+
fakeSources := ctest.NewFakeReadyProvider()
32+
33+
gcContainer, err := NewContainerGC(runtime, GCPolicy{}, fakeSources)
34+
if err != nil {
35+
t.Errorf("unexpected error")
36+
}
37+
38+
cases := []struct {
39+
name string
40+
containerFs []*runtimeapi.FilesystemUsage
41+
imageFs []*runtimeapi.FilesystemUsage
42+
writeableSeparateFromReadOnly bool
43+
}{
44+
{
45+
name: "Only images",
46+
imageFs: []*runtimeapi.FilesystemUsage{{FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: "image"}}},
47+
writeableSeparateFromReadOnly: false,
48+
},
49+
{
50+
name: "images and containers",
51+
imageFs: []*runtimeapi.FilesystemUsage{{FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: "image"}}},
52+
containerFs: []*runtimeapi.FilesystemUsage{{FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: "container"}}},
53+
writeableSeparateFromReadOnly: true,
54+
},
55+
{
56+
name: "same filesystem",
57+
imageFs: []*runtimeapi.FilesystemUsage{{FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: "image"}}},
58+
containerFs: []*runtimeapi.FilesystemUsage{{FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: "image"}}},
59+
writeableSeparateFromReadOnly: false,
60+
},
61+
62+
{
63+
name: "Only containers",
64+
containerFs: []*runtimeapi.FilesystemUsage{{FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: "image"}}},
65+
writeableSeparateFromReadOnly: false,
66+
},
67+
{
68+
name: "neither are specified",
69+
writeableSeparateFromReadOnly: false,
70+
},
71+
{
72+
name: "both are empty arrays",
73+
writeableSeparateFromReadOnly: false,
74+
containerFs: []*runtimeapi.FilesystemUsage{},
75+
imageFs: []*runtimeapi.FilesystemUsage{},
76+
},
77+
{
78+
name: "FsId does not exist",
79+
writeableSeparateFromReadOnly: false,
80+
containerFs: []*runtimeapi.FilesystemUsage{{UsedBytes: &runtimeapi.UInt64Value{Value: 10}}},
81+
imageFs: []*runtimeapi.FilesystemUsage{{UsedBytes: &runtimeapi.UInt64Value{Value: 10}}},
82+
},
83+
}
84+
85+
for _, tc := range cases {
86+
runtime.SetContainerFsStats(tc.containerFs)
87+
runtime.SetImageFsStats(tc.imageFs)
88+
actualCommand := gcContainer.IsContainerFsSeparateFromImageFs(context.TODO())
89+
90+
if e, a := tc.writeableSeparateFromReadOnly, actualCommand; !reflect.DeepEqual(e, a) {
91+
t.Errorf("%v: unexpected value; expected %v, got %v", tc.name, e, a)
92+
}
93+
runtime.SetContainerFsStats(nil)
94+
runtime.SetImageFsStats(nil)
95+
}
96+
}

pkg/kubelet/eviction/eviction_manager.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -252,17 +252,13 @@ func (m *managerImpl) synchronize(diskInfoProvider DiskInfoProvider, podFunc Act
252252
// build the ranking functions (if not yet known)
253253
// TODO: have a function in cadvisor that lets us know if global housekeeping has completed
254254
if m.dedicatedImageFs == nil {
255-
hasImageFs, imageFsErr := diskInfoProvider.HasDedicatedImageFs(ctx)
256-
if imageFsErr != nil {
257-
klog.ErrorS(imageFsErr, "Eviction manager: failed to get HasDedicatedImageFs")
258-
return nil, fmt.Errorf("eviction manager: failed to get HasDedicatedImageFs: %w", imageFsErr)
255+
hasImageFs, splitDiskError := diskInfoProvider.HasDedicatedImageFs(ctx)
256+
if splitDiskError != nil {
257+
klog.ErrorS(splitDiskError, "Eviction manager: failed to get HasDedicatedImageFs")
258+
return nil, fmt.Errorf("eviction manager: failed to get HasDedicatedImageFs: %w", splitDiskError)
259259
}
260260
m.dedicatedImageFs = &hasImageFs
261-
splitContainerImageFs, splitErr := diskInfoProvider.HasDedicatedContainerFs(ctx)
262-
if splitErr != nil {
263-
klog.ErrorS(splitErr, "Eviction manager: failed to get HasDedicatedContainerFs")
264-
return nil, fmt.Errorf("eviction manager: failed to get HasDedicatedContainerFs: %w", splitErr)
265-
}
261+
splitContainerImageFs := m.containerGC.IsContainerFsSeparateFromImageFs(ctx)
266262

267263
// If we are a split filesystem but the feature is turned off
268264
// we should return an error.

pkg/kubelet/eviction/eviction_manager_test.go

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,27 +66,22 @@ func (m *mockPodKiller) killPodNow(pod *v1.Pod, evict bool, gracePeriodOverride
6666

6767
// mockDiskInfoProvider is used to simulate testing.
6868
type mockDiskInfoProvider struct {
69-
dedicatedImageFs *bool
70-
dedicatedContainerFs *bool
69+
dedicatedImageFs *bool
7170
}
7271

7372
// HasDedicatedImageFs returns the mocked value
7473
func (m *mockDiskInfoProvider) HasDedicatedImageFs(_ context.Context) (bool, error) {
7574
return ptr.Deref(m.dedicatedImageFs, false), nil
7675
}
7776

78-
// HasDedicatedContainerFs returns the mocked value
79-
func (m *mockDiskInfoProvider) HasDedicatedContainerFs(_ context.Context) (bool, error) {
80-
return ptr.Deref(m.dedicatedContainerFs, false), nil
81-
}
82-
8377
// mockDiskGC is used to simulate invoking image and container garbage collection.
8478
type mockDiskGC struct {
85-
err error
86-
imageGCInvoked bool
87-
containerGCInvoked bool
88-
fakeSummaryProvider *fakeSummaryProvider
89-
summaryAfterGC *statsapi.Summary
79+
err error
80+
imageGCInvoked bool
81+
containerGCInvoked bool
82+
readAndWriteSeparate bool
83+
fakeSummaryProvider *fakeSummaryProvider
84+
summaryAfterGC *statsapi.Summary
9085
}
9186

9287
// DeleteUnusedImages returns the mocked values.
@@ -107,6 +102,10 @@ func (m *mockDiskGC) DeleteAllUnusedContainers(_ context.Context) error {
107102
return m.err
108103
}
109104

105+
func (m *mockDiskGC) IsContainerFsSeparateFromImageFs(_ context.Context) bool {
106+
return m.readAndWriteSeparate
107+
}
108+
110109
func makePodWithMemoryStats(name string, priority int32, requests v1.ResourceList, limits v1.ResourceList, memoryWorkingSet string) (*v1.Pod, statsapi.PodStats) {
111110
pod := newPod(name, priority, []v1.Container{
112111
newContainer(name, requests, limits),
@@ -385,7 +384,7 @@ func TestPIDPressure_VerifyPodStatus(t *testing.T) {
385384

386385
fakeClock := testingclock.NewFakeClock(time.Now())
387386
podKiller := &mockPodKiller{}
388-
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: ptr.To(false), dedicatedContainerFs: ptr.To(false)}
387+
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: ptr.To(false)}
389388
diskGC := &mockDiskGC{err: nil}
390389
nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
391390

@@ -563,8 +562,8 @@ func TestDiskPressureNodeFs_VerifyPodStatus(t *testing.T) {
563562

564563
fakeClock := testingclock.NewFakeClock(time.Now())
565564
podKiller := &mockPodKiller{}
566-
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: tc.dedicatedImageFs, dedicatedContainerFs: &tc.writeableSeparateFromReadOnly}
567-
diskGC := &mockDiskGC{err: nil}
565+
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: tc.dedicatedImageFs}
566+
diskGC := &mockDiskGC{err: nil, readAndWriteSeparate: tc.writeableSeparateFromReadOnly}
568567
nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
569568

570569
config := Config{
@@ -1308,8 +1307,8 @@ func TestDiskPressureNodeFs(t *testing.T) {
13081307

13091308
fakeClock := testingclock.NewFakeClock(time.Now())
13101309
podKiller := &mockPodKiller{}
1311-
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: tc.dedicatedImageFs, dedicatedContainerFs: &tc.writeableSeparateFromReadOnly}
1312-
diskGC := &mockDiskGC{err: nil}
1310+
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: tc.dedicatedImageFs}
1311+
diskGC := &mockDiskGC{err: nil, readAndWriteSeparate: tc.writeableSeparateFromReadOnly}
13131312
nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
13141313

13151314
config := Config{
@@ -1830,7 +1829,7 @@ func TestNodeReclaimFuncs(t *testing.T) {
18301829

18311830
fakeClock := testingclock.NewFakeClock(time.Now())
18321831
podKiller := &mockPodKiller{}
1833-
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: tc.dedicatedImageFs, dedicatedContainerFs: &tc.writeableSeparateFromReadOnly}
1832+
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: tc.dedicatedImageFs}
18341833
nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
18351834

18361835
config := Config{
@@ -1847,7 +1846,7 @@ func TestNodeReclaimFuncs(t *testing.T) {
18471846
// This is a constant that we use to test that disk pressure is over. Don't change!
18481847
diskStatConst := diskStatStart
18491848
summaryProvider := &fakeSummaryProvider{result: summaryStatsMaker(diskStatStart)}
1850-
diskGC := &mockDiskGC{fakeSummaryProvider: summaryProvider, err: nil}
1849+
diskGC := &mockDiskGC{fakeSummaryProvider: summaryProvider, err: nil, readAndWriteSeparate: tc.writeableSeparateFromReadOnly}
18511850
manager := &managerImpl{
18521851
clock: fakeClock,
18531852
killPodFunc: podKiller.killPodNow,
@@ -2293,8 +2292,8 @@ func TestInodePressureFsInodes(t *testing.T) {
22932292

22942293
fakeClock := testingclock.NewFakeClock(time.Now())
22952294
podKiller := &mockPodKiller{}
2296-
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: tc.dedicatedImageFs, dedicatedContainerFs: &tc.writeableSeparateFromReadOnly}
2297-
diskGC := &mockDiskGC{err: nil}
2295+
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: tc.dedicatedImageFs}
2296+
diskGC := &mockDiskGC{err: nil, readAndWriteSeparate: tc.writeableSeparateFromReadOnly}
22982297
nodeRef := &v1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
22992298

23002299
config := Config{

pkg/kubelet/eviction/types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,6 @@ type Manager interface {
7575
type DiskInfoProvider interface {
7676
// HasDedicatedImageFs returns true if the imagefs is on a separate device from the rootfs.
7777
HasDedicatedImageFs(ctx context.Context) (bool, error)
78-
// HasDedicatedContainerFs returns true if the container fs is on a separate device from the rootfs.
79-
HasDedicatedContainerFs(ctx context.Context) (bool, error)
8078
}
8179

8280
// ImageGC is responsible for performing garbage collection of unused images.
@@ -89,6 +87,8 @@ type ImageGC interface {
8987
type ContainerGC interface {
9088
// DeleteAllUnusedContainers deletes all unused containers, even those that belong to pods that are terminated, but not deleted.
9189
DeleteAllUnusedContainers(ctx context.Context) error
90+
// IsContainerFsSeparateFromImageFs checks if container filesystem is split from image filesystem.
91+
IsContainerFsSeparateFromImageFs(ctx context.Context) bool
9292
}
9393

9494
// KillPodFunc kills a pod.

pkg/kubelet/stats/cadvisor_stats_provider.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,11 +276,11 @@ func (p *cadvisorStatsProvider) ImageFsStats(ctx context.Context) (imageFsRet *s
276276
if imageStats == nil || len(imageStats.ImageFilesystems) == 0 || len(imageStats.ContainerFilesystems) == 0 {
277277
return nil, nil, fmt.Errorf("missing image stats: %+v", imageStats)
278278
}
279-
280279
splitFileSystem := false
281280
imageFs := imageStats.ImageFilesystems[0]
282281
containerFs := imageStats.ContainerFilesystems[0]
283282
if imageFs.FsId != nil && containerFs.FsId != nil && imageFs.FsId.Mountpoint != containerFs.FsId.Mountpoint {
283+
klog.InfoS("Detect Split Filesystem", "ImageFilesystems", imageFs, "ContainerFilesystems", containerFs)
284284
splitFileSystem = true
285285
}
286286
var imageFsInodesUsed *uint64
@@ -312,12 +312,6 @@ func (p *cadvisorStatsProvider) ImageFsStats(ctx context.Context) (imageFsRet *s
312312
if err != nil {
313313
return nil, nil, fmt.Errorf("failed to get container fs info: %w", err)
314314
}
315-
// ImageFs and ContainerFs could be on different paths on the same device.
316-
if containerFsInfo.Device == imageFsInfo.Device {
317-
return fsStats, fsStats, nil
318-
}
319-
320-
klog.InfoS("Detect Split Filesystem", "ImageFilesystems", imageStats.ImageFilesystems[0], "ContainerFilesystems", imageStats.ContainerFilesystems[0])
321315

322316
var containerFsInodesUsed *uint64
323317
if containerFsInfo.Inodes != nil && containerFsInfo.InodesFree != nil {

pkg/kubelet/stats/cadvisor_stats_provider_test.go

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ func TestCadvisorSplitImagesFsStats(t *testing.T) {
680680
mockRuntime = containertest.NewMockRuntime(t)
681681

682682
seed = 1000
683-
imageFsInfo = getTestFsInfoWithDifferentMount(seed, "image")
683+
imageFsInfo = getTestFsInfo(seed)
684684
containerSeed = 1001
685685
containerFsInfo = getTestFsInfo(containerSeed)
686686
)
@@ -723,59 +723,7 @@ func TestCadvisorSplitImagesFsStats(t *testing.T) {
723723
assert.Equal(containerFsInfo.InodesFree, containerfs.InodesFree)
724724
assert.Equal(containerFsInfo.Inodes, containerfs.Inodes)
725725
assert.Equal(*containerFsInfo.Inodes-*containerFsInfo.InodesFree, *containerfs.InodesUsed)
726-
}
727726

728-
func TestCadvisorSameDiskDifferentLocations(t *testing.T) {
729-
ctx := context.Background()
730-
var (
731-
assert = assert.New(t)
732-
mockCadvisor = cadvisortest.NewMockInterface(t)
733-
mockRuntime = containertest.NewMockRuntime(t)
734-
735-
seed = 1000
736-
imageFsInfo = getTestFsInfo(seed)
737-
containerSeed = 1001
738-
containerFsInfo = getTestFsInfo(containerSeed)
739-
)
740-
imageFsInfoCRI := &runtimeapi.FilesystemUsage{
741-
Timestamp: imageFsInfo.Timestamp.Unix(),
742-
FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: "images"},
743-
UsedBytes: &runtimeapi.UInt64Value{Value: imageFsInfo.Usage},
744-
InodesUsed: &runtimeapi.UInt64Value{Value: *imageFsInfo.Inodes},
745-
}
746-
containerFsInfoCRI := &runtimeapi.FilesystemUsage{
747-
Timestamp: containerFsInfo.Timestamp.Unix(),
748-
FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: "containers"},
749-
UsedBytes: &runtimeapi.UInt64Value{Value: containerFsInfo.Usage},
750-
InodesUsed: &runtimeapi.UInt64Value{Value: *containerFsInfo.Inodes},
751-
}
752-
imageFsInfoResponse := &runtimeapi.ImageFsInfoResponse{
753-
ImageFilesystems: []*runtimeapi.FilesystemUsage{imageFsInfoCRI},
754-
ContainerFilesystems: []*runtimeapi.FilesystemUsage{containerFsInfoCRI},
755-
}
756-
757-
mockCadvisor.EXPECT().ImagesFsInfo().Return(imageFsInfo, nil)
758-
mockCadvisor.EXPECT().ContainerFsInfo().Return(containerFsInfo, nil)
759-
mockRuntime.EXPECT().ImageFsInfo(ctx).Return(imageFsInfoResponse, nil)
760-
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.KubeletSeparateDiskGC, true)
761-
762-
provider := newCadvisorStatsProvider(mockCadvisor, &fakeResourceAnalyzer{}, mockRuntime, nil, NewFakeHostStatsProvider())
763-
stats, containerfs, err := provider.ImageFsStats(ctx)
764-
require.NoError(t, err, "imageFsStats should have no error")
765-
766-
assert.Equal(imageFsInfo.Timestamp, stats.Time.Time)
767-
assert.Equal(imageFsInfo.Available, *stats.AvailableBytes)
768-
assert.Equal(imageFsInfo.Capacity, *stats.CapacityBytes)
769-
assert.Equal(imageFsInfo.InodesFree, stats.InodesFree)
770-
assert.Equal(imageFsInfo.Inodes, stats.Inodes)
771-
assert.Equal(*imageFsInfo.Inodes-*imageFsInfo.InodesFree, *stats.InodesUsed)
772-
773-
assert.Equal(imageFsInfo.Timestamp, containerfs.Time.Time)
774-
assert.Equal(imageFsInfo.Available, *containerfs.AvailableBytes)
775-
assert.Equal(imageFsInfo.Capacity, *containerfs.CapacityBytes)
776-
assert.Equal(imageFsInfo.InodesFree, containerfs.InodesFree)
777-
assert.Equal(imageFsInfo.Inodes, containerfs.Inodes)
778-
assert.Equal(*imageFsInfo.Inodes-*imageFsInfo.InodesFree, *containerfs.InodesUsed)
779727
}
780728

781729
func TestCadvisorListPodStatsWhenContainerLogFound(t *testing.T) {

pkg/kubelet/stats/provider.go

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,6 @@ func (p *Provider) HasDedicatedImageFs(ctx context.Context) (bool, error) {
197197
return device != rootFsInfo.Device, nil
198198
}
199199

200-
// HasDedicatedImageFs returns true if a dedicated image filesystem exists for storing images.
201-
// KEP Issue Number 4191: Enhanced this to allow for the containers to be separate from images.
202-
func (p *Provider) HasDedicatedContainerFs(ctx context.Context) (bool, error) {
203-
imageFs, err := p.cadvisor.ImagesFsInfo()
204-
if err != nil {
205-
return false, err
206-
}
207-
containerFs, err := p.cadvisor.ContainerFsInfo()
208-
if err != nil {
209-
return false, err
210-
}
211-
return imageFs.Device != containerFs.Device, nil
212-
}
213-
214200
func equalFileSystems(a, b *statsapi.FsStats) bool {
215201
if a == nil || b == nil {
216202
return false

0 commit comments

Comments
 (0)