diff --git a/drivers/docker/config.go b/drivers/docker/config.go index 134c9b75e6ad..5d63cbaac993 100644 --- a/drivers/docker/config.go +++ b/drivers/docker/config.go @@ -300,6 +300,7 @@ var ( "cap_add": hclspec.NewAttr("cap_add", "list(string)", false), "cap_drop": hclspec.NewAttr("cap_drop", "list(string)", false), "command": hclspec.NewAttr("command", "string", false), + "cpuset_cpus": hclspec.NewAttr("cpuset_cpus", "string", false), "cpu_hard_limit": hclspec.NewAttr("cpu_hard_limit", "bool", false), "cpu_cfs_period": hclspec.NewDefault( hclspec.NewAttr("cpu_cfs_period", "number", false), @@ -407,6 +408,7 @@ type TaskConfig struct { Command string `codec:"command"` CPUCFSPeriod int64 `codec:"cpu_cfs_period"` CPUHardLimit bool `codec:"cpu_hard_limit"` + CPUSetCPUs string `codec:"cpuset_cpus"` Devices []DockerDevice `codec:"devices"` DNSSearchDomains []string `codec:"dns_search_domains"` DNSOptions []string `codec:"dns_options"` diff --git a/drivers/docker/driver.go b/drivers/docker/driver.go index 0cac7e460481..6a8a6833cc25 100644 --- a/drivers/docker/driver.go +++ b/drivers/docker/driver.go @@ -823,6 +823,12 @@ func (d *Driver) createContainerConfig(task *drivers.TaskConfig, driverConfig *T Runtime: containerRuntime, } + // This translates to docker create/run --cpuset-cpus option. + // --cpuset-cpus limit the specific CPUs or cores a container can use. + if driverConfig.CPUSetCPUs != "" { + hostConfig.CPUSetCPUs = driverConfig.CPUSetCPUs + } + // Calculate CPU Quota // cfs_quota_us is the time per core, so we must // multiply the time by the number of cores available diff --git a/drivers/docker/driver_test.go b/drivers/docker/driver_test.go index 400d86e21561..5bfb8d6eec68 100644 --- a/drivers/docker/driver_test.go +++ b/drivers/docker/driver_test.go @@ -1381,6 +1381,53 @@ func TestDockerDriver_DNS(t *testing.T) { } +func TestDockerDriver_CPUSetCPUs(t *testing.T) { + if !tu.IsCI() { + t.Parallel() + } + testutil.DockerCompatible(t) + if runtime.GOOS == "windows" { + t.Skip("Windows does not support CPUSetCPUs.") + } + + testCases := []struct { + Name string + CPUSetCPUs string + }{ + { + Name: "Single CPU", + CPUSetCPUs: "0", + }, + { + Name: "Comma separated list of CPUs", + CPUSetCPUs: "0,1", + }, + { + Name: "Range of CPUs", + CPUSetCPUs: "0-1", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + task, cfg, ports := dockerTask(t) + defer freeport.Return(ports) + + cfg.CPUSetCPUs = testCase.CPUSetCPUs + require.NoError(t, task.EncodeConcreteDriverConfig(cfg)) + + client, d, handle, cleanup := dockerSetup(t, task, nil) + defer cleanup() + require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second)) + + container, err := client.InspectContainer(handle.containerID) + require.NoError(t, err) + + require.Equal(t, cfg.CPUSetCPUs, container.HostConfig.CPUSetCPUs) + }) + } +} + func TestDockerDriver_MemoryHardLimit(t *testing.T) { if !tu.IsCI() { t.Parallel() diff --git a/website/pages/docs/drivers/docker.mdx b/website/pages/docs/drivers/docker.mdx index 271fb5eb6650..9d75f9dcab1a 100644 --- a/website/pages/docs/drivers/docker.mdx +++ b/website/pages/docs/drivers/docker.mdx @@ -77,6 +77,20 @@ The `docker` driver supports the following configuration in the job spec. Only command = "my-command" } ``` +- `cpuset_cpus` - (Optional) CPUs in which to allow execution (0-3, 0,1). +Limit the specific CPUs or cores a container can use. A comma-separated list +or hyphen-separated range of CPUs a container can use, if you have more than +one CPU. The first CPU is numbered 0. A valid value might be 0-3 (to use the +first, second, third, and fourth CPU) or 1,3 (to use the second and fourth CPU). + +Note: `cpuset_cpus` pins the workload to the CPUs but doesn't give the workload +exclusive access to those CPUs. + + ```hcl + config { + cpuset_cpus = "0-3" + } + ``` - `dns_search_domains` - (Optional) A list of DNS search domains for the container to use.