Allow passing mount propagation option shared, slave, or private as volume
property.
For example.
docker run -ti -v /root/mnt-source:/root/mnt-dest:slave fedora bash
Signed-off-by: Vivek Goyal <vgoyal@redhat.com>
| ... | ... |
@@ -403,7 +403,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
|
| 403 | 403 |
Source: container.ResolvConfPath, |
| 404 | 404 |
Destination: "/etc/resolv.conf", |
| 405 | 405 |
Writable: writable, |
| 406 |
- Private: true, |
|
| 406 |
+ Propagation: volume.DefaultPropagationMode, |
|
| 407 | 407 |
}) |
| 408 | 408 |
} |
| 409 | 409 |
} |
| ... | ... |
@@ -420,7 +420,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
|
| 420 | 420 |
Source: container.HostnamePath, |
| 421 | 421 |
Destination: "/etc/hostname", |
| 422 | 422 |
Writable: writable, |
| 423 |
- Private: true, |
|
| 423 |
+ Propagation: volume.DefaultPropagationMode, |
|
| 424 | 424 |
}) |
| 425 | 425 |
} |
| 426 | 426 |
} |
| ... | ... |
@@ -437,7 +437,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
|
| 437 | 437 |
Source: container.HostsPath, |
| 438 | 438 |
Destination: "/etc/hosts", |
| 439 | 439 |
Writable: writable, |
| 440 |
- Private: true, |
|
| 440 |
+ Propagation: volume.DefaultPropagationMode, |
|
| 441 | 441 |
}) |
| 442 | 442 |
} |
| 443 | 443 |
} |
| ... | ... |
@@ -534,7 +534,7 @@ func (container *Container) IpcMounts() []execdriver.Mount {
|
| 534 | 534 |
Source: container.ShmPath, |
| 535 | 535 |
Destination: "/dev/shm", |
| 536 | 536 |
Writable: true, |
| 537 |
- Private: true, |
|
| 537 |
+ Propagation: volume.DefaultPropagationMode, |
|
| 538 | 538 |
}) |
| 539 | 539 |
} |
| 540 | 540 |
|
| ... | ... |
@@ -544,7 +544,7 @@ func (container *Container) IpcMounts() []execdriver.Mount {
|
| 544 | 544 |
Source: container.MqueuePath, |
| 545 | 545 |
Destination: "/dev/mqueue", |
| 546 | 546 |
Writable: true, |
| 547 |
- Private: true, |
|
| 547 |
+ Propagation: volume.DefaultPropagationMode, |
|
| 548 | 548 |
}) |
| 549 | 549 |
} |
| 550 | 550 |
return mounts |
| ... | ... |
@@ -26,9 +26,8 @@ type Mount struct {
|
| 26 | 26 |
Source string `json:"source"` |
| 27 | 27 |
Destination string `json:"destination"` |
| 28 | 28 |
Writable bool `json:"writable"` |
| 29 |
- Private bool `json:"private"` |
|
| 30 |
- Slave bool `json:"slave"` |
|
| 31 | 29 |
Data string `json:"data"` |
| 30 |
+ Propagation string `json:"mountpropagation"` |
|
| 32 | 31 |
} |
| 33 | 32 |
|
| 34 | 33 |
// Resources contains all resource configs for a driver. |
| ... | ... |
@@ -125,6 +124,11 @@ type Command struct {
|
| 125 | 125 |
UTS *UTS `json:"uts"` |
| 126 | 126 |
} |
| 127 | 127 |
|
| 128 |
+// SetRootPropagation sets the root mount propagation mode. |
|
| 129 |
+func SetRootPropagation(config *configs.Config, propagation int) {
|
|
| 130 |
+ config.RootPropagation = propagation |
|
| 131 |
+} |
|
| 132 |
+ |
|
| 128 | 133 |
// InitContainer is the initialization of a container config. |
| 129 | 134 |
// It returns the initial configs for a container. It's mostly |
| 130 | 135 |
// defined by the default template. |
| ... | ... |
@@ -137,7 +141,9 @@ func InitContainer(c *Command) *configs.Config {
|
| 137 | 137 |
container.Devices = c.AutoCreatedDevices |
| 138 | 138 |
container.Rootfs = c.Rootfs |
| 139 | 139 |
container.Readonlyfs = c.ReadonlyRootfs |
| 140 |
- container.RootPropagation = mount.RPRIVATE |
|
| 140 |
+ // This can be overridden later by driver during mount setup based |
|
| 141 |
+ // on volume options |
|
| 142 |
+ SetRootPropagation(container, mount.RPRIVATE) |
|
| 141 | 143 |
|
| 142 | 144 |
// check to see if we are running in ramdisk to disable pivot root |
| 143 | 145 |
container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
|
| ... | ... |
@@ -12,6 +12,7 @@ import ( |
| 12 | 12 |
derr "github.com/docker/docker/errors" |
| 13 | 13 |
"github.com/docker/docker/pkg/mount" |
| 14 | 14 |
|
| 15 |
+ "github.com/docker/docker/volume" |
|
| 15 | 16 |
"github.com/opencontainers/runc/libcontainer/apparmor" |
| 16 | 17 |
"github.com/opencontainers/runc/libcontainer/configs" |
| 17 | 18 |
"github.com/opencontainers/runc/libcontainer/devices" |
| ... | ... |
@@ -278,6 +279,20 @@ func (d *Driver) setupRlimits(container *configs.Config, c *execdriver.Command) |
| 278 | 278 |
} |
| 279 | 279 |
} |
| 280 | 280 |
|
| 281 |
+// If rootfs mount propagation is RPRIVATE, that means all the volumes are |
|
| 282 |
+// going to be private anyway. There is no need to apply per volume |
|
| 283 |
+// propagation on top. This is just an optimzation so that cost of per volume |
|
| 284 |
+// propagation is paid only if user decides to make some volume non-private |
|
| 285 |
+// which will force rootfs mount propagation to be non RPRIVATE. |
|
| 286 |
+func checkResetVolumePropagation(container *configs.Config) {
|
|
| 287 |
+ if container.RootPropagation != mount.RPRIVATE {
|
|
| 288 |
+ return |
|
| 289 |
+ } |
|
| 290 |
+ for _, m := range container.Mounts {
|
|
| 291 |
+ m.PropagationFlags = nil |
|
| 292 |
+ } |
|
| 293 |
+} |
|
| 294 |
+ |
|
| 281 | 295 |
func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) error {
|
| 282 | 296 |
userMounts := make(map[string]struct{})
|
| 283 | 297 |
for _, m := range c.Mounts {
|
| ... | ... |
@@ -298,6 +313,15 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e |
| 298 | 298 |
} |
| 299 | 299 |
container.Mounts = defaultMounts |
| 300 | 300 |
|
| 301 |
+ mountPropagationMap := map[string]int{
|
|
| 302 |
+ "private": mount.PRIVATE, |
|
| 303 |
+ "rprivate": mount.RPRIVATE, |
|
| 304 |
+ "shared": mount.SHARED, |
|
| 305 |
+ "rshared": mount.RSHARED, |
|
| 306 |
+ "slave": mount.SLAVE, |
|
| 307 |
+ "rslave": mount.RSLAVE, |
|
| 308 |
+ } |
|
| 309 |
+ |
|
| 301 | 310 |
for _, m := range c.Mounts {
|
| 302 | 311 |
for _, cm := range container.Mounts {
|
| 303 | 312 |
if cm.Destination == m.Destination {
|
| ... | ... |
@@ -319,31 +343,59 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e |
| 319 | 319 |
} |
| 320 | 320 |
} |
| 321 | 321 |
container.Mounts = append(container.Mounts, &configs.Mount{
|
| 322 |
- Source: m.Source, |
|
| 323 |
- Destination: m.Destination, |
|
| 324 |
- Data: data, |
|
| 325 |
- Device: "tmpfs", |
|
| 326 |
- Flags: flags, |
|
| 327 |
- PremountCmds: genTmpfsPremountCmd(c.TmpDir, fulldest, m.Destination), |
|
| 328 |
- PostmountCmds: genTmpfsPostmountCmd(c.TmpDir, fulldest, m.Destination), |
|
| 322 |
+ Source: m.Source, |
|
| 323 |
+ Destination: m.Destination, |
|
| 324 |
+ Data: data, |
|
| 325 |
+ Device: "tmpfs", |
|
| 326 |
+ Flags: flags, |
|
| 327 |
+ PremountCmds: genTmpfsPremountCmd(c.TmpDir, fulldest, m.Destination), |
|
| 328 |
+ PostmountCmds: genTmpfsPostmountCmd(c.TmpDir, fulldest, m.Destination), |
|
| 329 |
+ PropagationFlags: []int{mountPropagationMap[volume.DefaultPropagationMode]},
|
|
| 329 | 330 |
}) |
| 330 | 331 |
continue |
| 331 | 332 |
} |
| 332 | 333 |
flags := syscall.MS_BIND | syscall.MS_REC |
| 334 |
+ var pFlag int |
|
| 333 | 335 |
if !m.Writable {
|
| 334 | 336 |
flags |= syscall.MS_RDONLY |
| 335 | 337 |
} |
| 336 |
- if m.Slave {
|
|
| 337 |
- flags |= syscall.MS_SLAVE |
|
| 338 |
+ |
|
| 339 |
+ // Determine property of RootPropagation based on volume |
|
| 340 |
+ // properties. If a volume is shared, then keep root propagtion |
|
| 341 |
+ // shared. This should work for slave and private volumes too. |
|
| 342 |
+ // |
|
| 343 |
+ // For slave volumes, it can be either [r]shared/[r]slave. |
|
| 344 |
+ // |
|
| 345 |
+ // For private volumes any root propagation value should work. |
|
| 346 |
+ |
|
| 347 |
+ pFlag = mountPropagationMap[m.Propagation] |
|
| 348 |
+ if pFlag == mount.SHARED || pFlag == mount.RSHARED {
|
|
| 349 |
+ rootpg := container.RootPropagation |
|
| 350 |
+ if rootpg != mount.SHARED && rootpg != mount.RSHARED {
|
|
| 351 |
+ execdriver.SetRootPropagation(container, mount.SHARED) |
|
| 352 |
+ } |
|
| 353 |
+ } else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE {
|
|
| 354 |
+ rootpg := container.RootPropagation |
|
| 355 |
+ if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
|
|
| 356 |
+ execdriver.SetRootPropagation(container, mount.RSLAVE) |
|
| 357 |
+ } |
|
| 338 | 358 |
} |
| 339 | 359 |
|
| 340 |
- container.Mounts = append(container.Mounts, &configs.Mount{
|
|
| 360 |
+ mount := &configs.Mount{
|
|
| 341 | 361 |
Source: m.Source, |
| 342 | 362 |
Destination: m.Destination, |
| 343 | 363 |
Device: "bind", |
| 344 | 364 |
Flags: flags, |
| 345 |
- }) |
|
| 365 |
+ } |
|
| 366 |
+ |
|
| 367 |
+ if pFlag != 0 {
|
|
| 368 |
+ mount.PropagationFlags = []int{pFlag}
|
|
| 369 |
+ } |
|
| 370 |
+ |
|
| 371 |
+ container.Mounts = append(container.Mounts, mount) |
|
| 346 | 372 |
} |
| 373 |
+ |
|
| 374 |
+ checkResetVolumePropagation(container) |
|
| 347 | 375 |
return nil |
| 348 | 376 |
} |
| 349 | 377 |
|
| ... | ... |
@@ -24,11 +24,13 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver. |
| 24 | 24 |
return nil, err |
| 25 | 25 |
} |
| 26 | 26 |
if !container.TrySetNetworkMount(m.Destination, path) {
|
| 27 |
- mounts = append(mounts, execdriver.Mount{
|
|
| 27 |
+ mnt := execdriver.Mount{
|
|
| 28 | 28 |
Source: path, |
| 29 | 29 |
Destination: m.Destination, |
| 30 | 30 |
Writable: m.RW, |
| 31 |
- }) |
|
| 31 |
+ Propagation: m.Propagation, |
|
| 32 |
+ } |
|
| 33 |
+ mounts = append(mounts, mnt) |
|
| 32 | 34 |
} |
| 33 | 35 |
} |
| 34 | 36 |
|
| ... | ... |
@@ -79,12 +79,12 @@ Creates a new container. |
| 79 | 79 |
-u, --user="" Username or UID |
| 80 | 80 |
--ulimit=[] Ulimit options |
| 81 | 81 |
--uts="" UTS namespace to use |
| 82 |
- -v, --volume=[] Bind mount a volume with: [host-src:]container-dest[:<options>], where |
|
| 83 |
- options are comma delimited and selected from [rw|ro] and [z|Z]. |
|
| 84 |
- The 'host-src' can either be an absolute path or a name value. |
|
| 85 |
- If 'host-src' is missing, then docker creates a new volume. |
|
| 86 |
- If neither 'rw' or 'ro' is specified then the volume is mounted |
|
| 87 |
- in read-write mode. |
|
| 82 |
+ -v, --volume=[host-src:]container-dest[:<options>] |
|
| 83 |
+ Bind mount a volume. The comma-delimited |
|
| 84 |
+ `options` are [rw|ro], [z|Z], or |
|
| 85 |
+ [[r]shared|[r]slave|[r]private]. The |
|
| 86 |
+ 'host-src' is an absolute path or a name |
|
| 87 |
+ value. |
|
| 88 | 88 |
--volume-driver="" Container's volume driver |
| 89 | 89 |
--volumes-from=[] Mount volumes from the specified container(s) |
| 90 | 90 |
-w, --workdir="" Working directory inside the container |
| ... | ... |
@@ -80,12 +80,12 @@ parent = "smn_cli" |
| 80 | 80 |
-u, --user="" Username or UID (format: <name|uid>[:<group|gid>]) |
| 81 | 81 |
--ulimit=[] Ulimit options |
| 82 | 82 |
--uts="" UTS namespace to use |
| 83 |
- -v, --volume=[] Bind mount a volume with: [host-src:]container-dest[:<options>], where |
|
| 84 |
- options are comma delimited and selected from [rw|ro] and [z|Z]. |
|
| 85 |
- The 'host-src' can either be an absolute path or a name value. |
|
| 86 |
- If 'host-src' is missing, then docker creates a new volume. |
|
| 87 |
- If neither 'rw' or 'ro' is specified then the volume is mounted |
|
| 88 |
- in read-write mode. |
|
| 83 |
+ -v, --volume=[host-src:]container-dest[:<options>] |
|
| 84 |
+ Bind mount a volume. The comma-delimited |
|
| 85 |
+ `options` are [rw|ro], [z|Z], or |
|
| 86 |
+ [[r]shared|[r]slave|[r]private]. The |
|
| 87 |
+ 'host-src' is an absolute path or a name |
|
| 88 |
+ value. |
|
| 89 | 89 |
--volume-driver="" Container's volume driver |
| 90 | 90 |
--volumes-from=[] Mount volumes from the specified container(s) |
| 91 | 91 |
-w, --workdir="" Working directory inside the container |
| ... | ... |
@@ -1330,11 +1330,14 @@ Similarly the operator can set the **hostname** with `-h`. |
| 1330 | 1330 |
|
| 1331 | 1331 |
### VOLUME (shared filesystems) |
| 1332 | 1332 |
|
| 1333 |
- -v=[]: Create a bind mount with: [host-src:]container-dest[:<options>], where |
|
| 1334 |
- options are comma delimited and selected from [rw|ro] and [z|Z]. |
|
| 1335 |
- If 'host-src' is missing, then docker creates a new volume. |
|
| 1336 |
- If neither 'rw' or 'ro' is specified then the volume is mounted |
|
| 1337 |
- in read-write mode. |
|
| 1333 |
+ -v, --volume=[host-src:]container-dest[:<options>]: Bind mount a volume. |
|
| 1334 |
+ The comma-delimited `options` are [rw|ro], [z|Z], or |
|
| 1335 |
+ [[r]shared|[r]slave|[r]private]. The 'host-src' is an absolute path or a |
|
| 1336 |
+ name value. |
|
| 1337 |
+ |
|
| 1338 |
+ If neither 'rw' or 'ro' is specified then the volume is mounted in |
|
| 1339 |
+ read-write mode. |
|
| 1340 |
+ |
|
| 1338 | 1341 |
--volumes-from="": Mount all volumes from the given container(s) |
| 1339 | 1342 |
|
| 1340 | 1343 |
> **Note**: |
| ... | ... |
@@ -64,7 +64,7 @@ docker-create - Create a new container |
| 64 | 64 |
[**-u**|**--user**[=*USER*]] |
| 65 | 65 |
[**--ulimit**[=*[]*]] |
| 66 | 66 |
[**--uts**[=*[]*]] |
| 67 |
-[**-v**|**--volume**[=*[]*]] |
|
| 67 |
+[**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*]] |
|
| 68 | 68 |
[**--volume-driver**[=*DRIVER*]] |
| 69 | 69 |
[**--volumes-from**[=*[]*]] |
| 70 | 70 |
[**-w**|**--workdir**[=*WORKDIR*]] |
| ... | ... |
@@ -311,8 +311,78 @@ any options, the systems uses the following options: |
| 311 | 311 |
**host**: use the host's UTS namespace inside the container. |
| 312 | 312 |
Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. |
| 313 | 313 |
|
| 314 |
-**-v**, **--volume**=[] |
|
| 315 |
- Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container) |
|
| 314 |
+**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] |
|
| 315 |
+ Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Docker |
|
| 316 |
+ bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Docker |
|
| 317 |
+ container. If 'HOST-DIR' is omitted, Docker automatically creates the new |
|
| 318 |
+ volume on the host. The `OPTIONS` are a comma delimited list and can be: |
|
| 319 |
+ |
|
| 320 |
+ * [rw|ro] |
|
| 321 |
+ * [z|Z] |
|
| 322 |
+ * [`[r]shared`|`[r]slave`|`[r]private`] |
|
| 323 |
+ |
|
| 324 |
+The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` |
|
| 325 |
+can be an absolute path or a `name` value. A `name` value must start with an |
|
| 326 |
+alphanumeric character, followed by `a-z0-9`, `_` (underscore), `.` (period) or |
|
| 327 |
+`-` (hyphen). An absolute path starts with a `/` (forward slash). |
|
| 328 |
+ |
|
| 329 |
+If you supply a `HOST-DIR` that is an absolute path, Docker bind-mounts to the |
|
| 330 |
+path you specify. If you supply a `name`, Docker creates a named volume by that |
|
| 331 |
+`name`. For example, you can specify either `/foo` or `foo` for a `HOST-DIR` |
|
| 332 |
+value. If you supply the `/foo` value, Docker creates a bind-mount. If you |
|
| 333 |
+supply the `foo` specification, Docker creates a named volume. |
|
| 334 |
+ |
|
| 335 |
+You can specify multiple **-v** options to mount one or more mounts to a |
|
| 336 |
+container. To use these same mounts in other containers, specify the |
|
| 337 |
+**--volumes-from** option also. |
|
| 338 |
+ |
|
| 339 |
+You can add `:ro` or `:rw` suffix to a volume to mount it read-only or |
|
| 340 |
+read-write mode, respectively. By default, the volumes are mounted read-write. |
|
| 341 |
+See examples. |
|
| 342 |
+ |
|
| 343 |
+Labeling systems like SELinux require that proper labels are placed on volume |
|
| 344 |
+content mounted into a container. Without a label, the security system might |
|
| 345 |
+prevent the processes running inside the container from using the content. By |
|
| 346 |
+default, Docker does not change the labels set by the OS. |
|
| 347 |
+ |
|
| 348 |
+To change a label in the container context, you can add either of two suffixes |
|
| 349 |
+`:z` or `:Z` to the volume mount. These suffixes tell Docker to relabel file |
|
| 350 |
+objects on the shared volumes. The `z` option tells Docker that two containers |
|
| 351 |
+share the volume content. As a result, Docker labels the content with a shared |
|
| 352 |
+content label. Shared volume labels allow all containers to read/write content. |
|
| 353 |
+The `Z` option tells Docker to label the content with a private unshared label. |
|
| 354 |
+Only the current container can use a private volume. |
|
| 355 |
+ |
|
| 356 |
+By default bind mounted volumes are `private`. That means any mounts done |
|
| 357 |
+inside container will not be visible on host and vice-a-versa. One can change |
|
| 358 |
+this behavior by specifying a volume mount propagation property. Making a |
|
| 359 |
+volume `shared` mounts done under that volume inside container will be |
|
| 360 |
+visible on host and vice-a-versa. Making a volume `slave` enables only one |
|
| 361 |
+way mount propagation and that is mounts done on host under that volume |
|
| 362 |
+will be visible inside container but not the other way around. |
|
| 363 |
+ |
|
| 364 |
+To control mount propagation property of volume one can use `:[r]shared`, |
|
| 365 |
+`:[r]slave` or `:[r]private` propagation flag. Propagation property can |
|
| 366 |
+be specified only for bind mounted volumes and not for internal volumes or |
|
| 367 |
+named volumes. For mount propagation to work source mount point (mount point |
|
| 368 |
+where source dir is mounted on) has to have right propagation properties. For |
|
| 369 |
+shared volumes, source mount point has to be shared. And for slave volumes, |
|
| 370 |
+source mount has to be either shared or slave. |
|
| 371 |
+ |
|
| 372 |
+Use `df <source-dir>` to figure out the source mount and then use |
|
| 373 |
+`findmnt -o TARGET,PROPAGATION <source-mount-dir>` to figure out propagation |
|
| 374 |
+properties of source mount. If `findmnt` utility is not available, then one |
|
| 375 |
+can look at mount entry for source mount point in `/proc/self/mountinfo`. Look |
|
| 376 |
+at `optional fields` and see if any propagaion properties are specified. |
|
| 377 |
+`shared:X` means mount is `shared`, `master:X` means mount is `slave` and if |
|
| 378 |
+nothing is there that means mount is `private`. |
|
| 379 |
+ |
|
| 380 |
+To change propagation properties of a mount point use `mount` command. For |
|
| 381 |
+example, if one wants to bind mount source directory `/foo` one can do |
|
| 382 |
+`mount --bind /foo /foo` and `mount --make-private --make-shared /foo`. This |
|
| 383 |
+will convert /foo into a `shared` mount point. Alternatively one can directly |
|
| 384 |
+change propagation properties of source mount. Say `/` is source mount for |
|
| 385 |
+`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount. |
|
| 316 | 386 |
|
| 317 | 387 |
**--volume-driver**="" |
| 318 | 388 |
Container's volume driver. This driver creates volumes specified either from |
| ... | ... |
@@ -67,7 +67,7 @@ docker-run - Run a command in a new container |
| 67 | 67 |
[**-u**|**--user**[=*USER*]] |
| 68 | 68 |
[**--ulimit**[=*[]*]] |
| 69 | 69 |
[**--uts**[=*[]*]] |
| 70 |
-[**-v**|**--volume**[=*[]*]] |
|
| 70 |
+[**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*]] |
|
| 71 | 71 |
[**--volume-driver**[=*DRIVER*]] |
| 72 | 72 |
[**--volumes-from**[=*[]*]] |
| 73 | 73 |
[**-w**|**--workdir**[=*WORKDIR*]] |
| ... | ... |
@@ -476,24 +476,34 @@ any options, the systems uses the following options: |
| 476 | 476 |
**--ulimit**=[] |
| 477 | 477 |
Ulimit options |
| 478 | 478 |
|
| 479 |
-**-v**, **--volume**=[] Create a bind mount |
|
| 480 |
- (format: `[host-dir:]container-dir[:<suffix options>]`, where suffix options |
|
| 481 |
-are comma delimited and selected from [rw|ro] and [z|Z].) |
|
| 479 |
+**-v**|**--volume**[=*[[HOST-DIR:]CONTAINER-DIR[:OPTIONS]]*] |
|
| 480 |
+ Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, Docker |
|
| 481 |
+ bind mounts `/HOST-DIR` in the host to `/CONTAINER-DIR` in the Docker |
|
| 482 |
+ container. If 'HOST-DIR' is omitted, Docker automatically creates the new |
|
| 483 |
+ volume on the host. The `OPTIONS` are a comma delimited list and can be: |
|
| 482 | 484 |
|
| 483 |
- (e.g., using -v /host-dir:/container-dir, bind mounts /host-dir in the |
|
| 484 |
-host to /container-dir in the Docker container) |
|
| 485 |
+ * [rw|ro] |
|
| 486 |
+ * [z|Z] |
|
| 487 |
+ * [`[r]shared`|`[r]slave`|`[r]private`] |
|
| 485 | 488 |
|
| 486 |
- If 'host-dir' is missing, then docker automatically creates the new volume |
|
| 487 |
-on the host. **This auto-creation of the host path has been deprecated in |
|
| 488 |
-Release: v1.9.** |
|
| 489 |
+The `CONTAINER-DIR` must be an absolute path such as `/src/docs`. The `HOST-DIR` |
|
| 490 |
+can be an absolute path or a `name` value. A `name` value must start with an |
|
| 491 |
+alphanumeric character, followed by `a-z0-9`, `_` (underscore), `.` (period) or |
|
| 492 |
+`-` (hyphen). An absolute path starts with a `/` (forward slash). |
|
| 489 | 493 |
|
| 490 |
- The **-v** option can be used one or |
|
| 491 |
-more times to add one or more mounts to a container. These mounts can then be |
|
| 492 |
-used in other containers using the **--volumes-from** option. |
|
| 494 |
+If you supply a `HOST-DIR` that is an absolute path, Docker bind-mounts to the |
|
| 495 |
+path you specify. If you supply a `name`, Docker creates a named volume by that |
|
| 496 |
+`name`. For example, you can specify either `/foo` or `foo` for a `HOST-DIR` |
|
| 497 |
+value. If you supply the `/foo` value, Docker creates a bind-mount. If you |
|
| 498 |
+supply the `foo` specification, Docker creates a named volume. |
|
| 493 | 499 |
|
| 494 |
- The volume may be optionally suffixed with :ro or :rw to mount the volumes in |
|
| 495 |
-read-only or read-write mode, respectively. By default, the volumes are mounted |
|
| 496 |
-read-write. See examples. |
|
| 500 |
+You can specify multiple **-v** options to mount one or more mounts to a |
|
| 501 |
+container. To use these same mounts in other containers, specify the |
|
| 502 |
+**--volumes-from** option also. |
|
| 503 |
+ |
|
| 504 |
+You can add `:ro` or `:rw` suffix to a volume to mount it read-only or |
|
| 505 |
+read-write mode, respectively. By default, the volumes are mounted read-write. |
|
| 506 |
+See examples. |
|
| 497 | 507 |
|
| 498 | 508 |
Labeling systems like SELinux require that proper labels are placed on volume |
| 499 | 509 |
content mounted into a container. Without a label, the security system might |
| ... | ... |
@@ -508,18 +518,36 @@ content label. Shared volume labels allow all containers to read/write content. |
| 508 | 508 |
The `Z` option tells Docker to label the content with a private unshared label. |
| 509 | 509 |
Only the current container can use a private volume. |
| 510 | 510 |
|
| 511 |
-The `container-dir` must always be an absolute path such as `/src/docs`. |
|
| 512 |
-The `host-dir` can either be an absolute path or a `name` value. If you |
|
| 513 |
-supply an absolute path for the `host-dir`, Docker bind-mounts to the path |
|
| 514 |
-you specify. If you supply a `name`, Docker creates a named volume by that `name`. |
|
| 515 |
- |
|
| 516 |
-A `name` value must start with start with an alphanumeric character, |
|
| 517 |
-followed by `a-z0-9`, `_` (underscore), `.` (period) or `-` (hyphen). |
|
| 518 |
-An absolute path starts with a `/` (forward slash). |
|
| 519 |
- |
|
| 520 |
-For example, you can specify either `/foo` or `foo` for a `host-dir` value. |
|
| 521 |
-If you supply the `/foo` value, Docker creates a bind-mount. If you supply |
|
| 522 |
-the `foo` specification, Docker creates a named volume. |
|
| 511 |
+By default bind mounted volumes are `private`. That means any mounts done |
|
| 512 |
+inside container will not be visible on host and vice-a-versa. One can change |
|
| 513 |
+this behavior by specifying a volume mount propagation property. Making a |
|
| 514 |
+volume `shared` mounts done under that volume inside container will be |
|
| 515 |
+visible on host and vice-a-versa. Making a volume `slave` enables only one |
|
| 516 |
+way mount propagation and that is mounts done on host under that volume |
|
| 517 |
+will be visible inside container but not the other way around. |
|
| 518 |
+ |
|
| 519 |
+To control mount propagation property of volume one can use `:[r]shared`, |
|
| 520 |
+`:[r]slave` or `:[r]private` propagation flag. Propagation property can |
|
| 521 |
+be specified only for bind mounted volumes and not for internal volumes or |
|
| 522 |
+named volumes. For mount propagation to work source mount point (mount point |
|
| 523 |
+where source dir is mounted on) has to have right propagation properties. For |
|
| 524 |
+shared volumes, source mount point has to be shared. And for slave volumes, |
|
| 525 |
+source mount has to be either shared or slave. |
|
| 526 |
+ |
|
| 527 |
+Use `df <source-dir>` to figure out the source mount and then use |
|
| 528 |
+`findmnt -o TARGET,PROPAGATION <source-mount-dir>` to figure out propagation |
|
| 529 |
+properties of source mount. If `findmnt` utility is not available, then one |
|
| 530 |
+can look at mount entry for source mount point in `/proc/self/mountinfo`. Look |
|
| 531 |
+at `optional fields` and see if any propagaion properties are specified. |
|
| 532 |
+`shared:X` means mount is `shared`, `master:X` means mount is `slave` and if |
|
| 533 |
+nothing is there that means mount is `private`. |
|
| 534 |
+ |
|
| 535 |
+To change propagation properties of a mount point use `mount` command. For |
|
| 536 |
+example, if one wants to bind mount source directory `/foo` one can do |
|
| 537 |
+`mount --bind /foo /foo` and `mount --make-private --make-shared /foo`. This |
|
| 538 |
+will convert /foo into a `shared` mount point. Alternatively one can directly |
|
| 539 |
+change propagation properties of source mount. Say `/` is source mount for |
|
| 540 |
+`/foo`, then use `mount --make-shared /` to convert `/` into a `shared` mount. |
|
| 523 | 541 |
|
| 524 | 542 |
**--volume-driver**="" |
| 525 | 543 |
Container's volume driver. This driver creates volumes specified either from |
| ... | ... |
@@ -52,6 +52,9 @@ type MountPoint struct {
|
| 52 | 52 |
|
| 53 | 53 |
// Note Mode is not used on Windows |
| 54 | 54 |
Mode string `json:"Relabel"` // Originally field was `Relabel`" |
| 55 |
+ |
|
| 56 |
+ // Note Propagation is not used on Windows |
|
| 57 |
+ Propagation string // Mount propagation string |
|
| 55 | 58 |
} |
| 56 | 59 |
|
| 57 | 60 |
// Setup sets up a mount point by either mounting the volume if it is |
| ... | ... |
@@ -85,17 +88,6 @@ func (m *MountPoint) Path() string {
|
| 85 | 85 |
return m.Source |
| 86 | 86 |
} |
| 87 | 87 |
|
| 88 |
-// ValidMountMode will make sure the mount mode is valid. |
|
| 89 |
-// returns if it's a valid mount mode or not. |
|
| 90 |
-func ValidMountMode(mode string) bool {
|
|
| 91 |
- return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)] |
|
| 92 |
-} |
|
| 93 |
- |
|
| 94 |
-// ReadWrite tells you if a mode string is a valid read-write mode or not. |
|
| 95 |
-func ReadWrite(mode string) bool {
|
|
| 96 |
- return rwModes[strings.ToLower(mode)] |
|
| 97 |
-} |
|
| 98 |
- |
|
| 99 | 88 |
// ParseVolumesFrom ensure that the supplied volumes-from is valid. |
| 100 | 89 |
func ParseVolumesFrom(spec string) (string, string, error) {
|
| 101 | 90 |
if len(spec) == 0 {
|
| ... | ... |
@@ -111,6 +103,13 @@ func ParseVolumesFrom(spec string) (string, string, error) {
|
| 111 | 111 |
if !ValidMountMode(mode) {
|
| 112 | 112 |
return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(mode) |
| 113 | 113 |
} |
| 114 |
+ // For now don't allow propagation properties while importing |
|
| 115 |
+ // volumes from data container. These volumes will inherit |
|
| 116 |
+ // the same propagation property as of the original volume |
|
| 117 |
+ // in data container. This probably can be relaxed in future. |
|
| 118 |
+ if HasPropagation(mode) {
|
|
| 119 |
+ return "", "", derr.ErrorCodeVolumeInvalidMode.WithArgs(mode) |
|
| 120 |
+ } |
|
| 114 | 121 |
} |
| 115 | 122 |
return id, mode, nil |
| 116 | 123 |
} |
| 117 | 124 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,44 @@ |
| 0 |
+// +build linux |
|
| 1 |
+ |
|
| 2 |
+package volume |
|
| 3 |
+ |
|
| 4 |
+import ( |
|
| 5 |
+ "strings" |
|
| 6 |
+) |
|
| 7 |
+ |
|
| 8 |
+// DefaultPropagationMode defines what propagation mode should be used by |
|
| 9 |
+// default if user has not specified one explicitly. |
|
| 10 |
+const DefaultPropagationMode string = "rprivate" |
|
| 11 |
+ |
|
| 12 |
+// propagation modes |
|
| 13 |
+var propagationModes = map[string]bool{
|
|
| 14 |
+ "private": true, |
|
| 15 |
+ "rprivate": true, |
|
| 16 |
+ "slave": true, |
|
| 17 |
+ "rslave": true, |
|
| 18 |
+ "shared": true, |
|
| 19 |
+ "rshared": true, |
|
| 20 |
+} |
|
| 21 |
+ |
|
| 22 |
+// GetPropagation extracts and returns the mount propagation mode. If there |
|
| 23 |
+// are no specifications, then by default it is "private". |
|
| 24 |
+func GetPropagation(mode string) string {
|
|
| 25 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 26 |
+ if propagationModes[o] {
|
|
| 27 |
+ return o |
|
| 28 |
+ } |
|
| 29 |
+ } |
|
| 30 |
+ return DefaultPropagationMode |
|
| 31 |
+} |
|
| 32 |
+ |
|
| 33 |
+// HasPropagation checks if there is a valid propagation mode present in |
|
| 34 |
+// passed string. Returns true if a valid propagatio mode specifier is |
|
| 35 |
+// present, false otherwise. |
|
| 36 |
+func HasPropagation(mode string) bool {
|
|
| 37 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 38 |
+ if propagationModes[o] {
|
|
| 39 |
+ return true |
|
| 40 |
+ } |
|
| 41 |
+ } |
|
| 42 |
+ return false |
|
| 43 |
+} |
| 0 | 44 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,22 @@ |
| 0 |
+// +build !linux |
|
| 1 |
+ |
|
| 2 |
+package volume |
|
| 3 |
+ |
|
| 4 |
+// DefaultPropagationMode is used only in linux. In other cases it returns |
|
| 5 |
+// empty string. |
|
| 6 |
+const DefaultPropagationMode string = "" |
|
| 7 |
+ |
|
| 8 |
+// propagation modes not supported on this platform. |
|
| 9 |
+var propagationModes = map[string]bool{}
|
|
| 10 |
+ |
|
| 11 |
+// GetPropagation is not supported. Return empty string. |
|
| 12 |
+func GetPropagation(mode string) string {
|
|
| 13 |
+ return DefaultPropagationMode |
|
| 14 |
+} |
|
| 15 |
+ |
|
| 16 |
+// HasPropagation checks if there is a valid propagation mode present in |
|
| 17 |
+// passed string. Returns true if a valid propagatio mode specifier is |
|
| 18 |
+// present, false otherwise. |
|
| 19 |
+func HasPropagation(mode string) bool {
|
|
| 20 |
+ return false |
|
| 21 |
+} |
| ... | ... |
@@ -12,22 +12,14 @@ import ( |
| 12 | 12 |
|
| 13 | 13 |
// read-write modes |
| 14 | 14 |
var rwModes = map[string]bool{
|
| 15 |
- "rw": true, |
|
| 16 |
- "rw,Z": true, |
|
| 17 |
- "rw,z": true, |
|
| 18 |
- "z,rw": true, |
|
| 19 |
- "Z,rw": true, |
|
| 20 |
- "Z": true, |
|
| 21 |
- "z": true, |
|
| 15 |
+ "rw": true, |
|
| 16 |
+ "ro": true, |
|
| 22 | 17 |
} |
| 23 | 18 |
|
| 24 |
-// read-only modes |
|
| 25 |
-var roModes = map[string]bool{
|
|
| 26 |
- "ro": true, |
|
| 27 |
- "ro,Z": true, |
|
| 28 |
- "ro,z": true, |
|
| 29 |
- "z,ro": true, |
|
| 30 |
- "Z,ro": true, |
|
| 19 |
+// label modes |
|
| 20 |
+var labelModes = map[string]bool{
|
|
| 21 |
+ "Z": true, |
|
| 22 |
+ "z": true, |
|
| 31 | 23 |
} |
| 32 | 24 |
|
| 33 | 25 |
// BackwardsCompatible decides whether this mount point can be |
| ... | ... |
@@ -51,7 +43,8 @@ func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
|
| 51 | 51 |
spec = filepath.ToSlash(spec) |
| 52 | 52 |
|
| 53 | 53 |
mp := &MountPoint{
|
| 54 |
- RW: true, |
|
| 54 |
+ RW: true, |
|
| 55 |
+ Propagation: DefaultPropagationMode, |
|
| 55 | 56 |
} |
| 56 | 57 |
if strings.Count(spec, ":") > 2 {
|
| 57 | 58 |
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) |
| ... | ... |
@@ -84,6 +77,7 @@ func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
|
| 84 | 84 |
return nil, derr.ErrorCodeVolumeInvalidMode.WithArgs(mp.Mode) |
| 85 | 85 |
} |
| 86 | 86 |
mp.RW = ReadWrite(mp.Mode) |
| 87 |
+ mp.Propagation = GetPropagation(mp.Mode) |
|
| 87 | 88 |
default: |
| 88 | 89 |
return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) |
| 89 | 90 |
} |
| ... | ... |
@@ -106,6 +100,17 @@ func ParseMountSpec(spec, volumeDriver string) (*MountPoint, error) {
|
| 106 | 106 |
if len(mp.Driver) == 0 {
|
| 107 | 107 |
mp.Driver = DefaultDriverName |
| 108 | 108 |
} |
| 109 |
+ // Named volumes can't have propagation properties specified. |
|
| 110 |
+ // Their defaults will be decided by docker. This is just a |
|
| 111 |
+ // safeguard. Don't want to get into situations where named |
|
| 112 |
+ // volumes were mounted as '[r]shared' inside container and |
|
| 113 |
+ // container does further mounts under that volume and these |
|
| 114 |
+ // mounts become visible on host and later original volume |
|
| 115 |
+ // cleanup becomes an issue if container does not unmount |
|
| 116 |
+ // submounts explicitly. |
|
| 117 |
+ if HasPropagation(mp.Mode) {
|
|
| 118 |
+ return nil, derr.ErrorCodeVolumeInvalid.WithArgs(spec) |
|
| 119 |
+ } |
|
| 109 | 120 |
} else {
|
| 110 | 121 |
mp.Source = filepath.Clean(source) |
| 111 | 122 |
} |
| ... | ... |
@@ -130,3 +135,48 @@ func ParseVolumeSource(spec string) (string, string) {
|
| 130 | 130 |
func IsVolumeNameValid(name string) (bool, error) {
|
| 131 | 131 |
return true, nil |
| 132 | 132 |
} |
| 133 |
+ |
|
| 134 |
+// ValidMountMode will make sure the mount mode is valid. |
|
| 135 |
+// returns if it's a valid mount mode or not. |
|
| 136 |
+func ValidMountMode(mode string) bool {
|
|
| 137 |
+ rwModeCount := 0 |
|
| 138 |
+ labelModeCount := 0 |
|
| 139 |
+ propagationModeCount := 0 |
|
| 140 |
+ |
|
| 141 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 142 |
+ if rwModes[o] {
|
|
| 143 |
+ rwModeCount++ |
|
| 144 |
+ continue |
|
| 145 |
+ } else if labelModes[o] {
|
|
| 146 |
+ labelModeCount++ |
|
| 147 |
+ continue |
|
| 148 |
+ } else if propagationModes[o] {
|
|
| 149 |
+ propagationModeCount++ |
|
| 150 |
+ continue |
|
| 151 |
+ } |
|
| 152 |
+ return false |
|
| 153 |
+ } |
|
| 154 |
+ |
|
| 155 |
+ // Only one string for each mode is allowed. |
|
| 156 |
+ if rwModeCount > 1 || labelModeCount > 1 || propagationModeCount > 1 {
|
|
| 157 |
+ return false |
|
| 158 |
+ } |
|
| 159 |
+ return true |
|
| 160 |
+} |
|
| 161 |
+ |
|
| 162 |
+// ReadWrite tells you if a mode string is a valid read-write mode or not. |
|
| 163 |
+// If there are no specifications w.r.t read write mode, then by default |
|
| 164 |
+// it returs true. |
|
| 165 |
+func ReadWrite(mode string) bool {
|
|
| 166 |
+ if !ValidMountMode(mode) {
|
|
| 167 |
+ return false |
|
| 168 |
+ } |
|
| 169 |
+ |
|
| 170 |
+ for _, o := range strings.Split(mode, ",") {
|
|
| 171 |
+ if o == "ro" {
|
|
| 172 |
+ return false |
|
| 173 |
+ } |
|
| 174 |
+ } |
|
| 175 |
+ |
|
| 176 |
+ return true |
|
| 177 |
+} |
| ... | ... |
@@ -186,3 +186,14 @@ func IsVolumeNameValid(name string) (bool, error) {
|
| 186 | 186 |
} |
| 187 | 187 |
return true, nil |
| 188 | 188 |
} |
| 189 |
+ |
|
| 190 |
+// ValidMountMode will make sure the mount mode is valid. |
|
| 191 |
+// returns if it's a valid mount mode or not. |
|
| 192 |
+func ValidMountMode(mode string) bool {
|
|
| 193 |
+ return roModes[strings.ToLower(mode)] || rwModes[strings.ToLower(mode)] |
|
| 194 |
+} |
|
| 195 |
+ |
|
| 196 |
+// ReadWrite tells you if a mode string is a valid read-write mode or not. |
|
| 197 |
+func ReadWrite(mode string) bool {
|
|
| 198 |
+ return rwModes[strings.ToLower(mode)] |
|
| 199 |
+} |