Change:
<!-- Please only use this template for submitting enhancement requests --What would you like to be : Allow to have an additional flag (e.g. or ) to allow a to drain pods off a node, even if they violate PodDisruptionBudgets Why is this needed: There are times where you want to drain pods off a node even if violating a PDB. For example, if you are running a one-worker node kubernetes cluster (not minikube), and you want to upgrade it. Or, like me, you are running a test cluster and want to scale down to zero nodes when you're not going to be using the cluster for a while in order to save costs. If that worker node has a deployment with a PDB, then will get stuck as it cannot get rid of the pod while respecting the PDB, which is expected; but in this case, it is a hindrance. Adding the switch will allow users to explicitly acknowledge that they want to ignore the PDBs (and accept the corresponding downtime for an application/service)
/sig node
+1
+1 Our company offers Kubernetes as PaaS. We manage Kubernetes, including IaaS OS updates and if a customer does not specify the PDB correctly, we are not able to drain the node for the OS updates. A flag would be werry helpfull for our use case.
+1
/active
I have created to address this. This is a pretty simple implementation, but I think it achieves the desired results. A suitable workflow would be: 1) Configure you component to use drain as normal 2) After some timeout, run drain with --disable-eviction option.
I'll wait until the PR is merged and released before testing, but if I understand right, it would require me do this, right? If so, I think it would be better if we did it a different way and have one call, something like this: Where implies that if the drain is still running after this time, switch to the mode without having to call kubectl again
I think that would require a sizeable refactor or add a great deal of complexity to the code, the same could be achieved with In practical terms, I'm focused on automatic node remediation via destroying the node and getting a new one. The timeout would be very large, the drain library is blocking, so I want it to fail quickly and requeue. I'm going to be performing the calculation of when it's appropriate to evict/delete outside of the drain library, and I suspect most will be as well, so there's not really a great incentive to do it in the drain library itself IMO.
That's all good, I have no issue using the flag as long as does error out and return a non-zero code as expected (there's been a few times where it seems to error out, but still return 0)

Discussion:
cmd.Flags().DurationVar(&o.drainer.Timeout, "timeout", o.drainer.Timeout, "The length of time to wait before giving up, zero means infinite") cmd.Flags().StringVarP(&o.drainer.Selector, "selector", "l", o.drainer.Selector, "Selector (label query) to filter on") cmd.Flags().StringVarP(&o.drainer.PodSelector, "pod-selector", "", o.drainer.PodSelector, "Label selector to filter pods on the node") <ins> 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.") </ins> cmdutil.AddDryRunFlag(cmd) return cmd

DeleteLocalData     bool Selector            string PodSelector         string <del> Out                 io.Writer ErrOut              io.Writer </del> <ins> // DisableEviction forces drain to use delete rather than evict DisableEviction bool Out    io.Writer ErrOut io.Writer </ins> // TODO(justinsb): unnecessary? DryRun bool

return nil } <del> policyGroupVersion, err := CheckEvictionSupport(d.Client) if err != nil { return err } </del> // TODO(justinsb): unnecessary? getPodFn := func(namespace, name string) (*corev1.Pod, error) { return d.Client.CoreV1().Pods(namespace).Get(name, metav1.GetOptions{}) } <del> if len(policyGroupVersion) > 0 { return d.evictPods(pods, policyGroupVersion, getPodFn) </del> <ins> if !d.DisableEviction { policyGroupVersion, err := CheckEvictionSupport(d.Client) if err != nil { return err } if len(policyGroupVersion) > 0 { return d.evictPods(pods, policyGroupVersion, getPodFn) } </ins> } return d.deletePods(pods, getPodFn)

} func TestDeleteOrEvict(t *testing.T) { <del> for _, evictionSupported := range []bool{true, false} { evictionSupported := evictionSupported t.Run(fmt.Sprintf("evictionSupported=%v", evictionSupported), func(t *testing.T) { h := &Helper{ Out:                os.Stdout, GracePeriodSeconds: 10, } // Create 4 pods, and try to remove the first 2 var expectedEvictions []policyv1beta1.Eviction var create []runtime.Object deletePods := []corev1.Pod{} for i := 1; i <= 4; i++ { pod := &corev1.Pod{} pod.Name = fmt.Sprintf("mypod-%d", i) pod.Namespace = "default" </del> <ins> tests := []struct { description       string evictionSupported bool disableEviction   bool }{ { description:       "eviction supported/enabled", evictionSupported: true, disableEviction:   false, }, { description:       "eviction unsupported/disabled", evictionSupported: false, disableEviction:   false, }, { description:       "eviction supported/disabled", evictionSupported: true, disableEviction:   true, }, { description:       "eviction unsupported/disabled", evictionSupported: false, disableEviction:   false, }, } for _, tc := range tests { t.Run(tc.description, func(t *testing.T) { h := &Helper{ Out:                os.Stdout, GracePeriodSeconds: 10, } </ins> <del> create = append(create, pod) if i <= 2 { deletePods = append(deletePods, *pod) </del> <ins> // Create 4 pods, and try to remove the first 2 var expectedEvictions []policyv1beta1.Eviction var create []runtime.Object deletePods := []corev1.Pod{} for i := 1; i <= 4; i++ { pod := &corev1.Pod{} pod.Name = fmt.Sprintf("mypod-%d", i) pod.Namespace = "default" </ins> <del> if evictionSupported { eviction := policyv1beta1.Eviction{} eviction.Kind = "Eviction" eviction.APIVersion = "policy/v1" eviction.Namespace = pod.Namespace eviction.Name = pod.Name </del> <ins> create = append(create, pod) if i <= 2 { deletePods = append(deletePods, *pod) </ins> <del> gracePeriodSeconds := int64(h.GracePeriodSeconds) eviction.DeleteOptions = &metav1.DeleteOptions{ GracePeriodSeconds: &gracePeriodSeconds, } </del> <ins> if tc.evictionSupported && !tc.disableEviction { eviction := policyv1beta1.Eviction{} eviction.Kind = "Eviction" eviction.APIVersion = "policy/v1" eviction.Namespace = pod.Namespace eviction.Name = pod.Name </ins> <del> expectedEvictions = append(expectedEvictions, eviction) </del> <ins> gracePeriodSeconds := int64(h.GracePeriodSeconds) eviction.DeleteOptions = &metav1.DeleteOptions{ GracePeriodSeconds: &gracePeriodSeconds, </ins> } <ins> expectedEvictions = append(expectedEvictions, eviction) </ins> } } <ins> } </ins> <del> // Build the fake client k := fake.NewSimpleClientset(create...) if evictionSupported { addEvictionSupport(t, k) } h.Client = k </del> <ins> // Build the fake client k := fake.NewSimpleClientset(create...) if tc.evictionSupported { addEvictionSupport(t, k) } h.Client = k h.DisableEviction = tc.disableEviction // Do the eviction if err := h.DeleteOrEvictPods(deletePods); err != nil { t.Fatalf("error from DeleteOrEvictPods: %v", err) } </ins> <del> // Do the eviction if err := h.DeleteOrEvictPods(deletePods); err != nil { t.Fatalf("error from DeleteOrEvictPods: %v", err) </del> <ins> // Test that other pods are still there var remainingPods []string { podList, err := k.CoreV1().Pods("").List(metav1.ListOptions{}) if err != nil { t.Fatalf("error listing pods: %v", err) </ins> } <del> // Test that other pods are still there var remainingPods []string { podList, err := k.CoreV1().Pods("").List(metav1.ListOptions{}) if err != nil { t.Fatalf("error listing pods: %v", err) } for _, pod := range podList.Items { remainingPods = append(remainingPods, pod.Namespace+"/"+pod.Name) } sort.Strings(remainingPods) } expected := []string{"default/mypod-3", "default/mypod-4"} if !reflect.DeepEqual(remainingPods, expected) { t.Errorf("unexpected remaining pods after DeleteOrEvictPods; actual %v; expected %v", remainingPods, expected) </del> <ins> for _, pod := range podList.Items { remainingPods = append(remainingPods, pod.Namespace+"/"+pod.Name) </ins> } <ins> sort.Strings(remainingPods) } expected := []string{"default/mypod-3", "default/mypod-4"} if !reflect.DeepEqual(remainingPods, expected) { t.Errorf("%s: unexpected remaining pods after DeleteOrEvictPods; actual %v; expected %v", tc.description, remainingPods, expected) } </ins> <del> // Test that pods were evicted as expected var actualEvictions []policyv1beta1.Eviction for _, action := range k.Actions() { if action.GetVerb() != "create" || action.GetResource().Resource != "pods" || action.GetSubresource() != "eviction" { continue } eviction := *action.(ktest.CreateAction).GetObject().(*policyv1beta1.Eviction) actualEvictions = append(actualEvictions, eviction) } sort.Slice(actualEvictions, func(i, j int) bool { return actualEvictions[i].Name < actualEvictions[j].Name }) if !reflect.DeepEqual(actualEvictions, expectedEvictions) { t.Errorf("unexpected evictions; actual %v; expected %v", actualEvictions, expectedEvictions) </del> <ins> // Test that pods were evicted as expected var actualEvictions []policyv1beta1.Eviction for _, action := range k.Actions() { if action.GetVerb() != "create" || action.GetResource().Resource != "pods" || action.GetSubresource() != "eviction" { continue </ins> } <ins> eviction := *action.(ktest.CreateAction).GetObject().(*policyv1beta1.Eviction) actualEvictions = append(actualEvictions, eviction) } sort.Slice(actualEvictions, func(i, j int) bool { return actualEvictions[i].Name < actualEvictions[j].Name </ins> }) <ins> if !reflect.DeepEqual(actualEvictions, expectedEvictions) { t.Errorf("%s: unexpected evictions; actual %v; expected %v", tc.description, actualEvictions, expectedEvictions) } }) </ins> } }