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 }