daemon_windows.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600
  1. package daemon // import "github.com/docker/docker/daemon"
  2. import (
  3. "context"
  4. "fmt"
  5. "math"
  6. "path/filepath"
  7. "runtime"
  8. "strings"
  9. "github.com/Microsoft/hcsshim"
  10. "github.com/Microsoft/hcsshim/osversion"
  11. "github.com/containerd/containerd/log"
  12. containertypes "github.com/docker/docker/api/types/container"
  13. "github.com/docker/docker/container"
  14. "github.com/docker/docker/daemon/config"
  15. "github.com/docker/docker/libcontainerd/local"
  16. "github.com/docker/docker/libcontainerd/remote"
  17. "github.com/docker/docker/libnetwork"
  18. nwconfig "github.com/docker/docker/libnetwork/config"
  19. "github.com/docker/docker/libnetwork/datastore"
  20. winlibnetwork "github.com/docker/docker/libnetwork/drivers/windows"
  21. "github.com/docker/docker/libnetwork/netlabel"
  22. "github.com/docker/docker/libnetwork/options"
  23. "github.com/docker/docker/pkg/idtools"
  24. "github.com/docker/docker/pkg/parsers"
  25. "github.com/docker/docker/pkg/parsers/operatingsystem"
  26. "github.com/docker/docker/pkg/sysinfo"
  27. "github.com/docker/docker/pkg/system"
  28. "github.com/docker/docker/runconfig"
  29. "github.com/pkg/errors"
  30. "golang.org/x/sys/windows"
  31. "golang.org/x/sys/windows/svc/mgr"
  32. )
  33. const (
  34. isWindows = true
  35. windowsMinCPUShares = 1
  36. windowsMaxCPUShares = 10000
  37. windowsMinCPUPercent = 1
  38. windowsMaxCPUPercent = 100
  39. windowsV1RuntimeName = "com.docker.hcsshim.v1"
  40. windowsV2RuntimeName = "io.containerd.runhcs.v1"
  41. )
  42. // Windows containers are much larger than Linux containers and each of them
  43. // have > 20 system processes which why we use much smaller parallelism value.
  44. func adjustParallelLimit(n int, limit int) int {
  45. return int(math.Max(1, math.Floor(float64(runtime.NumCPU())*.8)))
  46. }
  47. // Windows has no concept of an execution state directory. So use config.Root here.
  48. func getPluginExecRoot(cfg *config.Config) string {
  49. return filepath.Join(cfg.Root, "plugins")
  50. }
  51. func (daemon *Daemon) parseSecurityOpt(daemonCfg *config.Config, securityOptions *container.SecurityOptions, hostConfig *containertypes.HostConfig) error {
  52. return nil
  53. }
  54. func setupInitLayer(idMapping idtools.IdentityMapping) func(string) error {
  55. return nil
  56. }
  57. // adaptContainerSettings is called during container creation to modify any
  58. // settings necessary in the HostConfig structure.
  59. func (daemon *Daemon) adaptContainerSettings(daemonCfg *config.Config, hostConfig *containertypes.HostConfig, adjustCPUShares bool) error {
  60. return nil
  61. }
  62. // verifyPlatformContainerResources performs platform-specific validation of the container's resource-configuration
  63. func verifyPlatformContainerResources(resources *containertypes.Resources, isHyperv bool) (warnings []string, err error) {
  64. fixMemorySwappiness(resources)
  65. if !isHyperv {
  66. // The processor resource controls are mutually exclusive on
  67. // Windows Server Containers, the order of precedence is
  68. // CPUCount first, then CPUShares, and CPUPercent last.
  69. if resources.CPUCount > 0 {
  70. if resources.CPUShares > 0 {
  71. warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU shares on Windows Server Containers. CPU shares discarded")
  72. resources.CPUShares = 0
  73. }
  74. if resources.CPUPercent > 0 {
  75. warnings = append(warnings, "Conflicting options: CPU count takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
  76. resources.CPUPercent = 0
  77. }
  78. } else if resources.CPUShares > 0 {
  79. if resources.CPUPercent > 0 {
  80. warnings = append(warnings, "Conflicting options: CPU shares takes priority over CPU percent on Windows Server Containers. CPU percent discarded")
  81. resources.CPUPercent = 0
  82. }
  83. }
  84. }
  85. if resources.CPUShares < 0 || resources.CPUShares > windowsMaxCPUShares {
  86. return warnings, fmt.Errorf("range of CPUShares is from %d to %d", windowsMinCPUShares, windowsMaxCPUShares)
  87. }
  88. if resources.CPUPercent < 0 || resources.CPUPercent > windowsMaxCPUPercent {
  89. return warnings, fmt.Errorf("range of CPUPercent is from %d to %d", windowsMinCPUPercent, windowsMaxCPUPercent)
  90. }
  91. if resources.CPUCount < 0 {
  92. return warnings, fmt.Errorf("invalid CPUCount: CPUCount cannot be negative")
  93. }
  94. if resources.NanoCPUs > 0 && resources.CPUPercent > 0 {
  95. return warnings, fmt.Errorf("conflicting options: Nano CPUs and CPU Percent cannot both be set")
  96. }
  97. if resources.NanoCPUs > 0 && resources.CPUShares > 0 {
  98. return warnings, fmt.Errorf("conflicting options: Nano CPUs and CPU Shares cannot both be set")
  99. }
  100. // The precision we could get is 0.01, because on Windows we have to convert to CPUPercent.
  101. // We don't set the lower limit here and it is up to the underlying platform (e.g., Windows) to return an error.
  102. if resources.NanoCPUs < 0 || resources.NanoCPUs > int64(sysinfo.NumCPU())*1e9 {
  103. return warnings, fmt.Errorf("range of CPUs is from 0.01 to %d.00, as there are only %d CPUs available", sysinfo.NumCPU(), sysinfo.NumCPU())
  104. }
  105. if len(resources.BlkioDeviceReadBps) > 0 {
  106. return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceReadBps")
  107. }
  108. if len(resources.BlkioDeviceReadIOps) > 0 {
  109. return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceReadIOps")
  110. }
  111. if len(resources.BlkioDeviceWriteBps) > 0 {
  112. return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceWriteBps")
  113. }
  114. if len(resources.BlkioDeviceWriteIOps) > 0 {
  115. return warnings, fmt.Errorf("invalid option: Windows does not support BlkioDeviceWriteIOps")
  116. }
  117. if resources.BlkioWeight > 0 {
  118. return warnings, fmt.Errorf("invalid option: Windows does not support BlkioWeight")
  119. }
  120. if len(resources.BlkioWeightDevice) > 0 {
  121. return warnings, fmt.Errorf("invalid option: Windows does not support BlkioWeightDevice")
  122. }
  123. if resources.CgroupParent != "" {
  124. return warnings, fmt.Errorf("invalid option: Windows does not support CgroupParent")
  125. }
  126. if resources.CPUPeriod != 0 {
  127. return warnings, fmt.Errorf("invalid option: Windows does not support CPUPeriod")
  128. }
  129. if resources.CpusetCpus != "" {
  130. return warnings, fmt.Errorf("invalid option: Windows does not support CpusetCpus")
  131. }
  132. if resources.CpusetMems != "" {
  133. return warnings, fmt.Errorf("invalid option: Windows does not support CpusetMems")
  134. }
  135. if resources.KernelMemory != 0 {
  136. return warnings, fmt.Errorf("invalid option: Windows does not support KernelMemory")
  137. }
  138. if resources.MemoryReservation != 0 {
  139. return warnings, fmt.Errorf("invalid option: Windows does not support MemoryReservation")
  140. }
  141. if resources.MemorySwap != 0 {
  142. return warnings, fmt.Errorf("invalid option: Windows does not support MemorySwap")
  143. }
  144. if resources.MemorySwappiness != nil {
  145. return warnings, fmt.Errorf("invalid option: Windows does not support MemorySwappiness")
  146. }
  147. if resources.OomKillDisable != nil && *resources.OomKillDisable {
  148. return warnings, fmt.Errorf("invalid option: Windows does not support OomKillDisable")
  149. }
  150. if resources.PidsLimit != nil && *resources.PidsLimit != 0 {
  151. return warnings, fmt.Errorf("invalid option: Windows does not support PidsLimit")
  152. }
  153. if len(resources.Ulimits) != 0 {
  154. return warnings, fmt.Errorf("invalid option: Windows does not support Ulimits")
  155. }
  156. return warnings, nil
  157. }
  158. // verifyPlatformContainerSettings performs platform-specific validation of the
  159. // hostconfig and config structures.
  160. func verifyPlatformContainerSettings(daemon *Daemon, daemonCfg *configStore, hostConfig *containertypes.HostConfig, update bool) (warnings []string, err error) {
  161. if hostConfig == nil {
  162. return nil, nil
  163. }
  164. return verifyPlatformContainerResources(&hostConfig.Resources, daemon.runAsHyperVContainer(hostConfig))
  165. }
  166. // verifyDaemonSettings performs validation of daemon config struct
  167. func verifyDaemonSettings(config *config.Config) error {
  168. return nil
  169. }
  170. // checkSystem validates platform-specific requirements
  171. func checkSystem() error {
  172. // Validate the OS version. Note that dockerd.exe must be manifested for this
  173. // call to return the correct version.
  174. if osversion.Get().MajorVersion < 10 || osversion.Build() < osversion.RS5 {
  175. return fmt.Errorf("this version of Windows does not support the docker daemon (Windows build %d or higher is required)", osversion.RS5)
  176. }
  177. vmcompute := windows.NewLazySystemDLL("vmcompute.dll")
  178. if vmcompute.Load() != nil {
  179. return fmt.Errorf("failed to load vmcompute.dll, ensure that the Containers feature is installed")
  180. }
  181. // Ensure that the required Host Network Service and vmcompute services
  182. // are running. Docker will fail in unexpected ways if this is not present.
  183. requiredServices := []string{"hns", "vmcompute"}
  184. if err := ensureServicesInstalled(requiredServices); err != nil {
  185. return errors.Wrap(err, "a required service is not installed, ensure the Containers feature is installed")
  186. }
  187. return nil
  188. }
  189. func ensureServicesInstalled(services []string) error {
  190. m, err := mgr.Connect()
  191. if err != nil {
  192. return err
  193. }
  194. defer m.Disconnect()
  195. for _, service := range services {
  196. s, err := m.OpenService(service)
  197. if err != nil {
  198. return errors.Wrapf(err, "failed to open service %s", service)
  199. }
  200. s.Close()
  201. }
  202. return nil
  203. }
  204. // configureKernelSecuritySupport configures and validate security support for the kernel
  205. func configureKernelSecuritySupport(config *config.Config, driverName string) error {
  206. return nil
  207. }
  208. // configureMaxThreads sets the Go runtime max threads threshold
  209. func configureMaxThreads(config *config.Config) error {
  210. return nil
  211. }
  212. func (daemon *Daemon) initNetworkController(daemonCfg *config.Config, activeSandboxes map[string]interface{}) error {
  213. netOptions, err := daemon.networkOptions(daemonCfg, nil, nil)
  214. if err != nil {
  215. return err
  216. }
  217. daemon.netController, err = libnetwork.New(netOptions...)
  218. if err != nil {
  219. return errors.Wrap(err, "error obtaining controller instance")
  220. }
  221. hnsresponse, err := hcsshim.HNSListNetworkRequest("GET", "", "")
  222. if err != nil {
  223. return err
  224. }
  225. // Remove networks not present in HNS
  226. for _, v := range daemon.netController.Networks() {
  227. hnsid := v.Info().DriverOptions()[winlibnetwork.HNSID]
  228. found := false
  229. for _, v := range hnsresponse {
  230. if v.Id == hnsid {
  231. found = true
  232. break
  233. }
  234. }
  235. if !found {
  236. // non-default nat networks should be re-created if missing from HNS
  237. if v.Type() == "nat" && v.Name() != "nat" {
  238. _, _, v4Conf, v6Conf := v.Info().IpamConfig()
  239. netOption := map[string]string{}
  240. for k, v := range v.Info().DriverOptions() {
  241. if k != winlibnetwork.NetworkName && k != winlibnetwork.HNSID {
  242. netOption[k] = v
  243. }
  244. }
  245. name := v.Name()
  246. id := v.ID()
  247. err = v.Delete()
  248. if err != nil {
  249. log.G(context.TODO()).Errorf("Error occurred when removing network %v", err)
  250. }
  251. _, err := daemon.netController.NewNetwork("nat", name, id,
  252. libnetwork.NetworkOptionGeneric(options.Generic{
  253. netlabel.GenericData: netOption,
  254. }),
  255. libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil),
  256. )
  257. if err != nil {
  258. log.G(context.TODO()).Errorf("Error occurred when creating network %v", err)
  259. }
  260. continue
  261. }
  262. // global networks should not be deleted by local HNS
  263. if v.Info().Scope() != datastore.GlobalScope {
  264. err = v.Delete()
  265. if err != nil {
  266. log.G(context.TODO()).Errorf("Error occurred when removing network %v", err)
  267. }
  268. }
  269. }
  270. }
  271. _, err = daemon.netController.NewNetwork("null", "none", "", libnetwork.NetworkOptionPersist(false))
  272. if err != nil {
  273. return err
  274. }
  275. defaultNetworkExists := false
  276. if network, err := daemon.netController.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil {
  277. hnsid := network.Info().DriverOptions()[winlibnetwork.HNSID]
  278. for _, v := range hnsresponse {
  279. if hnsid == v.Id {
  280. defaultNetworkExists = true
  281. break
  282. }
  283. }
  284. }
  285. // discover and add HNS networks to windows
  286. // network that exist are removed and added again
  287. for _, v := range hnsresponse {
  288. networkTypeNorm := strings.ToLower(v.Type)
  289. if networkTypeNorm == "private" || networkTypeNorm == "internal" {
  290. continue // workaround for HNS reporting unsupported networks
  291. }
  292. var n *libnetwork.Network
  293. s := func(current *libnetwork.Network) bool {
  294. hnsid := current.Info().DriverOptions()[winlibnetwork.HNSID]
  295. if hnsid == v.Id {
  296. n = current
  297. return true
  298. }
  299. return false
  300. }
  301. daemon.netController.WalkNetworks(s)
  302. drvOptions := make(map[string]string)
  303. nid := ""
  304. if n != nil {
  305. nid = n.ID()
  306. // global networks should not be deleted by local HNS
  307. if n.Info().Scope() == datastore.GlobalScope {
  308. continue
  309. }
  310. v.Name = n.Name()
  311. // This will not cause network delete from HNS as the network
  312. // is not yet populated in the libnetwork windows driver
  313. // restore option if it existed before
  314. drvOptions = n.Info().DriverOptions()
  315. n.Delete()
  316. }
  317. netOption := map[string]string{
  318. winlibnetwork.NetworkName: v.Name,
  319. winlibnetwork.HNSID: v.Id,
  320. }
  321. // add persisted driver options
  322. for k, v := range drvOptions {
  323. if k != winlibnetwork.NetworkName && k != winlibnetwork.HNSID {
  324. netOption[k] = v
  325. }
  326. }
  327. v4Conf := []*libnetwork.IpamConf{}
  328. for _, subnet := range v.Subnets {
  329. ipamV4Conf := libnetwork.IpamConf{}
  330. ipamV4Conf.PreferredPool = subnet.AddressPrefix
  331. ipamV4Conf.Gateway = subnet.GatewayAddress
  332. v4Conf = append(v4Conf, &ipamV4Conf)
  333. }
  334. name := v.Name
  335. // If there is no nat network create one from the first NAT network
  336. // encountered if it doesn't already exist
  337. if !defaultNetworkExists &&
  338. runconfig.DefaultDaemonNetworkMode() == containertypes.NetworkMode(strings.ToLower(v.Type)) &&
  339. n == nil {
  340. name = runconfig.DefaultDaemonNetworkMode().NetworkName()
  341. defaultNetworkExists = true
  342. }
  343. v6Conf := []*libnetwork.IpamConf{}
  344. _, err := daemon.netController.NewNetwork(strings.ToLower(v.Type), name, nid,
  345. libnetwork.NetworkOptionGeneric(options.Generic{
  346. netlabel.GenericData: netOption,
  347. }),
  348. libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil),
  349. )
  350. if err != nil {
  351. log.G(context.TODO()).Errorf("Error occurred when creating network %v", err)
  352. }
  353. }
  354. if !daemonCfg.DisableBridge {
  355. // Initialize default driver "bridge"
  356. if err := initBridgeDriver(daemon.netController, daemonCfg.BridgeConfig); err != nil {
  357. return err
  358. }
  359. }
  360. return nil
  361. }
  362. func initBridgeDriver(controller *libnetwork.Controller, config config.BridgeConfig) error {
  363. if _, err := controller.NetworkByName(runconfig.DefaultDaemonNetworkMode().NetworkName()); err == nil {
  364. return nil
  365. }
  366. netOption := map[string]string{
  367. winlibnetwork.NetworkName: runconfig.DefaultDaemonNetworkMode().NetworkName(),
  368. }
  369. var ipamOption libnetwork.NetworkOption
  370. var subnetPrefix string
  371. if config.FixedCIDR != "" {
  372. subnetPrefix = config.FixedCIDR
  373. }
  374. if subnetPrefix != "" {
  375. ipamV4Conf := libnetwork.IpamConf{PreferredPool: subnetPrefix}
  376. v4Conf := []*libnetwork.IpamConf{&ipamV4Conf}
  377. v6Conf := []*libnetwork.IpamConf{}
  378. ipamOption = libnetwork.NetworkOptionIpam("default", "", v4Conf, v6Conf, nil)
  379. }
  380. _, err := controller.NewNetwork(string(runconfig.DefaultDaemonNetworkMode()), runconfig.DefaultDaemonNetworkMode().NetworkName(), "",
  381. libnetwork.NetworkOptionGeneric(options.Generic{
  382. netlabel.GenericData: netOption,
  383. }),
  384. ipamOption,
  385. )
  386. if err != nil {
  387. return errors.Wrap(err, "error creating default network")
  388. }
  389. return nil
  390. }
  391. // registerLinks sets up links between containers and writes the
  392. // configuration out for persistence. As of Windows TP4, links are not supported.
  393. func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *containertypes.HostConfig) error {
  394. return nil
  395. }
  396. func (daemon *Daemon) cleanupMountsByID(in string) error {
  397. return nil
  398. }
  399. func (daemon *Daemon) cleanupMounts(*config.Config) error {
  400. return nil
  401. }
  402. func recursiveUnmount(_ string) error {
  403. return nil
  404. }
  405. func setupRemappedRoot(config *config.Config) (idtools.IdentityMapping, error) {
  406. return idtools.IdentityMapping{}, nil
  407. }
  408. func setupDaemonRoot(config *config.Config, rootDir string, rootIdentity idtools.Identity) error {
  409. config.Root = rootDir
  410. // Create the root directory if it doesn't exists
  411. if err := system.MkdirAllWithACL(config.Root, 0, system.SddlAdministratorsLocalSystem); err != nil {
  412. return err
  413. }
  414. return nil
  415. }
  416. // runasHyperVContainer returns true if we are going to run as a Hyper-V container
  417. func (daemon *Daemon) runAsHyperVContainer(hostConfig *containertypes.HostConfig) bool {
  418. if hostConfig.Isolation.IsDefault() {
  419. // Container is set to use the default, so take the default from the daemon configuration
  420. return daemon.defaultIsolation.IsHyperV()
  421. }
  422. // Container is requesting an isolation mode. Honour it.
  423. return hostConfig.Isolation.IsHyperV()
  424. }
  425. // conditionalMountOnStart is a platform specific helper function during the
  426. // container start to call mount.
  427. func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
  428. if daemon.runAsHyperVContainer(container.HostConfig) {
  429. // We do not mount if a Hyper-V container as it needs to be mounted inside the
  430. // utility VM, not the host.
  431. return nil
  432. }
  433. return daemon.Mount(container)
  434. }
  435. // conditionalUnmountOnCleanup is a platform specific helper function called
  436. // during the cleanup of a container to unmount.
  437. func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
  438. if daemon.runAsHyperVContainer(container.HostConfig) {
  439. // We do not unmount if a Hyper-V container
  440. return nil
  441. }
  442. return daemon.Unmount(container)
  443. }
  444. func driverOptions(_ *config.Config) nwconfig.Option {
  445. return nil
  446. }
  447. // setDefaultIsolation determine the default isolation mode for the
  448. // daemon to run in. This is only applicable on Windows
  449. func (daemon *Daemon) setDefaultIsolation(config *config.Config) error {
  450. // On client SKUs, default to Hyper-V. @engine maintainers. This
  451. // should not be removed. Ping Microsoft folks is there are PRs to
  452. // to change this.
  453. if operatingsystem.IsWindowsClient() {
  454. daemon.defaultIsolation = containertypes.IsolationHyperV
  455. } else {
  456. daemon.defaultIsolation = containertypes.IsolationProcess
  457. }
  458. for _, option := range config.ExecOptions {
  459. key, val, err := parsers.ParseKeyValueOpt(option)
  460. if err != nil {
  461. return err
  462. }
  463. key = strings.ToLower(key)
  464. switch key {
  465. case "isolation":
  466. if !containertypes.Isolation(val).IsValid() {
  467. return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val)
  468. }
  469. if containertypes.Isolation(val).IsHyperV() {
  470. daemon.defaultIsolation = containertypes.IsolationHyperV
  471. }
  472. if containertypes.Isolation(val).IsProcess() {
  473. daemon.defaultIsolation = containertypes.IsolationProcess
  474. }
  475. default:
  476. return fmt.Errorf("Unrecognised exec-opt '%s'\n", key)
  477. }
  478. }
  479. log.G(context.TODO()).Infof("Windows default isolation mode: %s", daemon.defaultIsolation)
  480. return nil
  481. }
  482. func setMayDetachMounts() error {
  483. return nil
  484. }
  485. func (daemon *Daemon) setupSeccompProfile(*config.Config) error {
  486. return nil
  487. }
  488. func setupResolvConf(config *config.Config) {}
  489. func getSysInfo(*config.Config) *sysinfo.SysInfo {
  490. return sysinfo.New()
  491. }
  492. func (daemon *Daemon) initLibcontainerd(ctx context.Context, cfg *config.Config) error {
  493. var err error
  494. rt := cfg.DefaultRuntime
  495. if rt == "" {
  496. if cfg.ContainerdAddr == "" {
  497. rt = windowsV1RuntimeName
  498. } else {
  499. rt = windowsV2RuntimeName
  500. }
  501. }
  502. switch rt {
  503. case windowsV1RuntimeName:
  504. daemon.containerd, err = local.NewClient(
  505. ctx,
  506. daemon.containerdClient,
  507. filepath.Join(cfg.ExecRoot, "containerd"),
  508. cfg.ContainerdNamespace,
  509. daemon,
  510. )
  511. case windowsV2RuntimeName:
  512. if cfg.ContainerdAddr == "" {
  513. return fmt.Errorf("cannot use the specified runtime %q without containerd", rt)
  514. }
  515. daemon.containerd, err = remote.NewClient(
  516. ctx,
  517. daemon.containerdClient,
  518. filepath.Join(cfg.ExecRoot, "containerd"),
  519. cfg.ContainerdNamespace,
  520. daemon,
  521. )
  522. default:
  523. return fmt.Errorf("unknown windows runtime %s", rt)
  524. }
  525. return err
  526. }