Add support for mount in runtime config

This could be used in LinuxKit now, as there are some examples, eg
https://github.com/linuxkit/linuxkit/blob/master/blueprints/docker-for-mac/base.yml#L33
which are creating containers to do a mount.

The main reason though is to in future change the ad hoc code that generates
overlay mounts for writeable containers with a runtime config which does
the same thing; this code needs to create both tmpfs and overlay mounts.

See https://github.com/moby/tool/pull/145

Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
Justin Cormack 2017-08-22 15:08:51 +01:00
parent dfc1068e32
commit c677b391fc

View File

@ -7,9 +7,11 @@ import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
"syscall" "strings"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/vishvananda/netlink" "github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
) )
const ( const (
@ -21,9 +23,10 @@ const (
// Runtime is the type of config processed at runtime, not used to build the OCI spec // Runtime is the type of config processed at runtime, not used to build the OCI spec
type Runtime struct { type Runtime struct {
Mounts []specs.Mount `yaml:"mounts" json:"mounts,omitempty"`
Mkdir []string `yaml:"mkdir" json:"mkdir,omitempty"` Mkdir []string `yaml:"mkdir" json:"mkdir,omitempty"`
Interfaces []Interface `yaml:"interfaces" json:"interfaces,omitempty"` Interfaces []Interface `yaml:"interfaces" json:"interfaces,omitempty"`
BindNS *Namespaces `yaml:"bindNS" json:"bindNS,omitempty"` BindNS Namespaces `yaml:"bindNS" json:"bindNS,omitempty"`
} }
// Namespaces is the type for configuring paths to bind namespaces // Namespaces is the type for configuring paths to bind namespaces
@ -61,12 +64,80 @@ func getRuntimeConfig(path string) Runtime {
return runtime return runtime
} }
// parseMountOptions takes fstab style mount options and parses them for
// use with a standard mount() syscall
// taken from containerd, where it is not exported
func parseMountOptions(options []string) (int, string) {
var (
flag int
data []string
)
flags := map[string]struct {
clear bool
flag int
}{
"async": {true, unix.MS_SYNCHRONOUS},
"atime": {true, unix.MS_NOATIME},
"bind": {false, unix.MS_BIND},
"defaults": {false, 0},
"dev": {true, unix.MS_NODEV},
"diratime": {true, unix.MS_NODIRATIME},
"dirsync": {false, unix.MS_DIRSYNC},
"exec": {true, unix.MS_NOEXEC},
"mand": {false, unix.MS_MANDLOCK},
"noatime": {false, unix.MS_NOATIME},
"nodev": {false, unix.MS_NODEV},
"nodiratime": {false, unix.MS_NODIRATIME},
"noexec": {false, unix.MS_NOEXEC},
"nomand": {true, unix.MS_MANDLOCK},
"norelatime": {true, unix.MS_RELATIME},
"nostrictatime": {true, unix.MS_STRICTATIME},
"nosuid": {false, unix.MS_NOSUID},
"rbind": {false, unix.MS_BIND | unix.MS_REC},
"relatime": {false, unix.MS_RELATIME},
"remount": {false, unix.MS_REMOUNT},
"ro": {false, unix.MS_RDONLY},
"rw": {true, unix.MS_RDONLY},
"strictatime": {false, unix.MS_STRICTATIME},
"suid": {true, unix.MS_NOSUID},
"sync": {false, unix.MS_SYNCHRONOUS},
}
for _, o := range options {
// If the option does not exist in the flags table or the flag
// is not supported on the platform,
// then it is a data value for a specific fs type
if f, exists := flags[o]; exists && f.flag != 0 {
if f.clear {
flag &^= f.flag
} else {
flag |= f.flag
}
} else {
data = append(data, o)
}
}
return flag, strings.Join(data, ",")
}
// prepareFilesystem sets up the mounts, before the container is created // prepareFilesystem sets up the mounts, before the container is created
func prepareFilesystem(path string, runtime Runtime) error { func prepareFilesystem(path string, runtime Runtime) error {
// execute the runtime config that should be done up front // execute the runtime config that should be done up front
// we execute Mounts before Mkdir so you can make a directory under a mount
// but we do mkdir of the destination path in case missing
for _, mount := range runtime.Mounts {
const mode os.FileMode = 0755
err := os.MkdirAll(mount.Destination, mode)
if err != nil {
return fmt.Errorf("Cannot create directory for mount destination %s: %v", mount.Destination, err)
}
opts, data := parseMountOptions(mount.Options)
if err := unix.Mount(mount.Source, mount.Destination, mount.Type, uintptr(opts), data); err != nil {
return fmt.Errorf("Failed to mount %s: %v", mount.Source, err)
}
}
for _, dir := range runtime.Mkdir { for _, dir := range runtime.Mkdir {
// in future we may need to change the structure to set mode, ownership // in future we may need to change the structure to set mode, ownership
var mode os.FileMode = 0755 const mode os.FileMode = 0755
err := os.MkdirAll(dir, mode) err := os.MkdirAll(dir, mode)
if err != nil { if err != nil {
return fmt.Errorf("Cannot create directory %s: %v", dir, err) return fmt.Errorf("Cannot create directory %s: %v", dir, err)
@ -86,7 +157,7 @@ func prepareFilesystem(path string, runtime Runtime) error {
func prepareRO(path string) error { func prepareRO(path string) error {
// make rootfs a mount point, as runc doesn't like it much otherwise // make rootfs a mount point, as runc doesn't like it much otherwise
rootfs := filepath.Join(path, "rootfs") rootfs := filepath.Join(path, "rootfs")
if err := syscall.Mount(rootfs, rootfs, "", syscall.MS_BIND, ""); err != nil { if err := unix.Mount(rootfs, rootfs, "", unix.MS_BIND, ""); err != nil {
return err return err
} }
return nil return nil
@ -96,11 +167,11 @@ func prepareRW(path string) error {
// mount a tmpfs on tmp for upper and workdirs // mount a tmpfs on tmp for upper and workdirs
// make it private as nothing else should be using this // make it private as nothing else should be using this
tmp := filepath.Join(path, "tmp") tmp := filepath.Join(path, "tmp")
if err := syscall.Mount("tmpfs", tmp, "tmpfs", 0, "size=10%"); err != nil { if err := unix.Mount("tmpfs", tmp, "tmpfs", 0, "size=10%"); err != nil {
return err return err
} }
// make it private as nothing else should be using this // make it private as nothing else should be using this
if err := syscall.Mount("", tmp, "", syscall.MS_REMOUNT|syscall.MS_PRIVATE, ""); err != nil { if err := unix.Mount("", tmp, "", unix.MS_REMOUNT|unix.MS_PRIVATE, ""); err != nil {
return err return err
} }
upper := filepath.Join(tmp, "upper") upper := filepath.Join(tmp, "upper")
@ -115,7 +186,7 @@ func prepareRW(path string) error {
lower := filepath.Join(path, "lower") lower := filepath.Join(path, "lower")
rootfs := filepath.Join(path, "rootfs") rootfs := filepath.Join(path, "rootfs")
opt := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work) opt := fmt.Sprintf("lowerdir=%s,upperdir=%s,workdir=%s", lower, upper, work)
if err := syscall.Mount("overlay", rootfs, "overlay", 0, opt); err != nil { if err := unix.Mount("overlay", rootfs, "overlay", 0, opt); err != nil {
return err return err
} }
return nil return nil
@ -138,7 +209,7 @@ func bindNS(ns string, path string, pid int) error {
if err := fi.Close(); err != nil { if err := fi.Close(); err != nil {
return err return err
} }
if err := syscall.Mount(fmt.Sprintf("/proc/%d/ns/%s", pid, ns), path, "", syscall.MS_BIND, ""); err != nil { if err := unix.Mount(fmt.Sprintf("/proc/%d/ns/%s", pid, ns), path, "", unix.MS_BIND, ""); err != nil {
return fmt.Errorf("Failed to bind %s namespace at %s: %v", ns, path, err) return fmt.Errorf("Failed to bind %s namespace at %s: %v", ns, path, err)
} }
return nil return nil
@ -203,7 +274,6 @@ func prepareProcess(pid int, runtime Runtime) error {
} }
} }
if runtime.BindNS != nil {
binds := []struct { binds := []struct {
ns string ns string
path string path string
@ -222,7 +292,6 @@ func prepareProcess(pid int, runtime Runtime) error {
return err return err
} }
} }
}
return nil return nil
} }
@ -240,16 +309,16 @@ func cleanup(path string) {
func cleanupRO(path string) { func cleanupRO(path string) {
// remove the bind mount // remove the bind mount
rootfs := filepath.Join(path, "rootfs") rootfs := filepath.Join(path, "rootfs")
_ = syscall.Unmount(rootfs, 0) _ = unix.Unmount(rootfs, 0)
} }
func cleanupRW(path string) { func cleanupRW(path string) {
// remove the overlay mount // remove the overlay mount
rootfs := filepath.Join(path, "rootfs") rootfs := filepath.Join(path, "rootfs")
_ = os.RemoveAll(rootfs) _ = os.RemoveAll(rootfs)
_ = syscall.Unmount(rootfs, 0) _ = unix.Unmount(rootfs, 0)
// remove the tmpfs // remove the tmpfs
tmp := filepath.Join(path, "tmp") tmp := filepath.Join(path, "tmp")
_ = os.RemoveAll(tmp) _ = os.RemoveAll(tmp)
_ = syscall.Unmount(tmp, 0) _ = unix.Unmount(tmp, 0)
} }