mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-13 11:25:19 +00:00
Merge pull request #99163 from ahg-g/ahg-pod-deletion
Implements pod deletion cost
This commit is contained in:
@@ -39,13 +39,16 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
clientretry "k8s.io/client-go/util/retry"
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
hashutil "k8s.io/kubernetes/pkg/util/hash"
|
||||
taintutils "k8s.io/kubernetes/pkg/util/taints"
|
||||
"k8s.io/utils/integer"
|
||||
@@ -791,15 +794,17 @@ func (s ActivePods) Less(i, j int) bool {
|
||||
// is unknown, and a pod whose phase is unknown comes before a running pod.
|
||||
// 3. If exactly one of the pods is ready, the pod that is not ready comes
|
||||
// before the ready pod.
|
||||
// 4. If the pods' ranks differ, the pod with greater rank comes before the pod
|
||||
// 4. If controller.kubernetes.io/pod-deletion-cost annotation is set, then
|
||||
// the pod with the lower value will come first.
|
||||
// 5. If the pods' ranks differ, the pod with greater rank comes before the pod
|
||||
// with lower rank.
|
||||
// 5. If both pods are ready but have not been ready for the same amount of
|
||||
// 6. If both pods are ready but have not been ready for the same amount of
|
||||
// time, the pod that has been ready for a shorter amount of time comes
|
||||
// before the pod that has been ready for longer.
|
||||
// 6. If one pod has a container that has restarted more than any container in
|
||||
// 7. If one pod has a container that has restarted more than any container in
|
||||
// the other pod, the pod with the container with more restarts comes
|
||||
// before the other pod.
|
||||
// 7. If the pods' creation times differ, the pod that was created more recently
|
||||
// 8. If the pods' creation times differ, the pod that was created more recently
|
||||
// comes before the older pod.
|
||||
//
|
||||
// If none of these rules matches, the second pod comes before the first pod.
|
||||
@@ -842,7 +847,17 @@ func (s ActivePodsWithRanks) Less(i, j int) bool {
|
||||
if podutil.IsPodReady(s.Pods[i]) != podutil.IsPodReady(s.Pods[j]) {
|
||||
return !podutil.IsPodReady(s.Pods[i])
|
||||
}
|
||||
// 4. Doubled up < not doubled up
|
||||
|
||||
// 4. higher pod-deletion-cost < lower pod-deletion cost
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.PodDeletionCost) {
|
||||
pi, _ := helper.GetDeletionCostFromPodAnnotations(s.Pods[i].Annotations)
|
||||
pj, _ := helper.GetDeletionCostFromPodAnnotations(s.Pods[j].Annotations)
|
||||
if pi != pj {
|
||||
return pi < pj
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Doubled up < not doubled up
|
||||
// If one of the two pods is on the same node as one or more additional
|
||||
// ready pods that belong to the same replicaset, whichever pod has more
|
||||
// colocated ready pods is less
|
||||
@@ -851,7 +866,7 @@ func (s ActivePodsWithRanks) Less(i, j int) bool {
|
||||
}
|
||||
// TODO: take availability into account when we push minReadySeconds information from deployment into pods,
|
||||
// see https://github.com/kubernetes/kubernetes/issues/22065
|
||||
// 5. Been ready for empty time < less time < more time
|
||||
// 6. Been ready for empty time < less time < more time
|
||||
// If both pods are ready, the latest ready one is smaller
|
||||
if podutil.IsPodReady(s.Pods[i]) && podutil.IsPodReady(s.Pods[j]) {
|
||||
readyTime1 := podReadyTime(s.Pods[i])
|
||||
@@ -860,11 +875,11 @@ func (s ActivePodsWithRanks) Less(i, j int) bool {
|
||||
return afterOrZero(readyTime1, readyTime2)
|
||||
}
|
||||
}
|
||||
// 6. Pods with containers with higher restart counts < lower restart counts
|
||||
// 7. Pods with containers with higher restart counts < lower restart counts
|
||||
if maxContainerRestarts(s.Pods[i]) != maxContainerRestarts(s.Pods[j]) {
|
||||
return maxContainerRestarts(s.Pods[i]) > maxContainerRestarts(s.Pods[j])
|
||||
}
|
||||
// 7. Empty creation time pods < newer pods < older pods
|
||||
// 8. Empty creation time pods < newer pods < older pods
|
||||
if !s.Pods[i].CreationTimestamp.Equal(&s.Pods[j].CreationTimestamp) {
|
||||
return afterOrZero(&s.Pods[i].CreationTimestamp, &s.Pods[j].CreationTimestamp)
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
clientscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
@@ -45,8 +46,11 @@ import (
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
"k8s.io/kubernetes/pkg/apis/core"
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
"k8s.io/kubernetes/pkg/controller/testutil"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -435,7 +439,7 @@ func TestSortingActivePodsWithRanks(t *testing.T) {
|
||||
now := metav1.Now()
|
||||
then := metav1.Time{Time: now.AddDate(0, -1, 0)}
|
||||
zeroTime := metav1.Time{}
|
||||
pod := func(podName, nodeName string, phase v1.PodPhase, ready bool, restarts int32, readySince metav1.Time, created metav1.Time) *v1.Pod {
|
||||
pod := func(podName, nodeName string, phase v1.PodPhase, ready bool, restarts int32, readySince metav1.Time, created metav1.Time, annotations map[string]string) *v1.Pod {
|
||||
var conditions []v1.PodCondition
|
||||
var containerStatuses []v1.ContainerStatus
|
||||
if ready {
|
||||
@@ -446,6 +450,7 @@ func TestSortingActivePodsWithRanks(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
CreationTimestamp: created,
|
||||
Name: podName,
|
||||
Annotations: annotations,
|
||||
},
|
||||
Spec: v1.PodSpec{NodeName: nodeName},
|
||||
Status: v1.PodStatus{
|
||||
@@ -456,15 +461,17 @@ func TestSortingActivePodsWithRanks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
var (
|
||||
unscheduledPod = pod("unscheduled", "", v1.PodPending, false, 0, zeroTime, zeroTime)
|
||||
scheduledPendingPod = pod("pending", "node", v1.PodPending, false, 0, zeroTime, zeroTime)
|
||||
unknownPhasePod = pod("unknown-phase", "node", v1.PodUnknown, false, 0, zeroTime, zeroTime)
|
||||
runningNotReadyPod = pod("not-ready", "node", v1.PodRunning, false, 0, zeroTime, zeroTime)
|
||||
runningReadyNoLastTransitionTimePod = pod("ready-no-last-transition-time", "node", v1.PodRunning, true, 0, zeroTime, zeroTime)
|
||||
runningReadyNow = pod("ready-now", "node", v1.PodRunning, true, 0, now, now)
|
||||
runningReadyThen = pod("ready-then", "node", v1.PodRunning, true, 0, then, then)
|
||||
runningReadyNowHighRestarts = pod("ready-high-restarts", "node", v1.PodRunning, true, 9001, now, now)
|
||||
runningReadyNowCreatedThen = pod("ready-now-created-then", "node", v1.PodRunning, true, 0, now, then)
|
||||
unscheduledPod = pod("unscheduled", "", v1.PodPending, false, 0, zeroTime, zeroTime, nil)
|
||||
scheduledPendingPod = pod("pending", "node", v1.PodPending, false, 0, zeroTime, zeroTime, nil)
|
||||
unknownPhasePod = pod("unknown-phase", "node", v1.PodUnknown, false, 0, zeroTime, zeroTime, nil)
|
||||
runningNotReadyPod = pod("not-ready", "node", v1.PodRunning, false, 0, zeroTime, zeroTime, nil)
|
||||
runningReadyNoLastTransitionTimePod = pod("ready-no-last-transition-time", "node", v1.PodRunning, true, 0, zeroTime, zeroTime, nil)
|
||||
runningReadyNow = pod("ready-now", "node", v1.PodRunning, true, 0, now, now, nil)
|
||||
runningReadyThen = pod("ready-then", "node", v1.PodRunning, true, 0, then, then, nil)
|
||||
runningReadyNowHighRestarts = pod("ready-high-restarts", "node", v1.PodRunning, true, 9001, now, now, nil)
|
||||
runningReadyNowCreatedThen = pod("ready-now-created-then", "node", v1.PodRunning, true, 0, now, then, nil)
|
||||
lowPodDeletionCost = pod("low-deletion-cost", "node", v1.PodRunning, true, 0, now, then, map[string]string{core.PodDeletionCost: "10"})
|
||||
highPodDeletionCost = pod("high-deletion-cost", "node", v1.PodRunning, true, 0, now, then, map[string]string{core.PodDeletionCost: "100"})
|
||||
)
|
||||
equalityTests := []*v1.Pod{
|
||||
unscheduledPod,
|
||||
@@ -491,33 +498,40 @@ func TestSortingActivePodsWithRanks(t *testing.T) {
|
||||
rank int
|
||||
}
|
||||
inequalityTests := []struct {
|
||||
lesser, greater podWithRank
|
||||
lesser, greater podWithRank
|
||||
disablePodDeletioncost bool
|
||||
}{
|
||||
{podWithRank{unscheduledPod, 1}, podWithRank{scheduledPendingPod, 2}},
|
||||
{podWithRank{unscheduledPod, 2}, podWithRank{scheduledPendingPod, 1}},
|
||||
{podWithRank{scheduledPendingPod, 1}, podWithRank{unknownPhasePod, 2}},
|
||||
{podWithRank{unknownPhasePod, 1}, podWithRank{runningNotReadyPod, 2}},
|
||||
{podWithRank{runningNotReadyPod, 1}, podWithRank{runningReadyNoLastTransitionTimePod, 1}},
|
||||
{podWithRank{runningReadyNoLastTransitionTimePod, 1}, podWithRank{runningReadyNow, 1}},
|
||||
{podWithRank{runningReadyNow, 2}, podWithRank{runningReadyNoLastTransitionTimePod, 1}},
|
||||
{podWithRank{runningReadyNow, 1}, podWithRank{runningReadyThen, 1}},
|
||||
{podWithRank{runningReadyNow, 2}, podWithRank{runningReadyThen, 1}},
|
||||
{podWithRank{runningReadyNowHighRestarts, 1}, podWithRank{runningReadyNow, 1}},
|
||||
{podWithRank{runningReadyNow, 2}, podWithRank{runningReadyNowHighRestarts, 1}},
|
||||
{podWithRank{runningReadyNow, 1}, podWithRank{runningReadyNowCreatedThen, 1}},
|
||||
{podWithRank{runningReadyNowCreatedThen, 2}, podWithRank{runningReadyNow, 1}},
|
||||
{lesser: podWithRank{unscheduledPod, 1}, greater: podWithRank{scheduledPendingPod, 2}},
|
||||
{lesser: podWithRank{unscheduledPod, 2}, greater: podWithRank{scheduledPendingPod, 1}},
|
||||
{lesser: podWithRank{scheduledPendingPod, 1}, greater: podWithRank{unknownPhasePod, 2}},
|
||||
{lesser: podWithRank{unknownPhasePod, 1}, greater: podWithRank{runningNotReadyPod, 2}},
|
||||
{lesser: podWithRank{runningNotReadyPod, 1}, greater: podWithRank{runningReadyNoLastTransitionTimePod, 1}},
|
||||
{lesser: podWithRank{runningReadyNoLastTransitionTimePod, 1}, greater: podWithRank{runningReadyNow, 1}},
|
||||
{lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyNoLastTransitionTimePod, 1}},
|
||||
{lesser: podWithRank{runningReadyNow, 1}, greater: podWithRank{runningReadyThen, 1}},
|
||||
{lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyThen, 1}},
|
||||
{lesser: podWithRank{runningReadyNowHighRestarts, 1}, greater: podWithRank{runningReadyNow, 1}},
|
||||
{lesser: podWithRank{runningReadyNow, 2}, greater: podWithRank{runningReadyNowHighRestarts, 1}},
|
||||
{lesser: podWithRank{runningReadyNow, 1}, greater: podWithRank{runningReadyNowCreatedThen, 1}},
|
||||
{lesser: podWithRank{runningReadyNowCreatedThen, 2}, greater: podWithRank{runningReadyNow, 1}},
|
||||
{lesser: podWithRank{lowPodDeletionCost, 2}, greater: podWithRank{highPodDeletionCost, 1}},
|
||||
{lesser: podWithRank{highPodDeletionCost, 2}, greater: podWithRank{lowPodDeletionCost, 1}, disablePodDeletioncost: true},
|
||||
}
|
||||
for _, test := range inequalityTests {
|
||||
podsWithRanks := ActivePodsWithRanks{
|
||||
Pods: []*v1.Pod{test.lesser.pod, test.greater.pod},
|
||||
Rank: []int{test.lesser.rank, test.greater.rank},
|
||||
}
|
||||
if !podsWithRanks.Less(0, 1) {
|
||||
t.Errorf("expected pod %q with rank %v to be less than %q with rank %v", podsWithRanks.Pods[0].Name, podsWithRanks.Rank[0], podsWithRanks.Pods[1].Name, podsWithRanks.Rank[1])
|
||||
}
|
||||
if podsWithRanks.Less(1, 0) {
|
||||
t.Errorf("expected pod %q with rank %v not to be less than %v with rank %v", podsWithRanks.Pods[1].Name, podsWithRanks.Rank[1], podsWithRanks.Pods[0].Name, podsWithRanks.Rank[0])
|
||||
}
|
||||
for i, test := range inequalityTests {
|
||||
t.Run(fmt.Sprintf("test%d", i), func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.PodDeletionCost, !test.disablePodDeletioncost)()
|
||||
|
||||
podsWithRanks := ActivePodsWithRanks{
|
||||
Pods: []*v1.Pod{test.lesser.pod, test.greater.pod},
|
||||
Rank: []int{test.lesser.rank, test.greater.rank},
|
||||
}
|
||||
if !podsWithRanks.Less(0, 1) {
|
||||
t.Errorf("expected pod %q with rank %v to be less than %q with rank %v", podsWithRanks.Pods[0].Name, podsWithRanks.Rank[0], podsWithRanks.Pods[1].Name, podsWithRanks.Rank[1])
|
||||
}
|
||||
if podsWithRanks.Less(1, 0) {
|
||||
t.Errorf("expected pod %q with rank %v not to be less than %v with rank %v", podsWithRanks.Pods[1].Name, podsWithRanks.Rank[1], podsWithRanks.Pods[0].Name, podsWithRanks.Rank[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user