diff --git a/api/client/stats.go b/api/client/stats.go index 208396b193..3b9b8d8d48 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -164,7 +164,7 @@ func (cli *DockerCli) CmdStats(args ...string) error { fmt.Fprint(cli.out, "\033[2J") fmt.Fprint(cli.out, "\033[H") } - io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n") + io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n") } for range time.Tick(500 * time.Millisecond) { diff --git a/api/client/stats_helpers.go b/api/client/stats_helpers.go index a02531ce5f..5c88b7424d 100644 --- a/api/client/stats_helpers.go +++ b/api/client/stats_helpers.go @@ -24,6 +24,7 @@ type containerStats struct { NetworkTx float64 BlockRead float64 BlockWrite float64 + PidsCurrent uint64 mu sync.RWMutex err error } @@ -167,13 +168,14 @@ func (s *containerStats) Display(w io.Writer) error { if s.err != nil { return s.err } - fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n", + fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n", s.Name, s.CPUPercentage, units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit), s.MemoryPercentage, units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx), - units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite)) + units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite), + s.PidsCurrent) return nil } diff --git a/api/client/stats_unit_test.go b/api/client/stats_unit_test.go index ce1c3a1741..36081c5772 100644 --- a/api/client/stats_unit_test.go +++ b/api/client/stats_unit_test.go @@ -19,6 +19,7 @@ func TestDisplay(t *testing.T) { NetworkTx: 800 * 1024 * 1024, BlockRead: 100 * 1024 * 1024, BlockWrite: 800 * 1024 * 1024, + PidsCurrent: 1, mu: sync.RWMutex{}, } var b bytes.Buffer @@ -26,7 +27,7 @@ func TestDisplay(t *testing.T) { t.Fatalf("c.Display() gave error: %s", err) } got := b.String() - want := "app\t30.00%\t104.9 MB / 2.147 GB\t4.88%\t104.9 MB / 838.9 MB\t104.9 MB / 838.9 MB\n" + want := "app\t30.00%\t104.9 MB / 2.147 GB\t4.88%\t104.9 MB / 838.9 MB\t104.9 MB / 838.9 MB\t1\n" if got != want { t.Fatalf("c.Display() = %q, want %q", got, want) } diff --git a/contrib/check-config.sh b/contrib/check-config.sh index d87c684fea..bcc90d4a1f 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -202,6 +202,9 @@ echo 'Optional Features:' { check_flags SECCOMP } +{ + check_flags CGROUP_PIDS +} { check_flags MEMCG_KMEM MEMCG_SWAP MEMCG_SWAP_ENABLED if is_set MEMCG_SWAP && ! is_set MEMCG_SWAP_ENABLED; then diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index cbb51c5d27..343e1dc717 100644 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1637,6 +1637,7 @@ _docker_run() { --net-alias --oom-score-adj --pid + --pids-limit --publish -p --restart --security-opt diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 3a1f399f51..0f2a361a6b 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -534,6 +534,7 @@ __docker_subcommand() { "($help)*--net-alias=[Add network-scoped alias for the container]:alias: " "($help)--oom-kill-disable[Disable OOM Killer]" "($help)--oom-score-adj[Tune the host's OOM preferences for containers (accepts -1000 to 1000)]" + "($help)--pids-limit[Tune container pids limit (set -1 for unlimited)]" "($help -P --publish-all)"{-P,--publish-all}"[Publish all exposed ports]" "($help)*"{-p=,--publish=}"[Expose a container's port to the host]:port:_ports" "($help)--pid=[PID namespace to use]:PID: " diff --git a/daemon/container_operations_unix.go b/daemon/container_operations_unix.go index f6e06e9640..92e21b11e3 100644 --- a/daemon/container_operations_unix.go +++ b/daemon/container_operations_unix.go @@ -198,6 +198,7 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro BlkioThrottleWriteBpsDevice: writeBpsDevice, BlkioThrottleReadIOpsDevice: readIOpsDevice, BlkioThrottleWriteIOpsDevice: writeIOpsDevice, + PidsLimit: c.HostConfig.PidsLimit, MemorySwappiness: -1, } diff --git a/daemon/daemon_unix.go b/daemon/daemon_unix.go index d8d4e56e39..cd0ff9c616 100644 --- a/daemon/daemon_unix.go +++ b/daemon/daemon_unix.go @@ -285,6 +285,12 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi resources.OomKillDisable = nil } + if resources.PidsLimit != 0 && !sysInfo.PidsLimit { + warnings = append(warnings, "Your kernel does not support pids limit capabilities, pids limit discarded.") + logrus.Warnf("Your kernel does not support pids limit capabilities, pids limit discarded.") + resources.PidsLimit = 0 + } + // cpu subsystem checks and adjustments if resources.CPUShares > 0 && !sysInfo.CPUShares { warnings = append(warnings, "Your kernel does not support CPU shares. Shares discarded.") diff --git a/daemon/execdriver/driver_unix.go b/daemon/execdriver/driver_unix.go index 3ed3c8170f..0e387f3a9b 100644 --- a/daemon/execdriver/driver_unix.go +++ b/daemon/execdriver/driver_unix.go @@ -50,6 +50,7 @@ type Resources struct { CPUPeriod int64 `json:"cpu_period"` Rlimits []*units.Rlimit `json:"rlimits"` OomKillDisable bool `json:"oom_kill_disable"` + PidsLimit int64 `json:"pids_limit"` MemorySwappiness int64 `json:"memory_swappiness"` } @@ -201,6 +202,7 @@ func SetupCgroups(container *configs.Config, c *Command) error { container.Cgroups.Resources.BlkioThrottleReadIOPSDevice = c.Resources.BlkioThrottleReadIOpsDevice container.Cgroups.Resources.BlkioThrottleWriteIOPSDevice = c.Resources.BlkioThrottleWriteIOpsDevice container.Cgroups.Resources.OomKillDisable = c.Resources.OomKillDisable + container.Cgroups.Resources.PidsLimit = c.Resources.PidsLimit container.Cgroups.Resources.MemorySwappiness = c.Resources.MemorySwappiness } diff --git a/daemon/stats_linux.go b/daemon/stats_linux.go index 201552a4e1..1a907e015a 100644 --- a/daemon/stats_linux.go +++ b/daemon/stats_linux.go @@ -61,6 +61,10 @@ func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON { Stats: mem.Stats, Failcnt: mem.Usage.Failcnt, } + pids := cs.PidsStats + s.PidsStats = types.PidsStats{ + Current: pids.Current, + } } return s diff --git a/docs/reference/api/docker_remote_api.md b/docs/reference/api/docker_remote_api.md index a4a07ef4b6..75edd928bf 100644 --- a/docs/reference/api/docker_remote_api.md +++ b/docs/reference/api/docker_remote_api.md @@ -123,6 +123,8 @@ This section lists each version from latest to oldest. Each listing includes a * `POST /networks/create` now supports enabling ipv6 on the network by setting the `EnableIPv6` field (doing this with a label will no longer work). * `GET /info` now returns `CgroupDriver` field showing what cgroup driver the daemon is using; `cgroupfs` or `systemd`. * `GET /info` now returns `KernelMemory` field, showing if "kernel memory limit" is supported. +* `POST /containers/create` now takes `PidsLimit` field, if the kernel is >= 4.3 and the pids cgroup is supported. +* `GET /containers/(id or name)/stats` now returns `pids_stats`, if the kernel is >= 4.3 and the pids cgroup is supported. ### v1.22 API changes diff --git a/docs/reference/api/docker_remote_api_v1.23.md b/docs/reference/api/docker_remote_api_v1.23.md index 2f4a66a0fb..c2902dac06 100644 --- a/docs/reference/api/docker_remote_api_v1.23.md +++ b/docs/reference/api/docker_remote_api_v1.23.md @@ -346,6 +346,7 @@ Json Parameters: - **MemorySwappiness** - Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. - **OomKillDisable** - Boolean value, whether to disable OOM Killer for the container or not. - **OomScoreAdj** - An integer value containing the score given to the container in order to tune OOM killer preferences. +- **PidsLimit** - Tune a container's pids limit. Set -1 for unlimited. - **AttachStdin** - Boolean value, attaches to `stdin`. - **AttachStdout** - Boolean value, attaches to `stdout`. - **AttachStderr** - Boolean value, attaches to `stderr`. @@ -823,6 +824,9 @@ This endpoint returns a live stream of a container's resource usage statistics. { "read" : "2015-01-08T22:57:31.547920715Z", + "pids_stats": { + "current": 3 + }, "networks": { "eth0": { "rx_bytes": 5338, diff --git a/docs/reference/commandline/create.md b/docs/reference/commandline/create.md index ad23995ac1..fa68b0feb1 100644 --- a/docs/reference/commandline/create.md +++ b/docs/reference/commandline/create.md @@ -74,6 +74,7 @@ Creates a new container. -P, --publish-all Publish all exposed ports to random ports -p, --publish=[] Publish a container's port(s) to the host --pid="" PID namespace to use + --pids-limit=-1 Tune container pids limit (set -1 for unlimited), kernel >= 4.3 --privileged Give extended privileges to this container --read-only Mount the container's root filesystem as read only --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) diff --git a/docs/reference/commandline/run.md b/docs/reference/commandline/run.md index 4da4397193..496ff4865d 100644 --- a/docs/reference/commandline/run.md +++ b/docs/reference/commandline/run.md @@ -74,6 +74,7 @@ parent = "smn_cli" -P, --publish-all Publish all exposed ports to random ports -p, --publish=[] Publish a container's port(s) to the host --pid="" PID namespace to use + --pids-limit=-1 Tune container pids limit (set -1 for unlimited), kernel >= 4.3 --privileged Give extended privileges to this container --read-only Mount the container's root filesystem as read only --restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 634765297b..d6dbdeec92 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -956,3 +956,15 @@ func (s *DockerSuite) TestRunDeviceSymlink(c *check.C) { c.Assert(err, check.NotNil) c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "not a device node", check.Commentf("expected output 'not a device node'")) } + +// TestRunPidsLimit makes sure the pids cgroup is set with --pids-limit +func (s *DockerSuite) TestRunPidsLimit(c *check.C) { + testRequires(c, pidsLimit) + + file := "/sys/fs/cgroup/pids/pids.max" + out, _ := dockerCmd(c, "run", "--name", "skittles", "--pids-limit", "2", "busybox", "cat", file) + c.Assert(strings.TrimSpace(out), checker.Equals, "2") + + out = inspectField(c, "skittles", "HostConfig.PidsLimit") + c.Assert(out, checker.Equals, "2", check.Commentf("setting the pids limit failed")) +} diff --git a/integration-cli/requirements_unix.go b/integration-cli/requirements_unix.go index f625985b0a..edc7bc1f91 100644 --- a/integration-cli/requirements_unix.go +++ b/integration-cli/requirements_unix.go @@ -33,6 +33,12 @@ var ( }, "Test requires Oom control enabled.", } + pidsLimit = testRequirement{ + func() bool { + return SysInfo.PidsLimit + }, + "Test requires pids limit enabled.", + } kernelMemorySupport = testRequirement{ func() bool { return SysInfo.KernelMemory diff --git a/man/docker-create.1.md b/man/docker-create.1.md index 6a2640d205..16f70a958d 100644 --- a/man/docker-create.1.md +++ b/man/docker-create.1.md @@ -58,6 +58,7 @@ docker-create - Create a new container [**-P**|**--publish-all**] [**-p**|**--publish**[=*[]*]] [**--pid**[=*[]*]] +[**--pids-limit**[=*PIDS_LIMIT*]] [**--privileged**] [**--read-only**] [**--restart**[=*RESTART*]] @@ -290,6 +291,9 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. **host**: use the host's PID namespace inside the container. Note: the host mode gives the container full access to local PID and is therefore considered insecure. +**--pids-limit**="" + Tune the container's pids limit. Set `-1` to have unlimited pids for the container. + **--privileged**=*true*|*false* Give extended privileges to this container. The default is *false*. diff --git a/man/docker-run.1.md b/man/docker-run.1.md index bf75fb68ef..b084754b49 100644 --- a/man/docker-run.1.md +++ b/man/docker-run.1.md @@ -60,6 +60,7 @@ docker-run - Run a command in a new container [**-P**|**--publish-all**] [**-p**|**--publish**[=*[]*]] [**--pid**[=*[]*]] +[**--pids-limit**[=*PIDS_LIMIT*]] [**--privileged**] [**--read-only**] [**--restart**[=*RESTART*]] @@ -420,6 +421,9 @@ Use `docker port` to see the actual mapping: `docker port CONTAINER $CONTAINERPO **host**: use the host's PID namespace inside the container. Note: the host mode gives the container full access to local PID and is therefore considered insecure. +**--pids-limit**="" + Tune the container's pids limit. Set `-1` to have unlimited pids for the container. + **--uts**=*host* Set the UTS mode for the container **host**: use the host's UTS namespace inside the container. diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 3adaa9454b..cbd0099957 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -14,6 +14,7 @@ type SysInfo struct { cgroupCPUInfo cgroupBlkioInfo cgroupCpusetInfo + cgroupPids // Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work IPv4ForwardingDisabled bool @@ -90,6 +91,11 @@ type cgroupCpusetInfo struct { Mems string } +type cgroupPids struct { + // Whether Pids Limit is supported or not + PidsLimit bool +} + // IsCpusetCpusAvailable returns `true` if the provided string set is contained // in cgroup's cpuset.cpus set, `false` otherwise. // If error is not nil a parsing error occurred. diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go index 7f584bbbdc..41fb0d2bb9 100644 --- a/pkg/sysinfo/sysinfo_linux.go +++ b/pkg/sysinfo/sysinfo_linux.go @@ -44,6 +44,7 @@ func New(quiet bool) *SysInfo { sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet) sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet) sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet) + sysInfo.cgroupPids = checkCgroupPids(quiet) } _, ok := cgMounts["devices"] @@ -216,6 +217,21 @@ func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetI } } +// checkCgroupPids reads the pids information from the pids cgroup mount point. +func checkCgroupPids(quiet bool) cgroupPids { + _, err := cgroups.FindCgroupMountpoint("pids") + if err != nil { + if !quiet { + logrus.Warn(err) + } + return cgroupPids{} + } + + return cgroupPids{ + PidsLimit: true, + } +} + func cgroupEnabled(mountPoint, name string) bool { _, err := os.Stat(path.Join(mountPoint, name)) return err == nil diff --git a/runconfig/opts/parse.go b/runconfig/opts/parse.go index 18f9fc45bd..decec771d8 100644 --- a/runconfig/opts/parse.go +++ b/runconfig/opts/parse.go @@ -85,6 +85,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host flIPv4Address = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)") flIPv6Address = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") + flPidsLimit = cmd.Int64([]string{"-pids-limit"}, 0, "Tune container pids limit (set -1 for unlimited)") flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits") flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only") flLoggingDriver = cmd.String([]string{"-log-driver"}, "", "Logging driver for container") @@ -343,6 +344,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host CpusetCpus: *flCpusetCpus, CpusetMems: *flCpusetMems, CPUQuota: *flCPUQuota, + PidsLimit: *flPidsLimit, BlkioWeight: *flBlkioWeight, BlkioWeightDevice: flBlkioWeightDevice.GetList(), BlkioDeviceReadBps: flDeviceReadBps.GetList(),