Add API support for PidsLimit on services
| ... | ... |
@@ -97,9 +97,10 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
|
| 97 | 97 |
} |
| 98 | 98 |
if versions.LessThan(cliVersion, "1.41") {
|
| 99 | 99 |
if service.TaskTemplate.ContainerSpec != nil {
|
| 100 |
- // Capabilities for docker swarm services weren't supported before |
|
| 101 |
- // API version 1.41 |
|
| 100 |
+ // Capabilities and PidsLimit for docker swarm services weren't |
|
| 101 |
+ // supported before API version 1.41 |
|
| 102 | 102 |
service.TaskTemplate.ContainerSpec.Capabilities = nil |
| 103 |
+ service.TaskTemplate.ContainerSpec.PidsLimit = 0 |
|
| 103 | 104 |
} |
| 104 | 105 |
|
| 105 | 106 |
// jobs were only introduced in API version 1.41. Nil out both Job |
| ... | ... |
@@ -17,7 +17,8 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
| 17 | 17 |
spec := &swarm.ServiceSpec{
|
| 18 | 18 |
TaskTemplate: swarm.TaskSpec{
|
| 19 | 19 |
ContainerSpec: &swarm.ContainerSpec{
|
| 20 |
- Sysctls: expectedSysctls, |
|
| 20 |
+ Sysctls: expectedSysctls, |
|
| 21 |
+ PidsLimit: 300, |
|
| 21 | 22 |
Privileges: &swarm.Privileges{
|
| 22 | 23 |
CredentialSpec: &swarm.CredentialSpec{
|
| 23 | 24 |
Config: "someconfig", |
| ... | ... |
@@ -49,11 +50,18 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
| 49 | 49 |
// first, does calling this with a later version correctly NOT strip |
| 50 | 50 |
// fields? do the later version first, so we can reuse this spec in the |
| 51 | 51 |
// next test. |
| 52 |
- adjustForAPIVersion("1.40", spec)
|
|
| 52 |
+ adjustForAPIVersion("1.41", spec)
|
|
| 53 | 53 |
if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls) {
|
| 54 | 54 |
t.Error("Sysctls was stripped from spec")
|
| 55 | 55 |
} |
| 56 | 56 |
|
| 57 |
+ if spec.TaskTemplate.ContainerSpec.PidsLimit == 0 {
|
|
| 58 |
+ t.Error("PidsLimit was stripped from spec")
|
|
| 59 |
+ } |
|
| 60 |
+ if spec.TaskTemplate.ContainerSpec.PidsLimit != 300 {
|
|
| 61 |
+ t.Error("PidsLimit did not preserve the value from spec")
|
|
| 62 |
+ } |
|
| 63 |
+ |
|
| 57 | 64 |
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "someconfig" {
|
| 58 | 65 |
t.Error("CredentialSpec.Config field was stripped from spec")
|
| 59 | 66 |
} |
| ... | ... |
@@ -72,6 +80,10 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
| 72 | 72 |
t.Error("Sysctls was not stripped from spec")
|
| 73 | 73 |
} |
| 74 | 74 |
|
| 75 |
+ if spec.TaskTemplate.ContainerSpec.PidsLimit != 0 {
|
|
| 76 |
+ t.Error("PidsLimit was not stripped from spec")
|
|
| 77 |
+ } |
|
| 78 |
+ |
|
| 75 | 79 |
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "" {
|
| 76 | 80 |
t.Error("CredentialSpec.Config field was not stripped from spec")
|
| 77 | 81 |
} |
| ... | ... |
@@ -2967,6 +2967,13 @@ definitions: |
| 2967 | 2967 |
description: "Run an init inside the container that forwards signals and reaps processes. This field is omitted if empty, and the default (as configured on the daemon) is used." |
| 2968 | 2968 |
type: "boolean" |
| 2969 | 2969 |
x-nullable: true |
| 2970 |
+ PidsLimit: |
|
| 2971 |
+ description: | |
|
| 2972 |
+ Tune a container's PIDs limit. Set `0` for unlimited. |
|
| 2973 |
+ type: "integer" |
|
| 2974 |
+ format: "int64" |
|
| 2975 |
+ default: 0 |
|
| 2976 |
+ example: 100 |
|
| 2970 | 2977 |
Sysctls: |
| 2971 | 2978 |
description: | |
| 2972 | 2979 |
Set kernel namedspaced parameters (sysctls) in the container. |
| ... | ... |
@@ -72,6 +72,7 @@ type ContainerSpec struct {
|
| 72 | 72 |
Secrets []*SecretReference `json:",omitempty"` |
| 73 | 73 |
Configs []*ConfigReference `json:",omitempty"` |
| 74 | 74 |
Isolation container.Isolation `json:",omitempty"` |
| 75 |
+ PidsLimit int64 `json:",omitempty"` |
|
| 75 | 76 |
Sysctls map[string]string `json:",omitempty"` |
| 76 | 77 |
Capabilities []string `json:",omitempty"` |
| 77 | 78 |
} |
| ... | ... |
@@ -36,6 +36,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
|
| 36 | 36 |
Configs: configReferencesFromGRPC(c.Configs), |
| 37 | 37 |
Isolation: IsolationFromGRPC(c.Isolation), |
| 38 | 38 |
Init: initFromGRPC(c.Init), |
| 39 |
+ PidsLimit: c.PidsLimit, |
|
| 39 | 40 |
Sysctls: c.Sysctls, |
| 40 | 41 |
Capabilities: c.Capabilities, |
| 41 | 42 |
} |
| ... | ... |
@@ -263,6 +264,7 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
| 263 | 263 |
Secrets: secretReferencesToGRPC(c.Secrets), |
| 264 | 264 |
Isolation: isolationToGRPC(c.Isolation), |
| 265 | 265 |
Init: initToGRPC(c.Init), |
| 266 |
+ PidsLimit: c.PidsLimit, |
|
| 266 | 267 |
Sysctls: c.Sysctls, |
| 267 | 268 |
Capabilities: c.Capabilities, |
| 268 | 269 |
} |
| ... | ... |
@@ -431,6 +431,12 @@ func (c *containerConfig) volumeCreateRequest(mount *api.Mount) *volumetypes.Vol |
| 431 | 431 |
func (c *containerConfig) resources() enginecontainer.Resources {
|
| 432 | 432 |
resources := enginecontainer.Resources{}
|
| 433 | 433 |
|
| 434 |
+ // set pids limit |
|
| 435 |
+ pidsLimit := c.spec().PidsLimit |
|
| 436 |
+ if pidsLimit > 0 {
|
|
| 437 |
+ resources.PidsLimit = &pidsLimit |
|
| 438 |
+ } |
|
| 439 |
+ |
|
| 434 | 440 |
// If no limits are specified let the engine use its defaults. |
| 435 | 441 |
// |
| 436 | 442 |
// TODO(aluzzardi): We might want to set some limits anyway otherwise |
| ... | ... |
@@ -30,6 +30,12 @@ keywords: "API, Docker, rcli, REST, documentation" |
| 30 | 30 |
* `POST /services/{id}/update` now accepts `Capabilities` as part of the `ContainerSpec`.
|
| 31 | 31 |
* `GET /tasks` now returns `Capabilities` as part of the `ContainerSpec`. |
| 32 | 32 |
* `GET /tasks/{id}` now returns `Capabilities` as part of the `ContainerSpec`.
|
| 33 |
+* `GET /services` now returns `PidsLimit` as part of the `ContainerSpec`. |
|
| 34 |
+* `GET /services/{id}` now returns `PidsLimit` as part of the `ContainerSpec`.
|
|
| 35 |
+* `POST /services/create` now accepts `PidsLimit` as part of the `ContainerSpec`. |
|
| 36 |
+* `POST /services/{id}/update` now accepts `PidsLimit` as part of the `ContainerSpec`.
|
|
| 37 |
+* `GET /tasks` now returns `PidsLimit` as part of the `ContainerSpec`. |
|
| 38 |
+* `GET /tasks/{id}` now returns `PidsLimit` as part of the `ContainerSpec`.
|
|
| 33 | 39 |
* `POST /containers/create` on Linux now accepts the `HostConfig.CgroupnsMode` property. |
| 34 | 40 |
Set the property to `host` to create the container in the daemon's cgroup namespace, or |
| 35 | 41 |
`private` to create the container in its own private cgroup namespace. The per-daemon |
| ... | ... |
@@ -196,6 +196,14 @@ func ServiceWithCapabilities(Capabilities []string) ServiceSpecOpt {
|
| 196 | 196 |
} |
| 197 | 197 |
} |
| 198 | 198 |
|
| 199 |
+// ServiceWithPidsLimit sets the PidsLimit option of the service's ContainerSpec. |
|
| 200 |
+func ServiceWithPidsLimit(limit int64) ServiceSpecOpt {
|
|
| 201 |
+ return func(spec *swarmtypes.ServiceSpec) {
|
|
| 202 |
+ ensureContainerSpec(spec) |
|
| 203 |
+ spec.TaskTemplate.ContainerSpec.PidsLimit = limit |
|
| 204 |
+ } |
|
| 205 |
+} |
|
| 206 |
+ |
|
| 199 | 207 |
// GetRunningTasks gets the list of running tasks for a service |
| 200 | 208 |
func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
|
| 201 | 209 |
t.Helper() |
| ... | ... |
@@ -5,7 +5,9 @@ import ( |
| 5 | 5 |
"testing" |
| 6 | 6 |
|
| 7 | 7 |
"github.com/docker/docker/api/types" |
| 8 |
+ "github.com/docker/docker/api/types/filters" |
|
| 8 | 9 |
swarmtypes "github.com/docker/docker/api/types/swarm" |
| 10 |
+ "github.com/docker/docker/api/types/versions" |
|
| 9 | 11 |
"github.com/docker/docker/client" |
| 10 | 12 |
"github.com/docker/docker/integration/internal/network" |
| 11 | 13 |
"github.com/docker/docker/integration/internal/swarm" |
| ... | ... |
@@ -248,6 +250,91 @@ func TestServiceUpdateNetwork(t *testing.T) {
|
| 248 | 248 |
assert.NilError(t, err) |
| 249 | 249 |
} |
| 250 | 250 |
|
| 251 |
+// TestServiceUpdatePidsLimit tests creating and updating a service with PidsLimit |
|
| 252 |
+func TestServiceUpdatePidsLimit(t *testing.T) {
|
|
| 253 |
+ skip.If( |
|
| 254 |
+ t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"), |
|
| 255 |
+ "setting pidslimit for services is not supported before api v1.41", |
|
| 256 |
+ ) |
|
| 257 |
+ skip.If(t, testEnv.DaemonInfo.OSType != "linux") |
|
| 258 |
+ tests := []struct {
|
|
| 259 |
+ name string |
|
| 260 |
+ pidsLimit int64 |
|
| 261 |
+ expected int64 |
|
| 262 |
+ }{
|
|
| 263 |
+ {
|
|
| 264 |
+ name: "create service with PidsLimit 300", |
|
| 265 |
+ pidsLimit: 300, |
|
| 266 |
+ expected: 300, |
|
| 267 |
+ }, |
|
| 268 |
+ {
|
|
| 269 |
+ name: "unset PidsLimit to 0", |
|
| 270 |
+ pidsLimit: 0, |
|
| 271 |
+ expected: 0, |
|
| 272 |
+ }, |
|
| 273 |
+ {
|
|
| 274 |
+ name: "update PidsLimit to 100", |
|
| 275 |
+ pidsLimit: 100, |
|
| 276 |
+ expected: 100, |
|
| 277 |
+ }, |
|
| 278 |
+ } |
|
| 279 |
+ |
|
| 280 |
+ defer setupTest(t)() |
|
| 281 |
+ d := swarm.NewSwarm(t, testEnv) |
|
| 282 |
+ defer d.Stop(t) |
|
| 283 |
+ cli := d.NewClientT(t) |
|
| 284 |
+ defer func() { _ = cli.Close() }()
|
|
| 285 |
+ ctx := context.Background() |
|
| 286 |
+ var ( |
|
| 287 |
+ serviceID string |
|
| 288 |
+ service swarmtypes.Service |
|
| 289 |
+ ) |
|
| 290 |
+ for i, tc := range tests {
|
|
| 291 |
+ t.Run(tc.name, func(t *testing.T) {
|
|
| 292 |
+ if i == 0 {
|
|
| 293 |
+ serviceID = swarm.CreateService(t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit)) |
|
| 294 |
+ } else {
|
|
| 295 |
+ service = getService(t, cli, serviceID) |
|
| 296 |
+ service.Spec.TaskTemplate.ContainerSpec.PidsLimit = tc.pidsLimit |
|
| 297 |
+ _, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
|
|
| 298 |
+ assert.NilError(t, err) |
|
| 299 |
+ poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll) |
|
| 300 |
+ } |
|
| 301 |
+ |
|
| 302 |
+ poll.WaitOn(t, swarm.RunningTasksCount(cli, serviceID, 1), swarm.ServicePoll) |
|
| 303 |
+ service = getService(t, cli, serviceID) |
|
| 304 |
+ container := getServiceTaskContainer(ctx, t, cli, serviceID) |
|
| 305 |
+ assert.Equal(t, service.Spec.TaskTemplate.ContainerSpec.PidsLimit, tc.expected) |
|
| 306 |
+ if tc.expected == 0 {
|
|
| 307 |
+ if container.HostConfig.Resources.PidsLimit != nil {
|
|
| 308 |
+ t.Fatalf("Expected container.HostConfig.Resources.PidsLimit to be nil")
|
|
| 309 |
+ } |
|
| 310 |
+ } else {
|
|
| 311 |
+ assert.Assert(t, container.HostConfig.Resources.PidsLimit != nil) |
|
| 312 |
+ assert.Equal(t, *container.HostConfig.Resources.PidsLimit, tc.expected) |
|
| 313 |
+ } |
|
| 314 |
+ }) |
|
| 315 |
+ } |
|
| 316 |
+ |
|
| 317 |
+ err := cli.ServiceRemove(ctx, serviceID) |
|
| 318 |
+ assert.NilError(t, err) |
|
| 319 |
+} |
|
| 320 |
+ |
|
| 321 |
+func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) types.ContainerJSON {
|
|
| 322 |
+ t.Helper() |
|
| 323 |
+ filter := filters.NewArgs() |
|
| 324 |
+ filter.Add("service", serviceID)
|
|
| 325 |
+ filter.Add("desired-state", "running")
|
|
| 326 |
+ tasks, err := cli.TaskList(ctx, types.TaskListOptions{Filters: filter})
|
|
| 327 |
+ assert.NilError(t, err) |
|
| 328 |
+ assert.Assert(t, len(tasks) > 0) |
|
| 329 |
+ |
|
| 330 |
+ ctr, err := cli.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID) |
|
| 331 |
+ assert.NilError(t, err) |
|
| 332 |
+ assert.Equal(t, ctr.State.Running, true) |
|
| 333 |
+ return ctr |
|
| 334 |
+} |
|
| 335 |
+ |
|
| 251 | 336 |
func getService(t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service {
|
| 252 | 337 |
t.Helper() |
| 253 | 338 |
service, _, err := cli.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|