1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #3271 from crosbymichael/mount-outside

Perform docker specific mounts outside of lxc
This commit is contained in:
Victor Vieux 2013-12-19 11:13:31 -08:00
commit f21bd80e90
13 changed files with 350 additions and 54 deletions

View file

@ -7,6 +7,7 @@ import (
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
"github.com/dotcloud/docker/mount"
"github.com/dotcloud/docker/term"
"github.com/dotcloud/docker/utils"
"github.com/kr/pty"
@ -48,7 +49,6 @@ type Container struct {
network *NetworkInterface
NetworkSettings *NetworkSettings
SysInitPath string
ResolvConfPath string
HostnamePath string
HostsPath string
@ -297,7 +297,11 @@ func (container *Container) generateEnvConfig(env []string) error {
if err != nil {
return err
}
ioutil.WriteFile(container.EnvConfigPath(), data, 0600)
p, err := container.EnvConfigPath()
if err != nil {
return err
}
ioutil.WriteFile(p, data, 0600)
return nil
}
@ -681,6 +685,45 @@ func (container *Container) Start() (err error) {
}
}
root := container.RootfsPath()
envPath, err := container.EnvConfigPath()
if err != nil {
return err
}
// Mount docker specific files into the containers root fs
if err := mount.Mount(runtime.sysInitPath, path.Join(root, "/.dockerinit"), "none", "bind,ro"); err != nil {
return err
}
if err := mount.Mount(envPath, path.Join(root, "/.dockerenv"), "none", "bind,ro"); err != nil {
return err
}
if err := mount.Mount(container.ResolvConfPath, path.Join(root, "/etc/resolv.conf"), "none", "bind,ro"); err != nil {
return err
}
if container.HostnamePath != "" && container.HostsPath != "" {
if err := mount.Mount(container.HostnamePath, path.Join(root, "/etc/hostname"), "none", "bind,ro"); err != nil {
return err
}
if err := mount.Mount(container.HostsPath, path.Join(root, "/etc/hosts"), "none", "bind,ro"); err != nil {
return err
}
}
// Mount user specified volumes
for r, v := range container.Volumes {
mountAs := "ro"
if container.VolumesRW[v] {
mountAs = "rw"
}
if err := mount.Mount(v, path.Join(root, r), "none", fmt.Sprintf("bind,%s", mountAs)); err != nil {
return err
}
}
container.cmd = exec.Command(params[0], params[1:]...)
// Setup logging of stdout and stderr to disk
@ -1358,6 +1401,32 @@ func (container *Container) GetImage() (*Image, error) {
}
func (container *Container) Unmount() error {
var (
err error
root = container.RootfsPath()
mounts = []string{
path.Join(root, "/.dockerinit"),
path.Join(root, "/.dockerenv"),
path.Join(root, "/etc/resolv.conf"),
}
)
if container.HostnamePath != "" && container.HostsPath != "" {
mounts = append(mounts, path.Join(root, "/etc/hostname"), path.Join(root, "/etc/hosts"))
}
for r := range container.Volumes {
mounts = append(mounts, path.Join(root, r))
}
for _, m := range mounts {
if lastError := mount.Unmount(m); lastError != nil {
err = lastError
}
}
if err != nil {
return err
}
return container.runtime.Unmount(container)
}
@ -1377,8 +1446,20 @@ func (container *Container) jsonPath() string {
return path.Join(container.root, "config.json")
}
func (container *Container) EnvConfigPath() string {
return path.Join(container.root, "config.env")
func (container *Container) EnvConfigPath() (string, error) {
p := path.Join(container.root, "config.env")
if _, err := os.Stat(p); err != nil {
if os.IsNotExist(err) {
f, err := os.Create(p)
if err != nil {
return "", err
}
f.Close()
} else {
return "", err
}
}
return p, nil
}
func (container *Container) lxcConfigPath() string {

View file

@ -25,6 +25,7 @@ import (
"fmt"
"github.com/dotcloud/docker/archive"
"github.com/dotcloud/docker/graphdriver"
mountpk "github.com/dotcloud/docker/mount"
"github.com/dotcloud/docker/utils"
"os"
"os/exec"
@ -295,7 +296,7 @@ func (a *Driver) unmount(id string) error {
func (a *Driver) mounted(id string) (bool, error) {
target := path.Join(a.rootPath(), "mnt", id)
return Mounted(target)
return mountpk.Mounted(target)
}
// During cleanup aufs needs to unmount all mountpoints

View file

@ -2,9 +2,7 @@ package aufs
import (
"github.com/dotcloud/docker/utils"
"os"
"os/exec"
"path/filepath"
"syscall"
)
@ -17,21 +15,3 @@ func Unmount(target string) error {
}
return nil
}
func Mounted(mountpoint string) (bool, error) {
mntpoint, err := os.Stat(mountpoint)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
parent, err := os.Stat(filepath.Join(mountpoint, ".."))
if err != nil {
return false, err
}
mntpointSt := mntpoint.Sys().(*syscall.Stat_t)
parentSt := parent.Sys().(*syscall.Stat_t)
return mntpointSt.Dev != parentSt.Dev, nil
}

View file

@ -21,12 +21,6 @@ lxc.network.mtu = 1500
{{$ROOTFS := .RootfsPath}}
lxc.rootfs = {{$ROOTFS}}
{{if and .HostnamePath .HostsPath}}
# enable domain name support
lxc.mount.entry = {{escapeFstabSpaces .HostnamePath}} {{escapeFstabSpaces $ROOTFS}}/etc/hostname none bind,ro 0 0
lxc.mount.entry = {{escapeFstabSpaces .HostsPath}} {{escapeFstabSpaces $ROOTFS}}/etc/hosts none bind,ro 0 0
{{end}}
# use a dedicated pts for the container (and limit the number of pseudo terminal
# available)
lxc.pts = 1024
@ -74,32 +68,20 @@ lxc.cgroup.devices.allow = c 10:200 rwm
# standard mount point
# Use mnt.putold as per https://bugs.launchpad.net/ubuntu/+source/lxc/+bug/986385
lxc.pivotdir = lxc_putold
# NOTICE: These mounts must be applied within the namespace
# WARNING: procfs is a known attack vector and should probably be disabled
# if your userspace allows it. eg. see http://blog.zx2c4.com/749
lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0
# WARNING: sysfs is a known attack vector and should probably be disabled
# if your userspace allows it. eg. see http://bit.ly/T9CkqJ
# WARNING: sysfs is a known attack vector and should probably be disabled
# if your userspace allows it. eg. see http://bit.ly/T9CkqJ
lxc.mount.entry = sysfs {{escapeFstabSpaces $ROOTFS}}/sys sysfs nosuid,nodev,noexec 0 0
lxc.mount.entry = devpts {{escapeFstabSpaces $ROOTFS}}/dev/pts devpts newinstance,ptmxmode=0666,nosuid,noexec 0 0
#lxc.mount.entry = varrun {{escapeFstabSpaces $ROOTFS}}/var/run tmpfs mode=755,size=4096k,nosuid,nodev,noexec 0 0
#lxc.mount.entry = varlock {{escapeFstabSpaces $ROOTFS}}/var/lock tmpfs size=1024k,nosuid,nodev,noexec 0 0
lxc.mount.entry = shm {{escapeFstabSpaces $ROOTFS}}/dev/shm tmpfs size=65536k,nosuid,nodev,noexec 0 0
# Inject dockerinit
lxc.mount.entry = {{escapeFstabSpaces .SysInitPath}} {{escapeFstabSpaces $ROOTFS}}/.dockerinit none bind,ro 0 0
# Inject env
lxc.mount.entry = {{escapeFstabSpaces .EnvConfigPath}} {{escapeFstabSpaces $ROOTFS}}/.dockerenv none bind,ro 0 0
# In order to get a working DNS environment, mount bind (ro) the host's /etc/resolv.conf into the container
lxc.mount.entry = {{escapeFstabSpaces .ResolvConfPath}} {{escapeFstabSpaces $ROOTFS}}/etc/resolv.conf none bind,ro 0 0
{{if .Volumes}}
{{ $rw := .VolumesRW }}
{{range $virtualPath, $realPath := .Volumes}}
lxc.mount.entry = {{escapeFstabSpaces $realPath}} {{escapeFstabSpaces $ROOTFS}}/{{escapeFstabSpaces $virtualPath}} none bind,{{ if index $rw $virtualPath }}rw{{else}}ro{{end}} 0 0
{{end}}
{{end}}
{{if (getHostConfig .).Privileged}}
{{if (getCapabilities .).AppArmor}}
lxc.aa_profile = unconfined

1
mount/MAINTAINERS Normal file
View file

@ -0,0 +1 @@
Michael Crosby <michael@crosbymichael.com> (@crosbymichael)

5
mount/flags_darwin.go Normal file
View file

@ -0,0 +1,5 @@
package mount
func parseOptions(options string) (int, string) {
panic("Not implemented")
}

61
mount/flags_linux.go Normal file
View file

@ -0,0 +1,61 @@
package mount
import (
"strings"
"syscall"
)
// Parse fstab type mount options into mount() flags
// and device specific data
func parseOptions(options string) (int, string) {
var (
flag int
data []string
)
flags := map[string]struct {
clear bool
flag int
}{
"defaults": {false, 0},
"ro": {false, syscall.MS_RDONLY},
"rw": {true, syscall.MS_RDONLY},
"suid": {true, syscall.MS_NOSUID},
"nosuid": {false, syscall.MS_NOSUID},
"dev": {true, syscall.MS_NODEV},
"nodev": {false, syscall.MS_NODEV},
"exec": {true, syscall.MS_NOEXEC},
"noexec": {false, syscall.MS_NOEXEC},
"sync": {false, syscall.MS_SYNCHRONOUS},
"async": {true, syscall.MS_SYNCHRONOUS},
"dirsync": {false, syscall.MS_DIRSYNC},
"remount": {false, syscall.MS_REMOUNT},
"mand": {false, syscall.MS_MANDLOCK},
"nomand": {true, syscall.MS_MANDLOCK},
"atime": {true, syscall.MS_NOATIME},
"noatime": {false, syscall.MS_NOATIME},
"diratime": {true, syscall.MS_NODIRATIME},
"nodiratime": {false, syscall.MS_NODIRATIME},
"bind": {false, syscall.MS_BIND},
"rbind": {false, syscall.MS_BIND | syscall.MS_REC},
"relatime": {false, syscall.MS_RELATIME},
"norelatime": {true, syscall.MS_RELATIME},
"strictatime": {false, syscall.MS_STRICTATIME},
"nostrictatime": {true, syscall.MS_STRICTATIME},
}
for _, o := range strings.Split(options, ",") {
// If the option does not exist in the flags table then it is a
// data value for a specific fs type
if f, exists := flags[o]; exists {
if f.clear {
flag &= ^f.flag
} else {
flag |= f.flag
}
} else {
data = append(data, o)
}
}
return flag, strings.Join(data, ",")
}

53
mount/mount.go Normal file
View file

@ -0,0 +1,53 @@
package mount
import (
"time"
)
// Looks at /proc/self/mountinfo to determine of the specified
// mountpoint has been mounted
func Mounted(mountpoint string) (bool, error) {
entries, err := parseMountTable()
if err != nil {
return false, err
}
// Search the table for the mountpoint
for _, e := range entries {
if e.mountpoint == mountpoint {
return true, nil
}
}
return false, nil
}
// Mount the specified options at the target path
// Options must be specified as fstab style
func Mount(device, target, mType, options string) error {
if mounted, err := Mounted(target); err != nil || mounted {
return err
}
flag, data := parseOptions(options)
if err := mount(device, target, mType, uintptr(flag), data); err != nil {
return err
}
return nil
}
// Unmount the target only if it is mounted
func Unmount(target string) (err error) {
if mounted, err := Mounted(target); err != nil || !mounted {
return err
}
// Simple retry logic for unmount
for i := 0; i < 10; i++ {
if err = unmount(target, 0); err == nil {
return nil
}
time.Sleep(100 * time.Millisecond)
}
return
}

67
mount/mount_test.go Normal file
View file

@ -0,0 +1,67 @@
package mount
import (
"os"
"path"
"syscall"
"testing"
)
func TestMountOptionsParsing(t *testing.T) {
options := "bind,ro,size=10k"
flag, data := parseOptions(options)
if data != "size=10k" {
t.Fatalf("Expected size=10 got %s", data)
}
expectedFlag := syscall.MS_BIND | syscall.MS_RDONLY
if flag != expectedFlag {
t.Fatalf("Expected %d got %d", expectedFlag, flag)
}
}
func TestMounted(t *testing.T) {
tmp := path.Join(os.TempDir(), "mount-tests")
if err := os.MkdirAll(tmp, 0777); err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
var (
sourcePath = path.Join(tmp, "sourcefile.txt")
targetPath = path.Join(tmp, "targetfile.txt")
)
f, err := os.Create(sourcePath)
if err != nil {
t.Fatal(err)
}
f.WriteString("hello")
f.Close()
f, err = os.Create(targetPath)
if err != nil {
t.Fatal(err)
}
f.Close()
if err := Mount(sourcePath, targetPath, "none", "bind,ro"); err != nil {
t.Fatal(err)
}
defer func() {
if err := Unmount(targetPath); err != nil {
t.Fatal(err)
}
}()
mounted, err := Mounted(targetPath)
if err != nil {
t.Fatal(err)
}
if !mounted {
t.Fatalf("Expected %s to be mounted", targetPath)
}
}

9
mount/mounter_darwin.go Normal file
View file

@ -0,0 +1,9 @@
package mount
func mount(device, target, mType string, flag uintptr, data string) error {
panic("Not implemented")
}
func unmount(target string, flag int) error {
panic("Not implemented")
}

13
mount/mounter_linux.go Normal file
View file

@ -0,0 +1,13 @@
package mount
import (
"syscall"
)
func mount(device, target, mType string, flag uintptr, data string) error {
return syscall.Mount(device, target, mType, flag, data)
}
func unmount(target string, flag int) error {
return syscall.Unmount(target, flag)
}

45
mount/mountinfo.go Normal file
View file

@ -0,0 +1,45 @@
package mount
import (
"bufio"
"fmt"
"os"
)
const (
mountinfoFormat = "%d %d %d:%d %s %s %s - %s %s %s"
)
// Represents one line from /proc/self/mountinfo
type procEntry struct {
id, parent, major, minor int
source, mountpoint, fstype, device string
vfsopts, opts string
}
// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts
func parseMountTable() ([]*procEntry, error) {
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return nil, err
}
defer f.Close()
s := bufio.NewScanner(f)
out := []*procEntry{}
for s.Scan() {
if err := s.Err(); err != nil {
return nil, err
}
p := &procEntry{}
if _, err := fmt.Sscanf(s.Text(), mountinfoFormat,
&p.id, &p.parent, &p.major, &p.minor,
&p.source, &p.mountpoint, &p.vfsopts, &p.fstype,
&p.device, &p.opts); err != nil {
return nil, err
}
out = append(out, p)
}
return out, nil
}

View file

@ -485,10 +485,8 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin
hostConfig: &HostConfig{},
Image: img.ID, // Always use the resolved image id
NetworkSettings: &NetworkSettings{},
// FIXME: do we need to store this in the container?
SysInitPath: runtime.sysInitPath,
Name: name,
Driver: runtime.driver.String(),
Name: name,
Driver: runtime.driver.String(),
}
container.root = runtime.containerRoot(container.ID)
// Step 1: create the container directory.