diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index c1f32724ee..836f2b1727 100644 --- a/api/server/router/container/container_routes.go +++ b/api/server/router/container/container_routes.go @@ -475,6 +475,12 @@ func (s *containerRouter) postContainersCreate(ctx context.Context, w http.Respo } } + // When using API 1.39 and under, KernelMemoryTCP should be ignored because it + // was added in API 1.40. + if hostConfig != nil && versions.LessThan(version, "1.40") { + hostConfig.KernelMemoryTCP = 0 + } + ccr, err := s.backend.ContainerCreate(types.ContainerCreateConfig{ Name: name, Config: config, diff --git a/api/swagger.yaml b/api/swagger.yaml index 49d3e0a83b..aa6fa2ca7e 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -429,6 +429,10 @@ definitions: description: "Kernel memory limit in bytes." type: "integer" format: "int64" + KernelMemoryTCP: + description: "Sets hard limit for kernel TCP buffer memory." + type: "integer" + format: "int64" MemoryReservation: description: "Memory soft limit in bytes." type: "integer" diff --git a/api/types/container/host_config.go b/api/types/container/host_config.go index 4ef26fa6c8..de48975fdb 100644 --- a/api/types/container/host_config.go +++ b/api/types/container/host_config.go @@ -329,6 +329,7 @@ type Resources struct { DeviceCgroupRules []string // List of rule to be added to the device cgroup DiskQuota int64 // Disk limit (in bytes) KernelMemory int64 // Kernel memory limit (in bytes) + KernelMemoryTCP int64 // Sets hard limit for kernel TCP buffer memory MemoryReservation int64 // Memory soft limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap MemorySwappiness *int64 // Tuning container memory swappiness behaviour diff --git a/api/types/types.go b/api/types/types.go index a8fae3ba32..2accda9d07 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -158,6 +158,7 @@ type Info struct { MemoryLimit bool SwapLimit bool KernelMemory bool + KernelMemoryTCP bool CPUCfsPeriod bool `json:"CpuCfsPeriod"` CPUCfsQuota bool `json:"CpuCfsQuota"` CPUShares bool diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index b69eede21c..b18fca17c7 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -111,6 +111,10 @@ func getMemoryResources(config containertypes.Resources) *specs.LinuxMemory { memory.Kernel = &config.KernelMemory } + if config.KernelMemoryTCP != 0 { + memory.KernelTCP = &config.KernelMemoryTCP + } + return &memory } diff --git a/daemon/info_unix.go b/daemon/info_unix.go index 60b2f99870..cbfee36b56 100644 --- a/daemon/info_unix.go +++ b/daemon/info_unix.go @@ -20,6 +20,7 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo) v.MemoryLimit = sysInfo.MemoryLimit v.SwapLimit = sysInfo.SwapLimit v.KernelMemory = sysInfo.KernelMemory + v.KernelMemoryTCP = sysInfo.KernelMemoryTCP v.OomKillDisable = sysInfo.OomKillDisable v.CPUCfsPeriod = sysInfo.CPUCfsPeriod v.CPUCfsQuota = sysInfo.CPUCfsQuota diff --git a/docs/api/version-history.md b/docs/api/version-history.md index a59a90322a..f0cdbdb189 100644 --- a/docs/api/version-history.md +++ b/docs/api/version-history.md @@ -32,6 +32,7 @@ keywords: "API, Docker, rcli, REST, documentation" * `POST /swarm/init` now accepts a `DataPathPort` property to set data path port number. * `GET /info` now returns information about `DataPathPort` that is currently used in swarm * `GET /swarm` endpoint now returns DataPathPort info +* `POST /containers/create` now takes `KernelMemoryTCP` field to set hard limit for kernel TCP buffer memory. ## V1.39 API changes diff --git a/integration/container/run_linux_test.go b/integration/container/run_linux_test.go new file mode 100644 index 0000000000..e9d816b81f --- /dev/null +++ b/integration/container/run_linux_test.go @@ -0,0 +1,51 @@ +package container // import "github.com/docker/docker/integration/container" + +import ( + "context" + "strconv" + "strings" + "testing" + "time" + + containertypes "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/versions" + "github.com/docker/docker/integration/internal/container" + "github.com/docker/docker/internal/test/request" + "gotest.tools/assert" + is "gotest.tools/assert/cmp" + "gotest.tools/poll" + "gotest.tools/skip" +) + +func TestKernelTCPMemory(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "skip test from new feature") + skip.If(t, !testEnv.DaemonInfo.KernelMemoryTCP) + + defer setupTest(t)() + client := request.NewAPIClient(t) + ctx := context.Background() + + const ( + kernelMemoryTCP int64 = 200 * 1024 * 1024 + ) + + cID := container.Run(t, ctx, client, func(c *container.TestContainerConfig) { + c.HostConfig.Resources = containertypes.Resources{ + KernelMemoryTCP: kernelMemoryTCP, + } + }) + + poll.WaitOn(t, container.IsInState(ctx, client, cID, "running"), poll.WithDelay(100*time.Millisecond)) + + inspect, err := client.ContainerInspect(ctx, cID) + assert.NilError(t, err) + assert.Check(t, is.Equal(kernelMemoryTCP, inspect.HostConfig.KernelMemoryTCP)) + + res, err := container.Exec(ctx, client, cID, + []string{"cat", "/sys/fs/cgroup/memory/memory.kmem.tcp.limit_in_bytes"}) + assert.NilError(t, err) + assert.Assert(t, is.Len(res.Stderr(), 0)) + assert.Equal(t, 0, res.ExitCode) + assert.Check(t, is.Equal(strconv.FormatInt(kernelMemoryTCP, 10), strings.TrimSpace(res.Stdout()))) +} diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 0f327d5068..5fa5a5628c 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -47,6 +47,9 @@ type cgroupMemInfo struct { // Whether kernel memory limit is supported or not KernelMemory bool + + // Whether kernel memory TCP limit is supported or not + KernelMemoryTCP bool } type cgroupCPUInfo struct { diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go index dde5be19bc..6492a1c9ac 100644 --- a/pkg/sysinfo/sysinfo_linux.go +++ b/pkg/sysinfo/sysinfo_linux.go @@ -95,6 +95,10 @@ func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo { if !quiet && !kernelMemory { logrus.Warn("Your kernel does not support kernel memory limit") } + kernelMemoryTCP := cgroupEnabled(mountPoint, "memory.kmem.tcp.limit_in_bytes") + if !quiet && !kernelMemoryTCP { + logrus.Warn("Your kernel does not support kernel memory TCP limit") + } return cgroupMemInfo{ MemoryLimit: true, @@ -103,6 +107,7 @@ func checkCgroupMem(cgMounts map[string]string, quiet bool) cgroupMemInfo { OomKillDisable: oomKillDisable, MemorySwappiness: memorySwappiness, KernelMemory: kernelMemory, + KernelMemoryTCP: kernelMemoryTCP, } }