Commit d7f1484e authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #33616 from jingxu97/statusupdater-9-22

Automatic merge from submit-queue Fix volume states out of sync problem after kubelet restarts When kubelet restarts, all the information about the volumes will be gone from actual/desired states. When update node status with mounted volumes, the volume list might be empty although there are still volumes are mounted and in turn causing master to detach those volumes since they are not in the mounted volumes list. This fix is to make sure only update mounted volumes list after reconciler starts sync states process. This sync state process will scan the existing volume directories and reconstruct actual states if they are missing. This PR also fixes the problem during orphaned pods' directories. In case of the pod directory is unmounted but has not yet deleted (e.g., interrupted with kubelet restarts), clean up routine will delete the directory so that the pod directoriy could be cleaned up (it is safe to delete directory since it is no longer mounted) The third issue this PR fixes is that during reconstruct volume in actual state, mounter could not be nil since it is required for creating container.VolumeMap. If it is nil, it might cause nil pointer exception in kubelet. Detailed design proposal is #33203
parents 67d94799 b0248170
...@@ -239,9 +239,9 @@ func (kl *Kubelet) GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64 { ...@@ -239,9 +239,9 @@ func (kl *Kubelet) GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64 {
return kl.volumeManager.GetExtraSupplementalGroupsForPod(pod) return kl.volumeManager.GetExtraSupplementalGroupsForPod(pod)
} }
// getPodVolumeNameListFromDisk returns a list of the volume names by reading the // getPodVolumePathListFromDisk returns a list of the volume paths by reading the
// volume directories for the given pod from the disk. // volume directories for the given pod from the disk.
func (kl *Kubelet) getPodVolumeNameListFromDisk(podUID types.UID) ([]string, error) { func (kl *Kubelet) getPodVolumePathListFromDisk(podUID types.UID) ([]string, error) {
volumes := []string{} volumes := []string{}
podVolDir := kl.getPodVolumesDir(podUID) podVolDir := kl.getPodVolumesDir(podUID)
volumePluginDirs, err := ioutil.ReadDir(podVolDir) volumePluginDirs, err := ioutil.ReadDir(podVolDir)
...@@ -254,9 +254,11 @@ func (kl *Kubelet) getPodVolumeNameListFromDisk(podUID types.UID) ([]string, err ...@@ -254,9 +254,11 @@ func (kl *Kubelet) getPodVolumeNameListFromDisk(podUID types.UID) ([]string, err
volumePluginPath := path.Join(podVolDir, volumePluginName) volumePluginPath := path.Join(podVolDir, volumePluginName)
volumeDirs, err := util.ReadDirNoStat(volumePluginPath) volumeDirs, err := util.ReadDirNoStat(volumePluginPath)
if err != nil { if err != nil {
return volumes, err return volumes, fmt.Errorf("Could not read directory %s: %v", volumePluginPath, err)
}
for _, volumeDir := range volumeDirs {
volumes = append(volumes, path.Join(volumePluginPath, volumeDir))
} }
volumes = append(volumes, volumeDirs...)
} }
return volumes, nil return volumes, nil
} }
...@@ -342,6 +342,8 @@ func (kl *Kubelet) tryUpdateNodeStatus() error { ...@@ -342,6 +342,8 @@ func (kl *Kubelet) tryUpdateNodeStatus() error {
} }
// Update the current status on the API server // Update the current status on the API server
updatedNode, err := kl.kubeClient.Core().Nodes().UpdateStatus(node) updatedNode, err := kl.kubeClient.Core().Nodes().UpdateStatus(node)
// If update finishes sucessfully, mark the volumeInUse as reportedInUse to indicate
// those volumes are already updated in the node's status
if err == nil { if err == nil {
kl.volumeManager.MarkVolumesAsReportedInUse( kl.volumeManager.MarkVolumesAsReportedInUse(
updatedNode.Status.VolumesInUse) updatedNode.Status.VolumesInUse)
...@@ -882,9 +884,13 @@ func (kl *Kubelet) recordNodeSchedulableEvent(node *api.Node) { ...@@ -882,9 +884,13 @@ func (kl *Kubelet) recordNodeSchedulableEvent(node *api.Node) {
} }
} }
// Update VolumesInUse field in Node Status // Update VolumesInUse field in Node Status only after states are synced up at least once
// in volume reconciler.
func (kl *Kubelet) setNodeVolumesInUseStatus(node *api.Node) { func (kl *Kubelet) setNodeVolumesInUseStatus(node *api.Node) {
node.Status.VolumesInUse = kl.volumeManager.GetVolumesInUse() // Make sure to only update node status after reconciler starts syncing up states
if kl.volumeManager.ReconcilerStatesHasBeenSynced() {
node.Status.VolumesInUse = kl.volumeManager.GetVolumesInUse()
}
} }
// setNodeStatus fills in the Status fields of the given Node, overwriting // setNodeStatus fills in the Status fields of the given Node, overwriting
......
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"k8s.io/kubernetes/pkg/securitycontext" "k8s.io/kubernetes/pkg/securitycontext"
"k8s.io/kubernetes/pkg/types" "k8s.io/kubernetes/pkg/types"
utilerrors "k8s.io/kubernetes/pkg/util/errors" utilerrors "k8s.io/kubernetes/pkg/util/errors"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/util/selinux" "k8s.io/kubernetes/pkg/util/selinux"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
...@@ -153,8 +154,20 @@ func (kl *Kubelet) cleanupOrphanedPodDirs( ...@@ -153,8 +154,20 @@ func (kl *Kubelet) cleanupOrphanedPodDirs(
continue continue
} }
// Check whether volume is still mounted on disk. If so, do not delete directory // Check whether volume is still mounted on disk. If so, do not delete directory
if volumeNames, err := kl.getPodVolumeNameListFromDisk(uid); err != nil || len(volumeNames) != 0 { volumePaths, err := kl.getPodVolumePathListFromDisk(uid)
glog.V(3).Infof("Orphaned pod %q found, but volumes are still mounted; err: %v, volumes: %v ", uid, err, volumeNames) if err != nil {
glog.Errorf("Orphaned pod %q found, but error %v occured during reading volume dir from disk", uid, err)
continue
} else if len(volumePaths) > 0 {
for _, path := range volumePaths {
notMount, err := mount.IsNotMountPoint(path)
if err == nil && notMount {
glog.V(2).Infof("Volume path %q is no longer mounted, remove it", path)
os.Remove(path)
} else {
glog.Errorf("Orphaned pod %q found, but it might still mounted with error %v", uid, err)
}
}
continue continue
} }
......
...@@ -186,6 +186,7 @@ func IsRemountRequiredError(err error) bool { ...@@ -186,6 +186,7 @@ func IsRemountRequiredError(err error) bool {
type actualStateOfWorld struct { type actualStateOfWorld struct {
// nodeName is the name of this node. This value is passed to Attach/Detach // nodeName is the name of this node. This value is passed to Attach/Detach
nodeName types.NodeName nodeName types.NodeName
// attachedVolumes is a map containing the set of volumes the kubelet volume // attachedVolumes is a map containing the set of volumes the kubelet volume
// manager believes to be successfully attached to this node. Volume types // manager believes to be successfully attached to this node. Volume types
// that do not implement an attacher interface are assumed to be in this // that do not implement an attacher interface are assumed to be in this
...@@ -193,6 +194,7 @@ type actualStateOfWorld struct { ...@@ -193,6 +194,7 @@ type actualStateOfWorld struct {
// The key in this map is the name of the volume and the value is an object // The key in this map is the name of the volume and the value is an object
// containing more information about the attached volume. // containing more information about the attached volume.
attachedVolumes map[api.UniqueVolumeName]attachedVolume attachedVolumes map[api.UniqueVolumeName]attachedVolume
// volumePluginMgr is the volume plugin manager used to create volume // volumePluginMgr is the volume plugin manager used to create volume
// plugin objects. // plugin objects.
volumePluginMgr *volume.VolumePluginMgr volumePluginMgr *volume.VolumePluginMgr
......
...@@ -58,7 +58,8 @@ type DesiredStateOfWorld interface { ...@@ -58,7 +58,8 @@ type DesiredStateOfWorld interface {
// ReportedInUse value is reset to false. The default ReportedInUse value // ReportedInUse value is reset to false. The default ReportedInUse value
// for a newly created volume is false. // for a newly created volume is false.
// When set to true this value indicates that the volume was successfully // When set to true this value indicates that the volume was successfully
// added to the VolumesInUse field in the node's status. // added to the VolumesInUse field in the node's status. Mount operation needs
// to check this value before issuing the operation.
// If a volume in the reportedVolumes list does not exist in the list of // If a volume in the reportedVolumes list does not exist in the list of
// volumes that should be attached to this node, it is skipped without error. // volumes that should be attached to this node, it is skipped without error.
MarkVolumesReportedInUse(reportedVolumes []api.UniqueVolumeName) MarkVolumesReportedInUse(reportedVolumes []api.UniqueVolumeName)
......
...@@ -42,8 +42,8 @@ import ( ...@@ -42,8 +42,8 @@ import (
const ( const (
// reconcilerLoopSleepDuration is the amount of time the reconciler loop // reconcilerLoopSleepDuration is the amount of time the reconciler loop
// waits between successive executions // waits between successive executions
reconcilerLoopSleepDuration time.Duration = 0 * time.Millisecond reconcilerLoopSleepDuration time.Duration = 0 * time.Millisecond
reconcilerReconstructSleepPeriod time.Duration = 10 * time.Minute reconcilerSyncStatesSleepPeriod time.Duration = 10 * time.Minute
// waitForAttachTimeout is the maximum amount of time a // waitForAttachTimeout is the maximum amount of time a
// operationexecutor.Mount call will wait for a volume to be attached. // operationexecutor.Mount call will wait for a volume to be attached.
waitForAttachTimeout time.Duration = 1 * time.Second waitForAttachTimeout time.Duration = 1 * time.Second
...@@ -65,7 +65,7 @@ func Test_Run_Positive_DoNothing(t *testing.T) { ...@@ -65,7 +65,7 @@ func Test_Run_Positive_DoNothing(t *testing.T) {
kubeClient, kubeClient,
false, /* controllerAttachDetachEnabled */ false, /* controllerAttachDetachEnabled */
reconcilerLoopSleepDuration, reconcilerLoopSleepDuration,
reconcilerReconstructSleepPeriod, reconcilerSyncStatesSleepPeriod,
waitForAttachTimeout, waitForAttachTimeout,
nodeName, nodeName,
dsw, dsw,
...@@ -102,7 +102,7 @@ func Test_Run_Positive_VolumeAttachAndMount(t *testing.T) { ...@@ -102,7 +102,7 @@ func Test_Run_Positive_VolumeAttachAndMount(t *testing.T) {
kubeClient, kubeClient,
false, /* controllerAttachDetachEnabled */ false, /* controllerAttachDetachEnabled */
reconcilerLoopSleepDuration, reconcilerLoopSleepDuration,
reconcilerReconstructSleepPeriod, reconcilerSyncStatesSleepPeriod,
waitForAttachTimeout, waitForAttachTimeout,
nodeName, nodeName,
dsw, dsw,
...@@ -173,7 +173,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabled(t *testing.T) { ...@@ -173,7 +173,7 @@ func Test_Run_Positive_VolumeMountControllerAttachEnabled(t *testing.T) {
kubeClient, kubeClient,
true, /* controllerAttachDetachEnabled */ true, /* controllerAttachDetachEnabled */
reconcilerLoopSleepDuration, reconcilerLoopSleepDuration,
reconcilerReconstructSleepPeriod, reconcilerSyncStatesSleepPeriod,
waitForAttachTimeout, waitForAttachTimeout,
nodeName, nodeName,
dsw, dsw,
...@@ -245,7 +245,7 @@ func Test_Run_Positive_VolumeAttachMountUnmountDetach(t *testing.T) { ...@@ -245,7 +245,7 @@ func Test_Run_Positive_VolumeAttachMountUnmountDetach(t *testing.T) {
kubeClient, kubeClient,
false, /* controllerAttachDetachEnabled */ false, /* controllerAttachDetachEnabled */
reconcilerLoopSleepDuration, reconcilerLoopSleepDuration,
reconcilerReconstructSleepPeriod, reconcilerSyncStatesSleepPeriod,
waitForAttachTimeout, waitForAttachTimeout,
nodeName, nodeName,
dsw, dsw,
...@@ -328,7 +328,7 @@ func Test_Run_Positive_VolumeUnmountControllerAttachEnabled(t *testing.T) { ...@@ -328,7 +328,7 @@ func Test_Run_Positive_VolumeUnmountControllerAttachEnabled(t *testing.T) {
kubeClient, kubeClient,
true, /* controllerAttachDetachEnabled */ true, /* controllerAttachDetachEnabled */
reconcilerLoopSleepDuration, reconcilerLoopSleepDuration,
reconcilerReconstructSleepPeriod, reconcilerSyncStatesSleepPeriod,
waitForAttachTimeout, waitForAttachTimeout,
nodeName, nodeName,
dsw, dsw,
......
...@@ -49,9 +49,9 @@ const ( ...@@ -49,9 +49,9 @@ const (
// between successive executions // between successive executions
reconcilerLoopSleepPeriod time.Duration = 100 * time.Millisecond reconcilerLoopSleepPeriod time.Duration = 100 * time.Millisecond
// reconcilerReconstructSleepPeriod is the amount of time the reconciler reconstruct process // reconcilerSyncStatesSleepPeriod is the amount of time the reconciler reconstruct process
// waits between successive executions // waits between successive executions
reconcilerReconstructSleepPeriod time.Duration = 3 * time.Minute reconcilerSyncStatesSleepPeriod time.Duration = 3 * time.Minute
// desiredStateOfWorldPopulatorLoopSleepPeriod is the amount of time the // desiredStateOfWorldPopulatorLoopSleepPeriod is the amount of time the
// DesiredStateOfWorldPopulator loop waits between successive executions // DesiredStateOfWorldPopulator loop waits between successive executions
...@@ -115,7 +115,7 @@ type VolumeManager interface { ...@@ -115,7 +115,7 @@ type VolumeManager interface {
// from annotations on persistent volumes that the pod depends on. // from annotations on persistent volumes that the pod depends on.
GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64 GetExtraSupplementalGroupsForPod(pod *api.Pod) []int64
// Returns a list of all volumes that implement the volume.Attacher // GetVolumesInUse returns a list of all volumes that implement the volume.Attacher
// interface and are currently in use according to the actual and desired // interface and are currently in use according to the actual and desired
// state of the world caches. A volume is considered "in use" as soon as it // state of the world caches. A volume is considered "in use" as soon as it
// is added to the desired state of world, indicating it *should* be // is added to the desired state of world, indicating it *should* be
...@@ -126,6 +126,11 @@ type VolumeManager interface { ...@@ -126,6 +126,11 @@ type VolumeManager interface {
// restarts. // restarts.
GetVolumesInUse() []api.UniqueVolumeName GetVolumesInUse() []api.UniqueVolumeName
// ReconcilerStatesHasBeenSynced returns true only after the actual states in reconciler
// has been synced at least once after kubelet starts so that it is safe to update mounted
// volume list retrieved from actual state.
ReconcilerStatesHasBeenSynced() bool
// VolumeIsAttached returns true if the given volume is attached to this // VolumeIsAttached returns true if the given volume is attached to this
// node. // node.
VolumeIsAttached(volumeName api.UniqueVolumeName) bool VolumeIsAttached(volumeName api.UniqueVolumeName) bool
...@@ -168,7 +173,7 @@ func NewVolumeManager( ...@@ -168,7 +173,7 @@ func NewVolumeManager(
kubeClient, kubeClient,
controllerAttachDetachEnabled, controllerAttachDetachEnabled,
reconcilerLoopSleepPeriod, reconcilerLoopSleepPeriod,
reconcilerReconstructSleepPeriod, reconcilerSyncStatesSleepPeriod,
waitForAttachTimeout, waitForAttachTimeout,
nodeName, nodeName,
vm.desiredStateOfWorld, vm.desiredStateOfWorld,
...@@ -305,6 +310,10 @@ func (vm *volumeManager) GetVolumesInUse() []api.UniqueVolumeName { ...@@ -305,6 +310,10 @@ func (vm *volumeManager) GetVolumesInUse() []api.UniqueVolumeName {
return volumesToReportInUse return volumesToReportInUse
} }
func (vm *volumeManager) ReconcilerStatesHasBeenSynced() bool {
return vm.reconciler.StatesHasBeenSynced()
}
func (vm *volumeManager) VolumeIsAttached( func (vm *volumeManager) VolumeIsAttached(
volumeName api.UniqueVolumeName) bool { volumeName api.UniqueVolumeName) bool {
return vm.actualStateOfWorld.VolumeExists(volumeName) return vm.actualStateOfWorld.VolumeExists(volumeName)
......
...@@ -101,9 +101,10 @@ func isBind(options []string) (bool, []string) { ...@@ -101,9 +101,10 @@ func isBind(options []string) (bool, []string) {
// doMount runs the mount command. // doMount runs the mount command.
func doMount(mountCmd string, source string, target string, fstype string, options []string) error { func doMount(mountCmd string, source string, target string, fstype string, options []string) error {
glog.V(5).Infof("Mounting %s %s %s %v with command: %q", source, target, fstype, options, mountCmd) glog.V(4).Infof("Mounting %s %s %s %v with command: %q", source, target, fstype, options, mountCmd)
mountArgs := makeMountArgs(source, target, fstype, options) mountArgs := makeMountArgs(source, target, fstype, options)
glog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgs)
command := exec.Command(mountCmd, mountArgs...) command := exec.Command(mountCmd, mountArgs...)
output, err := command.CombinedOutput() output, err := command.CombinedOutput()
if err != nil { if err != nil {
...@@ -135,7 +136,7 @@ func makeMountArgs(source, target, fstype string, options []string) []string { ...@@ -135,7 +136,7 @@ func makeMountArgs(source, target, fstype string, options []string) []string {
// Unmount unmounts the target. // Unmount unmounts the target.
func (mounter *Mounter) Unmount(target string) error { func (mounter *Mounter) Unmount(target string) error {
glog.V(5).Infof("Unmounting %s", target) glog.V(4).Infof("Unmounting %s", target)
command := exec.Command("umount", target) command := exec.Command("umount", target)
output, err := command.CombinedOutput() output, err := command.CombinedOutput()
if err != nil { if err != nil {
...@@ -156,6 +157,10 @@ func (*Mounter) List() ([]MountPoint, error) { ...@@ -156,6 +157,10 @@ func (*Mounter) List() ([]MountPoint, error) {
// will return true. When in fact /tmp/b is a mount point. If this situation // will return true. When in fact /tmp/b is a mount point. If this situation
// if of interest to you, don't use this function... // if of interest to you, don't use this function...
func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
return IsNotMountPoint(file)
}
func IsNotMountPoint(file string) (bool, error) {
stat, err := os.Stat(file) stat, err := os.Stat(file)
if err != nil { if err != nil {
return true, err return true, err
...@@ -173,9 +178,10 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) { ...@@ -173,9 +178,10 @@ func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
} }
// DeviceOpened checks if block device in use by calling Open with O_EXCL flag. // DeviceOpened checks if block device in use by calling Open with O_EXCL flag.
// Returns true if open returns errno EBUSY, and false if errno is nil. // If pathname is not a device, log and return false with nil error.
// Returns an error if errno is any error other than EBUSY. // If open returns errno EBUSY, return true with nil error.
// Returns with error if pathname is not a device. // If open returns nil, return false with nil error.
// Otherwise, return false with error
func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) { func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
return exclusiveOpenFailsOnDevice(pathname) return exclusiveOpenFailsOnDevice(pathname)
} }
...@@ -187,12 +193,17 @@ func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) { ...@@ -187,12 +193,17 @@ func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
} }
func exclusiveOpenFailsOnDevice(pathname string) (bool, error) { func exclusiveOpenFailsOnDevice(pathname string) (bool, error) {
if isDevice, err := pathIsDevice(pathname); !isDevice { isDevice, err := pathIsDevice(pathname)
if err != nil {
return false, fmt.Errorf( return false, fmt.Errorf(
"PathIsDevice failed for path %q: %v", "PathIsDevice failed for path %q: %v",
pathname, pathname,
err) err)
} }
if !isDevice {
glog.Errorf("Path %q is not refering to a device.", pathname)
return false, nil
}
fd, errno := syscall.Open(pathname, syscall.O_RDONLY|syscall.O_EXCL, 0) fd, errno := syscall.Open(pathname, syscall.O_RDONLY|syscall.O_EXCL, 0)
// If the device is in use, open will return an invalid fd. // If the device is in use, open will return an invalid fd.
// When this happens, it is expected that Close will fail and throw an error. // When this happens, it is expected that Close will fail and throw an error.
......
...@@ -247,7 +247,7 @@ func (b *gcePersistentDiskMounter) SetUp(fsGroup *int64) error { ...@@ -247,7 +247,7 @@ func (b *gcePersistentDiskMounter) SetUp(fsGroup *int64) error {
func (b *gcePersistentDiskMounter) SetUpAt(dir string, fsGroup *int64) error { func (b *gcePersistentDiskMounter) SetUpAt(dir string, fsGroup *int64) error {
// TODO: handle failed mounts here. // TODO: handle failed mounts here.
notMnt, err := b.mounter.IsLikelyNotMountPoint(dir) notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
glog.V(4).Infof("PersistentDisk set up: %s %v %v, pd name %v readOnly %v", dir, !notMnt, err, b.pdName, b.readOnly) glog.V(4).Infof("GCE PersistentDisk set up: Dir (%s) PD name (%q) Mounted (%t) Error (%v), ReadOnly (%t)", dir, b.pdName, !notMnt, err, b.readOnly)
if err != nil && !os.IsNotExist(err) { if err != nil && !os.IsNotExist(err) {
glog.Errorf("cannot validate mount point: %s %v", dir, err) glog.Errorf("cannot validate mount point: %s %v", dir, err)
return err return err
......
...@@ -758,11 +758,12 @@ func (oe *operationExecutor) generateMountVolumeFunc( ...@@ -758,11 +758,12 @@ func (oe *operationExecutor) generateMountVolumeFunc(
} }
glog.Infof( glog.Infof(
"MountVolume.MountDevice succeeded for volume %q (spec.Name: %q) pod %q (UID: %q).", "MountVolume.MountDevice succeeded for volume %q (spec.Name: %q) pod %q (UID: %q) device mount path %q",
volumeToMount.VolumeName, volumeToMount.VolumeName,
volumeToMount.VolumeSpec.Name(), volumeToMount.VolumeSpec.Name(),
volumeToMount.PodName, volumeToMount.PodName,
volumeToMount.Pod.UID) volumeToMount.Pod.UID,
deviceMountPath)
// Update actual state of world to reflect volume is globally mounted // Update actual state of world to reflect volume is globally mounted
markDeviceMountedErr := actualStateOfWorld.MarkDeviceAsMounted( markDeviceMountedErr := actualStateOfWorld.MarkDeviceAsMounted(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment