Commit 889afa5e authored by David Ashpole's avatar David Ashpole

trigger aggressive container garbage collection when under disk pressure

parent 07f85565
...@@ -39,7 +39,15 @@ type ContainerGCPolicy struct { ...@@ -39,7 +39,15 @@ type ContainerGCPolicy struct {
// Implementation is thread-compatible. // Implementation is thread-compatible.
type ContainerGC interface { type ContainerGC interface {
// Garbage collect containers. // Garbage collect containers.
GarbageCollect(allSourcesReady bool) error GarbageCollect() error
// Deletes all unused containers, including containers belonging to pods that are terminated but not deleted
DeleteAllUnusedContainers() error
}
// SourcesReadyProvider knows how to determine if configuration sources are ready
type SourcesReadyProvider interface {
// AllReady returns true if the currently configured sources have all been seen.
AllReady() bool
} }
// TODO(vmarmol): Preferentially remove pod infra containers. // TODO(vmarmol): Preferentially remove pod infra containers.
...@@ -49,20 +57,28 @@ type realContainerGC struct { ...@@ -49,20 +57,28 @@ type realContainerGC struct {
// Policy for garbage collection. // Policy for garbage collection.
policy ContainerGCPolicy policy ContainerGCPolicy
// sourcesReadyProvider provides the readyness of kubelet configuration sources.
sourcesReadyProvider SourcesReadyProvider
} }
// New ContainerGC instance with the specified policy. // New ContainerGC instance with the specified policy.
func NewContainerGC(runtime Runtime, policy ContainerGCPolicy) (ContainerGC, error) { func NewContainerGC(runtime Runtime, policy ContainerGCPolicy, sourcesReadyProvider SourcesReadyProvider) (ContainerGC, error) {
if policy.MinAge < 0 { if policy.MinAge < 0 {
return nil, fmt.Errorf("invalid minimum garbage collection age: %v", policy.MinAge) return nil, fmt.Errorf("invalid minimum garbage collection age: %v", policy.MinAge)
} }
return &realContainerGC{ return &realContainerGC{
runtime: runtime, runtime: runtime,
policy: policy, policy: policy,
sourcesReadyProvider: sourcesReadyProvider,
}, nil }, nil
} }
func (cgc *realContainerGC) GarbageCollect(allSourcesReady bool) error { func (cgc *realContainerGC) GarbageCollect() error {
return cgc.runtime.GarbageCollect(cgc.policy, allSourcesReady) return cgc.runtime.GarbageCollect(cgc.policy, cgc.sourcesReadyProvider.AllReady(), false)
}
func (cgc *realContainerGC) DeleteAllUnusedContainers() error {
return cgc.runtime.GarbageCollect(cgc.policy, cgc.sourcesReadyProvider.AllReady(), true)
} }
...@@ -82,8 +82,10 @@ type Runtime interface { ...@@ -82,8 +82,10 @@ type Runtime interface {
// complete list of pods from all avialble sources (e.g., apiserver, http, // complete list of pods from all avialble sources (e.g., apiserver, http,
// file). In this case, garbage collector should refrain itself from aggressive // file). In this case, garbage collector should refrain itself from aggressive
// behavior such as removing all containers of unrecognized pods (yet). // behavior such as removing all containers of unrecognized pods (yet).
// If evictNonDeletedPods is set to true, containers and sandboxes belonging to pods
// that are terminated, but not deleted will be evicted. Otherwise, only deleted pods will be GC'd.
// TODO: Revisit this method and make it cleaner. // TODO: Revisit this method and make it cleaner.
GarbageCollect(gcPolicy ContainerGCPolicy, allSourcesReady bool) error GarbageCollect(gcPolicy ContainerGCPolicy, allSourcesReady bool, evictNonDeletedPods bool) error
// Syncs the running pod into the desired pod. // Syncs the running pod into the desired pod.
SyncPod(pod *v1.Pod, apiPodStatus v1.PodStatus, podStatus *PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) PodSyncResult SyncPod(pod *v1.Pod, apiPodStatus v1.PodStatus, podStatus *PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) PodSyncResult
// KillPod kills all the containers of a pod. Pod may be nil, running pod must not be. // KillPod kills all the containers of a pod. Pod may be nil, running pod must not be.
......
...@@ -431,7 +431,7 @@ func (f *FakeRuntime) GetPodContainerID(pod *Pod) (ContainerID, error) { ...@@ -431,7 +431,7 @@ func (f *FakeRuntime) GetPodContainerID(pod *Pod) (ContainerID, error) {
return ContainerID{}, f.Err return ContainerID{}, f.Err
} }
func (f *FakeRuntime) GarbageCollect(gcPolicy ContainerGCPolicy, ready bool) error { func (f *FakeRuntime) GarbageCollect(gcPolicy ContainerGCPolicy, ready bool, evictNonDeletedPods bool) error {
f.Lock() f.Lock()
defer f.Unlock() defer f.Unlock()
......
...@@ -140,8 +140,8 @@ func (r *Mock) GetPodContainerID(pod *Pod) (ContainerID, error) { ...@@ -140,8 +140,8 @@ func (r *Mock) GetPodContainerID(pod *Pod) (ContainerID, error) {
return ContainerID{}, args.Error(0) return ContainerID{}, args.Error(0)
} }
func (r *Mock) GarbageCollect(gcPolicy ContainerGCPolicy, ready bool) error { func (r *Mock) GarbageCollect(gcPolicy ContainerGCPolicy, ready bool, evictNonDeletedPods bool) error {
args := r.Called(gcPolicy, ready) args := r.Called(gcPolicy, ready, evictNonDeletedPods)
return args.Error(0) return args.Error(0)
} }
......
...@@ -58,6 +58,8 @@ type managerImpl struct { ...@@ -58,6 +58,8 @@ type managerImpl struct {
killPodFunc KillPodFunc killPodFunc KillPodFunc
// the interface that knows how to do image gc // the interface that knows how to do image gc
imageGC ImageGC imageGC ImageGC
// the interface that knows how to do image gc
containerGC ContainerGC
// protects access to internal state // protects access to internal state
sync.RWMutex sync.RWMutex
// node conditions are the set of conditions present // node conditions are the set of conditions present
...@@ -95,6 +97,7 @@ func NewManager( ...@@ -95,6 +97,7 @@ func NewManager(
config Config, config Config,
killPodFunc KillPodFunc, killPodFunc KillPodFunc,
imageGC ImageGC, imageGC ImageGC,
containerGC ContainerGC,
recorder record.EventRecorder, recorder record.EventRecorder,
nodeRef *clientv1.ObjectReference, nodeRef *clientv1.ObjectReference,
clock clock.Clock) (Manager, lifecycle.PodAdmitHandler) { clock clock.Clock) (Manager, lifecycle.PodAdmitHandler) {
...@@ -102,6 +105,7 @@ func NewManager( ...@@ -102,6 +105,7 @@ func NewManager(
clock: clock, clock: clock,
killPodFunc: killPodFunc, killPodFunc: killPodFunc,
imageGC: imageGC, imageGC: imageGC,
containerGC: containerGC,
config: config, config: config,
recorder: recorder, recorder: recorder,
summaryProvider: summaryProvider, summaryProvider: summaryProvider,
...@@ -223,8 +227,7 @@ func (m *managerImpl) synchronize(diskInfoProvider DiskInfoProvider, podFunc Act ...@@ -223,8 +227,7 @@ func (m *managerImpl) synchronize(diskInfoProvider DiskInfoProvider, podFunc Act
} }
m.dedicatedImageFs = &hasImageFs m.dedicatedImageFs = &hasImageFs
m.resourceToRankFunc = buildResourceToRankFunc(hasImageFs) m.resourceToRankFunc = buildResourceToRankFunc(hasImageFs)
m.resourceToNodeReclaimFuncs = buildResourceToNodeReclaimFuncs(m.imageGC, hasImageFs) m.resourceToNodeReclaimFuncs = buildResourceToNodeReclaimFuncs(m.imageGC, m.containerGC, hasImageFs)
} }
activePods := podFunc() activePods := podFunc()
......
...@@ -77,17 +77,24 @@ func (m *mockNodeProvider) GetNode() (*v1.Node, error) { ...@@ -77,17 +77,24 @@ func (m *mockNodeProvider) GetNode() (*v1.Node, error) {
return &m.node, nil return &m.node, nil
} }
// mockImageGC is used to simulate invoking image garbage collection. // mockDiskGC is used to simulate invoking image and container garbage collection.
type mockImageGC struct { type mockDiskGC struct {
err error err error
freed int64 imageBytesFreed int64
invoked bool imageGCInvoked bool
containerGCInvoked bool
} }
// DeleteUnusedImages returns the mocked values. // DeleteUnusedImages returns the mocked values.
func (m *mockImageGC) DeleteUnusedImages() (int64, error) { func (m *mockDiskGC) DeleteUnusedImages() (int64, error) {
m.invoked = true m.imageGCInvoked = true
return m.freed, m.err return m.imageBytesFreed, m.err
}
// DeleteAllUnusedContainers returns the mocked value
func (m *mockDiskGC) DeleteAllUnusedContainers() error {
m.containerGCInvoked = true
return m.err
} }
func makePodWithMemoryStats(name string, requests v1.ResourceList, limits v1.ResourceList, memoryWorkingSet string) (*v1.Pod, statsapi.PodStats) { func makePodWithMemoryStats(name string, requests v1.ResourceList, limits v1.ResourceList, memoryWorkingSet string) (*v1.Pod, statsapi.PodStats) {
...@@ -194,7 +201,7 @@ func TestMemoryPressure(t *testing.T) { ...@@ -194,7 +201,7 @@ func TestMemoryPressure(t *testing.T) {
podKiller := &mockPodKiller{} podKiller := &mockPodKiller{}
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")}) nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")})
imageGC := &mockImageGC{freed: int64(0), err: nil} imageGC := &mockDiskGC{imageBytesFreed: int64(0), err: nil}
nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
config := Config{ config := Config{
...@@ -412,7 +419,7 @@ func TestDiskPressureNodeFs(t *testing.T) { ...@@ -412,7 +419,7 @@ func TestDiskPressureNodeFs(t *testing.T) {
podKiller := &mockPodKiller{} podKiller := &mockPodKiller{}
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")}) nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")})
imageGC := &mockImageGC{freed: int64(0), err: nil} diskGC := &mockDiskGC{imageBytesFreed: int64(0), err: nil}
nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
config := Config{ config := Config{
...@@ -440,7 +447,8 @@ func TestDiskPressureNodeFs(t *testing.T) { ...@@ -440,7 +447,8 @@ func TestDiskPressureNodeFs(t *testing.T) {
manager := &managerImpl{ manager := &managerImpl{
clock: fakeClock, clock: fakeClock,
killPodFunc: podKiller.killPodNow, killPodFunc: podKiller.killPodNow,
imageGC: imageGC, imageGC: diskGC,
containerGC: diskGC,
config: config, config: config,
recorder: &record.FakeRecorder{}, recorder: &record.FakeRecorder{},
summaryProvider: summaryProvider, summaryProvider: summaryProvider,
...@@ -610,7 +618,7 @@ func TestMinReclaim(t *testing.T) { ...@@ -610,7 +618,7 @@ func TestMinReclaim(t *testing.T) {
podKiller := &mockPodKiller{} podKiller := &mockPodKiller{}
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")}) nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")})
imageGC := &mockImageGC{freed: int64(0), err: nil} diskGC := &mockDiskGC{imageBytesFreed: int64(0), err: nil}
nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
config := Config{ config := Config{
...@@ -633,7 +641,8 @@ func TestMinReclaim(t *testing.T) { ...@@ -633,7 +641,8 @@ func TestMinReclaim(t *testing.T) {
manager := &managerImpl{ manager := &managerImpl{
clock: fakeClock, clock: fakeClock,
killPodFunc: podKiller.killPodNow, killPodFunc: podKiller.killPodNow,
imageGC: imageGC, imageGC: diskGC,
containerGC: diskGC,
config: config, config: config,
recorder: &record.FakeRecorder{}, recorder: &record.FakeRecorder{},
summaryProvider: summaryProvider, summaryProvider: summaryProvider,
...@@ -750,7 +759,7 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -750,7 +759,7 @@ func TestNodeReclaimFuncs(t *testing.T) {
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")}) nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")})
imageGcFree := resource.MustParse("700Mi") imageGcFree := resource.MustParse("700Mi")
imageGC := &mockImageGC{freed: imageGcFree.Value(), err: nil} diskGC := &mockDiskGC{imageBytesFreed: imageGcFree.Value(), err: nil}
nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
config := Config{ config := Config{
...@@ -773,7 +782,8 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -773,7 +782,8 @@ func TestNodeReclaimFuncs(t *testing.T) {
manager := &managerImpl{ manager := &managerImpl{
clock: fakeClock, clock: fakeClock,
killPodFunc: podKiller.killPodNow, killPodFunc: podKiller.killPodNow,
imageGC: imageGC, imageGC: diskGC,
containerGC: diskGC,
config: config, config: config,
recorder: &record.FakeRecorder{}, recorder: &record.FakeRecorder{},
summaryProvider: summaryProvider, summaryProvider: summaryProvider,
...@@ -801,7 +811,7 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -801,7 +811,7 @@ func TestNodeReclaimFuncs(t *testing.T) {
} }
// verify image gc was invoked // verify image gc was invoked
if !imageGC.invoked { if !diskGC.imageGCInvoked || !diskGC.containerGCInvoked {
t.Errorf("Manager should have invoked image gc") t.Errorf("Manager should have invoked image gc")
} }
...@@ -811,7 +821,8 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -811,7 +821,8 @@ func TestNodeReclaimFuncs(t *testing.T) {
} }
// reset state // reset state
imageGC.invoked = false diskGC.imageGCInvoked = false
diskGC.containerGCInvoked = false
// remove disk pressure // remove disk pressure
fakeClock.Step(20 * time.Minute) fakeClock.Step(20 * time.Minute)
...@@ -833,8 +844,8 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -833,8 +844,8 @@ func TestNodeReclaimFuncs(t *testing.T) {
t.Errorf("Manager should report disk pressure") t.Errorf("Manager should report disk pressure")
} }
// ensure image gc was invoked // ensure disk gc was invoked
if !imageGC.invoked { if !diskGC.imageGCInvoked || !diskGC.containerGCInvoked {
t.Errorf("Manager should have invoked image gc") t.Errorf("Manager should have invoked image gc")
} }
...@@ -850,8 +861,9 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -850,8 +861,9 @@ func TestNodeReclaimFuncs(t *testing.T) {
// reduce disk pressure // reduce disk pressure
fakeClock.Step(1 * time.Minute) fakeClock.Step(1 * time.Minute)
summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats) summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
imageGC.invoked = false // reset state diskGC.imageGCInvoked = false // reset state
podKiller.pod = nil // reset state diskGC.containerGCInvoked = false // reset state
podKiller.pod = nil // reset state
manager.synchronize(diskInfoProvider, activePodsFunc, nodeProvider) manager.synchronize(diskInfoProvider, activePodsFunc, nodeProvider)
// we should have disk pressure (because transition period not yet met) // we should have disk pressure (because transition period not yet met)
...@@ -860,7 +872,7 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -860,7 +872,7 @@ func TestNodeReclaimFuncs(t *testing.T) {
} }
// no image gc should have occurred // no image gc should have occurred
if imageGC.invoked { if diskGC.imageGCInvoked || diskGC.containerGCInvoked {
t.Errorf("Manager chose to perform image gc when it was not neeed") t.Errorf("Manager chose to perform image gc when it was not neeed")
} }
...@@ -872,8 +884,9 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -872,8 +884,9 @@ func TestNodeReclaimFuncs(t *testing.T) {
// move the clock past transition period to ensure that we stop reporting pressure // move the clock past transition period to ensure that we stop reporting pressure
fakeClock.Step(5 * time.Minute) fakeClock.Step(5 * time.Minute)
summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats) summaryProvider.result = summaryStatsMaker("16Gi", "200Gi", podStats)
imageGC.invoked = false // reset state diskGC.imageGCInvoked = false // reset state
podKiller.pod = nil // reset state diskGC.containerGCInvoked = false // reset state
podKiller.pod = nil // reset state
manager.synchronize(diskInfoProvider, activePodsFunc, nodeProvider) manager.synchronize(diskInfoProvider, activePodsFunc, nodeProvider)
// we should not have disk pressure (because transition period met) // we should not have disk pressure (because transition period met)
...@@ -882,7 +895,7 @@ func TestNodeReclaimFuncs(t *testing.T) { ...@@ -882,7 +895,7 @@ func TestNodeReclaimFuncs(t *testing.T) {
} }
// no image gc should have occurred // no image gc should have occurred
if imageGC.invoked { if diskGC.imageGCInvoked || diskGC.containerGCInvoked {
t.Errorf("Manager chose to perform image gc when it was not neeed") t.Errorf("Manager chose to perform image gc when it was not neeed")
} }
...@@ -943,7 +956,7 @@ func TestInodePressureNodeFsInodes(t *testing.T) { ...@@ -943,7 +956,7 @@ func TestInodePressureNodeFsInodes(t *testing.T) {
podKiller := &mockPodKiller{} podKiller := &mockPodKiller{}
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")}) nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")})
imageGC := &mockImageGC{freed: int64(0), err: nil} diskGC := &mockDiskGC{imageBytesFreed: int64(0), err: nil}
nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
config := Config{ config := Config{
...@@ -971,7 +984,8 @@ func TestInodePressureNodeFsInodes(t *testing.T) { ...@@ -971,7 +984,8 @@ func TestInodePressureNodeFsInodes(t *testing.T) {
manager := &managerImpl{ manager := &managerImpl{
clock: fakeClock, clock: fakeClock,
killPodFunc: podKiller.killPodNow, killPodFunc: podKiller.killPodNow,
imageGC: imageGC, imageGC: diskGC,
containerGC: diskGC,
config: config, config: config,
recorder: &record.FakeRecorder{}, recorder: &record.FakeRecorder{},
summaryProvider: summaryProvider, summaryProvider: summaryProvider,
...@@ -1144,7 +1158,7 @@ func TestCriticalPodsAreNotEvicted(t *testing.T) { ...@@ -1144,7 +1158,7 @@ func TestCriticalPodsAreNotEvicted(t *testing.T) {
podKiller := &mockPodKiller{} podKiller := &mockPodKiller{}
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")}) nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")})
imageGC := &mockImageGC{freed: int64(0), err: nil} diskGC := &mockDiskGC{imageBytesFreed: int64(0), err: nil}
nodeRef := &clientv1.ObjectReference{ nodeRef := &clientv1.ObjectReference{
Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: "", Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: "",
} }
...@@ -1174,7 +1188,8 @@ func TestCriticalPodsAreNotEvicted(t *testing.T) { ...@@ -1174,7 +1188,8 @@ func TestCriticalPodsAreNotEvicted(t *testing.T) {
manager := &managerImpl{ manager := &managerImpl{
clock: fakeClock, clock: fakeClock,
killPodFunc: podKiller.killPodNow, killPodFunc: podKiller.killPodNow,
imageGC: imageGC, imageGC: diskGC,
containerGC: diskGC,
config: config, config: config,
recorder: &record.FakeRecorder{}, recorder: &record.FakeRecorder{},
summaryProvider: summaryProvider, summaryProvider: summaryProvider,
...@@ -1276,7 +1291,7 @@ func TestAllocatableMemoryPressure(t *testing.T) { ...@@ -1276,7 +1291,7 @@ func TestAllocatableMemoryPressure(t *testing.T) {
podKiller := &mockPodKiller{} podKiller := &mockPodKiller{}
diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false} diskInfoProvider := &mockDiskInfoProvider{dedicatedImageFs: false}
nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")}) nodeProvider := newMockNodeProvider(v1.ResourceList{v1.ResourceMemory: *quantityMustParse("2Gi")})
imageGC := &mockImageGC{freed: int64(0), err: nil} diskGC := &mockDiskGC{imageBytesFreed: int64(0), err: nil}
nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""} nodeRef := &clientv1.ObjectReference{Kind: "Node", Name: "test", UID: types.UID("test"), Namespace: ""}
config := Config{ config := Config{
...@@ -1296,7 +1311,8 @@ func TestAllocatableMemoryPressure(t *testing.T) { ...@@ -1296,7 +1311,8 @@ func TestAllocatableMemoryPressure(t *testing.T) {
manager := &managerImpl{ manager := &managerImpl{
clock: fakeClock, clock: fakeClock,
killPodFunc: podKiller.killPodNow, killPodFunc: podKiller.killPodNow,
imageGC: imageGC, imageGC: diskGC,
containerGC: diskGC,
config: config, config: config,
recorder: &record.FakeRecorder{}, recorder: &record.FakeRecorder{},
summaryProvider: summaryProvider, summaryProvider: summaryProvider,
......
...@@ -1019,32 +1019,34 @@ func PodIsEvicted(podStatus v1.PodStatus) bool { ...@@ -1019,32 +1019,34 @@ func PodIsEvicted(podStatus v1.PodStatus) bool {
} }
// buildResourceToNodeReclaimFuncs returns reclaim functions associated with resources. // buildResourceToNodeReclaimFuncs returns reclaim functions associated with resources.
func buildResourceToNodeReclaimFuncs(imageGC ImageGC, withImageFs bool) map[v1.ResourceName]nodeReclaimFuncs { func buildResourceToNodeReclaimFuncs(imageGC ImageGC, containerGC ContainerGC, withImageFs bool) map[v1.ResourceName]nodeReclaimFuncs {
resourceToReclaimFunc := map[v1.ResourceName]nodeReclaimFuncs{} resourceToReclaimFunc := map[v1.ResourceName]nodeReclaimFuncs{}
// usage of an imagefs is optional // usage of an imagefs is optional
if withImageFs { if withImageFs {
// with an imagefs, nodefs pressure should just delete logs // with an imagefs, nodefs pressure should just delete logs
resourceToReclaimFunc[resourceNodeFs] = nodeReclaimFuncs{deleteLogs()} resourceToReclaimFunc[resourceNodeFs] = nodeReclaimFuncs{}
resourceToReclaimFunc[resourceNodeFsInodes] = nodeReclaimFuncs{deleteLogs()} resourceToReclaimFunc[resourceNodeFsInodes] = nodeReclaimFuncs{}
// with an imagefs, imagefs pressure should delete unused images // with an imagefs, imagefs pressure should delete unused images
resourceToReclaimFunc[resourceImageFs] = nodeReclaimFuncs{deleteImages(imageGC, true)} resourceToReclaimFunc[resourceImageFs] = nodeReclaimFuncs{deleteTerminatedContainers(containerGC), deleteImages(imageGC, true)}
resourceToReclaimFunc[resourceImageFsInodes] = nodeReclaimFuncs{deleteImages(imageGC, false)} resourceToReclaimFunc[resourceImageFsInodes] = nodeReclaimFuncs{deleteTerminatedContainers(containerGC), deleteImages(imageGC, false)}
} else { } else {
// without an imagefs, nodefs pressure should delete logs, and unused images // without an imagefs, nodefs pressure should delete logs, and unused images
// since imagefs and nodefs share a common device, they share common reclaim functions // since imagefs and nodefs share a common device, they share common reclaim functions
resourceToReclaimFunc[resourceNodeFs] = nodeReclaimFuncs{deleteLogs(), deleteImages(imageGC, true)} resourceToReclaimFunc[resourceNodeFs] = nodeReclaimFuncs{deleteTerminatedContainers(containerGC), deleteImages(imageGC, true)}
resourceToReclaimFunc[resourceNodeFsInodes] = nodeReclaimFuncs{deleteLogs(), deleteImages(imageGC, false)} resourceToReclaimFunc[resourceNodeFsInodes] = nodeReclaimFuncs{deleteTerminatedContainers(containerGC), deleteImages(imageGC, false)}
resourceToReclaimFunc[resourceImageFs] = nodeReclaimFuncs{deleteLogs(), deleteImages(imageGC, true)} resourceToReclaimFunc[resourceImageFs] = nodeReclaimFuncs{deleteTerminatedContainers(containerGC), deleteImages(imageGC, true)}
resourceToReclaimFunc[resourceImageFsInodes] = nodeReclaimFuncs{deleteLogs(), deleteImages(imageGC, false)} resourceToReclaimFunc[resourceImageFsInodes] = nodeReclaimFuncs{deleteTerminatedContainers(containerGC), deleteImages(imageGC, false)}
} }
return resourceToReclaimFunc return resourceToReclaimFunc
} }
// deleteLogs will delete logs to free up disk pressure. // deleteTerminatedContainers will delete terminated containers to free up disk pressure.
func deleteLogs() nodeReclaimFunc { func deleteTerminatedContainers(containerGC ContainerGC) nodeReclaimFunc {
return func() (*resource.Quantity, error) { return func() (*resource.Quantity, error) {
// TODO: not yet supported. glog.Infof("eviction manager: attempting to delete unused containers")
return resource.NewQuantity(int64(0), resource.BinarySI), nil err := containerGC.DeleteAllUnusedContainers()
// Calculating bytes freed is not yet supported.
return resource.NewQuantity(int64(0), resource.BinarySI), err
} }
} }
......
...@@ -81,6 +81,13 @@ type ImageGC interface { ...@@ -81,6 +81,13 @@ type ImageGC interface {
DeleteUnusedImages() (int64, error) DeleteUnusedImages() (int64, error)
} }
// ContainerGC is responsible for performing garbage collection of unused containers.
type ContainerGC interface {
// DeleteAllUnusedContainers deletes all unused containers, even those that belong to pods that are terminated, but not deleted.
// It returns an error if it is unsuccessful.
DeleteAllUnusedContainers() error
}
// KillPodFunc kills a pod. // KillPodFunc kills a pod.
// The pod status is updated, and then it is killed with the specified grace period. // The pod status is updated, and then it is killed with the specified grace period.
// This function must block until either the pod is killed or an error is encountered. // This function must block until either the pod is killed or an error is encountered.
......
...@@ -671,7 +671,7 @@ func NewMainKubelet(kubeCfg *componentconfig.KubeletConfiguration, kubeDeps *Kub ...@@ -671,7 +671,7 @@ func NewMainKubelet(kubeCfg *componentconfig.KubeletConfiguration, kubeDeps *Kub
klet.updatePodCIDR(kubeCfg.PodCIDR) klet.updatePodCIDR(kubeCfg.PodCIDR)
// setup containerGC // setup containerGC
containerGC, err := kubecontainer.NewContainerGC(klet.containerRuntime, containerGCPolicy) containerGC, err := kubecontainer.NewContainerGC(klet.containerRuntime, containerGCPolicy, klet.sourcesReady)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -761,7 +761,7 @@ func NewMainKubelet(kubeCfg *componentconfig.KubeletConfiguration, kubeDeps *Kub ...@@ -761,7 +761,7 @@ func NewMainKubelet(kubeCfg *componentconfig.KubeletConfiguration, kubeDeps *Kub
klet.setNodeStatusFuncs = klet.defaultNodeStatusFuncs() klet.setNodeStatusFuncs = klet.defaultNodeStatusFuncs()
// setup eviction manager // setup eviction manager
evictionManager, evictionAdmitHandler := eviction.NewManager(klet.resourceAnalyzer, evictionConfig, killPodNow(klet.podWorkers, kubeDeps.Recorder), klet.imageManager, kubeDeps.Recorder, nodeRef, klet.clock) evictionManager, evictionAdmitHandler := eviction.NewManager(klet.resourceAnalyzer, evictionConfig, killPodNow(klet.podWorkers, kubeDeps.Recorder), klet.imageManager, klet.containerGC, kubeDeps.Recorder, nodeRef, klet.clock)
klet.evictionManager = evictionManager klet.evictionManager = evictionManager
klet.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler) klet.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler)
...@@ -1184,7 +1184,7 @@ func (kl *Kubelet) setupDataDirs() error { ...@@ -1184,7 +1184,7 @@ func (kl *Kubelet) setupDataDirs() error {
func (kl *Kubelet) StartGarbageCollection() { func (kl *Kubelet) StartGarbageCollection() {
loggedContainerGCFailure := false loggedContainerGCFailure := false
go wait.Until(func() { go wait.Until(func() {
if err := kl.containerGC.GarbageCollect(kl.sourcesReady.AllReady()); err != nil { if err := kl.containerGC.GarbageCollect(); err != nil {
glog.Errorf("Container garbage collection failed: %v", err) glog.Errorf("Container garbage collection failed: %v", err)
kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.ContainerGCFailed, err.Error()) kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.ContainerGCFailed, err.Error())
loggedContainerGCFailure = true loggedContainerGCFailure = true
......
...@@ -270,7 +270,7 @@ func newTestKubeletWithImageList( ...@@ -270,7 +270,7 @@ func newTestKubeletWithImageList(
Namespace: "", Namespace: "",
} }
// setup eviction manager // setup eviction manager
evictionManager, evictionAdmitHandler := eviction.NewManager(kubelet.resourceAnalyzer, eviction.Config{}, killPodNow(kubelet.podWorkers, fakeRecorder), kubelet.imageManager, fakeRecorder, nodeRef, kubelet.clock) evictionManager, evictionAdmitHandler := eviction.NewManager(kubelet.resourceAnalyzer, eviction.Config{}, killPodNow(kubelet.podWorkers, fakeRecorder), kubelet.imageManager, kubelet.containerGC, fakeRecorder, nodeRef, kubelet.clock)
kubelet.evictionManager = evictionManager kubelet.evictionManager = evictionManager
kubelet.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler) kubelet.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler)
......
...@@ -209,7 +209,7 @@ func (cgc *containerGC) evictableContainers(minAge time.Duration) (containersByE ...@@ -209,7 +209,7 @@ func (cgc *containerGC) evictableContainers(minAge time.Duration) (containersByE
} }
// evict all containers that are evictable // evict all containers that are evictable
func (cgc *containerGC) evictContainers(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool) error { func (cgc *containerGC) evictContainers(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool, evictNonDeletedPods bool) error {
// Separate containers by evict units. // Separate containers by evict units.
evictUnits, err := cgc.evictableContainers(gcPolicy.MinAge) evictUnits, err := cgc.evictableContainers(gcPolicy.MinAge)
if err != nil { if err != nil {
...@@ -219,7 +219,7 @@ func (cgc *containerGC) evictContainers(gcPolicy kubecontainer.ContainerGCPolicy ...@@ -219,7 +219,7 @@ func (cgc *containerGC) evictContainers(gcPolicy kubecontainer.ContainerGCPolicy
// Remove deleted pod containers if all sources are ready. // Remove deleted pod containers if all sources are ready.
if allSourcesReady { if allSourcesReady {
for key, unit := range evictUnits { for key, unit := range evictUnits {
if cgc.isPodDeleted(key.uid) { if cgc.isPodDeleted(key.uid) || evictNonDeletedPods {
cgc.removeOldestN(unit, len(unit)) // Remove all. cgc.removeOldestN(unit, len(unit)) // Remove all.
delete(evictUnits, key) delete(evictUnits, key)
} }
...@@ -261,7 +261,7 @@ func (cgc *containerGC) evictContainers(gcPolicy kubecontainer.ContainerGCPolicy ...@@ -261,7 +261,7 @@ func (cgc *containerGC) evictContainers(gcPolicy kubecontainer.ContainerGCPolicy
// 2. contains no containers. // 2. contains no containers.
// 3. belong to a non-existent (i.e., already removed) pod, or is not the // 3. belong to a non-existent (i.e., already removed) pod, or is not the
// most recently created sandbox for the pod. // most recently created sandbox for the pod.
func (cgc *containerGC) evictSandboxes() error { func (cgc *containerGC) evictSandboxes(evictNonDeletedPods bool) error {
containers, err := cgc.manager.getKubeletContainers(true) containers, err := cgc.manager.getKubeletContainers(true)
if err != nil { if err != nil {
return err return err
...@@ -307,7 +307,7 @@ func (cgc *containerGC) evictSandboxes() error { ...@@ -307,7 +307,7 @@ func (cgc *containerGC) evictSandboxes() error {
} }
for podUID, sandboxes := range sandboxesByPod { for podUID, sandboxes := range sandboxesByPod {
if cgc.isPodDeleted(podUID) { if cgc.isPodDeleted(podUID) || evictNonDeletedPods {
// Remove all evictable sandboxes if the pod has been removed. // Remove all evictable sandboxes if the pod has been removed.
// Note that the latest dead sandbox is also removed if there is // Note that the latest dead sandbox is also removed if there is
// already an active one. // already an active one.
...@@ -367,14 +367,14 @@ func (cgc *containerGC) evictPodLogsDirectories(allSourcesReady bool) error { ...@@ -367,14 +367,14 @@ func (cgc *containerGC) evictPodLogsDirectories(allSourcesReady bool) error {
// * removes oldest dead containers by enforcing gcPolicy.MaxContainers. // * removes oldest dead containers by enforcing gcPolicy.MaxContainers.
// * gets evictable sandboxes which are not ready and contains no containers. // * gets evictable sandboxes which are not ready and contains no containers.
// * removes evictable sandboxes. // * removes evictable sandboxes.
func (cgc *containerGC) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool) error { func (cgc *containerGC) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool, evictNonDeletedPods bool) error {
// Remove evictable containers // Remove evictable containers
if err := cgc.evictContainers(gcPolicy, allSourcesReady); err != nil { if err := cgc.evictContainers(gcPolicy, allSourcesReady, evictNonDeletedPods); err != nil {
return err return err
} }
// Remove sandboxes with zero containers // Remove sandboxes with zero containers
if err := cgc.evictSandboxes(); err != nil { if err := cgc.evictSandboxes(evictNonDeletedPods); err != nil {
return err return err
} }
......
...@@ -877,8 +877,8 @@ func (m *kubeGenericRuntimeManager) GetNetNS(_ kubecontainer.ContainerID) (strin ...@@ -877,8 +877,8 @@ func (m *kubeGenericRuntimeManager) GetNetNS(_ kubecontainer.ContainerID) (strin
} }
// GarbageCollect removes dead containers using the specified container gc policy. // GarbageCollect removes dead containers using the specified container gc policy.
func (m *kubeGenericRuntimeManager) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool) error { func (m *kubeGenericRuntimeManager) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool, evictNonDeletedPods bool) error {
return m.containerGC.GarbageCollect(gcPolicy, allSourcesReady) return m.containerGC.GarbageCollect(gcPolicy, allSourcesReady, evictNonDeletedPods)
} }
// GetPodContainerID gets pod sandbox ID // GetPodContainerID gets pod sandbox ID
......
...@@ -1955,7 +1955,7 @@ func (r *Runtime) getPodSystemdServiceFiles() ([]os.FileInfo, error) { ...@@ -1955,7 +1955,7 @@ func (r *Runtime) getPodSystemdServiceFiles() ([]os.FileInfo, error) {
// - If the number of containers exceeds gcPolicy.MaxContainers, // - If the number of containers exceeds gcPolicy.MaxContainers,
// then containers whose ages are older than gcPolicy.minAge will // then containers whose ages are older than gcPolicy.minAge will
// be removed. // be removed.
func (r *Runtime) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool) error { func (r *Runtime) GarbageCollect(gcPolicy kubecontainer.ContainerGCPolicy, allSourcesReady bool, _ bool) error {
var errlist []error var errlist []error
var totalInactiveContainers int var totalInactiveContainers int
var inactivePods []*rktapi.Pod var inactivePods []*rktapi.Pod
......
...@@ -1831,7 +1831,8 @@ func TestGarbageCollect(t *testing.T) { ...@@ -1831,7 +1831,8 @@ func TestGarbageCollect(t *testing.T) {
} }
allSourcesReady := true allSourcesReady := true
err := rkt.GarbageCollect(tt.gcPolicy, allSourcesReady) evictNonDeletedPods := false
err := rkt.GarbageCollect(tt.gcPolicy, allSourcesReady, evictNonDeletedPods)
assert.NoError(t, err, testCaseHint) assert.NoError(t, err, testCaseHint)
sort.Sort(sortedStringList(tt.expectedCommands)) sort.Sort(sortedStringList(tt.expectedCommands))
......
...@@ -126,7 +126,7 @@ func TestRunOnce(t *testing.T) { ...@@ -126,7 +126,7 @@ func TestRunOnce(t *testing.T) {
fakeKillPodFunc := func(pod *v1.Pod, podStatus v1.PodStatus, gracePeriodOverride *int64) error { fakeKillPodFunc := func(pod *v1.Pod, podStatus v1.PodStatus, gracePeriodOverride *int64) error {
return nil return nil
} }
evictionManager, evictionAdmitHandler := eviction.NewManager(kb.resourceAnalyzer, eviction.Config{}, fakeKillPodFunc, nil, kb.recorder, nodeRef, kb.clock) evictionManager, evictionAdmitHandler := eviction.NewManager(kb.resourceAnalyzer, eviction.Config{}, fakeKillPodFunc, nil, nil, kb.recorder, nodeRef, kb.clock)
kb.evictionManager = evictionManager kb.evictionManager = evictionManager
kb.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler) kb.admitHandlers.AddPodAdmitHandler(evictionAdmitHandler)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment