daemon_windows.go 19 KB

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