Browse Source

daemon/c8d: Unmount container fs after unclean shutdown

BaseFS is not serialized and is lost after an unclean shutdown. Unmount
method in the containerd image service implementation will not work
correctly in that case.
This patch will allow Unmount to restore the BaseFS if the target is
still mounted.

The reason it works with graphdrivers is that it doesn't directly
operate on BaseFS. It uses RWLayer, which is explicitly restored
immediately as soon as container is loaded.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Gronowski 1 year ago
parent
commit
203bac0ec4
2 changed files with 44 additions and 3 deletions
  1. 14 3
      daemon/containerd/mount.go
  2. 30 0
      daemon/snapshotter/mount.go

+ 14 - 3
daemon/containerd/mount.go

@@ -2,6 +2,7 @@ package containerd
 
 
 import (
 import (
 	"context"
 	"context"
+	"errors"
 	"fmt"
 	"fmt"
 
 
 	"github.com/containerd/log"
 	"github.com/containerd/log"
@@ -30,11 +31,21 @@ func (i *ImageService) Mount(ctx context.Context, container *container.Container
 
 
 // Unmount unmounts the container base filesystem
 // Unmount unmounts the container base filesystem
 func (i *ImageService) Unmount(ctx context.Context, container *container.Container) error {
 func (i *ImageService) Unmount(ctx context.Context, container *container.Container) error {
-	root := container.BaseFS
+	baseFS := container.BaseFS
+	if baseFS == "" {
+		target, err := i.refCountMounter.Mounted(container.ID)
+		if err != nil {
+			log.G(ctx).WithField("containerID", container.ID).Warn("failed to determine if container is already mounted")
+		}
+		if target == "" {
+			return errors.New("BaseFS is empty")
+		}
+		baseFS = target
+	}
 
 
-	if err := i.refCountMounter.Unmount(root); err != nil {
+	if err := i.refCountMounter.Unmount(baseFS); err != nil {
 		log.G(ctx).WithField("container", container.ID).WithError(err).Error("error unmounting container")
 		log.G(ctx).WithField("container", container.ID).WithError(err).Error("error unmounting container")
-		return fmt.Errorf("failed to unmount %s: %w", root, err)
+		return fmt.Errorf("failed to unmount %s: %w", baseFS, err)
 	}
 	}
 
 
 	return nil
 	return nil

+ 30 - 0
daemon/snapshotter/mount.go

@@ -10,6 +10,7 @@ import (
 	"github.com/docker/docker/daemon/graphdriver"
 	"github.com/docker/docker/daemon/graphdriver"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/moby/locker"
 	"github.com/moby/locker"
+	"github.com/moby/sys/mountinfo"
 )
 )
 
 
 // List of known filesystems that can't be re-mounted or have shared layers
 // List of known filesystems that can't be re-mounted or have shared layers
@@ -22,6 +23,8 @@ type Mounter interface {
 	Mount(mounts []mount.Mount, containerID string) (string, error)
 	Mount(mounts []mount.Mount, containerID string) (string, error)
 	// Unmount unmounts the container rootfs
 	// Unmount unmounts the container rootfs
 	Unmount(target string) error
 	Unmount(target string) error
+	// Mounted returns a target mountpoint if it's already mounted
+	Mounted(containerID string) (string, error)
 }
 }
 
 
 // inSlice tests whether a string is contained in a slice of strings or not.
 // inSlice tests whether a string is contained in a slice of strings or not.
@@ -110,6 +113,23 @@ func (m *refCountMounter) Unmount(target string) error {
 	return nil
 	return nil
 }
 }
 
 
+func (m *refCountMounter) Mounted(containerID string) (string, error) {
+	mounted, err := m.base.Mounted(containerID)
+	if err != nil || mounted == "" {
+		return mounted, err
+	}
+
+	target := m.base.target(containerID)
+
+	// Check if the refcount is non-zero.
+	m.rc.Increment(target)
+	if m.rc.Decrement(target) > 0 {
+		return mounted, nil
+	}
+
+	return "", nil
+}
+
 type mounter struct {
 type mounter struct {
 	home        string
 	home        string
 	snapshotter string
 	snapshotter string
@@ -137,6 +157,16 @@ func (m mounter) Unmount(target string) error {
 	return unmount(target)
 	return unmount(target)
 }
 }
 
 
+func (m mounter) Mounted(containerID string) (string, error) {
+	target := m.target(containerID)
+
+	mounted, err := mountinfo.Mounted(target)
+	if err != nil || !mounted {
+		return "", err
+	}
+	return target, nil
+}
+
 func (m mounter) target(containerID string) string {
 func (m mounter) target(containerID string) string {
 	return filepath.Join(m.home, "rootfs", m.snapshotter, containerID)
 	return filepath.Join(m.home, "rootfs", m.snapshotter, containerID)
 }
 }