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 }