Add PIDs cgroup support to Docker
| ... | ... |
@@ -164,7 +164,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
| 164 | 164 |
fmt.Fprint(cli.out, "\033[2J") |
| 165 | 165 |
fmt.Fprint(cli.out, "\033[H") |
| 166 | 166 |
} |
| 167 |
- io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n") |
|
| 167 |
+ io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\tPIDS\n") |
|
| 168 | 168 |
} |
| 169 | 169 |
|
| 170 | 170 |
for range time.Tick(500 * time.Millisecond) {
|
| ... | ... |
@@ -24,6 +24,7 @@ type containerStats struct {
|
| 24 | 24 |
NetworkTx float64 |
| 25 | 25 |
BlockRead float64 |
| 26 | 26 |
BlockWrite float64 |
| 27 |
+ PidsCurrent uint64 |
|
| 27 | 28 |
mu sync.RWMutex |
| 28 | 29 |
err error |
| 29 | 30 |
} |
| ... | ... |
@@ -167,13 +168,14 @@ func (s *containerStats) Display(w io.Writer) error {
|
| 167 | 167 |
if s.err != nil {
|
| 168 | 168 |
return s.err |
| 169 | 169 |
} |
| 170 |
- fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n", |
|
| 170 |
+ fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\t%d\n", |
|
| 171 | 171 |
s.Name, |
| 172 | 172 |
s.CPUPercentage, |
| 173 | 173 |
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit), |
| 174 | 174 |
s.MemoryPercentage, |
| 175 | 175 |
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx), |
| 176 |
- units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite)) |
|
| 176 |
+ units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite), |
|
| 177 |
+ s.PidsCurrent) |
|
| 177 | 178 |
return nil |
| 178 | 179 |
} |
| 179 | 180 |
|
| ... | ... |
@@ -19,6 +19,7 @@ func TestDisplay(t *testing.T) {
|
| 19 | 19 |
NetworkTx: 800 * 1024 * 1024, |
| 20 | 20 |
BlockRead: 100 * 1024 * 1024, |
| 21 | 21 |
BlockWrite: 800 * 1024 * 1024, |
| 22 |
+ PidsCurrent: 1, |
|
| 22 | 23 |
mu: sync.RWMutex{},
|
| 23 | 24 |
} |
| 24 | 25 |
var b bytes.Buffer |
| ... | ... |
@@ -26,7 +27,7 @@ func TestDisplay(t *testing.T) {
|
| 26 | 26 |
t.Fatalf("c.Display() gave error: %s", err)
|
| 27 | 27 |
} |
| 28 | 28 |
got := b.String() |
| 29 |
- 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" |
|
| 29 |
+ 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" |
|
| 30 | 30 |
if got != want {
|
| 31 | 31 |
t.Fatalf("c.Display() = %q, want %q", got, want)
|
| 32 | 32 |
} |
| ... | ... |
@@ -203,6 +203,9 @@ echo 'Optional Features:' |
| 203 | 203 |
check_flags SECCOMP |
| 204 | 204 |
} |
| 205 | 205 |
{
|
| 206 |
+ check_flags CGROUP_PIDS |
|
| 207 |
+} |
|
| 208 |
+{
|
|
| 206 | 209 |
check_flags MEMCG_KMEM MEMCG_SWAP MEMCG_SWAP_ENABLED |
| 207 | 210 |
if is_set MEMCG_SWAP && ! is_set MEMCG_SWAP_ENABLED; then |
| 208 | 211 |
echo " $(wrap_color '(note that cgroup swap accounting is not enabled in your kernel config, you can enable it by setting boot option "swapaccount=1")' bold black)" |
| ... | ... |
@@ -534,6 +534,7 @@ __docker_subcommand() {
|
| 534 | 534 |
"($help)*--net-alias=[Add network-scoped alias for the container]:alias: " |
| 535 | 535 |
"($help)--oom-kill-disable[Disable OOM Killer]" |
| 536 | 536 |
"($help)--oom-score-adj[Tune the host's OOM preferences for containers (accepts -1000 to 1000)]" |
| 537 |
+ "($help)--pids-limit[Tune container pids limit (set -1 for unlimited)]" |
|
| 537 | 538 |
"($help -P --publish-all)"{-P,--publish-all}"[Publish all exposed ports]"
|
| 538 | 539 |
"($help)*"{-p=,--publish=}"[Expose a container's port to the host]:port:_ports"
|
| 539 | 540 |
"($help)--pid=[PID namespace to use]:PID: " |
| ... | ... |
@@ -198,6 +198,7 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro |
| 198 | 198 |
BlkioThrottleWriteBpsDevice: writeBpsDevice, |
| 199 | 199 |
BlkioThrottleReadIOpsDevice: readIOpsDevice, |
| 200 | 200 |
BlkioThrottleWriteIOpsDevice: writeIOpsDevice, |
| 201 |
+ PidsLimit: c.HostConfig.PidsLimit, |
|
| 201 | 202 |
MemorySwappiness: -1, |
| 202 | 203 |
} |
| 203 | 204 |
|
| ... | ... |
@@ -291,6 +291,12 @@ func verifyContainerResources(resources *containertypes.Resources, sysInfo *sysi |
| 291 | 291 |
resources.OomKillDisable = nil |
| 292 | 292 |
} |
| 293 | 293 |
|
| 294 |
+ if resources.PidsLimit != 0 && !sysInfo.PidsLimit {
|
|
| 295 |
+ warnings = append(warnings, "Your kernel does not support pids limit capabilities, pids limit discarded.") |
|
| 296 |
+ logrus.Warnf("Your kernel does not support pids limit capabilities, pids limit discarded.")
|
|
| 297 |
+ resources.PidsLimit = 0 |
|
| 298 |
+ } |
|
| 299 |
+ |
|
| 294 | 300 |
// cpu subsystem checks and adjustments |
| 295 | 301 |
if resources.CPUShares > 0 && !sysInfo.CPUShares {
|
| 296 | 302 |
warnings = append(warnings, "Your kernel does not support CPU shares. Shares discarded.") |
| ... | ... |
@@ -50,6 +50,7 @@ type Resources struct {
|
| 50 | 50 |
CPUPeriod int64 `json:"cpu_period"` |
| 51 | 51 |
Rlimits []*units.Rlimit `json:"rlimits"` |
| 52 | 52 |
OomKillDisable bool `json:"oom_kill_disable"` |
| 53 |
+ PidsLimit int64 `json:"pids_limit"` |
|
| 53 | 54 |
MemorySwappiness int64 `json:"memory_swappiness"` |
| 54 | 55 |
} |
| 55 | 56 |
|
| ... | ... |
@@ -202,6 +203,7 @@ func SetupCgroups(container *configs.Config, c *Command) error {
|
| 202 | 202 |
container.Cgroups.Resources.BlkioThrottleReadIOPSDevice = c.Resources.BlkioThrottleReadIOpsDevice |
| 203 | 203 |
container.Cgroups.Resources.BlkioThrottleWriteIOPSDevice = c.Resources.BlkioThrottleWriteIOpsDevice |
| 204 | 204 |
container.Cgroups.Resources.OomKillDisable = c.Resources.OomKillDisable |
| 205 |
+ container.Cgroups.Resources.PidsLimit = c.Resources.PidsLimit |
|
| 205 | 206 |
container.Cgroups.Resources.MemorySwappiness = c.Resources.MemorySwappiness |
| 206 | 207 |
} |
| 207 | 208 |
|
| ... | ... |
@@ -123,6 +123,8 @@ This section lists each version from latest to oldest. Each listing includes a |
| 123 | 123 |
* `POST /networks/create` now supports enabling ipv6 on the network by setting the `EnableIPv6` field (doing this with a label will no longer work). |
| 124 | 124 |
* `GET /info` now returns `CgroupDriver` field showing what cgroup driver the daemon is using; `cgroupfs` or `systemd`. |
| 125 | 125 |
* `GET /info` now returns `KernelMemory` field, showing if "kernel memory limit" is supported. |
| 126 |
+* `POST /containers/create` now takes `PidsLimit` field, if the kernel is >= 4.3 and the pids cgroup is supported. |
|
| 127 |
+* `GET /containers/(id or name)/stats` now returns `pids_stats`, if the kernel is >= 4.3 and the pids cgroup is supported. |
|
| 126 | 128 |
|
| 127 | 129 |
### v1.22 API changes |
| 128 | 130 |
|
| ... | ... |
@@ -346,6 +346,7 @@ Json Parameters: |
| 346 | 346 |
- **MemorySwappiness** - Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. |
| 347 | 347 |
- **OomKillDisable** - Boolean value, whether to disable OOM Killer for the container or not. |
| 348 | 348 |
- **OomScoreAdj** - An integer value containing the score given to the container in order to tune OOM killer preferences. |
| 349 |
+- **PidsLimit** - Tune a container's pids limit. Set -1 for unlimited. |
|
| 349 | 350 |
- **AttachStdin** - Boolean value, attaches to `stdin`. |
| 350 | 351 |
- **AttachStdout** - Boolean value, attaches to `stdout`. |
| 351 | 352 |
- **AttachStderr** - Boolean value, attaches to `stderr`. |
| ... | ... |
@@ -823,6 +824,9 @@ This endpoint returns a live stream of a container's resource usage statistics. |
| 823 | 823 |
|
| 824 | 824 |
{
|
| 825 | 825 |
"read" : "2015-01-08T22:57:31.547920715Z", |
| 826 |
+ "pids_stats": {
|
|
| 827 |
+ "current": 3 |
|
| 828 |
+ }, |
|
| 826 | 829 |
"networks": {
|
| 827 | 830 |
"eth0": {
|
| 828 | 831 |
"rx_bytes": 5338, |
| ... | ... |
@@ -74,6 +74,7 @@ Creates a new container. |
| 74 | 74 |
-P, --publish-all Publish all exposed ports to random ports |
| 75 | 75 |
-p, --publish=[] Publish a container's port(s) to the host |
| 76 | 76 |
--pid="" PID namespace to use |
| 77 |
+ --pids-limit=-1 Tune container pids limit (set -1 for unlimited), kernel >= 4.3 |
|
| 77 | 78 |
--privileged Give extended privileges to this container |
| 78 | 79 |
--read-only Mount the container's root filesystem as read only |
| 79 | 80 |
--restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) |
| ... | ... |
@@ -74,6 +74,7 @@ parent = "smn_cli" |
| 74 | 74 |
-P, --publish-all Publish all exposed ports to random ports |
| 75 | 75 |
-p, --publish=[] Publish a container's port(s) to the host |
| 76 | 76 |
--pid="" PID namespace to use |
| 77 |
+ --pids-limit=-1 Tune container pids limit (set -1 for unlimited), kernel >= 4.3 |
|
| 77 | 78 |
--privileged Give extended privileges to this container |
| 78 | 79 |
--read-only Mount the container's root filesystem as read only |
| 79 | 80 |
--restart="no" Restart policy (no, on-failure[:max-retry], always, unless-stopped) |
| ... | ... |
@@ -968,3 +968,15 @@ func (s *DockerSuite) TestRunDeviceSymlink(c *check.C) {
|
| 968 | 968 |
c.Assert(err, check.NotNil) |
| 969 | 969 |
c.Assert(strings.Trim(out, "\r\n"), checker.Contains, "not a device node", check.Commentf("expected output 'not a device node'"))
|
| 970 | 970 |
} |
| 971 |
+ |
|
| 972 |
+// TestRunPidsLimit makes sure the pids cgroup is set with --pids-limit |
|
| 973 |
+func (s *DockerSuite) TestRunPidsLimit(c *check.C) {
|
|
| 974 |
+ testRequires(c, pidsLimit) |
|
| 975 |
+ |
|
| 976 |
+ file := "/sys/fs/cgroup/pids/pids.max" |
|
| 977 |
+ out, _ := dockerCmd(c, "run", "--name", "skittles", "--pids-limit", "2", "busybox", "cat", file) |
|
| 978 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, "2") |
|
| 979 |
+ |
|
| 980 |
+ out = inspectField(c, "skittles", "HostConfig.PidsLimit") |
|
| 981 |
+ c.Assert(out, checker.Equals, "2", check.Commentf("setting the pids limit failed"))
|
|
| 982 |
+} |
| ... | ... |
@@ -33,6 +33,12 @@ var ( |
| 33 | 33 |
}, |
| 34 | 34 |
"Test requires Oom control enabled.", |
| 35 | 35 |
} |
| 36 |
+ pidsLimit = testRequirement{
|
|
| 37 |
+ func() bool {
|
|
| 38 |
+ return SysInfo.PidsLimit |
|
| 39 |
+ }, |
|
| 40 |
+ "Test requires pids limit enabled.", |
|
| 41 |
+ } |
|
| 36 | 42 |
kernelMemorySupport = testRequirement{
|
| 37 | 43 |
func() bool {
|
| 38 | 44 |
return SysInfo.KernelMemory |
| ... | ... |
@@ -58,6 +58,7 @@ docker-create - Create a new container |
| 58 | 58 |
[**-P**|**--publish-all**] |
| 59 | 59 |
[**-p**|**--publish**[=*[]*]] |
| 60 | 60 |
[**--pid**[=*[]*]] |
| 61 |
+[**--pids-limit**[=*PIDS_LIMIT*]] |
|
| 61 | 62 |
[**--privileged**] |
| 62 | 63 |
[**--read-only**] |
| 63 | 64 |
[**--restart**[=*RESTART*]] |
| ... | ... |
@@ -290,6 +291,9 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. |
| 290 | 290 |
**host**: use the host's PID namespace inside the container. |
| 291 | 291 |
Note: the host mode gives the container full access to local PID and is therefore considered insecure. |
| 292 | 292 |
|
| 293 |
+**--pids-limit**="" |
|
| 294 |
+ Tune the container's pids limit. Set `-1` to have unlimited pids for the container. |
|
| 295 |
+ |
|
| 293 | 296 |
**--privileged**=*true*|*false* |
| 294 | 297 |
Give extended privileges to this container. The default is *false*. |
| 295 | 298 |
|
| ... | ... |
@@ -60,6 +60,7 @@ docker-run - Run a command in a new container |
| 60 | 60 |
[**-P**|**--publish-all**] |
| 61 | 61 |
[**-p**|**--publish**[=*[]*]] |
| 62 | 62 |
[**--pid**[=*[]*]] |
| 63 |
+[**--pids-limit**[=*PIDS_LIMIT*]] |
|
| 63 | 64 |
[**--privileged**] |
| 64 | 65 |
[**--read-only**] |
| 65 | 66 |
[**--restart**[=*RESTART*]] |
| ... | ... |
@@ -420,6 +421,9 @@ Use `docker port` to see the actual mapping: `docker port CONTAINER $CONTAINERPO |
| 420 | 420 |
**host**: use the host's PID namespace inside the container. |
| 421 | 421 |
Note: the host mode gives the container full access to local PID and is therefore considered insecure. |
| 422 | 422 |
|
| 423 |
+**--pids-limit**="" |
|
| 424 |
+ Tune the container's pids limit. Set `-1` to have unlimited pids for the container. |
|
| 425 |
+ |
|
| 423 | 426 |
**--uts**=*host* |
| 424 | 427 |
Set the UTS mode for the container |
| 425 | 428 |
**host**: use the host's UTS namespace inside the container. |
| ... | ... |
@@ -14,6 +14,7 @@ type SysInfo struct {
|
| 14 | 14 |
cgroupCPUInfo |
| 15 | 15 |
cgroupBlkioInfo |
| 16 | 16 |
cgroupCpusetInfo |
| 17 |
+ cgroupPids |
|
| 17 | 18 |
|
| 18 | 19 |
// Whether IPv4 forwarding is supported or not, if this was disabled, networking will not work |
| 19 | 20 |
IPv4ForwardingDisabled bool |
| ... | ... |
@@ -90,6 +91,11 @@ type cgroupCpusetInfo struct {
|
| 90 | 90 |
Mems string |
| 91 | 91 |
} |
| 92 | 92 |
|
| 93 |
+type cgroupPids struct {
|
|
| 94 |
+ // Whether Pids Limit is supported or not |
|
| 95 |
+ PidsLimit bool |
|
| 96 |
+} |
|
| 97 |
+ |
|
| 93 | 98 |
// IsCpusetCpusAvailable returns `true` if the provided string set is contained |
| 94 | 99 |
// in cgroup's cpuset.cpus set, `false` otherwise. |
| 95 | 100 |
// If error is not nil a parsing error occurred. |
| ... | ... |
@@ -44,6 +44,7 @@ func New(quiet bool) *SysInfo {
|
| 44 | 44 |
sysInfo.cgroupCPUInfo = checkCgroupCPU(cgMounts, quiet) |
| 45 | 45 |
sysInfo.cgroupBlkioInfo = checkCgroupBlkioInfo(cgMounts, quiet) |
| 46 | 46 |
sysInfo.cgroupCpusetInfo = checkCgroupCpusetInfo(cgMounts, quiet) |
| 47 |
+ sysInfo.cgroupPids = checkCgroupPids(quiet) |
|
| 47 | 48 |
} |
| 48 | 49 |
|
| 49 | 50 |
_, ok := cgMounts["devices"] |
| ... | ... |
@@ -216,6 +217,21 @@ func checkCgroupCpusetInfo(cgMounts map[string]string, quiet bool) cgroupCpusetI |
| 216 | 216 |
} |
| 217 | 217 |
} |
| 218 | 218 |
|
| 219 |
+// checkCgroupPids reads the pids information from the pids cgroup mount point. |
|
| 220 |
+func checkCgroupPids(quiet bool) cgroupPids {
|
|
| 221 |
+ _, err := cgroups.FindCgroupMountpoint("pids")
|
|
| 222 |
+ if err != nil {
|
|
| 223 |
+ if !quiet {
|
|
| 224 |
+ logrus.Warn(err) |
|
| 225 |
+ } |
|
| 226 |
+ return cgroupPids{}
|
|
| 227 |
+ } |
|
| 228 |
+ |
|
| 229 |
+ return cgroupPids{
|
|
| 230 |
+ PidsLimit: true, |
|
| 231 |
+ } |
|
| 232 |
+} |
|
| 233 |
+ |
|
| 219 | 234 |
func cgroupEnabled(mountPoint, name string) bool {
|
| 220 | 235 |
_, err := os.Stat(path.Join(mountPoint, name)) |
| 221 | 236 |
return err == nil |
| ... | ... |
@@ -85,6 +85,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*container.Config, *container.Host |
| 85 | 85 |
flIPv4Address = cmd.String([]string{"-ip"}, "", "Container IPv4 address (e.g. 172.30.100.104)")
|
| 86 | 86 |
flIPv6Address = cmd.String([]string{"-ip6"}, "", "Container IPv6 address (e.g. 2001:db8::33)")
|
| 87 | 87 |
flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use")
|
| 88 |
+ flPidsLimit = cmd.Int64([]string{"-pids-limit"}, 0, "Tune container pids limit (set -1 for unlimited)")
|
|
| 88 | 89 |
flRestartPolicy = cmd.String([]string{"-restart"}, "no", "Restart policy to apply when a container exits")
|
| 89 | 90 |
flReadonlyRootfs = cmd.Bool([]string{"-read-only"}, false, "Mount the container's root filesystem as read only")
|
| 90 | 91 |
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 |
| 343 | 343 |
CpusetCpus: *flCpusetCpus, |
| 344 | 344 |
CpusetMems: *flCpusetMems, |
| 345 | 345 |
CPUQuota: *flCPUQuota, |
| 346 |
+ PidsLimit: *flPidsLimit, |
|
| 346 | 347 |
BlkioWeight: *flBlkioWeight, |
| 347 | 348 |
BlkioWeightDevice: flBlkioWeightDevice.GetList(), |
| 348 | 349 |
BlkioDeviceReadBps: flDeviceReadBps.GetList(), |