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  }