Skip to content

Commit 6c1d587

Browse files
committed
kubectl/drain: add disable-eviction option
Currently, if eviction is supported during a drain operation, eviction is always used. This commit allows the user to specify disabling eviction. This is particularly useful when you wish to ignore PodDisruptionBudgets after a normal drain has failed for some time.
1 parent 2b9aeab commit 6c1d587

File tree

3 files changed

+111
-79
lines changed

3 files changed

+111
-79
lines changed

staging/src/k8s.io/kubectl/pkg/cmd/drain/drain.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ func NewCmdDrain(f cmdutil.Factory, ioStreams genericclioptions.IOStreams) *cobr
192192
cmd.Flags().DurationVar(&o.drainer.Timeout, "timeout", o.drainer.Timeout, "The length of time to wait before giving up, zero means infinite")
193193
cmd.Flags().StringVarP(&o.drainer.Selector, "selector", "l", o.drainer.Selector, "Selector (label query) to filter on")
194194
cmd.Flags().StringVarP(&o.drainer.PodSelector, "pod-selector", "", o.drainer.PodSelector, "Label selector to filter pods on the node")
195+
cmd.Flags().BoolVar(&o.drainer.DisableEviction, "disable-eviction", o.drainer.DisableEviction, "Force drain to use delete, even if eviction is supported. This will bypass checking PodDisruptionBudgets, use with caution.")
195196

196197
cmdutil.AddDryRunFlag(cmd)
197198
return cmd

staging/src/k8s.io/kubectl/pkg/drain/drain.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,12 @@ type Helper struct {
5151
DeleteLocalData bool
5252
Selector string
5353
PodSelector string
54-
Out io.Writer
55-
ErrOut io.Writer
54+
55+
// DisableEviction forces drain to use delete rather than evict
56+
DisableEviction bool
57+
58+
Out io.Writer
59+
ErrOut io.Writer
5660

5761
// TODO(justinsb): unnecessary?
5862
DryRun bool
@@ -178,17 +182,20 @@ func (d *Helper) DeleteOrEvictPods(pods []corev1.Pod) error {
178182
return nil
179183
}
180184

181-
policyGroupVersion, err := CheckEvictionSupport(d.Client)
182-
if err != nil {
183-
return err
184-
}
185-
186185
// TODO(justinsb): unnecessary?
187186
getPodFn := func(namespace, name string) (*corev1.Pod, error) {
188187
return d.Client.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{})
189188
}
190-
if len(policyGroupVersion) > 0 {
191-
return d.evictPods(pods, policyGroupVersion, getPodFn)
189+
190+
if !d.DisableEviction {
191+
policyGroupVersion, err := CheckEvictionSupport(d.Client)
192+
if err != nil {
193+
return err
194+
}
195+
196+
if len(policyGroupVersion) > 0 {
197+
return d.evictPods(pods, policyGroupVersion, getPodFn)
198+
}
192199
}
193200

194201
return d.deletePods(pods, getPodFn)

staging/src/k8s.io/kubectl/pkg/drain/drain_test.go

Lines changed: 94 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -220,90 +220,114 @@ func TestCheckEvictionSupport(t *testing.T) {
220220
}
221221

222222
func TestDeleteOrEvict(t *testing.T) {
223-
for _, evictionSupported := range []bool{true, false} {
224-
evictionSupported := evictionSupported
225-
t.Run(fmt.Sprintf("evictionSupported=%v", evictionSupported),
226-
func(t *testing.T) {
227-
h := &Helper{
228-
Out: os.Stdout,
229-
GracePeriodSeconds: 10,
230-
}
231-
232-
// Create 4 pods, and try to remove the first 2
233-
var expectedEvictions []policyv1beta1.Eviction
234-
var create []runtime.Object
235-
deletePods := []corev1.Pod{}
236-
for i := 1; i <= 4; i++ {
237-
pod := &corev1.Pod{}
238-
pod.Name = fmt.Sprintf("mypod-%d", i)
239-
pod.Namespace = "default"
223+
tests := []struct {
224+
description string
225+
evictionSupported bool
226+
disableEviction bool
227+
}{
228+
{
229+
description: "eviction supported/enabled",
230+
evictionSupported: true,
231+
disableEviction: false,
232+
},
233+
{
234+
description: "eviction unsupported/disabled",
235+
evictionSupported: false,
236+
disableEviction: false,
237+
},
238+
{
239+
description: "eviction supported/disabled",
240+
evictionSupported: true,
241+
disableEviction: true,
242+
},
243+
{
244+
description: "eviction unsupported/disabled",
245+
evictionSupported: false,
246+
disableEviction: false,
247+
},
248+
}
249+
for _, tc := range tests {
250+
t.Run(tc.description, func(t *testing.T) {
251+
h := &Helper{
252+
Out: os.Stdout,
253+
GracePeriodSeconds: 10,
254+
}
240255

241-
create = append(create, pod)
242-
if i <= 2 {
243-
deletePods = append(deletePods, *pod)
256+
// Create 4 pods, and try to remove the first 2
257+
var expectedEvictions []policyv1beta1.Eviction
258+
var create []runtime.Object
259+
deletePods := []corev1.Pod{}
260+
for i := 1; i <= 4; i++ {
261+
pod := &corev1.Pod{}
262+
pod.Name = fmt.Sprintf("mypod-%d", i)
263+
pod.Namespace = "default"
244264

245-
if evictionSupported {
246-
eviction := policyv1beta1.Eviction{}
247-
eviction.Kind = "Eviction"
248-
eviction.APIVersion = "policy/v1"
249-
eviction.Namespace = pod.Namespace
250-
eviction.Name = pod.Name
265+
create = append(create, pod)
266+
if i <= 2 {
267+
deletePods = append(deletePods, *pod)
251268

252-
gracePeriodSeconds := int64(h.GracePeriodSeconds)
253-
eviction.DeleteOptions = &metav1.DeleteOptions{
254-
GracePeriodSeconds: &gracePeriodSeconds,
255-
}
269+
if tc.evictionSupported && !tc.disableEviction {
270+
eviction := policyv1beta1.Eviction{}
271+
eviction.Kind = "Eviction"
272+
eviction.APIVersion = "policy/v1"
273+
eviction.Namespace = pod.Namespace
274+
eviction.Name = pod.Name
256275

257-
expectedEvictions = append(expectedEvictions, eviction)
276+
gracePeriodSeconds := int64(h.GracePeriodSeconds)
277+
eviction.DeleteOptions = &metav1.DeleteOptions{
278+
GracePeriodSeconds: &gracePeriodSeconds,
258279
}
280+
281+
expectedEvictions = append(expectedEvictions, eviction)
259282
}
260283
}
284+
}
261285

262-
// Build the fake client
263-
k := fake.NewSimpleClientset(create...)
264-
if evictionSupported {
265-
addEvictionSupport(t, k)
266-
}
267-
h.Client = k
286+
// Build the fake client
287+
k := fake.NewSimpleClientset(create...)
288+
if tc.evictionSupported {
289+
addEvictionSupport(t, k)
290+
}
291+
h.Client = k
292+
h.DisableEviction = tc.disableEviction
293+
// Do the eviction
294+
if err := h.DeleteOrEvictPods(deletePods); err != nil {
295+
t.Fatalf("error from DeleteOrEvictPods: %v", err)
296+
}
268297

269-
// Do the eviction
270-
if err := h.DeleteOrEvictPods(deletePods); err != nil {
271-
t.Fatalf("error from DeleteOrEvictPods: %v", err)
298+
// Test that other pods are still there
299+
var remainingPods []string
300+
{
301+
podList, err := k.CoreV1().Pods("").List(metav1.ListOptions{})
302+
if err != nil {
303+
t.Fatalf("error listing pods: %v", err)
272304
}
273305

274-
// Test that other pods are still there
275-
var remainingPods []string
276-
{
277-
podList, err := k.CoreV1().Pods("").List(metav1.ListOptions{})
278-
if err != nil {
279-
t.Fatalf("error listing pods: %v", err)
280-
}
281-
282-
for _, pod := range podList.Items {
283-
remainingPods = append(remainingPods, pod.Namespace+"/"+pod.Name)
284-
}
285-
sort.Strings(remainingPods)
286-
}
287-
expected := []string{"default/mypod-3", "default/mypod-4"}
288-
if !reflect.DeepEqual(remainingPods, expected) {
289-
t.Errorf("unexpected remaining pods after DeleteOrEvictPods; actual %v; expected %v", remainingPods, expected)
306+
for _, pod := range podList.Items {
307+
remainingPods = append(remainingPods, pod.Namespace+"/"+pod.Name)
290308
}
309+
sort.Strings(remainingPods)
310+
}
311+
expected := []string{"default/mypod-3", "default/mypod-4"}
312+
if !reflect.DeepEqual(remainingPods, expected) {
313+
t.Errorf("%s: unexpected remaining pods after DeleteOrEvictPods; actual %v; expected %v", tc.description, remainingPods, expected)
314+
}
291315

292-
// Test that pods were evicted as expected
293-
var actualEvictions []policyv1beta1.Eviction
294-
for _, action := range k.Actions() {
295-
if action.GetVerb() != "create" || action.GetResource().Resource != "pods" || action.GetSubresource() != "eviction" {
296-
continue
297-
}
298-
eviction := *action.(ktest.CreateAction).GetObject().(*policyv1beta1.Eviction)
299-
actualEvictions = append(actualEvictions, eviction)
300-
}
301-
sort.Slice(actualEvictions, func(i, j int) bool {
302-
return actualEvictions[i].Name < actualEvictions[j].Name
303-
})
304-
if !reflect.DeepEqual(actualEvictions, expectedEvictions) {
305-
t.Errorf("unexpected evictions; actual %v; expected %v", actualEvictions, expectedEvictions)
316+
// Test that pods were evicted as expected
317+
var actualEvictions []policyv1beta1.Eviction
318+
for _, action := range k.Actions() {
319+
if action.GetVerb() != "create" || action.GetResource().Resource != "pods" || action.GetSubresource() != "eviction" {
320+
continue
306321
}
322+
eviction := *action.(ktest.CreateAction).GetObject().(*policyv1beta1.Eviction)
323+
actualEvictions = append(actualEvictions, eviction)
324+
}
325+
sort.Slice(actualEvictions, func(i, j int) bool {
326+
return actualEvictions[i].Name < actualEvictions[j].Name
307327
})
328+
if !reflect.DeepEqual(actualEvictions, expectedEvictions) {
329+
t.Errorf("%s: unexpected evictions; actual %v; expected %v", tc.description, actualEvictions, expectedEvictions)
330+
}
331+
})
308332
}
309333
}

0 commit comments

Comments
 (0)