diff --git a/.changelog/22419.txt b/.changelog/22419.txt new file mode 100644 index 000000000..39d22c80f --- /dev/null +++ b/.changelog/22419.txt @@ -0,0 +1,3 @@ +```release-note:improvement +docker: Added container_exists_attempts plugin configuration variable +``` diff --git a/drivers/docker/config.go b/drivers/docker/config.go index f3f9fb69f..2efab63a0 100644 --- a/drivers/docker/config.go +++ b/drivers/docker/config.go @@ -284,6 +284,11 @@ var ( hclspec.NewAttr("infra_image_pull_timeout", "string", false), hclspec.NewLiteral(`"5m"`), ), + // number of attempts to try to purge an existing container if it already exists + "container_exists_attempts": hclspec.NewDefault( + hclspec.NewAttr("container_exists_attempts", "number", false), + hclspec.NewLiteral(`5`), + ), // the duration that the driver will wait for activity from the Docker engine during an image pull // before canceling the request @@ -350,6 +355,7 @@ var ( hclspec.NewAttr("cpu_cfs_period", "number", false), hclspec.NewLiteral(`100000`), ), + "container_exists_attempts": hclspec.NewAttr("container_exists_attempts", "number", false), "devices": hclspec.NewBlockList("devices", hclspec.NewObject(map[string]*hclspec.Spec{ "host_path": hclspec.NewAttr("host_path", "string", false), "container_path": hclspec.NewAttr("container_path", "string", false), @@ -427,60 +433,61 @@ var ( ) type TaskConfig struct { - Image string `codec:"image"` - AdvertiseIPv6Addr bool `codec:"advertise_ipv6_address"` - Args []string `codec:"args"` - Auth DockerAuth `codec:"auth"` - AuthSoftFail bool `codec:"auth_soft_fail"` - CapAdd []string `codec:"cap_add"` - CapDrop []string `codec:"cap_drop"` - 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"` - DNSServers []string `codec:"dns_servers"` - Entrypoint []string `codec:"entrypoint"` - ExtraHosts []string `codec:"extra_hosts"` - ForcePull bool `codec:"force_pull"` - GroupAdd []string `codec:"group_add"` - Healthchecks DockerHealthchecks `codec:"healthchecks"` - Hostname string `codec:"hostname"` - Init bool `codec:"init"` - Interactive bool `codec:"interactive"` - IPCMode string `codec:"ipc_mode"` - IPv4Address string `codec:"ipv4_address"` - IPv6Address string `codec:"ipv6_address"` - Isolation string `codec:"isolation"` - Labels hclutils.MapStrStr `codec:"labels"` - LoadImage string `codec:"load"` - Logging DockerLogging `codec:"logging"` - MacAddress string `codec:"mac_address"` - MemoryHardLimit int64 `codec:"memory_hard_limit"` - Mounts []DockerMount `codec:"mount"` - NetworkAliases []string `codec:"network_aliases"` - NetworkMode string `codec:"network_mode"` - Runtime string `codec:"runtime"` - PidsLimit int64 `codec:"pids_limit"` - PidMode string `codec:"pid_mode"` - Ports []string `codec:"ports"` - PortMap hclutils.MapStrInt `codec:"port_map"` - Privileged bool `codec:"privileged"` - ImagePullTimeout string `codec:"image_pull_timeout"` - ReadonlyRootfs bool `codec:"readonly_rootfs"` - SecurityOpt []string `codec:"security_opt"` - ShmSize int64 `codec:"shm_size"` - StorageOpt map[string]string `codec:"storage_opt"` - Sysctl hclutils.MapStrStr `codec:"sysctl"` - TTY bool `codec:"tty"` - Ulimit hclutils.MapStrStr `codec:"ulimit"` - UTSMode string `codec:"uts_mode"` - UsernsMode string `codec:"userns_mode"` - Volumes []string `codec:"volumes"` - VolumeDriver string `codec:"volume_driver"` - WorkDir string `codec:"work_dir"` + Image string `codec:"image"` + AdvertiseIPv6Addr bool `codec:"advertise_ipv6_address"` + Args []string `codec:"args"` + Auth DockerAuth `codec:"auth"` + AuthSoftFail bool `codec:"auth_soft_fail"` + CapAdd []string `codec:"cap_add"` + CapDrop []string `codec:"cap_drop"` + Command string `codec:"command"` + ContainerExistsAttempts uint64 `codec:"container_exists_attempts"` + 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"` + DNSServers []string `codec:"dns_servers"` + Entrypoint []string `codec:"entrypoint"` + ExtraHosts []string `codec:"extra_hosts"` + ForcePull bool `codec:"force_pull"` + GroupAdd []string `codec:"group_add"` + Healthchecks DockerHealthchecks `codec:"healthchecks"` + Hostname string `codec:"hostname"` + Init bool `codec:"init"` + Interactive bool `codec:"interactive"` + IPCMode string `codec:"ipc_mode"` + IPv4Address string `codec:"ipv4_address"` + IPv6Address string `codec:"ipv6_address"` + Isolation string `codec:"isolation"` + Labels hclutils.MapStrStr `codec:"labels"` + LoadImage string `codec:"load"` + Logging DockerLogging `codec:"logging"` + MacAddress string `codec:"mac_address"` + MemoryHardLimit int64 `codec:"memory_hard_limit"` + Mounts []DockerMount `codec:"mount"` + NetworkAliases []string `codec:"network_aliases"` + NetworkMode string `codec:"network_mode"` + Runtime string `codec:"runtime"` + PidsLimit int64 `codec:"pids_limit"` + PidMode string `codec:"pid_mode"` + Ports []string `codec:"ports"` + PortMap hclutils.MapStrInt `codec:"port_map"` + Privileged bool `codec:"privileged"` + ImagePullTimeout string `codec:"image_pull_timeout"` + ReadonlyRootfs bool `codec:"readonly_rootfs"` + SecurityOpt []string `codec:"security_opt"` + ShmSize int64 `codec:"shm_size"` + StorageOpt map[string]string `codec:"storage_opt"` + Sysctl hclutils.MapStrStr `codec:"sysctl"` + TTY bool `codec:"tty"` + Ulimit hclutils.MapStrStr `codec:"ulimit"` + UTSMode string `codec:"uts_mode"` + UsernsMode string `codec:"userns_mode"` + Volumes []string `codec:"volumes"` + VolumeDriver string `codec:"volume_driver"` + WorkDir string `codec:"work_dir"` // MountsList supports the pre-1.0 mounts array syntax MountsList []DockerMount `codec:"mounts"` @@ -648,6 +655,7 @@ type DriverConfig struct { InfraImage string `codec:"infra_image"` InfraImagePullTimeout string `codec:"infra_image_pull_timeout"` infraImagePullTimeoutDuration time.Duration `codec:"-"` + ContainerExistsAttempts uint64 `codec:"container_exists_attempts"` DisableLogCollection bool `codec:"disable_log_collection"` PullActivityTimeout string `codec:"pull_activity_timeout"` PidsLimit int64 `codec:"pids_limit"` diff --git a/drivers/docker/config_test.go b/drivers/docker/config_test.go index f82f2a683..d20a7c278 100644 --- a/drivers/docker/config_test.go +++ b/drivers/docker/config_test.go @@ -215,6 +215,7 @@ config { cap_add = ["CAP_SYS_NICE"] cap_drop = ["CAP_SYS_ADMIN", "CAP_SYS_TIME"] command = "/bin/bash" + container_exists_attempts = 10 cpu_hard_limit = true cpu_cfs_period = 20 devices = [ @@ -351,8 +352,6 @@ config { }` expected := &TaskConfig{ - Image: "redis:7", - ImagePullTimeout: "15m", AdvertiseIPv6Addr: true, Args: []string{"command_arg1", "command_arg2"}, Auth: DockerAuth{ @@ -361,12 +360,13 @@ config { Email: "myemail@example.com", ServerAddr: "https://example.com", }, - AuthSoftFail: true, - CapAdd: []string{"CAP_SYS_NICE"}, - CapDrop: []string{"CAP_SYS_ADMIN", "CAP_SYS_TIME"}, - Command: "/bin/bash", - CPUHardLimit: true, - CPUCFSPeriod: 20, + AuthSoftFail: true, + CapAdd: []string{"CAP_SYS_NICE"}, + CapDrop: []string{"CAP_SYS_ADMIN", "CAP_SYS_TIME"}, + Command: "/bin/bash", + ContainerExistsAttempts: 10, + CPUHardLimit: true, + CPUCFSPeriod: 20, Devices: []DockerDevice{ { HostPath: "/dev/null", @@ -393,6 +393,8 @@ config { GroupAdd: []string{"group1", "group2"}, Healthchecks: DockerHealthchecks{Disable: true}, Hostname: "self.example.com", + Image: "redis:7", + ImagePullTimeout: "15m", Interactive: true, IPCMode: "host", IPv4Address: "10.0.2.1", @@ -689,6 +691,35 @@ func TestConfig_Capabilities(t *testing.T) { } } +func TestConfig_DriverConfig_ContainerExistsAttempts(t *testing.T) { + ci.Parallel(t) + + cases := []struct { + name string + config string + expected uint64 + }{ + { + name: "default", + config: `{}`, + expected: 5, + }, + { + name: "set explicitly", + config: `{ container_exists_attempts = 10 }`, + expected: 10, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var tc DriverConfig + hclutils.NewConfigParser(configSpec).ParseHCL(t, "config "+c.config, &tc) + must.Eq(t, c.expected, tc.ContainerExistsAttempts) + }) + } +} + func TestConfig_DriverConfig_InfraImagePullTimeout(t *testing.T) { ci.Parallel(t) diff --git a/drivers/docker/driver.go b/drivers/docker/driver.go index 80d4c969c..f9382b95c 100644 --- a/drivers/docker/driver.go +++ b/drivers/docker/driver.go @@ -538,7 +538,7 @@ CREATE: } } - if attempted < 5 { + if attempted < d.config.ContainerExistsAttempts { attempted++ backoff = helper.Backoff(50*time.Millisecond, time.Minute, attempted) time.Sleep(backoff) diff --git a/website/content/docs/drivers/docker.mdx b/website/content/docs/drivers/docker.mdx index c7b65fda6..fa0760cff 100644 --- a/website/content/docs/drivers/docker.mdx +++ b/website/content/docs/drivers/docker.mdx @@ -84,6 +84,10 @@ The `docker` driver supports the following configuration in the job spec. Only } ``` +- `container_exists_attempts` - (Optional) A number of attempts to be made to + purge a container if during task creation Nomad encounters an existing one in + non-running state for the same task. Defaults to `5`. + - `dns_search_domains` - (Optional) A list of DNS search domains for the container to use. If you are using bridge networking mode with a `network` block in the task group, you must set all DNS options in