Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

koordlet: kill container after calling eviction api success #1759

Merged
merged 5 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions pkg/koordlet/qosmanager/framework/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Config struct {
MemoryEvictIntervalSeconds int
MemoryEvictCoolTimeSeconds int
CPUEvictCoolTimeSeconds int
OnlyEvictByAPI bool
QOSExtensionCfg *QOSExtensionConfig
}

Expand All @@ -38,6 +39,7 @@ func NewDefaultConfig() *Config {
MemoryEvictIntervalSeconds: 1,
MemoryEvictCoolTimeSeconds: 4,
CPUEvictCoolTimeSeconds: 20,
OnlyEvictByAPI: false,
QOSExtensionCfg: &QOSExtensionConfig{FeatureGates: map[string]bool{}},
}
}
Expand All @@ -49,5 +51,6 @@ func (c *Config) InitFlags(fs *flag.FlagSet) {
fs.IntVar(&c.MemoryEvictIntervalSeconds, "memory-evict-interval-seconds", c.MemoryEvictIntervalSeconds, "evict be pod(memory) interval by seconds")
fs.IntVar(&c.MemoryEvictCoolTimeSeconds, "memory-evict-cool-time-seconds", c.MemoryEvictCoolTimeSeconds, "cooling time: memory next evict time should after lastEvictTime + MemoryEvictCoolTimeSeconds")
fs.IntVar(&c.CPUEvictCoolTimeSeconds, "cpu-evict-cool-time-seconds", c.CPUEvictCoolTimeSeconds, "cooltime: CPU next evict time should after lastEvictTime + CPUEvictCoolTimeSeconds")
fs.BoolVar(&c.OnlyEvictByAPI, "only-evict-by-api", c.OnlyEvictByAPI, "only evict pod if call eviction api successed")
c.QOSExtensionCfg.InitFlags(fs)
}
5 changes: 5 additions & 0 deletions pkg/koordlet/qosmanager/framework/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ func Test_NewDefaultConfig(t *testing.T) {
MemoryEvictIntervalSeconds: 1,
MemoryEvictCoolTimeSeconds: 4,
CPUEvictCoolTimeSeconds: 20,
OnlyEvictByAPI: false,
QOSExtensionCfg: &QOSExtensionConfig{FeatureGates: map[string]bool{}},
}
defaultConfig := NewDefaultConfig()
Expand All @@ -47,6 +48,7 @@ func Test_InitFlags(t *testing.T) {
"--memory-evict-cool-time-seconds=8",
"--cpu-evict-cool-time-seconds=40",
"--qos-extension-plugins=test-plugin=true",
"--only-evict-by-api=false",
}
fs := flag.NewFlagSet(cmdArgs[0], flag.ExitOnError)

Expand All @@ -57,6 +59,7 @@ func Test_InitFlags(t *testing.T) {
MemoryEvictIntervalSeconds int
MemoryEvictCoolTimeSeconds int
CPUEvictCoolTimeSeconds int
OnlyEvictByAPI bool
QOSExtensionCfg *QOSExtensionConfig
}
type args struct {
Expand All @@ -76,6 +79,7 @@ func Test_InitFlags(t *testing.T) {
MemoryEvictIntervalSeconds: 2,
MemoryEvictCoolTimeSeconds: 8,
CPUEvictCoolTimeSeconds: 40,
OnlyEvictByAPI: false,
QOSExtensionCfg: &QOSExtensionConfig{FeatureGates: map[string]bool{"test-plugin": true}},
},
args: args{fs: fs},
Expand All @@ -90,6 +94,7 @@ func Test_InitFlags(t *testing.T) {
MemoryEvictIntervalSeconds: tt.fields.MemoryEvictIntervalSeconds,
MemoryEvictCoolTimeSeconds: tt.fields.MemoryEvictCoolTimeSeconds,
CPUEvictCoolTimeSeconds: tt.fields.CPUEvictCoolTimeSeconds,
OnlyEvictByAPI: tt.fields.OnlyEvictByAPI,
QOSExtensionCfg: tt.fields.QOSExtensionCfg,
}
c := NewDefaultConfig()
Expand Down
8 changes: 5 additions & 3 deletions pkg/koordlet/qosmanager/framework/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (r *Evictor) Start(stopCh <-chan struct{}) error {

func (r *Evictor) EvictPodsIfNotEvicted(evictPods []*corev1.Pod, node *corev1.Node, reason string, message string) {
for _, evictPod := range evictPods {
r.evictPodIfNotEvicted(evictPod, node, reason, message)
r.EvictPodIfNotEvicted(evictPod, node, reason, message)
}
}

Expand All @@ -74,16 +74,18 @@ func (r *Evictor) IsPodEvicted(pod *corev1.Pod) bool {
return evicted
}

func (r *Evictor) evictPodIfNotEvicted(evictPod *corev1.Pod, node *corev1.Node, reason string, message string) {
func (r *Evictor) EvictPodIfNotEvicted(evictPod *corev1.Pod, node *corev1.Node, reason string, message string) bool {
j4ckstraw marked this conversation as resolved.
Show resolved Hide resolved
_, evicted := r.podsEvicted.Get(string(evictPod.UID))
if evicted {
klog.V(5).Infof("Pod has been evicted! podID: %v, evict reason: %s", evictPod.UID, reason)
return
return true
}
success := r.evictPod(evictPod, reason, message)
if success {
_ = r.podsEvicted.SetDefault(string(evictPod.UID), evictPod.UID)
}

return success
}

func (r *Evictor) evictPod(evictPod *corev1.Pod, reason string, message string) bool {
Expand Down
90 changes: 90 additions & 0 deletions pkg/koordlet/qosmanager/framework/context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,101 @@ import (

apiext "github.com/koordinator-sh/koordinator/apis/extension"
"github.com/koordinator-sh/koordinator/pkg/koordlet/qosmanager/helpers"
"github.com/koordinator-sh/koordinator/pkg/koordlet/resourceexecutor"
mock_statesinformer "github.com/koordinator-sh/koordinator/pkg/koordlet/statesinformer/mockstatesinformer"
"github.com/koordinator-sh/koordinator/pkg/koordlet/util/testutil"
"github.com/koordinator-sh/koordinator/pkg/util"
)

func Test_EvictPodIfNotEvicted(t *testing.T) {
testpod := testutil.MockTestPod(apiext.QoSBE, "test_be_pod")
testnode := testutil.MockTestNode("80", "120G")

type args struct {
pod *corev1.Pod
node *corev1.Node
reason string
}
type wants struct {
result bool
evictObjectError bool
eventReason string
}

tests := []struct {
name string
pod *corev1.Pod
node *corev1.Node
args args
wanted wants
}{
{
name: "evict ok",
pod: testpod,
node: testnode,
args: args{
pod: testpod,
node: testnode,
reason: resourceexecutor.EvictPodByNodeMemoryUsage,
},
wanted: wants{
result: true,
evictObjectError: false,
eventReason: helpers.EvictPodSuccess,
},
},
{
name: "evict not found",
node: testnode,
args: args{
pod: testpod,
node: testnode,
reason: resourceexecutor.EvictPodByNodeMemoryUsage,
},
wanted: wants{
result: false,
evictObjectError: true,
eventReason: helpers.EvictPodFail,
},
},
// TODO add test for evict failed by forbidden
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// prepare
ctl := gomock.NewController(t)
defer ctl.Finish()
fakeRecorder := &testutil.FakeRecorder{}
client := clientsetfake.NewSimpleClientset()
r := NewEvictor(client, fakeRecorder, policyv1beta1.SchemeGroupVersion.Version)
stop := make(chan struct{})
err := r.podsEvicted.Run(stop)
assert.NoError(t, err)
defer func() { stop <- struct{}{} }()

if tt.pod != nil {
_, err = client.CoreV1().Pods(tt.pod.Namespace).Create(context.TODO(), tt.pod, metav1.CreateOptions{})
assert.NoError(t, err)
}

got := r.EvictPodIfNotEvicted(tt.args.pod, tt.args.node, tt.args.reason, "")
assert.Equal(t, tt.wanted.result, got)

getEvictObject, err := client.Tracker().Get(testutil.PodsResource, tt.args.pod.Namespace, tt.args.pod.Name)
if tt.wanted.evictObjectError {
assert.Error(t, err)
assert.Nil(t, getEvictObject)
assert.Equal(t, tt.wanted.eventReason, fakeRecorder.EventReason, "expect evict failed event! but got %s", fakeRecorder.EventReason)
} else {
assert.NoError(t, err)
assert.NotNil(t, getEvictObject)
assert.Equal(t, tt.wanted.eventReason, fakeRecorder.EventReason, "expect evict success event! but got %s", fakeRecorder.EventReason)
}
})
}
}

func Test_EvictPodsIfNotEvicted(t *testing.T) {
// test data
pod := testutil.MockTestPod(apiext.QoSBE, "test_be_pod")
Expand Down
30 changes: 19 additions & 11 deletions pkg/koordlet/qosmanager/plugins/cpuevict/cpu_evict.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ type cpuEvictor struct {
metricCache metriccache.MetricCache
evictor *framework.Evictor
lastEvictTime time.Time
onlyEvictByAPI bool
}

func New(opt *framework.Options) framework.QOSStrategy {
Expand All @@ -68,6 +69,7 @@ func New(opt *framework.Options) framework.QOSStrategy {
statesInformer: opt.StatesInformer,
metricCache: opt.MetricCache,
lastEvictTime: time.Now(),
onlyEvictByAPI: opt.Config.OnlyEvictByAPI,
}
}

Expand Down Expand Up @@ -295,24 +297,30 @@ func (c *cpuEvictor) killAndEvictBEPodsRelease(node *corev1.Node, bePodInfos []*
node.Name, cpuNeedMilliRelease)

cpuMilliReleased := int64(0)
var killedPods []*corev1.Pod
hasKillPods := false
for _, bePod := range bePodInfos {
if cpuMilliReleased >= cpuNeedMilliRelease {
break
}

podKillMsg := fmt.Sprintf("%s, kill pod: %s", message, util.GetPodKey(bePod.pod))
helpers.KillContainers(bePod.pod, podKillMsg)

killedPods = append(killedPods, bePod.pod)
cpuMilliReleased = cpuMilliReleased + bePod.milliRequest

klog.V(5).Infof("cpuEvict pick pod %s to evict", util.GetPodKey(bePod.pod))
if c.onlyEvictByAPI {
if c.evictor.EvictPodIfNotEvicted(bePod.pod, node, resourceexecutor.EvictPodByBECPUSatisfaction, message) {
cpuMilliReleased = cpuMilliReleased + bePod.milliRequest
klog.V(5).Infof("cpuEvict pick pod %s to evict", util.GetPodKey(bePod.pod))
hasKillPods = true
} else {
klog.V(5).Infof("cpuEvict pick pod %s to evict, failed", util.GetPodKey(bePod.pod))
}
} else {
podKillMsg := fmt.Sprintf("%s, kill pod: %s", message, util.GetPodKey(bePod.pod))
helpers.KillContainers(bePod.pod, podKillMsg)
cpuMilliReleased = cpuMilliReleased + bePod.milliRequest
klog.V(5).Infof("cpuEvict pick pod %s to evict", util.GetPodKey(bePod.pod))
hasKillPods = true
}
}

c.evictor.EvictPodsIfNotEvicted(killedPods, node, resourceexecutor.EvictPodByBECPUSatisfaction, message)

if len(killedPods) > 0 {
if hasKillPods {
c.lastEvictTime = time.Now()
}
klog.V(5).Infof("killAndEvictBEPodsRelease finished! cpuNeedMilliRelease(%d) cpuMilliReleased(%d)",
Expand Down
65 changes: 62 additions & 3 deletions pkg/koordlet/qosmanager/plugins/cpuevict/cpu_evict_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -576,12 +576,71 @@ func Test_killAndEvictBEPodsRelease(t *testing.T) {

// evict subresource will not be creat or update in client go testing, check evict object
// https://github.com/kubernetes/client-go/blob/v0.28.7/testing/fixture.go#L117
assert.True(t, cpuEvictor.evictor.IsPodEvicted(podEvictInfosSorted[0].pod))
assert.True(t, cpuEvictor.evictor.IsPodEvicted(podEvictInfosSorted[1].pod))

// evict by API is false, so podsEvicted cache will not record killed pod
assert.False(t, cpuEvictor.evictor.IsPodEvicted(podEvictInfosSorted[0].pod))
assert.False(t, cpuEvictor.evictor.IsPodEvicted(podEvictInfosSorted[1].pod))
assert.False(t, cpuEvictor.evictor.IsPodEvicted(podEvictInfosSorted[2].pod))
}

assert.True(t, cpuEvictor.lastEvictTime.After(time.Now().Add(-5*time.Second)), "checkLastTime")
func Test_killAndEvictBEPodsReleaseWithEvictionAPIOnly(t *testing.T) {
podEvictInfosSorted := []*podEvictCPUInfo{
{
pod: mockBEPodForCPUEvict("pod_be_3_priority10", 16*1000, 10),
milliRequest: 16 * 1000,
},
{
pod: mockBEPodForCPUEvict("pod_be_2_priority100", 16*1000, 100),
milliRequest: 16 * 1000,
},
{
pod: mockBEPodForCPUEvict("pod_be_1_priority100", 16*1000, 100),
milliRequest: 16 * 1000,
},
}

ctl := gomock.NewController(t)
defer ctl.Finish()

fakeRecorder := &testutil.FakeRecorder{}
client := clientsetfake.NewSimpleClientset()

stop := make(chan struct{})
evictor := framework.NewEvictor(client, fakeRecorder, policyv1beta1.SchemeGroupVersion.Version)
evictor.Start(stop)
defer func() { stop <- struct{}{} }()

node := testutil.MockTestNode("100", "500G")
runtime.DockerHandler = handler.NewFakeRuntimeHandler()
// create pod
var containers []*critesting.FakeContainer
for _, podInfo := range podEvictInfosSorted {
pod := podInfo.pod
_, _ = client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
for _, containerStatus := range pod.Status.ContainerStatuses {
_, containerId, _ := util.ParseContainerId(containerStatus.ContainerID)
fakeContainer := &critesting.FakeContainer{
SandboxID: string(pod.UID),
ContainerStatus: runtimeapi.ContainerStatus{Id: containerId},
}
containers = append(containers, fakeContainer)
}
}
runtime.DockerHandler.(*handler.FakeRuntimeHandler).SetFakeContainers(containers)

cpuEvictor := &cpuEvictor{
evictor: evictor,
onlyEvictByAPI: true,
lastEvictTime: time.Now().Add(-5 * time.Minute),
}

cpuEvictor.killAndEvictBEPodsRelease(node, podEvictInfosSorted, 18*1000)

// evict subresource will not be creat or update in client go testing, check evict object
// https://github.com/kubernetes/client-go/blob/v0.28.7/testing/fixture.go#L117
assert.True(t, cpuEvictor.evictor.IsPodEvicted(podEvictInfosSorted[0].pod))
assert.True(t, cpuEvictor.evictor.IsPodEvicted(podEvictInfosSorted[1].pod))
assert.False(t, cpuEvictor.evictor.IsPodEvicted(podEvictInfosSorted[2].pod))
}

func Test_isSatisfactionConfigValid(t *testing.T) {
Expand Down
35 changes: 25 additions & 10 deletions pkg/koordlet/qosmanager/plugins/memoryevict/memory_evict.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/koordinator-sh/koordinator/pkg/koordlet/qosmanager/helpers"
"github.com/koordinator-sh/koordinator/pkg/koordlet/resourceexecutor"
"github.com/koordinator-sh/koordinator/pkg/koordlet/statesinformer"
"github.com/koordinator-sh/koordinator/pkg/util"
)

const (
Expand All @@ -50,6 +51,7 @@ type memoryEvictor struct {
metricCache metriccache.MetricCache
evictor *framework.Evictor
lastEvictTime time.Time
onlyEvictByAPI bool
}

type podInfo struct {
Expand All @@ -64,6 +66,7 @@ func New(opt *framework.Options) framework.QOSStrategy {
metricCollectInterval: opt.MetricAdvisorConfig.CollectResUsedInterval,
statesInformer: opt.StatesInformer,
metricCache: opt.MetricCache,
onlyEvictByAPI: opt.Config.OnlyEvictByAPI,
}
}

Expand Down Expand Up @@ -164,24 +167,36 @@ func (m *memoryEvictor) killAndEvictBEPods(node *corev1.Node, podMetrics map[str
bePodInfos := m.getSortedBEPodInfos(podMetrics)
message := fmt.Sprintf("killAndEvictBEPods for node, need to release memory: %v", memoryNeedRelease)
memoryReleased := int64(0)

var killedPods []*corev1.Pod
hasKillPods := false
for _, bePod := range bePodInfos {
if memoryReleased >= memoryNeedRelease {
break
}

killMsg := fmt.Sprintf("%v, kill pod: %v", message, bePod.pod.Name)
helpers.KillContainers(bePod.pod, killMsg)
killedPods = append(killedPods, bePod.pod)
if bePod.memUsed != 0 {
memoryReleased += int64(bePod.memUsed)
if m.onlyEvictByAPI {
if m.evictor.EvictPodIfNotEvicted(bePod.pod, node, resourceexecutor.EvictPodByNodeMemoryUsage, message) {
hasKillPods = true
if bePod.memUsed != 0 {
memoryReleased += int64(bePod.memUsed)
}
klog.V(5).Infof("memoryEvict pick pod %s to evict", util.GetPodKey(bePod.pod))
} else {
klog.V(5).Infof("memoryEvict pick pod %s to evict", util.GetPodKey(bePod.pod))
}
} else {
killMsg := fmt.Sprintf("%v, kill pod: %v", message, bePod.pod.Name)
helpers.KillContainers(bePod.pod, killMsg)
hasKillPods = true
if bePod.memUsed != 0 {
memoryReleased += int64(bePod.memUsed)
}
klog.V(5).Infof("memoryEvict pick pod %s to evict", util.GetPodKey(bePod.pod))
}
}
if hasKillPods {
m.lastEvictTime = time.Now()
saintube marked this conversation as resolved.
Show resolved Hide resolved
}

m.evictor.EvictPodsIfNotEvicted(killedPods, node, resourceexecutor.EvictPodByNodeMemoryUsage, message)

m.lastEvictTime = time.Now()
klog.Infof("killAndEvictBEPods completed, memoryNeedRelease(%v) memoryReleased(%v)", memoryNeedRelease, memoryReleased)
}

Expand Down
Loading