This is only for the case when dockerd has had to re-mount the daemon
root as shared.
Signed-off-by: Brian Goff <cpuguy83@gmail.com>
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
|
| 11 | 11 |
"github.com/docker/docker/pkg/fileutils" |
| 12 | 12 |
"github.com/docker/docker/pkg/mount" |
| 13 |
+ "github.com/pkg/errors" |
|
| 13 | 14 |
"github.com/sirupsen/logrus" |
| 14 | 15 |
) |
| 15 | 16 |
|
| ... | ... |
@@ -67,9 +68,29 @@ func (daemon *Daemon) cleanupMountsFromReaderByID(reader io.Reader, id string, u |
| 67 | 67 |
return nil |
| 68 | 68 |
} |
| 69 | 69 |
|
| 70 |
-// cleanupMounts umounts shm/mqueue mounts for old containers |
|
| 70 |
+// cleanupMounts umounts used by container resources and the daemon root mount |
|
| 71 | 71 |
func (daemon *Daemon) cleanupMounts() error {
|
| 72 |
- return daemon.cleanupMountsByID("")
|
|
| 72 |
+ if err := daemon.cleanupMountsByID(""); err != nil {
|
|
| 73 |
+ return err |
|
| 74 |
+ } |
|
| 75 |
+ |
|
| 76 |
+ infos, err := mount.GetMounts() |
|
| 77 |
+ if err != nil {
|
|
| 78 |
+ return errors.Wrap(err, "error reading mount table for cleanup") |
|
| 79 |
+ } |
|
| 80 |
+ |
|
| 81 |
+ info := getMountInfo(infos, daemon.root) |
|
| 82 |
+ // `info.Root` here is the root mountpoint of the passed in path (`daemon.root`). |
|
| 83 |
+ // The ony cases that need to be cleaned up is when the daemon has performed a |
|
| 84 |
+ // `mount --bind /daemon/root /daemon/root && mount --make-shared /daemon/root` |
|
| 85 |
+ // This is only done when the daemon is started up and `/daemon/root` is not |
|
| 86 |
+ // already on a shared mountpoint. |
|
| 87 |
+ if !shouldUnmountRoot(daemon.root, info) {
|
|
| 88 |
+ return nil |
|
| 89 |
+ } |
|
| 90 |
+ |
|
| 91 |
+ logrus.WithField("mountpoint", daemon.root).Debug("unmounting daemon root")
|
|
| 92 |
+ return mount.Unmount(daemon.root) |
|
| 73 | 93 |
} |
| 74 | 94 |
|
| 75 | 95 |
func getCleanPatterns(id string) (regexps []*regexp.Regexp) {
|
| ... | ... |
@@ -91,3 +112,16 @@ func getCleanPatterns(id string) (regexps []*regexp.Regexp) {
|
| 91 | 91 |
func getRealPath(path string) (string, error) {
|
| 92 | 92 |
return fileutils.ReadSymlinkedDirectory(path) |
| 93 | 93 |
} |
| 94 |
+ |
|
| 95 |
+func shouldUnmountRoot(root string, info *mount.Info) bool {
|
|
| 96 |
+ if info == nil {
|
|
| 97 |
+ return false |
|
| 98 |
+ } |
|
| 99 |
+ if info.Mountpoint != root {
|
|
| 100 |
+ return false |
|
| 101 |
+ } |
|
| 102 |
+ if !strings.HasSuffix(root, info.Root) {
|
|
| 103 |
+ return false |
|
| 104 |
+ } |
|
| 105 |
+ return hasMountinfoOption(info.Optional, sharedPropagationOption) |
|
| 106 |
+} |
| ... | ... |
@@ -10,6 +10,7 @@ import ( |
| 10 | 10 |
"github.com/docker/docker/container" |
| 11 | 11 |
"github.com/docker/docker/oci" |
| 12 | 12 |
"github.com/docker/docker/pkg/idtools" |
| 13 |
+ "github.com/docker/docker/pkg/mount" |
|
| 13 | 14 |
|
| 14 | 15 |
"github.com/stretchr/testify/assert" |
| 15 | 16 |
) |
| ... | ... |
@@ -164,3 +165,66 @@ func TestValidateContainerIsolationLinux(t *testing.T) {
|
| 164 | 164 |
_, err := d.verifyContainerSettings("linux", &containertypes.HostConfig{Isolation: containertypes.IsolationHyperV}, nil, false)
|
| 165 | 165 |
assert.EqualError(t, err, "invalid isolation 'hyperv' on linux") |
| 166 | 166 |
} |
| 167 |
+ |
|
| 168 |
+func TestShouldUnmountRoot(t *testing.T) {
|
|
| 169 |
+ for _, test := range []struct {
|
|
| 170 |
+ desc string |
|
| 171 |
+ root string |
|
| 172 |
+ info *mount.Info |
|
| 173 |
+ expect bool |
|
| 174 |
+ }{
|
|
| 175 |
+ {
|
|
| 176 |
+ desc: "root is at /", |
|
| 177 |
+ root: "/docker", |
|
| 178 |
+ info: &mount.Info{Root: "/docker", Mountpoint: "/docker"},
|
|
| 179 |
+ expect: true, |
|
| 180 |
+ }, |
|
| 181 |
+ {
|
|
| 182 |
+ desc: "not a mountpoint", |
|
| 183 |
+ root: "/docker", |
|
| 184 |
+ info: nil, |
|
| 185 |
+ expect: false, |
|
| 186 |
+ }, |
|
| 187 |
+ {
|
|
| 188 |
+ desc: "root is at in a submount from `/`", |
|
| 189 |
+ root: "/foo/docker", |
|
| 190 |
+ info: &mount.Info{Root: "/docker", Mountpoint: "/foo/docker"},
|
|
| 191 |
+ expect: true, |
|
| 192 |
+ }, |
|
| 193 |
+ {
|
|
| 194 |
+ desc: "root is mounted in from a parent mount namespace same root dir", // dind is an example of this |
|
| 195 |
+ root: "/docker", |
|
| 196 |
+ info: &mount.Info{Root: "/docker/volumes/1234657/_data", Mountpoint: "/docker"},
|
|
| 197 |
+ expect: false, |
|
| 198 |
+ }, |
|
| 199 |
+ {
|
|
| 200 |
+ desc: "root is mounted in from a parent mount namespace different root dir", |
|
| 201 |
+ root: "/foo/bar", |
|
| 202 |
+ info: &mount.Info{Root: "/docker/volumes/1234657/_data", Mountpoint: "/foo/bar"},
|
|
| 203 |
+ expect: false, |
|
| 204 |
+ }, |
|
| 205 |
+ } {
|
|
| 206 |
+ t.Run(test.desc, func(t *testing.T) {
|
|
| 207 |
+ for _, options := range []struct {
|
|
| 208 |
+ desc string |
|
| 209 |
+ Optional string |
|
| 210 |
+ expect bool |
|
| 211 |
+ }{
|
|
| 212 |
+ {desc: "shared", Optional: "shared:", expect: true},
|
|
| 213 |
+ {desc: "slave", Optional: "slave:", expect: false},
|
|
| 214 |
+ {desc: "private", Optional: "private:", expect: false},
|
|
| 215 |
+ } {
|
|
| 216 |
+ t.Run(options.desc, func(t *testing.T) {
|
|
| 217 |
+ expect := options.expect |
|
| 218 |
+ if expect {
|
|
| 219 |
+ expect = test.expect |
|
| 220 |
+ } |
|
| 221 |
+ if test.info != nil {
|
|
| 222 |
+ test.info.Optional = options.Optional |
|
| 223 |
+ } |
|
| 224 |
+ assert.Equal(t, expect, shouldUnmountRoot(test.root, test.info)) |
|
| 225 |
+ }) |
|
| 226 |
+ } |
|
| 227 |
+ }) |
|
| 228 |
+ } |
|
| 229 |
+} |
| ... | ... |
@@ -24,6 +24,7 @@ import ( |
| 24 | 24 |
"github.com/opencontainers/runc/libcontainer/devices" |
| 25 | 25 |
"github.com/opencontainers/runc/libcontainer/user" |
| 26 | 26 |
specs "github.com/opencontainers/runtime-spec/specs-go" |
| 27 |
+ "github.com/pkg/errors" |
|
| 27 | 28 |
"github.com/sirupsen/logrus" |
| 28 | 29 |
"golang.org/x/sys/unix" |
| 29 | 30 |
) |
| ... | ... |
@@ -419,52 +420,46 @@ func getSourceMount(source string) (string, string, error) {
|
| 419 | 419 |
return "", "", fmt.Errorf("Could not find source mount of %s", source)
|
| 420 | 420 |
} |
| 421 | 421 |
|
| 422 |
+const ( |
|
| 423 |
+ sharedPropagationOption = "shared:" |
|
| 424 |
+ slavePropagationOption = "master:" |
|
| 425 |
+) |
|
| 426 |
+ |
|
| 427 |
+// hasMountinfoOption checks if any of the passed any of the given option values |
|
| 428 |
+// are set in the passed in option string. |
|
| 429 |
+func hasMountinfoOption(opts string, vals ...string) bool {
|
|
| 430 |
+ for _, opt := range strings.Split(opts, " ") {
|
|
| 431 |
+ for _, val := range vals {
|
|
| 432 |
+ if strings.HasPrefix(opt, val) {
|
|
| 433 |
+ return true |
|
| 434 |
+ } |
|
| 435 |
+ } |
|
| 436 |
+ } |
|
| 437 |
+ return false |
|
| 438 |
+} |
|
| 439 |
+ |
|
| 422 | 440 |
// Ensure mount point on which path is mounted, is shared. |
| 423 | 441 |
func ensureShared(path string) error {
|
| 424 |
- sharedMount := false |
|
| 425 |
- |
|
| 426 | 442 |
sourceMount, optionalOpts, err := getSourceMount(path) |
| 427 | 443 |
if err != nil {
|
| 428 | 444 |
return err |
| 429 | 445 |
} |
| 430 | 446 |
// Make sure source mount point is shared. |
| 431 |
- optsSplit := strings.Split(optionalOpts, " ") |
|
| 432 |
- for _, opt := range optsSplit {
|
|
| 433 |
- if strings.HasPrefix(opt, "shared:") {
|
|
| 434 |
- sharedMount = true |
|
| 435 |
- break |
|
| 436 |
- } |
|
| 437 |
- } |
|
| 438 |
- |
|
| 439 |
- if !sharedMount {
|
|
| 440 |
- return fmt.Errorf("path %s is mounted on %s but it is not a shared mount", path, sourceMount)
|
|
| 447 |
+ if !hasMountinfoOption(optionalOpts, sharedPropagationOption) {
|
|
| 448 |
+ return errors.Errorf("path %s is mounted on %s but it is not a shared mount", path, sourceMount)
|
|
| 441 | 449 |
} |
| 442 | 450 |
return nil |
| 443 | 451 |
} |
| 444 | 452 |
|
| 445 | 453 |
// Ensure mount point on which path is mounted, is either shared or slave. |
| 446 | 454 |
func ensureSharedOrSlave(path string) error {
|
| 447 |
- sharedMount := false |
|
| 448 |
- slaveMount := false |
|
| 449 |
- |
|
| 450 | 455 |
sourceMount, optionalOpts, err := getSourceMount(path) |
| 451 | 456 |
if err != nil {
|
| 452 | 457 |
return err |
| 453 | 458 |
} |
| 454 |
- // Make sure source mount point is shared. |
|
| 455 |
- optsSplit := strings.Split(optionalOpts, " ") |
|
| 456 |
- for _, opt := range optsSplit {
|
|
| 457 |
- if strings.HasPrefix(opt, "shared:") {
|
|
| 458 |
- sharedMount = true |
|
| 459 |
- break |
|
| 460 |
- } else if strings.HasPrefix(opt, "master:") {
|
|
| 461 |
- slaveMount = true |
|
| 462 |
- break |
|
| 463 |
- } |
|
| 464 |
- } |
|
| 465 | 459 |
|
| 466 |
- if !sharedMount && !slaveMount {
|
|
| 467 |
- return fmt.Errorf("path %s is mounted on %s but it is not a shared or slave mount", path, sourceMount)
|
|
| 460 |
+ if !hasMountinfoOption(optionalOpts, sharedPropagationOption, slavePropagationOption) {
|
|
| 461 |
+ return errors.Errorf("path %s is mounted on %s but it is not a shared or slave mount", path, sourceMount)
|
|
| 468 | 462 |
} |
| 469 | 463 |
return nil |
| 470 | 464 |
} |