diff --git a/components/engine/cli/command/container/update.go b/components/engine/cli/command/container/update.go index 283cd3314e..a650815e8e 100644 --- a/components/engine/cli/command/container/update.go +++ b/components/engine/cli/command/container/update.go @@ -28,6 +28,7 @@ type updateOptions struct { memorySwap opts.MemSwapBytes kernelMemory opts.MemBytes restartPolicy string + cpus opts.NanoCPUs nFlag int @@ -66,6 +67,9 @@ func NewUpdateCommand(dockerCli *command.DockerCli) *cobra.Command { flags.Var(&opts.kernelMemory, "kernel-memory", "Kernel memory limit") flags.StringVar(&opts.restartPolicy, "restart", "", "Restart policy to apply when a container exits") + flags.Var(&opts.cpus, "cpus", "Number of CPUs") + flags.SetAnnotation("cpus", "version", []string{"1.29"}) + return cmd } @@ -97,6 +101,7 @@ func runUpdate(dockerCli *command.DockerCli, opts *updateOptions) error { CPUQuota: opts.cpuQuota, CPURealtimePeriod: opts.cpuRealtimePeriod, CPURealtimeRuntime: opts.cpuRealtimeRuntime, + NanoCPUs: opts.cpus.Value(), } updateConfig := containertypes.UpdateConfig{ diff --git a/components/engine/container/container_unix.go b/components/engine/container/container_unix.go index 2e43342983..bd497a9260 100644 --- a/components/engine/container/container_unix.go +++ b/components/engine/container/container_unix.go @@ -286,12 +286,33 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi // update resources of container resources := hostConfig.Resources cResources := &container.HostConfig.Resources + + // validate NanoCPUs, CPUPeriod, and CPUQuota + // Becuase NanoCPU effectively updates CPUPeriod/CPUQuota, + // once NanoCPU is already set, updating CPUPeriod/CPUQuota will be blocked, and vice versa. + // In the following we make sure the intended update (resources) does not conflict with the existing (cResource). + if resources.NanoCPUs > 0 && cResources.CPUPeriod > 0 { + return fmt.Errorf("Conflicting options: Nano CPUs cannot be updated as CPU Period has already been set") + } + if resources.NanoCPUs > 0 && cResources.CPUQuota > 0 { + return fmt.Errorf("Conflicting options: Nano CPUs cannot be updated as CPU Quota has already been set") + } + if resources.CPUPeriod > 0 && cResources.NanoCPUs > 0 { + return fmt.Errorf("Conflicting options: CPU Period cannot be updated as NanoCPUs has already been set") + } + if resources.CPUQuota > 0 && cResources.NanoCPUs > 0 { + return fmt.Errorf("Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set") + } + if resources.BlkioWeight != 0 { cResources.BlkioWeight = resources.BlkioWeight } if resources.CPUShares != 0 { cResources.CPUShares = resources.CPUShares } + if resources.NanoCPUs != 0 { + cResources.NanoCPUs = resources.NanoCPUs + } if resources.CPUPeriod != 0 { cResources.CPUPeriod = resources.CPUPeriod } diff --git a/components/engine/daemon/update_linux.go b/components/engine/daemon/update_linux.go index f422325272..c128967218 100644 --- a/components/engine/daemon/update_linux.go +++ b/components/engine/daemon/update_linux.go @@ -3,6 +3,8 @@ package daemon import ( + "time" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/libcontainerd" ) @@ -11,8 +13,13 @@ func toContainerdResources(resources container.Resources) libcontainerd.Resource var r libcontainerd.Resources r.BlkioWeight = uint64(resources.BlkioWeight) r.CpuShares = uint64(resources.CPUShares) - r.CpuPeriod = uint64(resources.CPUPeriod) - r.CpuQuota = uint64(resources.CPUQuota) + if resources.NanoCPUs != 0 { + r.CpuPeriod = uint64(100 * time.Millisecond / time.Microsecond) + r.CpuQuota = uint64(resources.NanoCPUs) * r.CpuPeriod / 1e9 + } else { + r.CpuPeriod = uint64(resources.CPUPeriod) + r.CpuQuota = uint64(resources.CPUQuota) + } r.CpusetCpus = resources.CpusetCpus r.CpusetMems = resources.CpusetMems r.MemoryLimit = uint64(resources.Memory) diff --git a/components/engine/docs/api/version-history.md b/components/engine/docs/api/version-history.md index 7acdec2b8b..5bf2460313 100644 --- a/components/engine/docs/api/version-history.md +++ b/components/engine/docs/api/version-history.md @@ -39,6 +39,7 @@ keywords: "API, Docker, rcli, REST, documentation" * `POST /services/create` and `POST /services/(id or name)/update` now accept a `rollback` value for `FailureAction`. * `POST /services/create` and `POST /services/(id or name)/update` now accept an optional `RollbackConfig` object which specifies rollback options. * `GET /services` now supports a `mode` filter to filter services based on the service mode (either `global` or `replicated`). +* `POST /containers/(name)/update` now supports updating `NanoCPUs` that represents CPU quota in units of 10-9 CPUs. ## v1.27 API changes diff --git a/components/engine/docs/reference/commandline/update.md b/components/engine/docs/reference/commandline/update.md index f41cf39db8..935dc9bf36 100644 --- a/components/engine/docs/reference/commandline/update.md +++ b/components/engine/docs/reference/commandline/update.md @@ -21,12 +21,13 @@ Usage: docker update [OPTIONS] CONTAINER [CONTAINER...] Update configuration of one or more containers Options: - --blkio-weight value Block IO (relative weight), between 10 and 1000 + --blkio-weight uint16 Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0) --cpu-period int Limit CPU CFS (Completely Fair Scheduler) period --cpu-quota int Limit CPU CFS (Completely Fair Scheduler) quota - -c, --cpu-shares int CPU shares (relative weight) --cpu-rt-period int Limit the CPU real-time period in microseconds --cpu-rt-runtime int Limit the CPU real-time runtime in microseconds + -c, --cpu-shares int CPU shares (relative weight) + --cpus decimal Number of CPUs (default 0.000) --cpuset-cpus string CPUs in which to allow execution (0-3, 0,1) --cpuset-mems string MEMs in which to allow execution (0-3, 0,1) --help Print usage diff --git a/components/engine/integration-cli/docker_cli_update_unix_test.go b/components/engine/integration-cli/docker_cli_update_unix_test.go index 643ce0312a..be2274bb39 100644 --- a/components/engine/integration-cli/docker_cli_update_unix_test.go +++ b/components/engine/integration-cli/docker_cli_update_unix_test.go @@ -282,3 +282,38 @@ func (s *DockerSuite) TestUpdateNotAffectMonitorRestartPolicy(c *check.C) { c.Assert(err, checker.IsNil) c.Assert(waitRun(id), checker.IsNil) } + +func (s *DockerSuite) TestUpdateWithNanoCPUs(c *check.C) { + testRequires(c, cpuCfsQuota, cpuCfsPeriod) + + file1 := "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" + file2 := "/sys/fs/cgroup/cpu/cpu.cfs_period_us" + + out, _ := dockerCmd(c, "run", "-d", "--cpus", "0.5", "--name", "top", "busybox", "top") + c.Assert(strings.TrimSpace(out), checker.Not(checker.Equals), "") + + out, _ = dockerCmd(c, "exec", "top", "sh", "-c", fmt.Sprintf("cat %s && cat %s", file1, file2)) + c.Assert(strings.TrimSpace(out), checker.Equals, "50000\n100000") + + out = inspectField(c, "top", "HostConfig.NanoCpus") + c.Assert(out, checker.Equals, "5e+08", check.Commentf("setting the Nano CPUs failed")) + out = inspectField(c, "top", "HostConfig.CpuQuota") + c.Assert(out, checker.Equals, "0", check.Commentf("CPU CFS quota should be 0")) + out = inspectField(c, "top", "HostConfig.CpuPeriod") + c.Assert(out, checker.Equals, "0", check.Commentf("CPU CFS period should be 0")) + + out, _, err := dockerCmdWithError("update", "--cpu-quota", "80000", "top") + c.Assert(err, checker.NotNil) + c.Assert(out, checker.Contains, "Conflicting options: CPU Quota cannot be updated as NanoCPUs has already been set") + + out, _ = dockerCmd(c, "update", "--cpus", "0.8", "top") + out = inspectField(c, "top", "HostConfig.NanoCpus") + c.Assert(out, checker.Equals, "8e+08", check.Commentf("updating the Nano CPUs failed")) + out = inspectField(c, "top", "HostConfig.CpuQuota") + c.Assert(out, checker.Equals, "0", check.Commentf("CPU CFS quota should be 0")) + out = inspectField(c, "top", "HostConfig.CpuPeriod") + c.Assert(out, checker.Equals, "0", check.Commentf("CPU CFS period should be 0")) + + out, _ = dockerCmd(c, "exec", "top", "sh", "-c", fmt.Sprintf("cat %s && cat %s", file1, file2)) + c.Assert(strings.TrimSpace(out), checker.Equals, "80000\n100000") +}