k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/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.TypedRateLimitingInterface[string] 75 } 76 77 // NewController creates an ephemeral volume controller. 78 func NewController( 79 ctx context.Context, 80 kubeClient clientset.Interface, 81 podInformer coreinformers.PodInformer, 82 pvcInformer coreinformers.PersistentVolumeClaimInformer) (Controller, error) { 83 84 ec := &ephemeralController{ 85 kubeClient: kubeClient, 86 podLister: podInformer.Lister(), 87 podIndexer: podInformer.Informer().GetIndexer(), 88 podSynced: podInformer.Informer().HasSynced, 89 pvcLister: pvcInformer.Lister(), 90 pvcsSynced: pvcInformer.Informer().HasSynced, 91 queue: workqueue.NewTypedRateLimitingQueueWithConfig( 92 workqueue.DefaultTypedControllerRateLimiter[string](), 93 workqueue.TypedRateLimitingQueueConfig[string]{Name: "ephemeral_volume"}, 94 ), 95 } 96 97 ephemeralvolumemetrics.RegisterMetrics() 98 99 eventBroadcaster := record.NewBroadcaster(record.WithContext(ctx)) 100 eventBroadcaster.StartLogging(klog.Infof) 101 eventBroadcaster.StartRecordingToSink(&v1core.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}) 102 ec.recorder = eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "ephemeral_volume"}) 103 104 podInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 105 AddFunc: ec.enqueuePod, 106 // The pod spec is immutable. Therefore the controller can ignore pod updates 107 // because there cannot be any changes that have to be copied into the generated 108 // PVC. 109 // Deletion of the PVC is handled through the owner reference and garbage collection. 110 // Therefore pod deletions also can be ignored. 111 }) 112 pvcInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ 113 DeleteFunc: ec.onPVCDelete, 114 }) 115 if err := common.AddPodPVCIndexerIfNotPresent(ec.podIndexer); err != nil { 116 return nil, fmt.Errorf("could not initialize ephemeral volume controller: %w", err) 117 } 118 119 return ec, nil 120 } 121 122 func (ec *ephemeralController) enqueuePod(obj interface{}) { 123 pod, ok := obj.(*v1.Pod) 124 if !ok { 125 return 126 } 127 128 // Ignore pods which are already getting deleted. 129 if pod.DeletionTimestamp != nil { 130 return 131 } 132 133 for _, vol := range pod.Spec.Volumes { 134 if vol.Ephemeral != nil { 135 // It has at least one ephemeral inline volume, work on it. 136 key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(pod) 137 if err != nil { 138 runtime.HandleError(fmt.Errorf("couldn't get key for object %#v: %v", pod, err)) 139 return 140 } 141 ec.queue.Add(key) 142 break 143 } 144 } 145 } 146 147 func (ec *ephemeralController) onPVCDelete(obj interface{}) { 148 pvc, ok := obj.(*v1.PersistentVolumeClaim) 149 if !ok { 150 return 151 } 152 153 // Someone deleted a PVC, either intentionally or 154 // accidentally. If there is a pod referencing it because of 155 // an ephemeral volume, then we should re-create the PVC. 156 // The common indexer does some prefiltering for us by 157 // limiting the list to those pods which reference 158 // the PVC. 159 objs, err := ec.podIndexer.ByIndex(common.PodPVCIndex, fmt.Sprintf("%s/%s", pvc.Namespace, pvc.Name)) 160 if err != nil { 161 runtime.HandleError(fmt.Errorf("listing pods from cache: %v", err)) 162 return 163 } 164 for _, obj := range objs { 165 ec.enqueuePod(obj) 166 } 167 } 168 169 func (ec *ephemeralController) Run(ctx context.Context, workers int) { 170 defer runtime.HandleCrash() 171 defer ec.queue.ShutDown() 172 logger := klog.FromContext(ctx) 173 logger.Info("Starting ephemeral volume controller") 174 defer logger.Info("Shutting down ephemeral volume controller") 175 176 if !cache.WaitForNamedCacheSync("ephemeral", ctx.Done(), ec.podSynced, ec.pvcsSynced) { 177 return 178 } 179 180 for i := 0; i < workers; i++ { 181 go wait.UntilWithContext(ctx, ec.runWorker, time.Second) 182 } 183 184 <-ctx.Done() 185 } 186 187 func (ec *ephemeralController) runWorker(ctx context.Context) { 188 for ec.processNextWorkItem(ctx) { 189 } 190 } 191 192 func (ec *ephemeralController) processNextWorkItem(ctx context.Context) bool { 193 key, shutdown := ec.queue.Get() 194 if shutdown { 195 return false 196 } 197 defer ec.queue.Done(key) 198 199 err := ec.syncHandler(ctx, key) 200 if err == nil { 201 ec.queue.Forget(key) 202 return true 203 } 204 205 runtime.HandleError(fmt.Errorf("%v failed with: %v", key, err)) 206 ec.queue.AddRateLimited(key) 207 208 return true 209 } 210 211 // syncHandler is invoked for each pod which might need to be processed. 212 // If an error is returned from this function, the pod will be requeued. 213 func (ec *ephemeralController) syncHandler(ctx context.Context, key string) error { 214 namespace, name, err := cache.SplitMetaNamespaceKey(key) 215 if err != nil { 216 return err 217 } 218 pod, err := ec.podLister.Pods(namespace).Get(name) 219 logger := klog.FromContext(ctx) 220 if err != nil { 221 if errors.IsNotFound(err) { 222 logger.V(5).Info("Ephemeral: nothing to do for pod, it is gone", "podKey", key) 223 return nil 224 } 225 logger.V(5).Info("Error getting pod from informer", "pod", klog.KObj(pod), "podUID", pod.UID, "err", err) 226 return err 227 } 228 229 // Ignore pods which are already getting deleted. 230 if pod.DeletionTimestamp != nil { 231 logger.V(5).Info("Ephemeral: nothing to do for pod, it is marked for deletion", "podKey", key) 232 return nil 233 } 234 235 for _, vol := range pod.Spec.Volumes { 236 if err := ec.handleVolume(ctx, pod, vol); err != nil { 237 ec.recorder.Event(pod, v1.EventTypeWarning, events.FailedBinding, fmt.Sprintf("ephemeral volume %s: %v", vol.Name, err)) 238 return fmt.Errorf("pod %s, ephemeral volume %s: %v", key, vol.Name, err) 239 } 240 } 241 242 return nil 243 } 244 245 // handleEphemeralVolume is invoked for each volume of a pod. 246 func (ec *ephemeralController) handleVolume(ctx context.Context, pod *v1.Pod, vol v1.Volume) error { 247 logger := klog.FromContext(ctx) 248 logger.V(5).Info("Ephemeral: checking volume", "volumeName", vol.Name) 249 if vol.Ephemeral == nil { 250 return nil 251 } 252 253 pvcName := ephemeral.VolumeClaimName(pod, &vol) 254 pvc, err := ec.pvcLister.PersistentVolumeClaims(pod.Namespace).Get(pvcName) 255 if err != nil && !errors.IsNotFound(err) { 256 return err 257 } 258 if pvc != nil { 259 if err := ephemeral.VolumeIsForPod(pod, pvc); err != nil { 260 return err 261 } 262 // Already created, nothing more to do. 263 logger.V(5).Info("Ephemeral: PVC already created", "volumeName", vol.Name, "PVC", klog.KObj(pvc)) 264 return nil 265 } 266 267 // Create the PVC with pod as owner. 268 isTrue := true 269 pvc = &v1.PersistentVolumeClaim{ 270 ObjectMeta: metav1.ObjectMeta{ 271 Name: pvcName, 272 OwnerReferences: []metav1.OwnerReference{ 273 { 274 APIVersion: "v1", 275 Kind: "Pod", 276 Name: pod.Name, 277 UID: pod.UID, 278 Controller: &isTrue, 279 BlockOwnerDeletion: &isTrue, 280 }, 281 }, 282 Annotations: vol.Ephemeral.VolumeClaimTemplate.Annotations, 283 Labels: vol.Ephemeral.VolumeClaimTemplate.Labels, 284 }, 285 Spec: vol.Ephemeral.VolumeClaimTemplate.Spec, 286 } 287 ephemeralvolumemetrics.EphemeralVolumeCreateAttempts.Inc() 288 _, err = ec.kubeClient.CoreV1().PersistentVolumeClaims(pod.Namespace).Create(ctx, pvc, metav1.CreateOptions{}) 289 if err != nil { 290 ephemeralvolumemetrics.EphemeralVolumeCreateFailures.Inc() 291 return fmt.Errorf("create PVC %s: %v", pvcName, err) 292 } 293 return nil 294 }