k8s.io/kubernetes@v1.29.3/pkg/controller/volume/pvcprotection/pvc_protection_controller.go (about)

     1  /*
     2  Copyright 2017 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 pvcprotection
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    28  	"k8s.io/apimachinery/pkg/util/wait"
    29  	coreinformers "k8s.io/client-go/informers/core/v1"
    30  	clientset "k8s.io/client-go/kubernetes"
    31  	corelisters "k8s.io/client-go/listers/core/v1"
    32  	"k8s.io/client-go/tools/cache"
    33  	"k8s.io/client-go/util/workqueue"
    34  	"k8s.io/component-helpers/storage/ephemeral"
    35  	"k8s.io/klog/v2"
    36  	"k8s.io/kubernetes/pkg/controller/volume/common"
    37  	"k8s.io/kubernetes/pkg/controller/volume/protectionutil"
    38  	"k8s.io/kubernetes/pkg/util/slice"
    39  	volumeutil "k8s.io/kubernetes/pkg/volume/util"
    40  )
    41  
    42  // Controller is controller that removes PVCProtectionFinalizer
    43  // from PVCs that are used by no pods.
    44  type Controller struct {
    45  	client clientset.Interface
    46  
    47  	pvcLister       corelisters.PersistentVolumeClaimLister
    48  	pvcListerSynced cache.InformerSynced
    49  
    50  	podLister       corelisters.PodLister
    51  	podListerSynced cache.InformerSynced
    52  	podIndexer      cache.Indexer
    53  
    54  	queue workqueue.RateLimitingInterface
    55  }
    56  
    57  // NewPVCProtectionController returns a new instance of PVCProtectionController.
    58  func NewPVCProtectionController(logger klog.Logger, pvcInformer coreinformers.PersistentVolumeClaimInformer, podInformer coreinformers.PodInformer, cl clientset.Interface) (*Controller, error) {
    59  	e := &Controller{
    60  		client: cl,
    61  		queue:  workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "pvcprotection"),
    62  	}
    63  
    64  	e.pvcLister = pvcInformer.Lister()
    65  	e.pvcListerSynced = pvcInformer.Informer().HasSynced
    66  	pvcInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    67  		AddFunc: func(obj interface{}) {
    68  			e.pvcAddedUpdated(logger, obj)
    69  		},
    70  		UpdateFunc: func(old, new interface{}) {
    71  			e.pvcAddedUpdated(logger, new)
    72  		},
    73  	})
    74  
    75  	e.podLister = podInformer.Lister()
    76  	e.podListerSynced = podInformer.Informer().HasSynced
    77  	e.podIndexer = podInformer.Informer().GetIndexer()
    78  	if err := common.AddIndexerIfNotPresent(e.podIndexer, common.PodPVCIndex, common.PodPVCIndexFunc()); err != nil {
    79  		return nil, fmt.Errorf("could not initialize pvc protection controller: %w", err)
    80  	}
    81  	podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
    82  		AddFunc: func(obj interface{}) {
    83  			e.podAddedDeletedUpdated(logger, nil, obj, false)
    84  		},
    85  		DeleteFunc: func(obj interface{}) {
    86  			e.podAddedDeletedUpdated(logger, nil, obj, true)
    87  		},
    88  		UpdateFunc: func(old, new interface{}) {
    89  			e.podAddedDeletedUpdated(logger, old, new, false)
    90  		},
    91  	})
    92  
    93  	return e, nil
    94  }
    95  
    96  // Run runs the controller goroutines.
    97  func (c *Controller) Run(ctx context.Context, workers int) {
    98  	defer utilruntime.HandleCrash()
    99  	defer c.queue.ShutDown()
   100  
   101  	logger := klog.FromContext(ctx)
   102  	logger.Info("Starting PVC protection controller")
   103  	defer logger.Info("Shutting down PVC protection controller")
   104  
   105  	if !cache.WaitForNamedCacheSync("PVC protection", ctx.Done(), c.pvcListerSynced, c.podListerSynced) {
   106  		return
   107  	}
   108  
   109  	for i := 0; i < workers; i++ {
   110  		go wait.UntilWithContext(ctx, c.runWorker, time.Second)
   111  	}
   112  
   113  	<-ctx.Done()
   114  }
   115  
   116  func (c *Controller) runWorker(ctx context.Context) {
   117  	for c.processNextWorkItem(ctx) {
   118  	}
   119  }
   120  
   121  // processNextWorkItem deals with one pvcKey off the queue.  It returns false when it's time to quit.
   122  func (c *Controller) processNextWorkItem(ctx context.Context) bool {
   123  	pvcKey, quit := c.queue.Get()
   124  	if quit {
   125  		return false
   126  	}
   127  	defer c.queue.Done(pvcKey)
   128  
   129  	pvcNamespace, pvcName, err := cache.SplitMetaNamespaceKey(pvcKey.(string))
   130  	if err != nil {
   131  		utilruntime.HandleError(fmt.Errorf("error parsing PVC key %q: %v", pvcKey, err))
   132  		return true
   133  	}
   134  
   135  	err = c.processPVC(ctx, pvcNamespace, pvcName)
   136  	if err == nil {
   137  		c.queue.Forget(pvcKey)
   138  		return true
   139  	}
   140  
   141  	utilruntime.HandleError(fmt.Errorf("PVC %v failed with : %v", pvcKey, err))
   142  	c.queue.AddRateLimited(pvcKey)
   143  
   144  	return true
   145  }
   146  
   147  func (c *Controller) processPVC(ctx context.Context, pvcNamespace, pvcName string) error {
   148  	logger := klog.FromContext(ctx)
   149  	logger.V(4).Info("Processing PVC", "PVC", klog.KRef(pvcNamespace, pvcName))
   150  	startTime := time.Now()
   151  	defer func() {
   152  		logger.V(4).Info("Finished processing PVC", "PVC", klog.KRef(pvcNamespace, pvcName), "duration", time.Since(startTime))
   153  	}()
   154  
   155  	pvc, err := c.pvcLister.PersistentVolumeClaims(pvcNamespace).Get(pvcName)
   156  	if apierrors.IsNotFound(err) {
   157  		logger.V(4).Info("PVC not found, ignoring", "PVC", klog.KRef(pvcNamespace, pvcName))
   158  		return nil
   159  	}
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	if protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) {
   165  		// PVC should be deleted. Check if it's used and remove finalizer if
   166  		// it's not.
   167  		isUsed, err := c.isBeingUsed(ctx, pvc)
   168  		if err != nil {
   169  			return err
   170  		}
   171  		if !isUsed {
   172  			return c.removeFinalizer(ctx, pvc)
   173  		}
   174  		logger.V(2).Info("Keeping PVC because it is being used", "PVC", klog.KObj(pvc))
   175  	}
   176  
   177  	if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) {
   178  		// PVC is not being deleted -> it should have the finalizer. The
   179  		// finalizer should be added by admission plugin, this is just to add
   180  		// the finalizer to old PVCs that were created before the admission
   181  		// plugin was enabled.
   182  		return c.addFinalizer(ctx, pvc)
   183  	}
   184  	return nil
   185  }
   186  
   187  func (c *Controller) addFinalizer(ctx context.Context, pvc *v1.PersistentVolumeClaim) error {
   188  	claimClone := pvc.DeepCopy()
   189  	claimClone.ObjectMeta.Finalizers = append(claimClone.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer)
   190  	_, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).Update(ctx, claimClone, metav1.UpdateOptions{})
   191  	logger := klog.FromContext(ctx)
   192  	if err != nil {
   193  		logger.Error(err, "Error adding protection finalizer to PVC", "PVC", klog.KObj(pvc))
   194  		return err
   195  	}
   196  	logger.V(3).Info("Added protection finalizer to PVC", "PVC", klog.KObj(pvc))
   197  	return nil
   198  }
   199  
   200  func (c *Controller) removeFinalizer(ctx context.Context, pvc *v1.PersistentVolumeClaim) error {
   201  	claimClone := pvc.DeepCopy()
   202  	claimClone.ObjectMeta.Finalizers = slice.RemoveString(claimClone.ObjectMeta.Finalizers, volumeutil.PVCProtectionFinalizer, nil)
   203  	_, err := c.client.CoreV1().PersistentVolumeClaims(claimClone.Namespace).Update(ctx, claimClone, metav1.UpdateOptions{})
   204  	logger := klog.FromContext(ctx)
   205  	if err != nil {
   206  		logger.Error(err, "Error removing protection finalizer from PVC", "PVC", klog.KObj(pvc))
   207  		return err
   208  	}
   209  	logger.V(3).Info("Removed protection finalizer from PVC", "PVC", klog.KObj(pvc))
   210  	return nil
   211  }
   212  
   213  func (c *Controller) isBeingUsed(ctx context.Context, pvc *v1.PersistentVolumeClaim) (bool, error) {
   214  	// Look for a Pod using pvc in the Informer's cache. If one is found the
   215  	// correct decision to keep pvc is taken without doing an expensive live
   216  	// list.
   217  	logger := klog.FromContext(ctx)
   218  	if inUse, err := c.askInformer(logger, pvc); err != nil {
   219  		// No need to return because a live list will follow.
   220  		logger.Error(err, "")
   221  	} else if inUse {
   222  		return true, nil
   223  	}
   224  
   225  	// Even if no Pod using pvc was found in the Informer's cache it doesn't
   226  	// mean such a Pod doesn't exist: it might just not be in the cache yet. To
   227  	// be 100% confident that it is safe to delete pvc make sure no Pod is using
   228  	// it among those returned by a live list.
   229  	return c.askAPIServer(ctx, pvc)
   230  }
   231  
   232  func (c *Controller) askInformer(logger klog.Logger, pvc *v1.PersistentVolumeClaim) (bool, error) {
   233  	logger.V(4).Info("Looking for Pods using PVC in the Informer's cache", "PVC", klog.KObj(pvc))
   234  
   235  	// The indexer is used to find pods which might use the PVC.
   236  	objs, err := c.podIndexer.ByIndex(common.PodPVCIndex, fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name))
   237  	if err != nil {
   238  		return false, fmt.Errorf("cache-based list of pods failed while processing %s/%s: %s", pvc.Namespace, pvc.Name, err.Error())
   239  	}
   240  	for _, obj := range objs {
   241  		pod, ok := obj.(*v1.Pod)
   242  		if !ok {
   243  			continue
   244  		}
   245  
   246  		// We still need to look at each volume: that's redundant for volume.PersistentVolumeClaim,
   247  		// but for volume.Ephemeral we need to be sure that this particular PVC is the one
   248  		// created for the ephemeral volume.
   249  		if c.podUsesPVC(logger, pod, pvc) {
   250  			return true, nil
   251  		}
   252  	}
   253  
   254  	logger.V(4).Info("No Pod using PVC was found in the Informer's cache", "PVC", klog.KObj(pvc))
   255  	return false, nil
   256  }
   257  
   258  func (c *Controller) askAPIServer(ctx context.Context, pvc *v1.PersistentVolumeClaim) (bool, error) {
   259  	logger := klog.FromContext(ctx)
   260  	logger.V(4).Info("Looking for Pods using PVC with a live list", "PVC", klog.KObj(pvc))
   261  
   262  	podsList, err := c.client.CoreV1().Pods(pvc.Namespace).List(ctx, metav1.ListOptions{})
   263  	if err != nil {
   264  		return false, fmt.Errorf("live list of pods failed: %s", err.Error())
   265  	}
   266  
   267  	for _, pod := range podsList.Items {
   268  		if c.podUsesPVC(logger, &pod, pvc) {
   269  			return true, nil
   270  		}
   271  	}
   272  
   273  	logger.V(2).Info("PVC is unused", "PVC", klog.KObj(pvc))
   274  	return false, nil
   275  }
   276  
   277  func (c *Controller) podUsesPVC(logger klog.Logger, pod *v1.Pod, pvc *v1.PersistentVolumeClaim) bool {
   278  	// Check whether pvc is used by pod only if pod is scheduled, because
   279  	// kubelet sees pods after they have been scheduled and it won't allow
   280  	// starting a pod referencing a PVC with a non-nil deletionTimestamp.
   281  	if pod.Spec.NodeName != "" {
   282  		for _, volume := range pod.Spec.Volumes {
   283  			if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc.Name ||
   284  				!podIsShutDown(pod) && volume.Ephemeral != nil && ephemeral.VolumeClaimName(pod, &volume) == pvc.Name && ephemeral.VolumeIsForPod(pod, pvc) == nil {
   285  				logger.V(2).Info("Pod uses PVC", "pod", klog.KObj(pod), "PVC", klog.KObj(pvc))
   286  				return true
   287  			}
   288  		}
   289  	}
   290  	return false
   291  }
   292  
   293  // podIsShutDown returns true if kubelet is done with the pod or
   294  // it was force-deleted.
   295  func podIsShutDown(pod *v1.Pod) bool {
   296  	// A pod that has a deletionTimestamp and a zero
   297  	// deletionGracePeriodSeconds
   298  	// a) has been processed by kubelet and was set up for deletion
   299  	//    by the apiserver:
   300  	//    - canBeDeleted has verified that volumes were unpublished
   301  	//      https://github.com/kubernetes/kubernetes/blob/5404b5a28a2114299608bab00e4292960dd864a0/pkg/kubelet/kubelet_pods.go#L980
   302  	//    - deletionGracePeriodSeconds was set via a delete
   303  	//      with zero GracePeriodSeconds
   304  	//      https://github.com/kubernetes/kubernetes/blob/5404b5a28a2114299608bab00e4292960dd864a0/pkg/kubelet/status/status_manager.go#L580-L592
   305  	// or
   306  	// b) was force-deleted.
   307  	//
   308  	// It's now just waiting for garbage collection. We could wait
   309  	// for it to actually get removed, but that may be blocked by
   310  	// finalizers for the pod and thus get delayed.
   311  	//
   312  	// Worse, it is possible that there is a cyclic dependency
   313  	// (pod finalizer waits for PVC to get removed, PVC protection
   314  	// controller waits for pod to get removed).  By considering
   315  	// the PVC unused in this case, we allow the PVC to get
   316  	// removed and break such a cycle.
   317  	//
   318  	// Therefore it is better to proceed with PVC removal,
   319  	// which is safe (case a) and/or desirable (case b).
   320  	return pod.DeletionTimestamp != nil && pod.DeletionGracePeriodSeconds != nil && *pod.DeletionGracePeriodSeconds == 0
   321  }
   322  
   323  // pvcAddedUpdated reacts to pvc added/updated events
   324  func (c *Controller) pvcAddedUpdated(logger klog.Logger, obj interface{}) {
   325  	pvc, ok := obj.(*v1.PersistentVolumeClaim)
   326  	if !ok {
   327  		utilruntime.HandleError(fmt.Errorf("PVC informer returned non-PVC object: %#v", obj))
   328  		return
   329  	}
   330  	key, err := cache.MetaNamespaceKeyFunc(pvc)
   331  	if err != nil {
   332  		utilruntime.HandleError(fmt.Errorf("couldn't get key for Persistent Volume Claim %#v: %v", pvc, err))
   333  		return
   334  	}
   335  	logger.V(4).Info("Got event on PVC", "pvc", klog.KObj(pvc))
   336  
   337  	if protectionutil.NeedToAddFinalizer(pvc, volumeutil.PVCProtectionFinalizer) || protectionutil.IsDeletionCandidate(pvc, volumeutil.PVCProtectionFinalizer) {
   338  		c.queue.Add(key)
   339  	}
   340  }
   341  
   342  // podAddedDeletedUpdated reacts to Pod events
   343  func (c *Controller) podAddedDeletedUpdated(logger klog.Logger, old, new interface{}, deleted bool) {
   344  	if pod := c.parsePod(new); pod != nil {
   345  		c.enqueuePVCs(logger, pod, deleted)
   346  
   347  		// An update notification might mask the deletion of a pod X and the
   348  		// following creation of a pod Y with the same namespaced name as X. If
   349  		// that's the case X needs to be processed as well to handle the case
   350  		// where it is blocking deletion of a PVC not referenced by Y, otherwise
   351  		// such PVC will never be deleted.
   352  		if oldPod := c.parsePod(old); oldPod != nil && oldPod.UID != pod.UID {
   353  			c.enqueuePVCs(logger, oldPod, true)
   354  		}
   355  	}
   356  }
   357  
   358  func (*Controller) parsePod(obj interface{}) *v1.Pod {
   359  	if obj == nil {
   360  		return nil
   361  	}
   362  	pod, ok := obj.(*v1.Pod)
   363  	if !ok {
   364  		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
   365  		if !ok {
   366  			utilruntime.HandleError(fmt.Errorf("couldn't get object from tombstone %#v", obj))
   367  			return nil
   368  		}
   369  		pod, ok = tombstone.Obj.(*v1.Pod)
   370  		if !ok {
   371  			utilruntime.HandleError(fmt.Errorf("tombstone contained object that is not a Pod %#v", obj))
   372  			return nil
   373  		}
   374  	}
   375  	return pod
   376  }
   377  
   378  func (c *Controller) enqueuePVCs(logger klog.Logger, pod *v1.Pod, deleted bool) {
   379  	// Filter out pods that can't help us to remove a finalizer on PVC
   380  	if !deleted && !volumeutil.IsPodTerminated(pod, pod.Status) && pod.Spec.NodeName != "" {
   381  		return
   382  	}
   383  
   384  	logger.V(4).Info("Enqueuing PVCs for Pod", "pod", klog.KObj(pod), "podUID", pod.UID)
   385  
   386  	// Enqueue all PVCs that the pod uses
   387  	for _, volume := range pod.Spec.Volumes {
   388  		switch {
   389  		case volume.PersistentVolumeClaim != nil:
   390  			c.queue.Add(pod.Namespace + "/" + volume.PersistentVolumeClaim.ClaimName)
   391  		case volume.Ephemeral != nil:
   392  			c.queue.Add(pod.Namespace + "/" + ephemeral.VolumeClaimName(pod, &volume))
   393  		}
   394  	}
   395  }