k8s.io/kubernetes@v1.29.3/pkg/kubelet/volumemanager/reconciler/reconciler_common.go (about)

     1  /*
     2  Copyright 2022 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 reconciler
    18  
    19  import (
    20  	"fmt"
    21  	"sync"
    22  	"time"
    23  
    24  	v1 "k8s.io/api/core/v1"
    25  	"k8s.io/apimachinery/pkg/api/resource"
    26  	"k8s.io/apimachinery/pkg/types"
    27  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    28  	clientset "k8s.io/client-go/kubernetes"
    29  	"k8s.io/klog/v2"
    30  	"k8s.io/kubernetes/pkg/features"
    31  	"k8s.io/kubernetes/pkg/kubelet/volumemanager/cache"
    32  	"k8s.io/kubernetes/pkg/util/goroutinemap/exponentialbackoff"
    33  	volumepkg "k8s.io/kubernetes/pkg/volume"
    34  	"k8s.io/kubernetes/pkg/volume/util/hostutil"
    35  	"k8s.io/kubernetes/pkg/volume/util/nestedpendingoperations"
    36  	"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
    37  	"k8s.io/mount-utils"
    38  )
    39  
    40  // Reconciler runs a periodic loop to reconcile the desired state of the world
    41  // with the actual state of the world by triggering attach, detach, mount, and
    42  // unmount operations.
    43  // Note: This is distinct from the Reconciler implemented by the attach/detach
    44  // controller. This reconciles state for the kubelet volume manager. That
    45  // reconciles state for the attach/detach controller.
    46  type Reconciler interface {
    47  	// Starts running the reconciliation loop which executes periodically, checks
    48  	// if volumes that should be mounted are mounted and volumes that should
    49  	// be unmounted are unmounted. If not, it will trigger mount/unmount
    50  	// operations to rectify.
    51  	// If attach/detach management is enabled, the manager will also check if
    52  	// volumes that should be attached are attached and volumes that should
    53  	// be detached are detached and trigger attach/detach operations as needed.
    54  	Run(stopCh <-chan struct{})
    55  
    56  	// StatesHasBeenSynced returns true only after syncStates process starts to sync
    57  	// states at least once after kubelet starts
    58  	StatesHasBeenSynced() bool
    59  }
    60  
    61  // NewReconciler returns a new instance of Reconciler.
    62  //
    63  // controllerAttachDetachEnabled - if true, indicates that the attach/detach
    64  // controller is responsible for managing the attach/detach operations for
    65  // this node, and therefore the volume manager should not
    66  //
    67  // loopSleepDuration - the amount of time the reconciler loop sleeps between
    68  // successive executions
    69  //
    70  // waitForAttachTimeout - the amount of time the Mount function will wait for
    71  // the volume to be attached
    72  //
    73  // nodeName - the Name for this node, used by Attach and Detach methods
    74  //
    75  // desiredStateOfWorld - cache containing the desired state of the world
    76  //
    77  // actualStateOfWorld - cache containing the actual state of the world
    78  //
    79  // populatorHasAddedPods - checker for whether the populator has finished
    80  // adding pods to the desiredStateOfWorld cache at least once after sources
    81  // are all ready (before sources are ready, pods are probably missing)
    82  //
    83  // operationExecutor - used to trigger attach/detach/mount/unmount operations
    84  // safely (prevents more than one operation from being triggered on the same
    85  // volume)
    86  //
    87  // mounter - mounter passed in from kubelet, passed down unmount path
    88  //
    89  // hostutil - hostutil passed in from kubelet
    90  //
    91  // volumePluginMgr - volume plugin manager passed from kubelet
    92  func NewReconciler(
    93  	kubeClient clientset.Interface,
    94  	controllerAttachDetachEnabled bool,
    95  	loopSleepDuration time.Duration,
    96  	waitForAttachTimeout time.Duration,
    97  	nodeName types.NodeName,
    98  	desiredStateOfWorld cache.DesiredStateOfWorld,
    99  	actualStateOfWorld cache.ActualStateOfWorld,
   100  	populatorHasAddedPods func() bool,
   101  	operationExecutor operationexecutor.OperationExecutor,
   102  	mounter mount.Interface,
   103  	hostutil hostutil.HostUtils,
   104  	volumePluginMgr *volumepkg.VolumePluginMgr,
   105  	kubeletPodsDir string) Reconciler {
   106  	return &reconciler{
   107  		kubeClient:                      kubeClient,
   108  		controllerAttachDetachEnabled:   controllerAttachDetachEnabled,
   109  		loopSleepDuration:               loopSleepDuration,
   110  		waitForAttachTimeout:            waitForAttachTimeout,
   111  		nodeName:                        nodeName,
   112  		desiredStateOfWorld:             desiredStateOfWorld,
   113  		actualStateOfWorld:              actualStateOfWorld,
   114  		populatorHasAddedPods:           populatorHasAddedPods,
   115  		operationExecutor:               operationExecutor,
   116  		mounter:                         mounter,
   117  		hostutil:                        hostutil,
   118  		skippedDuringReconstruction:     map[v1.UniqueVolumeName]*globalVolumeInfo{},
   119  		volumePluginMgr:                 volumePluginMgr,
   120  		kubeletPodsDir:                  kubeletPodsDir,
   121  		timeOfLastSync:                  time.Time{},
   122  		volumesFailedReconstruction:     make([]podVolume, 0),
   123  		volumesNeedUpdateFromNodeStatus: make([]v1.UniqueVolumeName, 0),
   124  		volumesNeedReportedInUse:        make([]v1.UniqueVolumeName, 0),
   125  	}
   126  }
   127  
   128  type reconciler struct {
   129  	kubeClient                    clientset.Interface
   130  	controllerAttachDetachEnabled bool
   131  	loopSleepDuration             time.Duration
   132  	waitForAttachTimeout          time.Duration
   133  	nodeName                      types.NodeName
   134  	desiredStateOfWorld           cache.DesiredStateOfWorld
   135  	actualStateOfWorld            cache.ActualStateOfWorld
   136  	populatorHasAddedPods         func() bool
   137  	operationExecutor             operationexecutor.OperationExecutor
   138  	mounter                       mount.Interface
   139  	hostutil                      hostutil.HostUtils
   140  	volumePluginMgr               *volumepkg.VolumePluginMgr
   141  	skippedDuringReconstruction   map[v1.UniqueVolumeName]*globalVolumeInfo
   142  	kubeletPodsDir                string
   143  	// lock protects timeOfLastSync for updating and checking
   144  	timeOfLastSyncLock              sync.Mutex
   145  	timeOfLastSync                  time.Time
   146  	volumesFailedReconstruction     []podVolume
   147  	volumesNeedUpdateFromNodeStatus []v1.UniqueVolumeName
   148  	volumesNeedReportedInUse        []v1.UniqueVolumeName
   149  }
   150  
   151  func (rc *reconciler) Run(stopCh <-chan struct{}) {
   152  	if utilfeature.DefaultFeatureGate.Enabled(features.NewVolumeManagerReconstruction) {
   153  		rc.runNew(stopCh)
   154  		return
   155  	}
   156  
   157  	rc.runOld(stopCh)
   158  }
   159  
   160  func (rc *reconciler) unmountVolumes() {
   161  	// Ensure volumes that should be unmounted are unmounted.
   162  	for _, mountedVolume := range rc.actualStateOfWorld.GetAllMountedVolumes() {
   163  		if !rc.desiredStateOfWorld.PodExistsInVolume(mountedVolume.PodName, mountedVolume.VolumeName, mountedVolume.SELinuxMountContext) {
   164  			// Volume is mounted, unmount it
   165  			klog.V(5).InfoS(mountedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountVolume", ""))
   166  			err := rc.operationExecutor.UnmountVolume(
   167  				mountedVolume.MountedVolume, rc.actualStateOfWorld, rc.kubeletPodsDir)
   168  			if err != nil && !isExpectedError(err) {
   169  				klog.ErrorS(err, mountedVolume.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.UnmountVolume failed (controllerAttachDetachEnabled %v)", rc.controllerAttachDetachEnabled), err).Error())
   170  			}
   171  			if err == nil {
   172  				klog.InfoS(mountedVolume.GenerateMsgDetailed("operationExecutor.UnmountVolume started", ""))
   173  			}
   174  		}
   175  	}
   176  }
   177  
   178  func (rc *reconciler) mountOrAttachVolumes() {
   179  	// Ensure volumes that should be attached/mounted are attached/mounted.
   180  	for _, volumeToMount := range rc.desiredStateOfWorld.GetVolumesToMount() {
   181  		volMounted, devicePath, err := rc.actualStateOfWorld.PodExistsInVolume(volumeToMount.PodName, volumeToMount.VolumeName, volumeToMount.DesiredPersistentVolumeSize, volumeToMount.SELinuxLabel)
   182  		volumeToMount.DevicePath = devicePath
   183  		if cache.IsSELinuxMountMismatchError(err) {
   184  			// The volume is mounted, but with an unexpected SELinux context.
   185  			// It will get unmounted in unmountVolumes / unmountDetachDevices and
   186  			// then removed from actualStateOfWorld.
   187  			rc.desiredStateOfWorld.AddErrorToPod(volumeToMount.PodName, err.Error())
   188  			continue
   189  		} else if cache.IsVolumeNotAttachedError(err) {
   190  			rc.waitForVolumeAttach(volumeToMount)
   191  		} else if !volMounted || cache.IsRemountRequiredError(err) {
   192  			rc.mountAttachedVolumes(volumeToMount, err)
   193  		} else if cache.IsFSResizeRequiredError(err) {
   194  			fsResizeRequiredErr, _ := err.(cache.FsResizeRequiredError)
   195  			rc.expandVolume(volumeToMount, fsResizeRequiredErr.CurrentSize)
   196  		}
   197  	}
   198  }
   199  
   200  func (rc *reconciler) expandVolume(volumeToMount cache.VolumeToMount, currentSize resource.Quantity) {
   201  	klog.V(4).InfoS(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.ExpandInUseVolume", ""), "pod", klog.KObj(volumeToMount.Pod))
   202  	err := rc.operationExecutor.ExpandInUseVolume(volumeToMount.VolumeToMount, rc.actualStateOfWorld, currentSize)
   203  
   204  	if err != nil && !isExpectedError(err) {
   205  		klog.ErrorS(err, volumeToMount.GenerateErrorDetailed("operationExecutor.ExpandInUseVolume failed", err).Error(), "pod", klog.KObj(volumeToMount.Pod))
   206  	}
   207  
   208  	if err == nil {
   209  		klog.V(4).InfoS(volumeToMount.GenerateMsgDetailed("operationExecutor.ExpandInUseVolume started", ""), "pod", klog.KObj(volumeToMount.Pod))
   210  	}
   211  }
   212  
   213  func (rc *reconciler) mountAttachedVolumes(volumeToMount cache.VolumeToMount, podExistError error) {
   214  	// Volume is not mounted, or is already mounted, but requires remounting
   215  	remountingLogStr := ""
   216  	isRemount := cache.IsRemountRequiredError(podExistError)
   217  	if isRemount {
   218  		remountingLogStr = "Volume is already mounted to pod, but remount was requested."
   219  	}
   220  	klog.V(4).InfoS(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.MountVolume", remountingLogStr), "pod", klog.KObj(volumeToMount.Pod))
   221  	err := rc.operationExecutor.MountVolume(
   222  		rc.waitForAttachTimeout,
   223  		volumeToMount.VolumeToMount,
   224  		rc.actualStateOfWorld,
   225  		isRemount)
   226  	if err != nil && !isExpectedError(err) {
   227  		klog.ErrorS(err, volumeToMount.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.MountVolume failed (controllerAttachDetachEnabled %v)", rc.controllerAttachDetachEnabled), err).Error(), "pod", klog.KObj(volumeToMount.Pod))
   228  	}
   229  	if err == nil {
   230  		if remountingLogStr == "" {
   231  			klog.V(1).InfoS(volumeToMount.GenerateMsgDetailed("operationExecutor.MountVolume started", remountingLogStr), "pod", klog.KObj(volumeToMount.Pod))
   232  		} else {
   233  			klog.V(5).InfoS(volumeToMount.GenerateMsgDetailed("operationExecutor.MountVolume started", remountingLogStr), "pod", klog.KObj(volumeToMount.Pod))
   234  		}
   235  	}
   236  }
   237  
   238  func (rc *reconciler) waitForVolumeAttach(volumeToMount cache.VolumeToMount) {
   239  	logger := klog.TODO()
   240  	if rc.controllerAttachDetachEnabled || !volumeToMount.PluginIsAttachable {
   241  		//// lets not spin a goroutine and unnecessarily trigger exponential backoff if this happens
   242  		if volumeToMount.PluginIsAttachable && !volumeToMount.ReportedInUse {
   243  			klog.V(5).InfoS(volumeToMount.GenerateMsgDetailed("operationExecutor.VerifyControllerAttachedVolume failed", " volume not marked in-use"), "pod", klog.KObj(volumeToMount.Pod))
   244  			return
   245  		}
   246  		// Volume is not attached (or doesn't implement attacher), kubelet attach is disabled, wait
   247  		// for controller to finish attaching volume.
   248  		klog.V(5).InfoS(volumeToMount.GenerateMsgDetailed("Starting operationExecutor.VerifyControllerAttachedVolume", ""), "pod", klog.KObj(volumeToMount.Pod))
   249  		err := rc.operationExecutor.VerifyControllerAttachedVolume(
   250  			logger,
   251  			volumeToMount.VolumeToMount,
   252  			rc.nodeName,
   253  			rc.actualStateOfWorld)
   254  		if err != nil && !isExpectedError(err) {
   255  			klog.ErrorS(err, volumeToMount.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.VerifyControllerAttachedVolume failed (controllerAttachDetachEnabled %v)", rc.controllerAttachDetachEnabled), err).Error(), "pod", klog.KObj(volumeToMount.Pod))
   256  		}
   257  		if err == nil {
   258  			klog.InfoS(volumeToMount.GenerateMsgDetailed("operationExecutor.VerifyControllerAttachedVolume started", ""), "pod", klog.KObj(volumeToMount.Pod))
   259  		}
   260  	} else {
   261  		// Volume is not attached to node, kubelet attach is enabled, volume implements an attacher,
   262  		// so attach it
   263  		volumeToAttach := operationexecutor.VolumeToAttach{
   264  			VolumeName: volumeToMount.VolumeName,
   265  			VolumeSpec: volumeToMount.VolumeSpec,
   266  			NodeName:   rc.nodeName,
   267  		}
   268  		klog.V(5).InfoS(volumeToAttach.GenerateMsgDetailed("Starting operationExecutor.AttachVolume", ""), "pod", klog.KObj(volumeToMount.Pod))
   269  		err := rc.operationExecutor.AttachVolume(logger, volumeToAttach, rc.actualStateOfWorld)
   270  		if err != nil && !isExpectedError(err) {
   271  			klog.ErrorS(err, volumeToMount.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.AttachVolume failed (controllerAttachDetachEnabled %v)", rc.controllerAttachDetachEnabled), err).Error(), "pod", klog.KObj(volumeToMount.Pod))
   272  		}
   273  		if err == nil {
   274  			klog.InfoS(volumeToMount.GenerateMsgDetailed("operationExecutor.AttachVolume started", ""), "pod", klog.KObj(volumeToMount.Pod))
   275  		}
   276  	}
   277  }
   278  
   279  func (rc *reconciler) unmountDetachDevices() {
   280  	for _, attachedVolume := range rc.actualStateOfWorld.GetUnmountedVolumes() {
   281  		// Check IsOperationPending to avoid marking a volume as detached if it's in the process of mounting.
   282  		if !rc.desiredStateOfWorld.VolumeExists(attachedVolume.VolumeName, attachedVolume.SELinuxMountContext) &&
   283  			!rc.operationExecutor.IsOperationPending(attachedVolume.VolumeName, nestedpendingoperations.EmptyUniquePodName, nestedpendingoperations.EmptyNodeName) {
   284  			if attachedVolume.DeviceMayBeMounted() {
   285  				// Volume is globally mounted to device, unmount it
   286  				klog.V(5).InfoS(attachedVolume.GenerateMsgDetailed("Starting operationExecutor.UnmountDevice", ""))
   287  				err := rc.operationExecutor.UnmountDevice(
   288  					attachedVolume.AttachedVolume, rc.actualStateOfWorld, rc.hostutil)
   289  				if err != nil && !isExpectedError(err) {
   290  					klog.ErrorS(err, attachedVolume.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.UnmountDevice failed (controllerAttachDetachEnabled %v)", rc.controllerAttachDetachEnabled), err).Error())
   291  				}
   292  				if err == nil {
   293  					klog.InfoS(attachedVolume.GenerateMsgDetailed("operationExecutor.UnmountDevice started", ""))
   294  				}
   295  			} else {
   296  				// Volume is attached to node, detach it
   297  				// Kubelet not responsible for detaching or this volume has a non-attachable volume plugin.
   298  				if rc.controllerAttachDetachEnabled || !attachedVolume.PluginIsAttachable {
   299  					rc.actualStateOfWorld.MarkVolumeAsDetached(attachedVolume.VolumeName, attachedVolume.NodeName)
   300  					klog.InfoS(attachedVolume.GenerateMsgDetailed("Volume detached", fmt.Sprintf("DevicePath %q", attachedVolume.DevicePath)))
   301  				} else {
   302  					// Only detach if kubelet detach is enabled
   303  					klog.V(5).InfoS(attachedVolume.GenerateMsgDetailed("Starting operationExecutor.DetachVolume", ""))
   304  					err := rc.operationExecutor.DetachVolume(
   305  						klog.TODO(), attachedVolume.AttachedVolume, false /* verifySafeToDetach */, rc.actualStateOfWorld)
   306  					if err != nil && !isExpectedError(err) {
   307  						klog.ErrorS(err, attachedVolume.GenerateErrorDetailed(fmt.Sprintf("operationExecutor.DetachVolume failed (controllerAttachDetachEnabled %v)", rc.controllerAttachDetachEnabled), err).Error())
   308  					}
   309  					if err == nil {
   310  						klog.InfoS(attachedVolume.GenerateMsgDetailed("operationExecutor.DetachVolume started", ""))
   311  					}
   312  				}
   313  			}
   314  		}
   315  	}
   316  }
   317  
   318  // ignore nestedpendingoperations.IsAlreadyExists and exponentialbackoff.IsExponentialBackoff errors, they are expected.
   319  func isExpectedError(err error) bool {
   320  	return nestedpendingoperations.IsAlreadyExists(err) || exponentialbackoff.IsExponentialBackoff(err) || operationexecutor.IsMountFailedPreconditionError(err)
   321  }