Browse code

Add pids-limit support in docker update

- Adds updating PidsLimit in UpdateContainer().
- Adds setting PidsLimit in toContainerResources().

Signed-off-by: Sunny Gogoi <indiasuny000@gmail.com>
Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Sunny Gogoi authored on 2017/04/11 20:28:13
Showing 11 changed files
... ...
@@ -460,9 +460,10 @@ definitions:
460 460
         type: "boolean"
461 461
         x-nullable: true
462 462
       PidsLimit:
463
-        description: "Tune a container's pids limit. Set -1 for unlimited."
463
+        description: "Tune a container's pids limit. Set 0 or -1 for unlimited. Leave null to not change"
464 464
         type: "integer"
465 465
         format: "int64"
466
+        x-nullable: true
466 467
       Ulimits:
467 468
         description: |
468 469
           A list of resource limits to set in the container. For example: `{"Name": "nofile", "Soft": 1024, "Hard": 2048}`"
... ...
@@ -3689,6 +3690,10 @@ definitions:
3689 3689
           See [cpuset(7)](https://www.kernel.org/doc/Documentation/cgroup-v1/cpusets.txt)
3690 3690
         type: "boolean"
3691 3691
         example: true
3692
+      PidsLimit:
3693
+        description: "Indicates if the host kernel has PID limit support enabled."
3694
+        type: "boolean"
3695
+        example: true
3692 3696
       OomKillDisable:
3693 3697
         description: "Indicates if OOM killer disable is supported on the host."
3694 3698
         type: "boolean"
... ...
@@ -4625,7 +4630,7 @@ paths:
4625 4625
                 OomKillDisable: false
4626 4626
                 OomScoreAdj: 500
4627 4627
                 PidMode: ""
4628
-                PidsLimit: -1
4628
+                PidsLimit: 0
4629 4629
                 PortBindings:
4630 4630
                   22/tcp:
4631 4631
                     - HostPort: "11022"
... ...
@@ -334,7 +334,7 @@ type Resources struct {
334 334
 	MemorySwap           int64           // Total memory usage (memory + swap); set `-1` to enable unlimited swap
335 335
 	MemorySwappiness     *int64          // Tuning container memory swappiness behaviour
336 336
 	OomKillDisable       *bool           // Whether to disable OOM Killer or not
337
-	PidsLimit            int64           // Setting pids limit for a container
337
+	PidsLimit            *int64          // Setting pids limit for a container
338 338
 	Ulimits              []*units.Ulimit // List of ulimits to be set in the container
339 339
 
340 340
 	// Applicable to Windows
... ...
@@ -164,6 +164,7 @@ type Info struct {
164 164
 	CPUCfsQuota        bool `json:"CpuCfsQuota"`
165 165
 	CPUShares          bool
166 166
 	CPUSet             bool
167
+	PidsLimit          bool
167 168
 	IPv4Forwarding     bool
168 169
 	BridgeNfIptables   bool
169 170
 	BridgeNfIP6tables  bool `json:"BridgeNfIp6tables"`
... ...
@@ -342,6 +342,9 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
342 342
 	if resources.CPURealtimeRuntime != 0 {
343 343
 		cResources.CPURealtimeRuntime = resources.CPURealtimeRuntime
344 344
 	}
345
+	if resources.PidsLimit != nil {
346
+		cResources.PidsLimit = resources.PidsLimit
347
+	}
345 348
 
346 349
 	// update HostConfig of container
347 350
 	if hostConfig.RestartPolicy.Name != "" {
... ...
@@ -159,7 +159,7 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
159 159
 		resources.MemorySwap != 0 ||
160 160
 		resources.MemorySwappiness != nil ||
161 161
 		resources.OomKillDisable != nil ||
162
-		resources.PidsLimit != 0 ||
162
+		(resources.PidsLimit != nil && *resources.PidsLimit != 0) ||
163 163
 		len(resources.Ulimits) != 0 ||
164 164
 		resources.CPUCount != 0 ||
165 165
 		resources.CPUPercent != 0 ||
... ...
@@ -118,6 +118,19 @@ func getMemoryResources(config containertypes.Resources) *specs.LinuxMemory {
118 118
 	return &memory
119 119
 }
120 120
 
121
+func getPidsLimit(config containertypes.Resources) *specs.LinuxPids {
122
+	limit := &specs.LinuxPids{}
123
+	if config.PidsLimit != nil {
124
+		limit.Limit = *config.PidsLimit
125
+		if limit.Limit == 0 {
126
+			// docker API allows 0 to unset this to be consistent with default values.
127
+			// when updating values, runc requires -1
128
+			limit.Limit = -1
129
+		}
130
+	}
131
+	return limit
132
+}
133
+
121 134
 func getCPUResources(config containertypes.Resources) (*specs.LinuxCPU, error) {
122 135
 	cpu := specs.LinuxCPU{}
123 136
 
... ...
@@ -453,9 +466,10 @@ func verifyPlatformContainerResources(resources *containertypes.Resources, sysIn
453 453
 	if resources.OomKillDisable != nil && *resources.OomKillDisable && resources.Memory == 0 {
454 454
 		warnings = append(warnings, "OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources.")
455 455
 	}
456
-	if resources.PidsLimit != 0 && !sysInfo.PidsLimit {
456
+	if resources.PidsLimit != nil && *resources.PidsLimit != 0 && !sysInfo.PidsLimit {
457 457
 		warnings = append(warnings, "Your kernel does not support pids limit capabilities or the cgroup is not mounted. PIDs limit discarded.")
458
-		resources.PidsLimit = 0
458
+		var limit int64
459
+		resources.PidsLimit = &limit
459 460
 	}
460 461
 
461 462
 	// cpu subsystem checks and adjustments
... ...
@@ -181,7 +181,7 @@ func verifyPlatformContainerResources(resources *containertypes.Resources, isHyp
181 181
 	if resources.OomKillDisable != nil && *resources.OomKillDisable {
182 182
 		return warnings, fmt.Errorf("invalid option: Windows does not support OomKillDisable")
183 183
 	}
184
-	if resources.PidsLimit != 0 {
184
+	if resources.PidsLimit != nil && *resources.PidsLimit != 0 {
185 185
 		return warnings, fmt.Errorf("invalid option: Windows does not support PidsLimit")
186 186
 	}
187 187
 	if len(resources.Ulimits) != 0 {
... ...
@@ -27,6 +27,7 @@ func (daemon *Daemon) fillPlatformInfo(v *types.Info, sysInfo *sysinfo.SysInfo)
27 27
 	v.CPUCfsQuota = sysInfo.CPUCfsQuota
28 28
 	v.CPUShares = sysInfo.CPUShares
29 29
 	v.CPUSet = sysInfo.Cpuset
30
+	v.PidsLimit = sysInfo.PidsLimit
30 31
 	v.Runtimes = daemon.configStore.GetAllRuntimes()
31 32
 	v.DefaultRuntime = daemon.configStore.GetDefaultRuntimeName()
32 33
 	v.InitBinary = daemon.configStore.GetInitPath()
... ...
@@ -72,9 +72,7 @@ func setResources(s *specs.Spec, r containertypes.Resources) error {
72 72
 			ThrottleReadIOPSDevice:  readIOpsDevice,
73 73
 			ThrottleWriteIOPSDevice: writeIOpsDevice,
74 74
 		},
75
-		Pids: &specs.LinuxPids{
76
-			Limit: r.PidsLimit,
77
-		},
75
+		Pids: getPidsLimit(r),
78 76
 	}
79 77
 
80 78
 	if s.Linux.Resources != nil && len(s.Linux.Resources.Devices) > 0 {
... ...
@@ -50,5 +50,6 @@ func toContainerdResources(resources container.Resources) *libcontainerd.Resourc
50 50
 		r.Memory.Swap = &resources.MemorySwap
51 51
 	}
52 52
 
53
+	r.Pids = getPidsLimit(resources)
53 54
 	return &r
54 55
 }
... ...
@@ -7,6 +7,7 @@ import (
7 7
 	"testing"
8 8
 	"time"
9 9
 
10
+	"github.com/docker/docker/api/types"
10 11
 	containertypes "github.com/docker/docker/api/types/container"
11 12
 	"github.com/docker/docker/integration/internal/container"
12 13
 	"gotest.tools/assert"
... ...
@@ -101,3 +102,67 @@ func TestUpdateCPUQuota(t *testing.T) {
101 101
 		assert.Check(t, is.Equal(strconv.FormatInt(test.update, 10), strings.TrimSpace(res.Stdout())))
102 102
 	}
103 103
 }
104
+
105
+func TestUpdatePidsLimit(t *testing.T) {
106
+	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
107
+	skip.If(t, !testEnv.DaemonInfo.PidsLimit)
108
+
109
+	defer setupTest(t)()
110
+	client := testEnv.APIClient()
111
+	ctx := context.Background()
112
+
113
+	cID := container.Run(t, ctx, client)
114
+
115
+	intPtr := func(i int64) *int64 {
116
+		return &i
117
+	}
118
+
119
+	for _, test := range []struct {
120
+		desc     string
121
+		update   *int64
122
+		expect   int64
123
+		expectCg string
124
+	}{
125
+		{desc: "update from none", update: intPtr(32), expect: 32, expectCg: "32"},
126
+		{desc: "no change", update: nil, expectCg: "32"},
127
+		{desc: "update lower", update: intPtr(16), expect: 16, expectCg: "16"},
128
+		{desc: "unset limit", update: intPtr(0), expect: 0, expectCg: "max"},
129
+	} {
130
+		var before types.ContainerJSON
131
+		if test.update == nil {
132
+			var err error
133
+			before, err = client.ContainerInspect(ctx, cID)
134
+			assert.NilError(t, err)
135
+		}
136
+
137
+		t.Run(test.desc, func(t *testing.T) {
138
+			_, err := client.ContainerUpdate(ctx, cID, containertypes.UpdateConfig{
139
+				Resources: containertypes.Resources{
140
+					PidsLimit: test.update,
141
+				},
142
+			})
143
+			assert.NilError(t, err)
144
+
145
+			inspect, err := client.ContainerInspect(ctx, cID)
146
+			assert.NilError(t, err)
147
+			assert.Assert(t, inspect.HostConfig.Resources.PidsLimit != nil)
148
+
149
+			if test.update == nil {
150
+				assert.Assert(t, before.HostConfig.Resources.PidsLimit != nil)
151
+				assert.Equal(t, *before.HostConfig.Resources.PidsLimit, *inspect.HostConfig.Resources.PidsLimit)
152
+			} else {
153
+				assert.Equal(t, *inspect.HostConfig.Resources.PidsLimit, test.expect)
154
+			}
155
+
156
+			ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
157
+			defer cancel()
158
+
159
+			res, err := container.Exec(ctx, client, cID, []string{"cat", "/sys/fs/cgroup/pids/pids.max"})
160
+			assert.NilError(t, err)
161
+			assert.Assert(t, is.Len(res.Stderr(), 0))
162
+
163
+			out := strings.TrimSpace(res.Stdout())
164
+			assert.Equal(t, out, test.expectCg)
165
+		})
166
+	}
167
+}