Skip to content

Commit 1bab014

Browse files
Add time based drainability rule for non-pdb-assigned system pods
1 parent e1e1c32 commit 1bab014

File tree

5 files changed

+110
-9
lines changed

5 files changed

+110
-9
lines changed

cluster-autoscaler/simulator/drain.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ func GetPodsToMove(nodeInfo *framework.NodeInfo, deleteOptions options.NodeDelet
4646
remainingPdbTracker = pdb.NewBasicRemainingPdbTracker()
4747
}
4848
drainCtx := &drainability.DrainContext{
49-
RemainingPdbTracker: remainingPdbTracker,
50-
Listers: listers,
51-
Timestamp: timestamp,
49+
RemainingPdbTracker: remainingPdbTracker,
50+
Listers: listers,
51+
Timestamp: timestamp,
52+
BspChosenNodeToEvict: "",
5253
}
5354
for _, podInfo := range nodeInfo.Pods() {
5455
pod := podInfo.Pod

cluster-autoscaler/simulator/drain_test.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import (
3030
"k8s.io/autoscaler/cluster-autoscaler/core/scaledown/pdb"
3131
"k8s.io/autoscaler/cluster-autoscaler/simulator/drainability"
3232
"k8s.io/autoscaler/cluster-autoscaler/simulator/drainability/rules"
33+
system_rules "k8s.io/autoscaler/cluster-autoscaler/simulator/drainability/rules/system"
3334
"k8s.io/autoscaler/cluster-autoscaler/simulator/framework"
3435
"k8s.io/autoscaler/cluster-autoscaler/simulator/options"
3536
"k8s.io/autoscaler/cluster-autoscaler/utils/drain"
@@ -43,7 +44,10 @@ import (
4344

4445
func TestGetPodsToMove(t *testing.T) {
4546
var (
46-
testTime = time.Date(2020, time.December, 18, 17, 0, 0, 0, time.UTC)
47+
testTime = time.Date(2020, time.December, 18, 17, 0, 0, 0, time.UTC)
48+
creationTimeBeforeBspDisturptionTimeout = testTime.Add(-system_rules.BspDisruptionTimeout).Add(-time.Minute)
49+
creationTimeAfterBspDisturptionTimeout = testTime.Add(-system_rules.BspDisruptionTimeout).Add(time.Minute)
50+
4751
replicas = int32(5)
4852

4953
unreplicatedPod = &apiv1.Pod{
@@ -68,6 +72,22 @@ func TestGetPodsToMove(t *testing.T) {
6872
OwnerReferences: GenerateOwnerReferences("rs", "ReplicaSet", "extensions/v1beta1", ""),
6973
},
7074
}
75+
drainableBlockingSystemPod = &apiv1.Pod{
76+
ObjectMeta: metav1.ObjectMeta{
77+
Name: "systemPod",
78+
Namespace: "kube-system",
79+
OwnerReferences: GenerateOwnerReferences("rs", "ReplicaSet", "extensions/v1beta1", ""),
80+
CreationTimestamp: metav1.Time{Time: creationTimeBeforeBspDisturptionTimeout},
81+
},
82+
}
83+
nonDrainableBlockingSystemPod = &apiv1.Pod{
84+
ObjectMeta: metav1.ObjectMeta{
85+
Name: "systemPod",
86+
Namespace: "kube-system",
87+
OwnerReferences: GenerateOwnerReferences("rs", "ReplicaSet", "extensions/v1beta1", ""),
88+
CreationTimestamp: metav1.Time{Time: creationTimeAfterBspDisturptionTimeout},
89+
},
90+
}
7191
localStoragePod = &apiv1.Pod{
7292
ObjectMeta: metav1.ObjectMeta{
7393
Name: "localStoragePod",
@@ -541,6 +561,28 @@ func TestGetPodsToMove(t *testing.T) {
541561
Reason: drain.UnmovableKubeSystemPod,
542562
},
543563
},
564+
{
565+
desc: "Kube-system no pdb system pods blocking",
566+
pods: []*apiv1.Pod{nonDrainableBlockingSystemPod},
567+
wantErr: true,
568+
wantBlocking: &drain.BlockingPod{
569+
Pod: nonDrainableBlockingSystemPod,
570+
Reason: drain.UnmovableKubeSystemPod,
571+
}},
572+
{
573+
desc: "Kube-system no pdb system pods allowing",
574+
pods: []*apiv1.Pod{drainableBlockingSystemPod},
575+
wantPods: []*apiv1.Pod{drainableBlockingSystemPod},
576+
},
577+
{
578+
desc: "Kube-system no pdb system pods blocking",
579+
pods: []*apiv1.Pod{drainableBlockingSystemPod, nonDrainableBlockingSystemPod},
580+
wantErr: true,
581+
wantBlocking: &drain.BlockingPod{
582+
Pod: nonDrainableBlockingSystemPod,
583+
Reason: drain.UnmovableKubeSystemPod,
584+
},
585+
},
544586
{
545587
desc: "Local storage",
546588
pods: []*apiv1.Pod{localStoragePod},

cluster-autoscaler/simulator/drainability/context.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import (
2525

2626
// DrainContext contains parameters for drainability rules.
2727
type DrainContext struct {
28-
RemainingPdbTracker pdb.RemainingPdbTracker
29-
Listers kube_util.ListerRegistry
30-
Timestamp time.Time
28+
RemainingPdbTracker pdb.RemainingPdbTracker
29+
Listers kube_util.ListerRegistry
30+
Timestamp time.Time
31+
BspChosenNodeToEvict string
3132
}

cluster-autoscaler/simulator/drainability/rules/system/rule.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,20 @@ package system
1818

1919
import (
2020
"fmt"
21+
"time"
2122

2223
apiv1 "k8s.io/api/core/v1"
2324
"k8s.io/autoscaler/cluster-autoscaler/simulator/drainability"
2425
"k8s.io/autoscaler/cluster-autoscaler/simulator/framework"
2526
"k8s.io/autoscaler/cluster-autoscaler/utils/drain"
2627
)
2728

29+
const (
30+
// BspDisruptionTimeout is the timeout afterwhich CA will evict non-pdb-assigned blocking system pods
31+
BspDisruptionTimeout = time.Hour
32+
KubeSystemNamespace = "kube-system"
33+
)
34+
2835
// Rule is a drainability rule on how to handle system pods.
2936
type Rule struct{}
3037

@@ -40,8 +47,25 @@ func (r *Rule) Name() string {
4047

4148
// Drainable decides what to do with system pods on node drain.
4249
func (r *Rule) Drainable(drainCtx *drainability.DrainContext, pod *apiv1.Pod, _ *framework.NodeInfo) drainability.Status {
43-
if pod.Namespace == "kube-system" && len(drainCtx.RemainingPdbTracker.MatchingPdbs(pod)) == 0 {
50+
if isBlockingSystemPod(drainCtx, pod) {
51+
if isBspPassedDisruptionTimeout(pod, drainCtx.Timestamp) && isChosenNode(pod, drainCtx.BspChosenNodeToEvict) {
52+
drainCtx.BspChosenNodeToEvict = pod.Spec.NodeName
53+
return drainability.NewDrainableStatus()
54+
}
4455
return drainability.NewBlockedStatus(drain.UnmovableKubeSystemPod, fmt.Errorf("non-daemonset, non-mirrored, non-pdb-assigned kube-system pod present: %s", pod.Name))
4556
}
4657
return drainability.NewUndefinedStatus()
4758
}
59+
60+
func isBlockingSystemPod(drainCtx *drainability.DrainContext, pod *apiv1.Pod) bool {
61+
return pod.Namespace == KubeSystemNamespace && len(drainCtx.RemainingPdbTracker.MatchingPdbs(pod)) == 0
62+
}
63+
64+
func isBspPassedDisruptionTimeout(pod *apiv1.Pod, drainTime time.Time) bool {
65+
return !pod.ObjectMeta.CreationTimestamp.IsZero() &&
66+
drainTime.After(pod.ObjectMeta.CreationTimestamp.Add(BspDisruptionTimeout))
67+
}
68+
69+
func isChosenNode(pod *apiv1.Pod, bspChosenNodeToEvict string) bool {
70+
return bspChosenNodeToEvict == "" || pod.Spec.NodeName == bspChosenNodeToEvict
71+
}

cluster-autoscaler/simulator/drainability/rules/system/rule_test.go

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ import (
3434

3535
func TestDrainable(t *testing.T) {
3636
var (
37-
testTime = time.Date(2020, time.December, 18, 17, 0, 0, 0, time.UTC)
37+
testTime = time.Date(2020, time.December, 18, 17, 0, 0, 0, time.UTC)
38+
creationTimeBeforeBspDisturptionTimeout = testTime.Add(-BspDisruptionTimeout).Add(-time.Minute)
39+
creationTimeAfterBspDisturptionTimeout = testTime.Add(-BspDisruptionTimeout).Add(time.Minute)
40+
3841
replicas = int32(5)
3942

4043
rc = apiv1.ReplicationController{
@@ -84,6 +87,24 @@ func TestDrainable(t *testing.T) {
8487
},
8588
}
8689

90+
drainableBlockingSystemPod = &apiv1.Pod{
91+
ObjectMeta: metav1.ObjectMeta{
92+
Name: "systemPod",
93+
Namespace: "kube-system",
94+
OwnerReferences: test.GenerateOwnerReferences("rs", "ReplicaSet", "extensions/v1beta1", ""),
95+
CreationTimestamp: metav1.Time{Time: creationTimeBeforeBspDisturptionTimeout},
96+
},
97+
}
98+
99+
nonDrainableBlockingSystemPod = &apiv1.Pod{
100+
ObjectMeta: metav1.ObjectMeta{
101+
Name: "systemPod",
102+
Namespace: "kube-system",
103+
OwnerReferences: test.GenerateOwnerReferences("rs", "ReplicaSet", "extensions/v1beta1", ""),
104+
CreationTimestamp: metav1.Time{Time: creationTimeAfterBspDisturptionTimeout},
105+
},
106+
}
107+
87108
emptyPDB = &policyv1.PodDisruptionBudget{}
88109

89110
kubeSystemPDB = &policyv1.PodDisruptionBudget{
@@ -164,6 +185,18 @@ func TestDrainable(t *testing.T) {
164185
wantReason: drain.UnmovableKubeSystemPod,
165186
wantError: true,
166187
},
188+
"block non-pdb system pod existing for less than BspDisruptionTimeout": {
189+
pod: nonDrainableBlockingSystemPod,
190+
rcs: []*apiv1.ReplicationController{&kubeSystemRc},
191+
pdbs: []*policyv1.PodDisruptionBudget{emptyPDB},
192+
wantReason: drain.UnmovableKubeSystemPod,
193+
wantError: true,
194+
},
195+
"allow non-pdb system pod existing for more than BspDisruptionTimeout": {
196+
pod: drainableBlockingSystemPod,
197+
rcs: []*apiv1.ReplicationController{&kubeSystemRc},
198+
pdbs: []*policyv1.PodDisruptionBudget{kubeSystemPDB},
199+
},
167200
} {
168201
t.Run(desc, func(t *testing.T) {
169202
tracker := pdb.NewBasicRemainingPdbTracker()

0 commit comments

Comments
 (0)