k8s.io/kubernetes@v1.29.3/pkg/controller/volume/ephemeral/controller.go (about) 1 /* 2 Copyright 2020 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 ephemeral 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 "k8s.io/klog/v2" 25 26 v1 "k8s.io/api/core/v1" 27 "k8s.io/apimachinery/pkg/api/errors" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 "k8s.io/apimachinery/pkg/util/runtime" 30 "k8s.io/apimachinery/pkg/util/wait" 31 coreinformers "k8s.io/client-go/informers/core/v1" 32 clientset "k8s.io/client-go/kubernetes" 33 "k8s.io/client-go/kubernetes/scheme" 34 v1core "k8s.io/client-go/kubernetes/typed/core/v1" 35 corelisters "k8s.io/client-go/listers/core/v1" 36 "k8s.io/client-go/tools/cache" 37 "k8s.io/client-go/tools/record" 38 "k8s.io/client-go/util/workqueue" 39 "k8s.io/component-helpers/storage/ephemeral" 40 "k8s.io/kubernetes/pkg/controller/volume/common" 41 ephemeralvolumemetrics "k8s.io/kubernetes/pkg/controller/volume/ephemeral/metrics" 42 "k8s.io/kubernetes/pkg/controller/volume/events" 43 ) 44 45 // Controller creates PVCs for ephemeral inline volumes in a pod spec. 46 type Controller interface { 47 Run(ctx context.Context, workers int) 48 } 49 50 type ephemeralController struct { 51 // kubeClient is the kube API client used by volumehost to communicate with 52 // the API server. 53 kubeClient clientset.Interface 54 55 // pvcLister is the shared PVC lister used to fetch and store PVC 56 // objects from the API server. It is shared with other controllers and 57 // therefore the PVC objects in its store should be treated as immutable. 58 pvcLister corelisters.PersistentVolumeClaimLister 59 pvcsSynced cache.InformerSynced 60 61 // podLister is the shared Pod lister used to fetch Pod 62 // objects from the API server. It is shared with other controllers and 63 // therefore the Pod objects in its store should be treated as immutable. 64 podLister corelisters.PodLister 65 podSynced cache.InformerSynced 66 67 // podIndexer has the common PodPVC indexer indexer installed To 68 // limit iteration over pods to those of interest. 69 podIndexer cache.Indexer 70 71 // recorder is used to record events in the API server 72 recorder record.EventRecorder 73 74 queue workqueue.RateLimitingInterface 75 } 76 77 // NewController creates an ephemeral volume controller. 78 func NewController( 79 kubeClient clientset.Interface, 80 podInformer coreinformers.PodInformer, 81 pvcInformer coreinformers.PersistentVolumeClaimInformer) (Controller, error) { 82 83 ec := &ephemeralController{ 84 kubeClient: kubeClient, 85 podLister: podInformer.Lister(), 86 podIndexer: podInformer.Informer().GetIndexer(), 87 podSynced: podInformer.Informer().HasSynced, 88 pvcLister: pvcInformer.Lister(), 89 pvcsSynced: pvcInformer.Informer().HasSynced, 90 queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "ephemeral_volume"), 91 } 92 93 ephemeralvolumemetrics.RegisterMetrics() 94 95 eventBroadcaster := record.NewBroadcaster() 96 eventBroadcaster.StartLogging(klog.Infof) 97 eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) 98 ec.recorder = eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "ephemeral_volume"}) 99 100 podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 101 AddFunc: ec.enqueuePod, 102 // The pod spec is immutable. Therefore the controller can ignore pod updates 103 // because there cannot be any changes that have to be copied into the generated 104 // PVC. 105 // Deletion of the PVC is handled through the owner reference and garbage collection. 106 // Therefore pod deletions also can be ignored. 107 }) 108 pvcInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 109 DeleteFunc: ec.onPVCDelete, 110 }) 111 if err := common.AddPodPVCIndexerIfNotPresent(ec.podIndexer); err != nil { 112 return nil, fmt.Errorf("could not initialize ephemeral volume controller: %w", err) 113 } 114 115 return ec, nil 116 } 117 118 func (ec *ephemeralController) enqueuePod(obj interface{}) { 119 pod, ok := obj.(*v1.Pod) 120 if !ok { 121 return 122 } 123 124 // Ignore pods which are already getting deleted. 125 if pod.DeletionTimestamp != nil { 126 return 127 } 128 129 for _, vol := range pod.Spec.Volumes { 130 if vol.Ephemeral != nil { 131 // It has at least one ephemeral inline volume, work on it. 132 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(pod) 133 if err != nil { 134 runtime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", pod, err)) 135 return 136 } 137 ec.queue.Add(key) 138 break 139 } 140 } 141 } 142 143 func (ec *ephemeralController) onPVCDelete(obj interface{}) { 144 pvc, ok := obj.(*v1.PersistentVolumeClaim) 145 if !ok { 146 return 147 } 148 149 // Someone deleted a PVC, either intentionally or 150 // accidentally. If there is a pod referencing it because of 151 // an ephemeral volume, then we should re-create the PVC. 152 // The common indexer does some prefiltering for us by 153 // limiting the list to those pods which reference 154 // the PVC. 155 objs, err := ec.podIndexer.ByIndex(common.PodPVCIndex, fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)) 156 if err != nil { 157 runtime.HandleError(fmt.Errorf("listing pods from cache: %v", err)) 158 return 159 } 160 for _, obj := range objs { 161 ec.enqueuePod(obj) 162 } 163 } 164 165 func (ec *ephemeralController) Run(ctx context.Context, workers int) { 166 defer runtime.HandleCrash() 167 defer ec.queue.ShutDown() 168 logger := klog.FromContext(ctx) 169 logger.Info("Starting ephemeral volume controller") 170 defer logger.Info("Shutting down ephemeral volume controller") 171 172 if !cache.WaitForNamedCacheSync("ephemeral", ctx.Done(), ec.podSynced, ec.pvcsSynced) { 173 return 174 } 175 176 for i := 0; i < workers; i++ { 177 go wait.UntilWithContext(ctx, ec.runWorker, time.Second) 178 } 179 180 <-ctx.Done() 181 } 182 183 func (ec *ephemeralController) runWorker(ctx context.Context) { 184 for ec.processNextWorkItem(ctx) { 185 } 186 } 187 188 func (ec *ephemeralController) processNextWorkItem(ctx context.Context) bool { 189 key, shutdown := ec.queue.Get() 190 if shutdown { 191 return false 192 } 193 defer ec.queue.Done(key) 194 195 err := ec.syncHandler(ctx, key.(string)) 196 if err == nil { 197 ec.queue.Forget(key) 198 return true 199 } 200 201 runtime.HandleError(fmt.Errorf("%v failed with: %v", key, err)) 202 ec.queue.AddRateLimited(key) 203 204 return true 205 } 206 207 // syncHandler is invoked for each pod which might need to be processed. 208 // If an error is returned from this function, the pod will be requeued. 209 func (ec *ephemeralController) syncHandler(ctx context.Context, key string) error { 210 namespace, name, err := cache.SplitMetaNamespaceKey(key) 211 if err != nil { 212 return err 213 } 214 pod, err := ec.podLister.Pods(namespace).Get(name) 215 logger := klog.FromContext(ctx) 216 if err != nil { 217 if errors.IsNotFound(err) { 218 logger.V(5).Info("Ephemeral: nothing to do for pod, it is gone", "podKey", key) 219 return nil 220 } 221 logger.V(5).Info("Error getting pod from informer", "pod", klog.KObj(pod), "podUID", pod.UID, "err", err) 222 return err 223 } 224 225 // Ignore pods which are already getting deleted. 226 if pod.DeletionTimestamp != nil { 227 logger.V(5).Info("Ephemeral: nothing to do for pod, it is marked for deletion", "podKey", key) 228 return nil 229 } 230 231 for _, vol := range pod.Spec.Volumes { 232 if err := ec.handleVolume(ctx, pod, vol); err != nil { 233 ec.recorder.Event(pod, v1.EventTypeWarning, events.FailedBinding, fmt.Sprintf("ephemeral volume %s: %v", vol.Name, err)) 234 return fmt.Errorf("pod %s, ephemeral volume %s: %v", key, vol.Name, err) 235 } 236 } 237 238 return nil 239 } 240 241 // handleEphemeralVolume is invoked for each volume of a pod. 242 func (ec *ephemeralController) handleVolume(ctx context.Context, pod *v1.Pod, vol v1.Volume) error { 243 logger := klog.FromContext(ctx) 244 logger.V(5).Info("Ephemeral: checking volume", "volumeName", vol.Name) 245 if vol.Ephemeral == nil { 246 return nil 247 } 248 249 pvcName := ephemeral.VolumeClaimName(pod, &vol) 250 pvc, err := ec.pvcLister.PersistentVolumeClaims(pod.Namespace).Get(pvcName) 251 if err != nil && !errors.IsNotFound(err) { 252 return err 253 } 254 if pvc != nil { 255 if err := ephemeral.VolumeIsForPod(pod, pvc); err != nil { 256 return err 257 } 258 // Already created, nothing more to do. 259 logger.V(5).Info("Ephemeral: PVC already created", "volumeName", vol.Name, "PVC", klog.KObj(pvc)) 260 return nil 261 } 262 263 // Create the PVC with pod as owner. 264 isTrue := true 265 pvc = &v1.PersistentVolumeClaim{ 266 ObjectMeta: metav1.ObjectMeta{ 267 Name: pvcName, 268 OwnerReferences: []metav1.OwnerReference{ 269 { 270 APIVersion: "v1", 271 Kind: "Pod", 272 Name: pod.Name, 273 UID: pod.UID, 274 Controller: &isTrue, 275 BlockOwnerDeletion: &isTrue, 276 }, 277 }, 278 Annotations: vol.Ephemeral.VolumeClaimTemplate.Annotations, 279 Labels: vol.Ephemeral.VolumeClaimTemplate.Labels, 280 }, 281 Spec: vol.Ephemeral.VolumeClaimTemplate.Spec, 282 } 283 ephemeralvolumemetrics.EphemeralVolumeCreateAttempts.Inc() 284 _, err = ec.kubeClient.CoreV1().PersistentVolumeClaims(pod.Namespace).Create(ctx, pvc, metav1.CreateOptions{}) 285 if err != nil { 286 ephemeralvolumemetrics.EphemeralVolumeCreateFailures.Inc() 287 return fmt.Errorf("create PVC %s: %v", pvcName, err) 288 } 289 return nil 290 }