k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/volumemanager/reconciler/reconstruct.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  	"context"
    21  
    22  	v1 "k8s.io/api/core/v1"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/types"
    25  	"k8s.io/klog/v2"
    26  	"k8s.io/kubernetes/pkg/volume/util/operationexecutor"
    27  )
    28  
    29  // readyToUnmount returns true when reconciler can start unmounting volumes.
    30  func (rc *reconciler) readyToUnmount() bool {
    31  	// During kubelet startup, all volumes present on disk are added as uncertain to ASW.
    32  	// Allow unmount only when DSW is fully populated to prevent unmounting volumes that
    33  	// did not reach DSW yet.
    34  	if !rc.populatorHasAddedPods() {
    35  		return false
    36  	}
    37  
    38  	// Allow unmount only when ASW device paths were corrected from node.status to prevent
    39  	// calling unmount with a wrong devicePath.
    40  	if len(rc.volumesNeedUpdateFromNodeStatus) != 0 {
    41  		return false
    42  	}
    43  	return true
    44  }
    45  
    46  // reconstructVolumes tries to reconstruct the actual state of world by scanning all pods' volume
    47  // directories from the disk. For the volumes that cannot support or fail reconstruction, it will
    48  // put the volumes to volumesFailedReconstruction to be cleaned up later when DesiredStateOfWorld
    49  // is populated.
    50  func (rc *reconciler) reconstructVolumes() {
    51  	// Get volumes information by reading the pod's directory
    52  	podVolumes, err := getVolumesFromPodDir(rc.kubeletPodsDir)
    53  	if err != nil {
    54  		klog.ErrorS(err, "Cannot get volumes from disk, skip sync states for volume reconstruction")
    55  		return
    56  	}
    57  	reconstructedVolumes := make(map[v1.UniqueVolumeName]*globalVolumeInfo)
    58  	reconstructedVolumeNames := []v1.UniqueVolumeName{}
    59  	for _, volume := range podVolumes {
    60  		if rc.actualStateOfWorld.VolumeExistsWithSpecName(volume.podName, volume.volumeSpecName) {
    61  			klog.V(4).InfoS("Volume exists in actual state, skip cleaning up mounts", "podName", volume.podName, "volumeSpecName", volume.volumeSpecName)
    62  			// There is nothing to reconstruct
    63  			continue
    64  		}
    65  		reconstructedVolume, err := rc.reconstructVolume(volume)
    66  		if err != nil {
    67  			klog.InfoS("Could not construct volume information", "podName", volume.podName, "volumeSpecName", volume.volumeSpecName, "err", err)
    68  			// We can't reconstruct the volume. Remember to check DSW after it's fully populated and force unmount the volume when it's orphaned.
    69  			rc.volumesFailedReconstruction = append(rc.volumesFailedReconstruction, volume)
    70  			continue
    71  		}
    72  		klog.V(4).InfoS("Adding reconstructed volume to actual state and node status", "podName", volume.podName, "volumeSpecName", volume.volumeSpecName)
    73  		gvl := &globalVolumeInfo{
    74  			volumeName:        reconstructedVolume.volumeName,
    75  			volumeSpec:        reconstructedVolume.volumeSpec,
    76  			devicePath:        reconstructedVolume.devicePath,
    77  			deviceMounter:     reconstructedVolume.deviceMounter,
    78  			blockVolumeMapper: reconstructedVolume.blockVolumeMapper,
    79  			mounter:           reconstructedVolume.mounter,
    80  		}
    81  		if cachedInfo, ok := reconstructedVolumes[reconstructedVolume.volumeName]; ok {
    82  			gvl = cachedInfo
    83  		}
    84  		gvl.addPodVolume(reconstructedVolume)
    85  
    86  		reconstructedVolumeNames = append(reconstructedVolumeNames, reconstructedVolume.volumeName)
    87  		reconstructedVolumes[reconstructedVolume.volumeName] = gvl
    88  	}
    89  
    90  	if len(reconstructedVolumes) > 0 {
    91  		// Add the volumes to ASW
    92  		rc.updateStates(reconstructedVolumes)
    93  
    94  		// The reconstructed volumes are mounted, hence a previous kubelet must have already put it into node.status.volumesInUse.
    95  		// Remember to update DSW with this information.
    96  		rc.volumesNeedReportedInUse = reconstructedVolumeNames
    97  		// Remember to update devicePath from node.status.volumesAttached
    98  		rc.volumesNeedUpdateFromNodeStatus = reconstructedVolumeNames
    99  	}
   100  	klog.V(2).InfoS("Volume reconstruction finished")
   101  }
   102  
   103  func (rc *reconciler) updateStates(reconstructedVolumes map[v1.UniqueVolumeName]*globalVolumeInfo) {
   104  	for _, gvl := range reconstructedVolumes {
   105  		err := rc.actualStateOfWorld.AddAttachUncertainReconstructedVolume(
   106  			//TODO: the devicePath might not be correct for some volume plugins: see issue #54108
   107  			gvl.volumeName, gvl.volumeSpec, rc.nodeName, gvl.devicePath)
   108  		if err != nil {
   109  			klog.ErrorS(err, "Could not add volume information to actual state of world", "volumeName", gvl.volumeName)
   110  			continue
   111  		}
   112  		var seLinuxMountContext string
   113  		for _, volume := range gvl.podVolumes {
   114  			markVolumeOpts := operationexecutor.MarkVolumeOpts{
   115  				PodName:             volume.podName,
   116  				PodUID:              types.UID(volume.podName),
   117  				VolumeName:          volume.volumeName,
   118  				Mounter:             volume.mounter,
   119  				BlockVolumeMapper:   volume.blockVolumeMapper,
   120  				OuterVolumeSpecName: volume.outerVolumeSpecName,
   121  				VolumeGidVolume:     volume.volumeGidValue,
   122  				VolumeSpec:          volume.volumeSpec,
   123  				VolumeMountState:    operationexecutor.VolumeMountUncertain,
   124  				SELinuxMountContext: volume.seLinuxMountContext,
   125  			}
   126  
   127  			_, err = rc.actualStateOfWorld.CheckAndMarkVolumeAsUncertainViaReconstruction(markVolumeOpts)
   128  			if err != nil {
   129  				klog.ErrorS(err, "Could not add pod to volume information to actual state of world", "pod", klog.KObj(volume.pod))
   130  				continue
   131  			}
   132  			seLinuxMountContext = volume.seLinuxMountContext
   133  			klog.V(2).InfoS("Volume is marked as uncertain and added into the actual state", "pod", klog.KObj(volume.pod), "podName", volume.podName, "volumeName", volume.volumeName, "seLinuxMountContext", volume.seLinuxMountContext)
   134  		}
   135  		// If the volume has device to mount, we mark its device as uncertain.
   136  		if gvl.deviceMounter != nil || gvl.blockVolumeMapper != nil {
   137  			deviceMountPath, err := getDeviceMountPath(gvl)
   138  			if err != nil {
   139  				klog.ErrorS(err, "Could not find device mount path for volume", "volumeName", gvl.volumeName)
   140  				continue
   141  			}
   142  			err = rc.actualStateOfWorld.MarkDeviceAsUncertain(gvl.volumeName, gvl.devicePath, deviceMountPath, seLinuxMountContext)
   143  			if err != nil {
   144  				klog.ErrorS(err, "Could not mark device is uncertain to actual state of world", "volumeName", gvl.volumeName, "deviceMountPath", deviceMountPath)
   145  				continue
   146  			}
   147  			klog.V(2).InfoS("Volume is marked device as uncertain and added into the actual state", "volumeName", gvl.volumeName, "deviceMountPath", deviceMountPath)
   148  		}
   149  	}
   150  }
   151  
   152  // cleanOrphanVolumes tries to clean up all volumes that failed reconstruction.
   153  func (rc *reconciler) cleanOrphanVolumes() {
   154  	if len(rc.volumesFailedReconstruction) == 0 {
   155  		return
   156  	}
   157  
   158  	for _, volume := range rc.volumesFailedReconstruction {
   159  		if rc.desiredStateOfWorld.VolumeExistsWithSpecName(volume.podName, volume.volumeSpecName) {
   160  			// Some pod needs the volume, don't clean it up and hope that
   161  			// reconcile() calls SetUp and reconstructs the volume in ASW.
   162  			klog.V(4).InfoS("Volume exists in desired state, skip cleaning up mounts", "podName", volume.podName, "volumeSpecName", volume.volumeSpecName)
   163  			continue
   164  		}
   165  		klog.InfoS("Cleaning up mounts for volume that could not be reconstructed", "podName", volume.podName, "volumeSpecName", volume.volumeSpecName)
   166  		rc.cleanupMounts(volume)
   167  	}
   168  
   169  	klog.V(2).InfoS("Orphan volume cleanup finished")
   170  	// Clean the cache, cleanup is one shot operation.
   171  	rc.volumesFailedReconstruction = make([]podVolume, 0)
   172  }
   173  
   174  // updateReconstructedFromNodeStatus tries to file devicePaths of reconstructed volumes from
   175  // node.Status.VolumesAttached. This can be done only after connection to the API
   176  // server is established, i.e. it can't be part of reconstructVolumes().
   177  func (rc *reconciler) updateReconstructedFromNodeStatus() {
   178  	klog.V(4).InfoS("Updating reconstructed devicePaths")
   179  
   180  	if rc.kubeClient == nil {
   181  		// Skip reconstructing devicePath from node objects if kubelet is in standalone mode.
   182  		// Such kubelet is not expected to mount any attachable volume or Secrets / ConfigMap.
   183  		klog.V(2).InfoS("Skipped reconstruction of DevicePaths from node.status in standalone mode")
   184  		rc.volumesNeedUpdateFromNodeStatus = nil
   185  		return
   186  	}
   187  
   188  	node, fetchErr := rc.kubeClient.CoreV1().Nodes().Get(context.TODO(), string(rc.nodeName), metav1.GetOptions{})
   189  	if fetchErr != nil {
   190  		// This may repeat few times per second until kubelet is able to read its own status for the first time.
   191  		klog.V(4).ErrorS(fetchErr, "Failed to get Node status to reconstruct device paths")
   192  		return
   193  	}
   194  
   195  	for _, volumeID := range rc.volumesNeedUpdateFromNodeStatus {
   196  		attachable := false
   197  		for _, attachedVolume := range node.Status.VolumesAttached {
   198  			if volumeID != attachedVolume.Name {
   199  				continue
   200  			}
   201  			rc.actualStateOfWorld.UpdateReconstructedDevicePath(volumeID, attachedVolume.DevicePath)
   202  			attachable = true
   203  			klog.V(4).InfoS("Updated devicePath from node status for volume", "volumeName", attachedVolume.Name, "path", attachedVolume.DevicePath)
   204  		}
   205  		rc.actualStateOfWorld.UpdateReconstructedVolumeAttachability(volumeID, attachable)
   206  	}
   207  
   208  	klog.V(2).InfoS("DevicePaths of reconstructed volumes updated")
   209  	rc.volumesNeedUpdateFromNodeStatus = nil
   210  }