...

Source file src/k8s.io/kubectl/pkg/drain/filters.go

Documentation: k8s.io/kubectl/pkg/drain

     1  /*
     2  Copyright 2019 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package drain
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"strings"
    23  	"time"
    24  
    25  	appsv1 "k8s.io/api/apps/v1"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  )
    30  
    31  const (
    32  	daemonSetFatal      = "DaemonSet-managed Pods (use --ignore-daemonsets to ignore)"
    33  	daemonSetWarning    = "ignoring DaemonSet-managed Pods"
    34  	localStorageFatal   = "Pods with local storage (use --delete-emptydir-data to override)"
    35  	localStorageWarning = "deleting Pods with local storage"
    36  	unmanagedFatal      = "cannot delete Pods that declare no controller (use --force to override)"
    37  	unmanagedWarning    = "deleting Pods that declare no controller"
    38  )
    39  
    40  // PodDelete informs filtering logic whether a pod should be deleted or not
    41  type PodDelete struct {
    42  	Pod    corev1.Pod
    43  	Status PodDeleteStatus
    44  }
    45  
    46  // PodDeleteList is a wrapper around []PodDelete
    47  type PodDeleteList struct {
    48  	items []PodDelete
    49  }
    50  
    51  // Pods returns a list of all pods marked for deletion after filtering.
    52  func (l *PodDeleteList) Pods() []corev1.Pod {
    53  	pods := []corev1.Pod{}
    54  	for _, i := range l.items {
    55  		if i.Status.Delete {
    56  			pods = append(pods, i.Pod)
    57  		}
    58  	}
    59  	return pods
    60  }
    61  
    62  // Warnings returns all warning messages concatenated into a string.
    63  func (l *PodDeleteList) Warnings() string {
    64  	ps := make(map[string][]string)
    65  	for _, i := range l.items {
    66  		if i.Status.Reason == PodDeleteStatusTypeWarning {
    67  			ps[i.Status.Message] = append(ps[i.Status.Message], fmt.Sprintf("%s/%s", i.Pod.Namespace, i.Pod.Name))
    68  		}
    69  	}
    70  
    71  	msgs := []string{}
    72  	for key, pods := range ps {
    73  		msgs = append(msgs, fmt.Sprintf("%s: %s", key, strings.Join(pods, ", ")))
    74  	}
    75  	return strings.Join(msgs, "; ")
    76  }
    77  
    78  func (l *PodDeleteList) errors() []error {
    79  	failedPods := make(map[string][]string)
    80  	for _, i := range l.items {
    81  		if i.Status.Reason == PodDeleteStatusTypeError {
    82  			msg := i.Status.Message
    83  			if msg == "" {
    84  				msg = "unexpected error"
    85  			}
    86  			failedPods[msg] = append(failedPods[msg], fmt.Sprintf("%s/%s", i.Pod.Namespace, i.Pod.Name))
    87  		}
    88  	}
    89  	errs := make([]error, 0, len(failedPods))
    90  	for msg, pods := range failedPods {
    91  		errs = append(errs, fmt.Errorf("cannot delete %s: %s", msg, strings.Join(pods, ", ")))
    92  	}
    93  	return errs
    94  }
    95  
    96  // PodDeleteStatus informs filters if a pod should be deleted
    97  type PodDeleteStatus struct {
    98  	Delete  bool
    99  	Reason  string
   100  	Message string
   101  }
   102  
   103  // PodFilter takes a pod and returns a PodDeleteStatus
   104  type PodFilter func(corev1.Pod) PodDeleteStatus
   105  
   106  const (
   107  	// PodDeleteStatusTypeOkay is "Okay"
   108  	PodDeleteStatusTypeOkay = "Okay"
   109  	// PodDeleteStatusTypeSkip is "Skip"
   110  	PodDeleteStatusTypeSkip = "Skip"
   111  	// PodDeleteStatusTypeWarning is "Warning"
   112  	PodDeleteStatusTypeWarning = "Warning"
   113  	// PodDeleteStatusTypeError is "Error"
   114  	PodDeleteStatusTypeError = "Error"
   115  )
   116  
   117  // MakePodDeleteStatusOkay is a helper method to return the corresponding PodDeleteStatus
   118  func MakePodDeleteStatusOkay() PodDeleteStatus {
   119  	return PodDeleteStatus{
   120  		Delete: true,
   121  		Reason: PodDeleteStatusTypeOkay,
   122  	}
   123  }
   124  
   125  // MakePodDeleteStatusSkip is a helper method to return the corresponding PodDeleteStatus
   126  func MakePodDeleteStatusSkip() PodDeleteStatus {
   127  	return PodDeleteStatus{
   128  		Delete: false,
   129  		Reason: PodDeleteStatusTypeSkip,
   130  	}
   131  }
   132  
   133  // MakePodDeleteStatusWithWarning is a helper method to return the corresponding PodDeleteStatus
   134  func MakePodDeleteStatusWithWarning(delete bool, message string) PodDeleteStatus {
   135  	return PodDeleteStatus{
   136  		Delete:  delete,
   137  		Reason:  PodDeleteStatusTypeWarning,
   138  		Message: message,
   139  	}
   140  }
   141  
   142  // MakePodDeleteStatusWithError is a helper method to return the corresponding PodDeleteStatus
   143  func MakePodDeleteStatusWithError(message string) PodDeleteStatus {
   144  	return PodDeleteStatus{
   145  		Delete:  false,
   146  		Reason:  PodDeleteStatusTypeError,
   147  		Message: message,
   148  	}
   149  }
   150  
   151  // The filters are applied in a specific order, only the last filter's
   152  // message will be retained if there are any warnings.
   153  func (d *Helper) makeFilters() []PodFilter {
   154  	baseFilters := []PodFilter{
   155  		d.skipDeletedFilter,
   156  		d.daemonSetFilter,
   157  		d.mirrorPodFilter,
   158  		d.localStorageFilter,
   159  		d.unreplicatedFilter,
   160  	}
   161  	return append(baseFilters, d.AdditionalFilters...)
   162  }
   163  
   164  func hasLocalStorage(pod corev1.Pod) bool {
   165  	for _, volume := range pod.Spec.Volumes {
   166  		if volume.EmptyDir != nil {
   167  			return true
   168  		}
   169  	}
   170  
   171  	return false
   172  }
   173  
   174  func (d *Helper) daemonSetFilter(pod corev1.Pod) PodDeleteStatus {
   175  	// Note that we return false in cases where the pod is DaemonSet managed,
   176  	// regardless of flags.
   177  	//
   178  	// The exception is for pods that are orphaned (the referencing
   179  	// management resource - including DaemonSet - is not found).
   180  	// Such pods will be deleted if --force is used.
   181  	controllerRef := metav1.GetControllerOf(&pod)
   182  	if controllerRef == nil || controllerRef.Kind != appsv1.SchemeGroupVersion.WithKind("DaemonSet").Kind {
   183  		return MakePodDeleteStatusOkay()
   184  	}
   185  	// Any finished pod can be removed.
   186  	if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
   187  		return MakePodDeleteStatusOkay()
   188  	}
   189  
   190  	if _, err := d.Client.AppsV1().DaemonSets(pod.Namespace).Get(context.TODO(), controllerRef.Name, metav1.GetOptions{}); err != nil {
   191  		// remove orphaned pods with a warning if --force is used
   192  		if apierrors.IsNotFound(err) && d.Force {
   193  			return MakePodDeleteStatusWithWarning(true, err.Error())
   194  		}
   195  
   196  		return MakePodDeleteStatusWithError(err.Error())
   197  	}
   198  
   199  	if !d.IgnoreAllDaemonSets {
   200  		return MakePodDeleteStatusWithError(daemonSetFatal)
   201  	}
   202  
   203  	return MakePodDeleteStatusWithWarning(false, daemonSetWarning)
   204  }
   205  
   206  func (d *Helper) mirrorPodFilter(pod corev1.Pod) PodDeleteStatus {
   207  	if _, found := pod.ObjectMeta.Annotations[corev1.MirrorPodAnnotationKey]; found {
   208  		return MakePodDeleteStatusSkip()
   209  	}
   210  	return MakePodDeleteStatusOkay()
   211  }
   212  
   213  func (d *Helper) localStorageFilter(pod corev1.Pod) PodDeleteStatus {
   214  	if !hasLocalStorage(pod) {
   215  		return MakePodDeleteStatusOkay()
   216  	}
   217  	// Any finished pod can be removed.
   218  	if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
   219  		return MakePodDeleteStatusOkay()
   220  	}
   221  	if !d.DeleteEmptyDirData {
   222  		return MakePodDeleteStatusWithError(localStorageFatal)
   223  	}
   224  
   225  	// TODO: this warning gets dropped by subsequent filters;
   226  	// consider accounting for multiple warning conditions or at least
   227  	// preserving the last warning message.
   228  	return MakePodDeleteStatusWithWarning(true, localStorageWarning)
   229  }
   230  
   231  func (d *Helper) unreplicatedFilter(pod corev1.Pod) PodDeleteStatus {
   232  	// any finished pod can be removed
   233  	if pod.Status.Phase == corev1.PodSucceeded || pod.Status.Phase == corev1.PodFailed {
   234  		return MakePodDeleteStatusOkay()
   235  	}
   236  
   237  	controllerRef := metav1.GetControllerOf(&pod)
   238  	if controllerRef != nil {
   239  		return MakePodDeleteStatusOkay()
   240  	}
   241  	if d.Force {
   242  		return MakePodDeleteStatusWithWarning(true, unmanagedWarning)
   243  	}
   244  	return MakePodDeleteStatusWithError(unmanagedFatal)
   245  }
   246  
   247  func shouldSkipPod(pod corev1.Pod, skipDeletedTimeoutSeconds int) bool {
   248  	return skipDeletedTimeoutSeconds > 0 &&
   249  		!pod.ObjectMeta.DeletionTimestamp.IsZero() &&
   250  		int(time.Now().Sub(pod.ObjectMeta.GetDeletionTimestamp().Time).Seconds()) > skipDeletedTimeoutSeconds
   251  }
   252  
   253  func (d *Helper) skipDeletedFilter(pod corev1.Pod) PodDeleteStatus {
   254  	if shouldSkipPod(pod, d.SkipWaitForDeleteTimeoutSeconds) {
   255  		return MakePodDeleteStatusSkip()
   256  	}
   257  	return MakePodDeleteStatusOkay()
   258  }
   259  

View as plain text