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  }