فهرست منبع

Merge pull request #13587 from rhatdan/volume-tmpfs

Add tmpfs as a valid volume source command.
David Calavera 9 سال پیش
والد
کامیت
d4be46def4

+ 1 - 0
Dockerfile

@@ -66,6 +66,7 @@ RUN apt-get update && apt-get install -y \
 	ubuntu-zfs \
 	xfsprogs \
 	libzfs-dev \
+	tar \
 	--no-install-recommends \
 	&& ln -snf /usr/bin/clang-3.8 /usr/local/bin/clang \
 	&& ln -snf /usr/bin/clang++-3.8 /usr/local/bin/clang++

+ 2 - 1
contrib/completion/bash/docker

@@ -1394,6 +1394,7 @@ _docker_run() {
 		--restart
 		--security-opt
 		--stop-signal
+		--tmpfs
 		--ulimit
 		--user -u
 		--uts
@@ -1443,7 +1444,7 @@ _docker_run() {
 			_filedir
 			return
 			;;
-		--device|--volume|-v)
+		--device|--tmpfs|--volume|-v)
 			case "$cur" in
 				*:*)
 					# TODO somehow do _filedir for stuff inside the image, if it's already specified (which is also somewhat difficult to determine)

+ 1 - 0
contrib/completion/fish/docker.fish

@@ -339,6 +339,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l sig-proxy -d 'P
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l stop-signal -d 'Signal to kill a container'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s t -l tty -d 'Allocate a pseudo-TTY'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s u -l user -d 'Username or UID'
+complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l tmpfs -d 'Mount tmpfs on a directory'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s v -l volume -d 'Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l volumes-from -d 'Mount volumes from the specified container(s)'
 complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s w -l workdir -d 'Working directory inside the container'

+ 1 - 0
contrib/completion/zsh/_docker

@@ -491,6 +491,7 @@ __docker_subcommand() {
         "($help)*--security-opt=[Security options]:security option: "
         "($help -t --tty)"{-t,--tty}"[Allocate a pseudo-tty]"
         "($help -u --user)"{-u=,--user=}"[Username or UID]:user:_users"
+        "($help)--tmpfs[mount tmpfs] "
         "($help)*-v[Bind mount a volume]:volume: "
         "($help)--volume-driver=[Optional volume driver for the container]:volume driver:(local)"
         "($help)*--volumes-from=[Mount volumes from the specified container]:volume: "

+ 12 - 0
daemon/container_unix.go

@@ -1534,3 +1534,15 @@ func (container *Container) unmountVolumes(forceSyscall bool) error {
 
 	return nil
 }
+
+func (container *Container) tmpfsMounts() []execdriver.Mount {
+	var mounts []execdriver.Mount
+	for dest, data := range container.hostConfig.Tmpfs {
+		mounts = append(mounts, execdriver.Mount{
+			Source:      "tmpfs",
+			Destination: dest,
+			Data:        data,
+		})
+	}
+	return mounts
+}

+ 4 - 0
daemon/container_windows.go

@@ -191,6 +191,10 @@ func (container *Container) ipcMounts() []execdriver.Mount {
 	return nil
 }
 
+func (container *Container) tmpfsMounts() []execdriver.Mount {
+	return nil
+}
+
 func getDefaultRouteMtu() (int, error) {
 	return -1, errSystemNotSupported
 }

+ 1 - 0
daemon/execdriver/driver.go

@@ -137,4 +137,5 @@ type CommonCommand struct {
 	Resources     *Resources    `json:"resources"`
 	Rootfs        string        `json:"rootfs"` // root fs of the container
 	WorkingDir    string        `json:"working_dir"`
+	TmpDir        string        `json:"tmpdir"` // Directory used to store docker tmpdirs.
 }

+ 1 - 0
daemon/execdriver/driver_unix.go

@@ -28,6 +28,7 @@ type Mount struct {
 	Writable    bool   `json:"writable"`
 	Private     bool   `json:"private"`
 	Slave       bool   `json:"slave"`
+	Data        string `json:"data"`
 }
 
 // Resources contains all resource configs for a driver.

+ 33 - 0
daemon/execdriver/native/create.go

@@ -4,10 +4,13 @@ package native
 
 import (
 	"fmt"
+	"path/filepath"
 	"strings"
 	"syscall"
 
 	"github.com/docker/docker/daemon/execdriver"
+	derr "github.com/docker/docker/errors"
+	"github.com/docker/docker/pkg/mount"
 
 	"github.com/opencontainers/runc/libcontainer/apparmor"
 	"github.com/opencontainers/runc/libcontainer/configs"
@@ -288,6 +291,36 @@ func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) e
 	container.Mounts = defaultMounts
 
 	for _, m := range c.Mounts {
+		for _, cm := range container.Mounts {
+			if cm.Destination == m.Destination {
+				return derr.ErrorCodeMountDup.WithArgs(m.Destination)
+			}
+		}
+
+		if m.Source == "tmpfs" {
+			var (
+				data  = "size=65536k"
+				flags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
+				err   error
+			)
+			fulldest := filepath.Join(c.Rootfs, m.Destination)
+			if m.Data != "" {
+				flags, data, err = mount.ParseTmpfsOptions(m.Data)
+				if err != nil {
+					return err
+				}
+			}
+			container.Mounts = append(container.Mounts, &configs.Mount{
+				Source:        m.Source,
+				Destination:   m.Destination,
+				Data:          data,
+				Device:        "tmpfs",
+				Flags:         flags,
+				PremountCmds:  genTmpfsPremountCmd(c.TmpDir, fulldest, m.Destination),
+				PostmountCmds: genTmpfsPostmountCmd(c.TmpDir, fulldest, m.Destination),
+			})
+			continue
+		}
 		flags := syscall.MS_BIND | syscall.MS_REC
 		if !m.Writable {
 			flags |= syscall.MS_RDONLY

+ 8 - 0
daemon/execdriver/native/driver.go

@@ -5,6 +5,7 @@ package native
 import (
 	"fmt"
 	"io"
+	"io/ioutil"
 	"os"
 	"os/exec"
 	"path/filepath"
@@ -128,6 +129,13 @@ type execOutput struct {
 // it calls libcontainer APIs to run a container.
 func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
 	destroyed := false
+	var err error
+	c.TmpDir, err = ioutil.TempDir("", c.ID)
+	if err != nil {
+		return execdriver.ExitStatus{ExitCode: -1}, err
+	}
+	defer os.RemoveAll(c.TmpDir)
+
 	// take the Command and populate the libcontainer.Config from it
 	container, err := d.createContainer(c, hooks)
 	if err != nil {

+ 56 - 0
daemon/execdriver/native/tmpfs.go

@@ -0,0 +1,56 @@
+package native
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+	"strings"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/opencontainers/runc/libcontainer/configs"
+)
+
+func genTmpfsPremountCmd(tmpDir string, fullDest string, dest string) []configs.Command {
+	var premount []configs.Command
+	tarPath, err := exec.LookPath("tar")
+	if err != nil {
+		logrus.Warn("tar command is not available for tmpfs mount: %s", err)
+		return premount
+	}
+	if _, err = exec.LookPath("rm"); err != nil {
+		logrus.Warn("rm command is not available for tmpfs mount: %s", err)
+		return premount
+	}
+	tarFile := fmt.Sprintf("%s/%s.tar", tmpDir, strings.Replace(dest, "/", "_", -1))
+	if _, err := os.Stat(fullDest); err == nil {
+		premount = append(premount, configs.Command{
+			Path: tarPath,
+			Args: []string{"-cf", tarFile, "-C", fullDest, "."},
+		})
+	}
+	return premount
+}
+
+func genTmpfsPostmountCmd(tmpDir string, fullDest string, dest string) []configs.Command {
+	var postmount []configs.Command
+	tarPath, err := exec.LookPath("tar")
+	if err != nil {
+		return postmount
+	}
+	rmPath, err := exec.LookPath("rm")
+	if err != nil {
+		return postmount
+	}
+	if _, err := os.Stat(fullDest); os.IsNotExist(err) {
+		return postmount
+	}
+	tarFile := fmt.Sprintf("%s/%s.tar", tmpDir, strings.Replace(dest, "/", "_", -1))
+	postmount = append(postmount, configs.Command{
+		Path: tarPath,
+		Args: []string{"-xf", tarFile, "-C", fullDest, "."},
+	})
+	return append(postmount, configs.Command{
+		Path: rmPath,
+		Args: []string{"-f", tarFile},
+	})
+}

+ 1 - 0
daemon/start.go

@@ -130,6 +130,7 @@ func (daemon *Daemon) containerStart(container *Container) (err error) {
 		return err
 	}
 	mounts = append(mounts, container.ipcMounts()...)
+	mounts = append(mounts, container.tmpfsMounts()...)
 
 	container.command.Mounts = mounts
 	if err := daemon.waitForStart(container); err != nil {

+ 1 - 1
daemon/volumes.go

@@ -121,7 +121,7 @@ func (daemon *Daemon) registerMountPoints(container *Container, hostConfig *runc
 		}
 
 		if binds[bind.Destination] {
-			return derr.ErrorCodeVolumeDup.WithArgs(bind.Destination)
+			return derr.ErrorCodeMountDup.WithArgs(bind.Destination)
 		}
 
 		if len(bind.Name) > 0 && len(bind.Driver) > 0 {

+ 8 - 0
docs/reference/commandline/run.md

@@ -153,6 +153,14 @@ flag exists to allow special use-cases, like running Docker within Docker.
 The `-w` lets the command being executed inside directory given, here
 `/path/to/dir/`. If the path does not exists it is created inside the container.
 
+### mount tmpfs (--tmpfs)
+
+    $ docker run -d --tmpfs /run:rw,noexec,nosuid,size=65536k my_image
+
+    The --tmpfs flag mounts a tmpfs into the container with the rw,noexec,nosuid,size=65536k options.
+
+    Underlying content from the /run in the my_image image is copied into tmpfs.
+
 ### Mount volume (-v, --read-only)
 
     $ docker  run  -v `pwd`:`pwd` -w `pwd` -i -t  ubuntu pwd

+ 8 - 0
docs/reference/run.md

@@ -1298,6 +1298,14 @@ above, or already defined by the developer with a Dockerfile `ENV`:
 
 Similarly the operator can set the **hostname** with `-h`.
 
+### TMPFS (mount tmpfs filesystems)
+
+    --tmpfs=[]: Create a tmpfs mount with: container-dir[:<options>], where the options are identical to the Linux `mount -t tmpfs -o` command.
+
+    Underlying content from the "container-dir" is copied into tmpfs.
+
+    $ docker run -d --tmpfs /run:rw,noexec,nosuid,size=65536k my_image
+
 ### VOLUME (shared filesystems)
 
     -v=[]: Create a bind mount with: [host-src:]container-dest[:<options>], where

+ 5 - 5
errors/daemon.go

@@ -444,12 +444,12 @@ var (
 		HTTPStatusCode: http.StatusInternalServerError,
 	})
 
-	// ErrorCodeVolumeDup is generated when we try to mount two volumes
+	// ErrorCodeMountDup is generated when we try to mount two mounts points
 	// to the same path.
-	ErrorCodeVolumeDup = errcode.Register(errGroup, errcode.ErrorDescriptor{
-		Value:          "VOLUMEDUP",
-		Message:        "Duplicate bind mount '%s'",
-		Description:    "An attempt was made to mount a volume but the specified destination location is already used in a previous mount",
+	ErrorCodeMountDup = errcode.Register(errGroup, errcode.ErrorDescriptor{
+		Value:          "MOUNTDUP",
+		Message:        "Duplicate mount point '%s'",
+		Description:    "An attempt was made to mount a content but the specified destination location is already used in a previous mount",
 		HTTPStatusCode: http.StatusInternalServerError,
 	})
 

+ 1 - 1
integration-cli/docker_api_containers_test.go

@@ -212,7 +212,7 @@ func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) {
 	status, body, err := sockRequest("POST", "/containers/"+name+"/start", config)
 	c.Assert(err, checker.IsNil)
 	c.Assert(status, checker.Equals, http.StatusInternalServerError)
-	c.Assert(string(body), checker.Contains, "Duplicate bind", check.Commentf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err))
+	c.Assert(string(body), checker.Contains, "Duplicate mount point", check.Commentf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err))
 }
 
 func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) {

+ 3 - 3
integration-cli/docker_cli_run_test.go

@@ -375,10 +375,10 @@ func (s *DockerSuite) TestRunNoDupVolumes(c *check.C) {
 	mountstr2 := path2 + someplace
 
 	if out, _, err := dockerCmdWithError("run", "-v", mountstr1, "-v", mountstr2, "busybox", "true"); err == nil {
-		c.Fatal("Expected error about duplicate volume definitions")
+		c.Fatal("Expected error about duplicate mount definitions")
 	} else {
-		if !strings.Contains(out, "Duplicate bind mount") {
-			c.Fatalf("Expected 'duplicate volume' error, got %v", out)
+		if !strings.Contains(out, "Duplicate mount point") {
+			c.Fatalf("Expected 'duplicate mount point' error, got %v", out)
 		}
 	}
 }

+ 18 - 0
integration-cli/docker_cli_run_unix_test.go

@@ -438,3 +438,21 @@ func (s *DockerSuite) TestRunWithShmSize(c *check.C) {
 	c.Assert(err, check.IsNil)
 	c.Assert(shmSize, check.Equals, "1073741824")
 }
+
+func (s *DockerSuite) TestRunTmpfsMounts(c *check.C) {
+	// TODO Windows (Post TP4): This test cannot run on a Windows daemon as
+	// Windows does not support tmpfs mounts.
+	testRequires(c, DaemonIsLinux)
+	if out, _, err := dockerCmdWithError("run", "--tmpfs", "/run", "busybox", "touch", "/run/somefile"); err != nil {
+		c.Fatalf("/run directory not mounted on tmpfs %q %s", err, out)
+	}
+	if out, _, err := dockerCmdWithError("run", "--tmpfs", "/run:noexec,nosuid,rw,size=5k,mode=700", "busybox", "touch", "/run/somefile"); err != nil {
+		c.Fatalf("/run failed to mount on tmpfs with valid options %q %s", err, out)
+	}
+	if _, _, err := dockerCmdWithError("run", "--tmpfs", "/run:foobar", "busybox", "touch", "/run/somefile"); err == nil {
+		c.Fatalf("/run mounted on tmpfs when it should have vailed within invalid mount option")
+	}
+	if _, _, err := dockerCmdWithError("run", "--tmpfs", "/run", "-v", "/run:/run", "busybox", "touch", "/run/somefile"); err == nil {
+		c.Fatalf("Should have generated an error saying Duplicate mount  points")
+	}
+}

+ 15 - 0
man/docker-create.1.md

@@ -57,6 +57,7 @@ docker-create - Create a new container
 [**--stop-signal**[=*SIGNAL*]]
 [**--shm-size**[=*[]*]]
 [**-t**|**--tty**[=*false*]]
+[**--tmpfs**[=*[CONTAINER-DIR[:<OPTIONS>]*]]
 [**-u**|**--user**[=*USER*]]
 [**--ulimit**[=*[]*]]
 [**--uts**[=*[]*]]
@@ -271,6 +272,20 @@ This value should always larger than **-m**, so you should always use this with
 **-t**, **--tty**=*true*|*false*
    Allocate a pseudo-TTY. The default is *false*.
 
+**--tmpfs**=[] Create a tmpfs mount
+
+   Mount a temporary filesystem (`tmpfs`) mount into a container, for example:
+
+   $ docker run -d --tmpfs /tmp:rw,size=787448k,mode=1777 my_image
+
+   This command mounts a `tmpfs` at `/tmp` within the container. The mount copies
+the underlying content of `my_image` into `/tmp`. For example if there was a
+directory `/tmp/content` in the base image, docker will copy this directory and
+all of its content on top of the tmpfs mounted on `/tmp`.  The supported mount
+options are the same as the Linux default `mount` flags. If you do not specify
+any options, the systems uses the following options:
+`rw,noexec,nosuid,nodev,size=65536k`.
+
 **-u**, **--user**=""
    Username or UID
 

+ 28 - 0
man/docker-run.1.md

@@ -60,6 +60,7 @@ docker-run - Run a command in a new container
 [**--shm-size**[=*[]*]]
 [**--sig-proxy**[=*true*]]
 [**-t**|**--tty**[=*false*]]
+[**--tmpfs**[=*[CONTAINER-DIR[:<OPTIONS>]*]]
 [**-u**|**--user**[=*USER*]]
 [**-v**|**--volume**[=*[]*]]
 [**--ulimit**[=*[]*]]
@@ -436,6 +437,20 @@ interactive shell. The default is false.
 The **-t** option is incompatible with a redirection of the docker client
 standard input.
 
+**--tmpfs**=[] Create a tmpfs mount
+
+   Mount a temporary filesystem (`tmpfs`) mount into a container, for example:
+
+   $ docker run -d --tmpfs /tmp:rw,size=787448k,mode=1777 my_image
+
+   This command mounts a `tmpfs` at `/tmp` within the container. The mount copies
+the underlying content of `my_image` into `/tmp`. For example if there was a
+directory `/tmp/content` in the base image, docker will copy this directory and
+all of its content on top of the tmpfs mounted on `/tmp`.  The supported mount
+options are the same as the Linux default `mount` flags. If you do not specify
+any options, the systems uses the following options:
+`rw,noexec,nosuid,nodev,size=65536k`.
+
 **-u**, **--user**=""
    Sets the username or UID used and optionally the groupname or GID for the specified command.
 
@@ -552,6 +567,19 @@ the exit codes follow the `chroot` standard, see below:
 
 # EXAMPLES
 
+## Running container in read-only mode
+
+During container image development, containers often need to write to the image
+content.  Installing packages into /usr, for example.  In production,
+applications seldom need to write to the image.  Container applications write
+to volumes if they need to write to file systems at all.  Applications can be
+made more secure by running them in read-only mode using the --read-only switch.
+This protects the containers image from modification. Read only containers may
+still need to write temporary data.  The best way to handle this is to mount
+tmpfs directories on /run and /tmp.
+
+    # docker run --read-only --tmpfs /run --tmpfs /tmp -i -t fedora /bin/bash
+
 ## Exposing log messages from the container to the host's log
 
 If you want messages that are logged in your container to show up in the host's

+ 22 - 0
pkg/mount/flags.go

@@ -1,6 +1,7 @@
 package mount
 
 import (
+	"fmt"
 	"strings"
 )
 
@@ -67,3 +68,24 @@ func parseOptions(options string) (int, string) {
 	}
 	return flag, strings.Join(data, ",")
 }
+
+// ParseTmpfsOptions parse fstab type mount options into flags and data
+func ParseTmpfsOptions(options string) (int, string, error) {
+	flags, data := parseOptions(options)
+	validFlags := map[string]bool{
+		"size":      true,
+		"mode":      true,
+		"uid":       true,
+		"gid":       true,
+		"nr_inodes": true,
+		"nr_blocks": true,
+		"mpol":      true,
+	}
+	for _, o := range strings.Split(data, ",") {
+		opt := strings.SplitN(o, "=", 2)
+		if !validFlags[opt[0]] {
+			return 0, "", fmt.Errorf("Invalid tmpfs option %q", opt)
+		}
+	}
+	return flags, data, nil
+}

+ 1 - 0
runconfig/hostconfig.go

@@ -217,6 +217,7 @@ type HostConfig struct {
 	PublishAllPorts bool                  // Should docker publish all exposed port for the container
 	ReadonlyRootfs  bool                  // Is the container root filesystem in read-only
 	SecurityOpt     []string              // List of string values to customize labels for MLS systems, such as SELinux.
+	Tmpfs           map[string]string     `json:",omitempty"` // List of tmpfs (mounts) used for the container
 	UTSMode         UTSMode               // UTS namespace to use for the container
 	ShmSize         *int64                // Total shm memory usage
 

+ 17 - 0
runconfig/parse.go

@@ -7,6 +7,7 @@ import (
 
 	"github.com/docker/docker/opts"
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/nat"
 	"github.com/docker/docker/pkg/parsers"
 	"github.com/docker/docker/pkg/signal"
@@ -50,6 +51,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		// FIXME: use utils.ListOpts for attach and volumes?
 		flAttach            = opts.NewListOpts(opts.ValidateAttach)
 		flVolumes           = opts.NewListOpts(nil)
+		flTmpfs             = opts.NewListOpts(nil)
 		flBlkioWeightDevice = opts.NewWeightdeviceOpt(opts.ValidateWeightDevice)
 		flLinks             = opts.NewListOpts(opts.ValidateLink)
 		flEnv               = opts.NewListOpts(opts.ValidateEnv)
@@ -111,6 +113,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
 	cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
+	cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
 	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
 	cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container")
 	cmd.Var(&flLabels, []string{"l", "-label"}, "Set meta data on a container")
@@ -221,6 +224,19 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		}
 	}
 
+	// Can't evalute options passed into --tmpfs until we actually mount
+	tmpfs := make(map[string]string)
+	for _, t := range flTmpfs.GetAll() {
+		if arr := strings.SplitN(t, ":", 2); len(arr) > 1 {
+			if _, _, err := mount.ParseTmpfsOptions(arr[1]); err != nil {
+				return nil, nil, cmd, err
+			}
+			tmpfs[arr[0]] = arr[1]
+		} else {
+			tmpfs[arr[0]] = ""
+		}
+	}
+
 	var (
 		parsedArgs = cmd.Args()
 		runCmd     *stringutils.StrSlice
@@ -396,6 +412,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		Isolation:      IsolationLevel(*flIsolation),
 		ShmSize:        parsedShm,
 		Resources:      resources,
+		Tmpfs:          tmpfs,
 	}
 
 	// When allocating stdin in attached mode, close stdin at client disconnect