github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/daemon/oci_windows.go (about)

     1  package daemon // import "github.com/docker/docker/daemon"
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"path/filepath"
     7  	"runtime"
     8  	"strings"
     9  
    10  	containertypes "github.com/docker/docker/api/types/container"
    11  	"github.com/docker/docker/container"
    12  	"github.com/docker/docker/oci"
    13  	"github.com/docker/docker/pkg/sysinfo"
    14  	"github.com/docker/docker/pkg/system"
    15  	"github.com/opencontainers/runtime-spec/specs-go"
    16  	"github.com/pkg/errors"
    17  	"golang.org/x/sys/windows"
    18  	"golang.org/x/sys/windows/registry"
    19  )
    20  
    21  const (
    22  	credentialSpecRegistryLocation = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\Containers\CredentialSpecs`
    23  	credentialSpecFileLocation     = "CredentialSpecs"
    24  )
    25  
    26  func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
    27  	img, err := daemon.imageService.GetImage(string(c.ImageID))
    28  	if err != nil {
    29  		return nil, err
    30  	}
    31  
    32  	s := oci.DefaultOSSpec(img.OS)
    33  
    34  	linkedEnv, err := daemon.setupLinkedContainers(c)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	// Note, unlike Unix, we do NOT call into SetupWorkingDirectory as
    40  	// this is done in VMCompute. Further, we couldn't do it for Hyper-V
    41  	// containers anyway.
    42  
    43  	// In base spec
    44  	s.Hostname = c.FullHostname()
    45  
    46  	if err := daemon.setupSecretDir(c); err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	if err := daemon.setupConfigDir(c); err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	// In s.Mounts
    55  	mounts, err := daemon.setupMounts(c)
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	var isHyperV bool
    61  	if c.HostConfig.Isolation.IsDefault() {
    62  		// Container using default isolation, so take the default from the daemon configuration
    63  		isHyperV = daemon.defaultIsolation.IsHyperV()
    64  	} else {
    65  		// Container may be requesting an explicit isolation mode.
    66  		isHyperV = c.HostConfig.Isolation.IsHyperV()
    67  	}
    68  
    69  	if isHyperV {
    70  		s.Windows.HyperV = &specs.WindowsHyperV{}
    71  	}
    72  
    73  	// If the container has not been started, and has configs or secrets
    74  	// secrets, create symlinks to each config and secret. If it has been
    75  	// started before, the symlinks should have already been created. Also, it
    76  	// is important to not mount a Hyper-V  container that has been started
    77  	// before, to protect the host from the container; for example, from
    78  	// malicious mutation of NTFS data structures.
    79  	if !c.HasBeenStartedBefore && (len(c.SecretReferences) > 0 || len(c.ConfigReferences) > 0) {
    80  		// The container file system is mounted before this function is called,
    81  		// except for Hyper-V containers, so mount it here in that case.
    82  		if isHyperV {
    83  			if err := daemon.Mount(c); err != nil {
    84  				return nil, err
    85  			}
    86  			defer daemon.Unmount(c)
    87  		}
    88  		if err := c.CreateSecretSymlinks(); err != nil {
    89  			return nil, err
    90  		}
    91  		if err := c.CreateConfigSymlinks(); err != nil {
    92  			return nil, err
    93  		}
    94  	}
    95  
    96  	secretMounts, err := c.SecretMounts()
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  	if secretMounts != nil {
   101  		mounts = append(mounts, secretMounts...)
   102  	}
   103  
   104  	configMounts := c.ConfigMounts()
   105  	if configMounts != nil {
   106  		mounts = append(mounts, configMounts...)
   107  	}
   108  
   109  	for _, mount := range mounts {
   110  		m := specs.Mount{
   111  			Source:      mount.Source,
   112  			Destination: mount.Destination,
   113  		}
   114  		if !mount.Writable {
   115  			m.Options = append(m.Options, "ro")
   116  		}
   117  		if img.OS != runtime.GOOS {
   118  			m.Type = "bind"
   119  			m.Options = append(m.Options, "rbind")
   120  			m.Options = append(m.Options, fmt.Sprintf("uvmpath=/tmp/gcs/%s/binds", c.ID))
   121  		}
   122  		s.Mounts = append(s.Mounts, m)
   123  	}
   124  
   125  	// In s.Process
   126  	s.Process.Args = append([]string{c.Path}, c.Args...)
   127  	if !c.Config.ArgsEscaped && img.OS == "windows" {
   128  		s.Process.Args = escapeArgs(s.Process.Args)
   129  	}
   130  
   131  	s.Process.Cwd = c.Config.WorkingDir
   132  	s.Process.Env = c.CreateDaemonEnvironment(c.Config.Tty, linkedEnv)
   133  	if c.Config.Tty {
   134  		s.Process.Terminal = c.Config.Tty
   135  		s.Process.ConsoleSize = &specs.Box{
   136  			Height: c.HostConfig.ConsoleSize[0],
   137  			Width:  c.HostConfig.ConsoleSize[1],
   138  		}
   139  	}
   140  	s.Process.User.Username = c.Config.User
   141  	s.Windows.LayerFolders, err = daemon.imageService.GetLayerFolders(img, c.RWLayer)
   142  	if err != nil {
   143  		return nil, errors.Wrapf(err, "container %s", c.ID)
   144  	}
   145  
   146  	dnsSearch := daemon.getDNSSearchSettings(c)
   147  
   148  	// Get endpoints for the libnetwork allocated networks to the container
   149  	var epList []string
   150  	AllowUnqualifiedDNSQuery := false
   151  	gwHNSID := ""
   152  	if c.NetworkSettings != nil {
   153  		for n := range c.NetworkSettings.Networks {
   154  			sn, err := daemon.FindNetwork(n)
   155  			if err != nil {
   156  				continue
   157  			}
   158  
   159  			ep, err := getEndpointInNetwork(c.Name, sn)
   160  			if err != nil {
   161  				continue
   162  			}
   163  
   164  			data, err := ep.DriverInfo()
   165  			if err != nil {
   166  				continue
   167  			}
   168  
   169  			if data["GW_INFO"] != nil {
   170  				gwInfo := data["GW_INFO"].(map[string]interface{})
   171  				if gwInfo["hnsid"] != nil {
   172  					gwHNSID = gwInfo["hnsid"].(string)
   173  				}
   174  			}
   175  
   176  			if data["hnsid"] != nil {
   177  				epList = append(epList, data["hnsid"].(string))
   178  			}
   179  
   180  			if data["AllowUnqualifiedDNSQuery"] != nil {
   181  				AllowUnqualifiedDNSQuery = true
   182  			}
   183  		}
   184  	}
   185  
   186  	var networkSharedContainerID string
   187  	if c.HostConfig.NetworkMode.IsContainer() {
   188  		networkSharedContainerID = c.NetworkSharedContainerID
   189  		for _, ep := range c.SharedEndpointList {
   190  			epList = append(epList, ep)
   191  		}
   192  	}
   193  
   194  	if gwHNSID != "" {
   195  		epList = append(epList, gwHNSID)
   196  	}
   197  
   198  	s.Windows.Network = &specs.WindowsNetwork{
   199  		AllowUnqualifiedDNSQuery:   AllowUnqualifiedDNSQuery,
   200  		DNSSearchList:              dnsSearch,
   201  		EndpointList:               epList,
   202  		NetworkSharedContainerName: networkSharedContainerID,
   203  	}
   204  
   205  	switch img.OS {
   206  	case "windows":
   207  		if err := daemon.createSpecWindowsFields(c, &s, isHyperV); err != nil {
   208  			return nil, err
   209  		}
   210  	case "linux":
   211  		if !system.LCOWSupported() {
   212  			return nil, fmt.Errorf("Linux containers on Windows are not supported")
   213  		}
   214  		if err := daemon.createSpecLinuxFields(c, &s); err != nil {
   215  			return nil, err
   216  		}
   217  	default:
   218  		return nil, fmt.Errorf("Unsupported platform %q", img.OS)
   219  	}
   220  
   221  	return (*specs.Spec)(&s), nil
   222  }
   223  
   224  // Sets the Windows-specific fields of the OCI spec
   225  func (daemon *Daemon) createSpecWindowsFields(c *container.Container, s *specs.Spec, isHyperV bool) error {
   226  	if len(s.Process.Cwd) == 0 {
   227  		// We default to C:\ to workaround the oddity of the case that the
   228  		// default directory for cmd running as LocalSystem (or
   229  		// ContainerAdministrator) is c:\windows\system32. Hence docker run
   230  		// <image> cmd will by default end in c:\windows\system32, rather
   231  		// than 'root' (/) on Linux. The oddity is that if you have a dockerfile
   232  		// which has no WORKDIR and has a COPY file ., . will be interpreted
   233  		// as c:\. Hence, setting it to default of c:\ makes for consistency.
   234  		s.Process.Cwd = `C:\`
   235  	}
   236  
   237  	s.Root.Readonly = false // Windows does not support a read-only root filesystem
   238  	if !isHyperV {
   239  		if c.BaseFS == nil {
   240  			return errors.New("createSpecWindowsFields: BaseFS of container " + c.ID + " is unexpectedly nil")
   241  		}
   242  
   243  		s.Root.Path = c.BaseFS.Path() // This is not set for Hyper-V containers
   244  		if !strings.HasSuffix(s.Root.Path, `\`) {
   245  			s.Root.Path = s.Root.Path + `\` // Ensure a correctly formatted volume GUID path \\?\Volume{GUID}\
   246  		}
   247  	}
   248  
   249  	// First boot optimization
   250  	s.Windows.IgnoreFlushesDuringBoot = !c.HasBeenStartedBefore
   251  
   252  	// In s.Windows.Resources
   253  	cpuShares := uint16(c.HostConfig.CPUShares)
   254  	cpuMaximum := uint16(c.HostConfig.CPUPercent) * 100
   255  	cpuCount := uint64(c.HostConfig.CPUCount)
   256  	if c.HostConfig.NanoCPUs > 0 {
   257  		if isHyperV {
   258  			cpuCount = uint64(c.HostConfig.NanoCPUs / 1e9)
   259  			leftoverNanoCPUs := c.HostConfig.NanoCPUs % 1e9
   260  			if leftoverNanoCPUs != 0 {
   261  				cpuCount++
   262  				cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(cpuCount) / (1e9 / 10000))
   263  				if cpuMaximum < 1 {
   264  					// The requested NanoCPUs is so small that we rounded to 0, use 1 instead
   265  					cpuMaximum = 1
   266  				}
   267  			}
   268  		} else {
   269  			cpuMaximum = uint16(c.HostConfig.NanoCPUs / int64(sysinfo.NumCPU()) / (1e9 / 10000))
   270  			if cpuMaximum < 1 {
   271  				// The requested NanoCPUs is so small that we rounded to 0, use 1 instead
   272  				cpuMaximum = 1
   273  			}
   274  		}
   275  	}
   276  	memoryLimit := uint64(c.HostConfig.Memory)
   277  	s.Windows.Resources = &specs.WindowsResources{
   278  		CPU: &specs.WindowsCPUResources{
   279  			Maximum: &cpuMaximum,
   280  			Shares:  &cpuShares,
   281  			Count:   &cpuCount,
   282  		},
   283  		Memory: &specs.WindowsMemoryResources{
   284  			Limit: &memoryLimit,
   285  		},
   286  		Storage: &specs.WindowsStorageResources{
   287  			Bps:  &c.HostConfig.IOMaximumBandwidth,
   288  			Iops: &c.HostConfig.IOMaximumIOps,
   289  		},
   290  	}
   291  
   292  	// Read and add credentials from the security options if a credential spec has been provided.
   293  	if c.HostConfig.SecurityOpt != nil {
   294  		cs := ""
   295  		for _, sOpt := range c.HostConfig.SecurityOpt {
   296  			sOpt = strings.ToLower(sOpt)
   297  			if !strings.Contains(sOpt, "=") {
   298  				return fmt.Errorf("invalid security option: no equals sign in supplied value %s", sOpt)
   299  			}
   300  			var splitsOpt []string
   301  			splitsOpt = strings.SplitN(sOpt, "=", 2)
   302  			if len(splitsOpt) != 2 {
   303  				return fmt.Errorf("invalid security option: %s", sOpt)
   304  			}
   305  			if splitsOpt[0] != "credentialspec" {
   306  				return fmt.Errorf("security option not supported: %s", splitsOpt[0])
   307  			}
   308  
   309  			var (
   310  				match   bool
   311  				csValue string
   312  				err     error
   313  			)
   314  			if match, csValue = getCredentialSpec("file://", splitsOpt[1]); match {
   315  				if csValue == "" {
   316  					return fmt.Errorf("no value supplied for file:// credential spec security option")
   317  				}
   318  				if cs, err = readCredentialSpecFile(c.ID, daemon.root, filepath.Clean(csValue)); err != nil {
   319  					return err
   320  				}
   321  			} else if match, csValue = getCredentialSpec("registry://", splitsOpt[1]); match {
   322  				if csValue == "" {
   323  					return fmt.Errorf("no value supplied for registry:// credential spec security option")
   324  				}
   325  				if cs, err = readCredentialSpecRegistry(c.ID, csValue); err != nil {
   326  					return err
   327  				}
   328  			} else {
   329  				return fmt.Errorf("invalid credential spec security option - value must be prefixed file:// or registry:// followed by a value")
   330  			}
   331  		}
   332  		s.Windows.CredentialSpec = cs
   333  	}
   334  
   335  	return nil
   336  }
   337  
   338  // Sets the Linux-specific fields of the OCI spec
   339  // TODO: @jhowardmsft LCOW Support. We need to do a lot more pulling in what can
   340  // be pulled in from oci_linux.go.
   341  func (daemon *Daemon) createSpecLinuxFields(c *container.Container, s *specs.Spec) error {
   342  	if len(s.Process.Cwd) == 0 {
   343  		s.Process.Cwd = `/`
   344  	}
   345  	s.Root.Path = "rootfs"
   346  	s.Root.Readonly = c.HostConfig.ReadonlyRootfs
   347  	if err := setCapabilities(s, c); err != nil {
   348  		return fmt.Errorf("linux spec capabilities: %v", err)
   349  	}
   350  	devPermissions, err := appendDevicePermissionsFromCgroupRules(nil, c.HostConfig.DeviceCgroupRules)
   351  	if err != nil {
   352  		return fmt.Errorf("linux runtime spec devices: %v", err)
   353  	}
   354  	s.Linux.Resources.Devices = devPermissions
   355  	return nil
   356  }
   357  
   358  func escapeArgs(args []string) []string {
   359  	escapedArgs := make([]string, len(args))
   360  	for i, a := range args {
   361  		escapedArgs[i] = windows.EscapeArg(a)
   362  	}
   363  	return escapedArgs
   364  }
   365  
   366  // mergeUlimits merge the Ulimits from HostConfig with daemon defaults, and update HostConfig
   367  // It will do nothing on non-Linux platform
   368  func (daemon *Daemon) mergeUlimits(c *containertypes.HostConfig) {
   369  	return
   370  }
   371  
   372  // getCredentialSpec is a helper function to get the value of a credential spec supplied
   373  // on the CLI, stripping the prefix
   374  func getCredentialSpec(prefix, value string) (bool, string) {
   375  	if strings.HasPrefix(value, prefix) {
   376  		return true, strings.TrimPrefix(value, prefix)
   377  	}
   378  	return false, ""
   379  }
   380  
   381  // readCredentialSpecRegistry is a helper function to read a credential spec from
   382  // the registry. If not found, we return an empty string and warn in the log.
   383  // This allows for staging on machines which do not have the necessary components.
   384  func readCredentialSpecRegistry(id, name string) (string, error) {
   385  	var (
   386  		k   registry.Key
   387  		err error
   388  		val string
   389  	)
   390  	if k, err = registry.OpenKey(registry.LOCAL_MACHINE, credentialSpecRegistryLocation, registry.QUERY_VALUE); err != nil {
   391  		return "", fmt.Errorf("failed handling spec %q for container %s - %s could not be opened", name, id, credentialSpecRegistryLocation)
   392  	}
   393  	if val, _, err = k.GetStringValue(name); err != nil {
   394  		if err == registry.ErrNotExist {
   395  			return "", fmt.Errorf("credential spec %q for container %s as it was not found", name, id)
   396  		}
   397  		return "", fmt.Errorf("error %v reading credential spec %q from registry for container %s", err, name, id)
   398  	}
   399  	return val, nil
   400  }
   401  
   402  // readCredentialSpecFile is a helper function to read a credential spec from
   403  // a file. If not found, we return an empty string and warn in the log.
   404  // This allows for staging on machines which do not have the necessary components.
   405  func readCredentialSpecFile(id, root, location string) (string, error) {
   406  	if filepath.IsAbs(location) {
   407  		return "", fmt.Errorf("invalid credential spec - file:// path cannot be absolute")
   408  	}
   409  	base := filepath.Join(root, credentialSpecFileLocation)
   410  	full := filepath.Join(base, location)
   411  	if !strings.HasPrefix(full, base) {
   412  		return "", fmt.Errorf("invalid credential spec - file:// path must be under %s", base)
   413  	}
   414  	bcontents, err := ioutil.ReadFile(full)
   415  	if err != nil {
   416  		return "", fmt.Errorf("credential spec '%s' for container %s as the file could not be read: %q", full, id, err)
   417  	}
   418  	return string(bcontents[:]), nil
   419  }