Browse code

Merge pull request #18697 from jfrazelle/pids-cgroup

Add PIDs cgroup support to Docker

David Calavera authored on 2016/03/09 07:03:36
Showing 21 changed files
... ...
@@ -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)"
... ...
@@ -1637,6 +1637,7 @@ _docker_run() {
1637 1637
 		--net-alias
1638 1638
 		--oom-score-adj
1639 1639
 		--pid
1640
+		--pids-limit
1640 1641
 		--publish -p
1641 1642
 		--restart
1642 1643
 		--security-opt
... ...
@@ -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
 
... ...
@@ -61,6 +61,10 @@ func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
61 61
 			Stats:    mem.Stats,
62 62
 			Failcnt:  mem.Usage.Failcnt,
63 63
 		}
64
+		pids := cs.PidsStats
65
+		s.PidsStats = types.PidsStats{
66
+			Current: pids.Current,
67
+		}
64 68
 	}
65 69
 
66 70
 	return s
... ...
@@ -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(),