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 }