Explorar el Código

Merge pull request #16412 from Microsoft/10662-hyper-v-containers

Windows [TP4]: Adds support for Hyper-V Containers
Michael Crosby hace 9 años
padre
commit
aadae4b503

+ 2 - 4
daemon/container.go

@@ -269,7 +269,7 @@ func (container *Container) Start() (err error) {
 		}
 	}()
 
-	if err := container.Mount(); err != nil {
+	if err := container.conditionalMountOnStart(); err != nil {
 		return err
 	}
 
@@ -341,9 +341,7 @@ func (container *Container) cleanup() {
 		logrus.Errorf("%s: Failed to umount ipc filesystems: %v", container.ID, err)
 	}
 
-	if err := container.Unmount(); err != nil {
-		logrus.Errorf("%s: Failed to umount filesystem: %v", container.ID, err)
-	}
+	container.conditionalUnmountOnCleanup()
 
 	for _, eConfig := range container.execCommands.s {
 		container.daemon.unregisterExecCommand(eConfig)

+ 17 - 0
daemon/container_unix.go

@@ -1416,3 +1416,20 @@ func (container *Container) ipcMounts() []execdriver.Mount {
 func detachMounted(path string) error {
 	return syscall.Unmount(path, syscall.MNT_DETACH)
 }
+
+// conditionalMountOnStart is a platform specific helper function during the
+// container start to call mount.
+func (container *Container) conditionalMountOnStart() error {
+	if err := container.Mount(); err != nil {
+		return err
+	}
+	return nil
+}
+
+// conditionalUnmountOnCleanup is a platform specific helper function called
+// during the cleanup of a container to unmount.
+func (container *Container) conditionalUnmountOnCleanup() {
+	if err := container.Unmount(); err != nil {
+		logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
+	}
+}

+ 25 - 0
daemon/container_windows.go

@@ -5,6 +5,7 @@ package daemon
 import (
 	"strings"
 
+	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/execdriver"
 	derr "github.com/docker/docker/errors"
 	"github.com/docker/docker/volume"
@@ -144,6 +145,7 @@ func populateCommand(c *Container, env []string) error {
 		LayerFolder:    layerFolder,
 		LayerPaths:     layerPaths,
 		Hostname:       c.Config.Hostname,
+		Isolated:       c.hostConfig.Isolation.IsHyperV(),
 	}
 
 	return nil
@@ -194,3 +196,26 @@ func (container *Container) ipcMounts() []execdriver.Mount {
 func getDefaultRouteMtu() (int, error) {
 	return -1, errSystemNotSupported
 }
+
+// conditionalMountOnStart is a platform specific helper function during the
+// container start to call mount.
+func (container *Container) conditionalMountOnStart() error {
+	// We do not mount if a Hyper-V container
+	if !container.hostConfig.Isolation.IsHyperV() {
+		if err := container.Mount(); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// conditionalUnmountOnCleanup is a platform specific helper function called
+// during the cleanup of a container to unmount.
+func (container *Container) conditionalUnmountOnCleanup() {
+	// We do not unmount if a Hyper-V container
+	if !container.hostConfig.Isolation.IsHyperV() {
+		if err := container.Unmount(); err != nil {
+			logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err)
+		}
+	}
+}

+ 1 - 0
daemon/execdriver/driver.go

@@ -210,4 +210,5 @@ type Command struct {
 	LayerPaths         []string          `json:"layer_paths"` // Windows needs to know the layer paths and folder for a command
 	LayerFolder        string            `json:"layer_folder"`
 	Hostname           string            `json:"hostname"` // Windows sets the hostname in the execdriver
+	Isolated           bool              `json:"isolated"` // Windows: Isolated is a Hyper-V container rather than Windows Server Container
 }

+ 10 - 0
daemon/execdriver/windows/run.go

@@ -77,6 +77,8 @@ type containerInit struct {
 	ProcessorWeight         int64       // CPU Shares 1..9 on Windows; or 0 is platform default.
 	HostName                string      // Hostname
 	MappedDirectories       []mappedDir // List of mapped directories (volumes/mounts)
+	SandboxPath             string      // Location of unmounted sandbox (used for Hyper-V containers, not Windows Server containers)
+	HvPartition             bool        // True if it a Hyper-V Container
 }
 
 // defaultOwner is a tag passed to HCS to allow it to differentiate between
@@ -108,6 +110,14 @@ func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execd
 		LayerFolderPath:         c.LayerFolder,
 		ProcessorWeight:         c.Resources.CPUShares,
 		HostName:                c.Hostname,
+		HvPartition:             c.Isolated,
+	}
+
+	if c.Isolated {
+		cu.SandboxPath = filepath.Dir(c.LayerFolder)
+	} else {
+		cu.VolumePath = c.Rootfs
+		cu.LayerFolderPath = c.LayerFolder
 	}
 
 	for _, layerPath := range c.LayerPaths {

+ 4 - 0
runconfig/config.go

@@ -75,6 +75,10 @@ func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
 		return nil, nil, err
 	}
 
+	// Validate the isolation level
+	if err := ValidateIsolationLevel(hc); err != nil {
+		return nil, nil, err
+	}
 	return w.Config, hc, nil
 }
 

+ 57 - 0
runconfig/config_test.go

@@ -2,9 +2,11 @@ package runconfig
 
 import (
 	"bytes"
+	"encoding/json"
 	"fmt"
 	"io/ioutil"
 	"runtime"
+	"strings"
 	"testing"
 
 	"github.com/docker/docker/pkg/stringutils"
@@ -60,3 +62,58 @@ func TestDecodeContainerConfig(t *testing.T) {
 		}
 	}
 }
+
+// TestDecodeContainerConfigIsolation validates the isolation level passed
+// to the daemon in the hostConfig structure. Note this is platform specific
+// as to what level of container isolation is supported.
+func TestDecodeContainerConfigIsolation(t *testing.T) {
+
+	// An invalid isolation level
+	if _, _, err := callDecodeContainerConfigIsolation("invalid"); err != nil {
+		if !strings.Contains(err.Error(), `invalid --isolation: "invalid"`) {
+			t.Fatal(err)
+		}
+	}
+
+	// Blank isolation level (== default)
+	if _, _, err := callDecodeContainerConfigIsolation(""); err != nil {
+		t.Fatal("Blank isolation should have succeeded")
+	}
+
+	// Default isolation level
+	if _, _, err := callDecodeContainerConfigIsolation("default"); err != nil {
+		t.Fatal("default isolation should have succeeded")
+	}
+
+	// Hyper-V Containers isolation level (Valid on Windows only)
+	if runtime.GOOS == "windows" {
+		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
+			t.Fatal("hyperv isolation should have succeeded")
+		}
+	} else {
+		if _, _, err := callDecodeContainerConfigIsolation("hyperv"); err != nil {
+			if !strings.Contains(err.Error(), `invalid --isolation: "hyperv"`) {
+				t.Fatal(err)
+			}
+		}
+	}
+}
+
+// callDecodeContainerConfigIsolation is a utility function to call
+// DecodeContainerConfig for validating isolation levels
+func callDecodeContainerConfigIsolation(isolation string) (*Config, *HostConfig, error) {
+	var (
+		b   []byte
+		err error
+	)
+	w := ContainerConfigWrapper{
+		Config: &Config{},
+		HostConfig: &HostConfig{
+			NetworkMode: "none",
+			Isolation:   IsolationLevel(isolation)},
+	}
+	if b, err = json.Marshal(w); err != nil {
+		return nil, nil, fmt.Errorf("Error on marshal %s", err.Error())
+	}
+	return DecodeContainerConfig(bytes.NewReader(b))
+}

+ 11 - 0
runconfig/hostconfig.go

@@ -19,6 +19,16 @@ type KeyValuePair struct {
 // NetworkMode represents the container network stack.
 type NetworkMode string
 
+// IsolationLevel represents the isolation level of a container. The supported
+// values are platform specific
+type IsolationLevel string
+
+// IsDefault indicates the default isolation level of a container. On Linux this
+// is LXC. On Windows, this is a Windows Server Container.
+func (i IsolationLevel) IsDefault() bool {
+	return strings.ToLower(string(i)) == "default" || string(i) == ""
+}
+
 // IpcMode represents the container ipc stack.
 type IpcMode string
 
@@ -254,6 +264,7 @@ type HostConfig struct {
 	CgroupParent      string                // Parent cgroup.
 	ConsoleSize       [2]int                // Initial console size on Windows
 	VolumeDriver      string                // Name of the volume driver used to mount volumes
+	Isolation         IsolationLevel        // Isolation level of the container (eg default, hyperv)
 }
 
 // DecodeHostConfig creates a HostConfig based on the specified Reader.

+ 5 - 0
runconfig/hostconfig_unix.go

@@ -6,6 +6,11 @@ import (
 	"strings"
 )
 
+// IsValid indicates is an isolation level is valid
+func (i IsolationLevel) IsValid() bool {
+	return i.IsDefault()
+}
+
 // IsPrivate indicates whether container uses it's private network stack.
 func (n NetworkMode) IsPrivate() bool {
 	return !(n.IsHost() || n.IsContainer())

+ 13 - 0
runconfig/hostconfig_windows.go

@@ -1,10 +1,23 @@
 package runconfig
 
+import "strings"
+
 // IsDefault indicates whether container uses the default network stack.
 func (n NetworkMode) IsDefault() bool {
 	return n == "default"
 }
 
+// IsHyperV indicates the use of Hyper-V Containers for isolation (as opposed
+// to Windows Server Containers
+func (i IsolationLevel) IsHyperV() bool {
+	return strings.ToLower(string(i)) == "hyperv"
+}
+
+// IsValid indicates is an isolation level is valid
+func (i IsolationLevel) IsValid() bool {
+	return i.IsDefault() || i.IsHyperV()
+}
+
 // DefaultDaemonNetworkMode returns the default network stack the daemon should
 // use.
 func DefaultDaemonNetworkMode() NetworkMode {

+ 17 - 16
runconfig/parse.go

@@ -55,22 +55,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 
 		flUlimits = opts.NewUlimitOpt(nil)
 
-		flPublish     = opts.NewListOpts(nil)
-		flExpose      = opts.NewListOpts(nil)
-		flDNS         = opts.NewListOpts(opts.ValidateIPAddress)
-		flDNSSearch   = opts.NewListOpts(opts.ValidateDNSSearch)
-		flDNSOptions  = opts.NewListOpts(nil)
-		flExtraHosts  = opts.NewListOpts(opts.ValidateExtraHost)
-		flVolumesFrom = opts.NewListOpts(nil)
-		flLxcOpts     = opts.NewListOpts(nil)
-		flEnvFile     = opts.NewListOpts(nil)
-		flCapAdd      = opts.NewListOpts(nil)
-		flCapDrop     = opts.NewListOpts(nil)
-		flGroupAdd    = opts.NewListOpts(nil)
-		flSecurityOpt = opts.NewListOpts(nil)
-		flLabelsFile  = opts.NewListOpts(nil)
-		flLoggingOpts = opts.NewListOpts(nil)
-
+		flPublish           = opts.NewListOpts(nil)
+		flExpose            = opts.NewListOpts(nil)
+		flDNS               = opts.NewListOpts(opts.ValidateIPAddress)
+		flDNSSearch         = opts.NewListOpts(opts.ValidateDNSSearch)
+		flDNSOptions        = opts.NewListOpts(nil)
+		flExtraHosts        = opts.NewListOpts(opts.ValidateExtraHost)
+		flVolumesFrom       = opts.NewListOpts(nil)
+		flLxcOpts           = opts.NewListOpts(nil)
+		flEnvFile           = opts.NewListOpts(nil)
+		flCapAdd            = opts.NewListOpts(nil)
+		flCapDrop           = opts.NewListOpts(nil)
+		flGroupAdd          = opts.NewListOpts(nil)
+		flSecurityOpt       = opts.NewListOpts(nil)
+		flLabelsFile        = opts.NewListOpts(nil)
+		flLoggingOpts       = opts.NewListOpts(nil)
 		flNetwork           = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
 		flPrivileged        = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
 		flPidMode           = cmd.String([]string{"-pid"}, "", "PID namespace to use")
@@ -104,6 +103,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flCgroupParent      = cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
 		flVolumeDriver      = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
 		flStopSignal        = cmd.String([]string{"-stop-signal"}, signal.DefaultStopSignal, fmt.Sprintf("Signal to stop a container, %v by default", signal.DefaultStopSignal))
+		flIsolation         = cmd.String([]string{"-isolation"}, "default", "Container isolation level")
 	)
 
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
@@ -377,6 +377,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		LogConfig:         LogConfig{Type: *flLoggingDriver, Config: loggingOpts},
 		CgroupParent:      *flCgroupParent,
 		VolumeDriver:      *flVolumeDriver,
+		Isolation:         IsolationLevel(*flIsolation),
 	}
 
 	// When allocating stdin in attached mode, close stdin at client disconnect

+ 15 - 0
runconfig/parse_unix.go

@@ -4,6 +4,7 @@ package runconfig
 
 import (
 	"fmt"
+	"runtime"
 	"strings"
 )
 
@@ -58,3 +59,17 @@ func ValidateNetMode(c *Config, hc *HostConfig) error {
 	}
 	return nil
 }
+
+// ValidateIsolationLevel performs platform specific validation of the
+// isolation level in the hostconfig structure. Linux only supports "default"
+// which is LXC container isolation
+func ValidateIsolationLevel(hc *HostConfig) error {
+	// We may not be passed a host config, such as in the case of docker commit
+	if hc == nil {
+		return nil
+	}
+	if !hc.Isolation.IsValid() {
+		return fmt.Errorf("invalid --isolation: %q - %s only supports 'default'", hc.Isolation, runtime.GOOS)
+	}
+	return nil
+}

+ 15 - 0
runconfig/parse_windows.go

@@ -20,3 +20,18 @@ func ValidateNetMode(c *Config, hc *HostConfig) error {
 	}
 	return nil
 }
+
+// ValidateIsolationLevel performs platform specific validation of the
+// isolation level in the hostconfig structure. Windows supports 'default' (or
+// blank), and 'hyperv'. These refer to Windows Server Containers and
+// Hyper-V Containers respectively.
+func ValidateIsolationLevel(hc *HostConfig) error {
+	// We may not be passed a host config, such as in the case of docker commit
+	if hc == nil {
+		return nil
+	}
+	if !hc.Isolation.IsValid() {
+		return fmt.Errorf("invalid --isolation: %q. Windows supports 'default' (Windows Server Container) or 'hyperv' (Hyper-V Container)", hc.Isolation)
+	}
+	return nil
+}