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 }