1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge pull request #39882 from thaJeztah/swarm_pids_limit

Add API support for PidsLimit on services
This commit is contained in:
Sebastiaan van Stijn 2020-04-16 21:02:30 +02:00 committed by GitHub
commit c8e31dc2f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 134 additions and 4 deletions

View file

@ -97,9 +97,10 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
}
if versions.LessThan(cliVersion, "1.41") {
if service.TaskTemplate.ContainerSpec != nil {
// Capabilities for docker swarm services weren't supported before
// API version 1.41
// Capabilities and PidsLimit for docker swarm services weren't
// supported before API version 1.41
service.TaskTemplate.ContainerSpec.Capabilities = nil
service.TaskTemplate.ContainerSpec.PidsLimit = 0
}
// jobs were only introduced in API version 1.41. Nil out both Job

View file

@ -17,7 +17,8 @@ func TestAdjustForAPIVersion(t *testing.T) {
spec := &swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
Sysctls: expectedSysctls,
Sysctls: expectedSysctls,
PidsLimit: 300,
Privileges: &swarm.Privileges{
CredentialSpec: &swarm.CredentialSpec{
Config: "someconfig",
@ -49,11 +50,18 @@ func TestAdjustForAPIVersion(t *testing.T) {
// first, does calling this with a later version correctly NOT strip
// fields? do the later version first, so we can reuse this spec in the
// next test.
adjustForAPIVersion("1.40", spec)
adjustForAPIVersion("1.41", spec)
if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls) {
t.Error("Sysctls was stripped from spec")
}
if spec.TaskTemplate.ContainerSpec.PidsLimit == 0 {
t.Error("PidsLimit was stripped from spec")
}
if spec.TaskTemplate.ContainerSpec.PidsLimit != 300 {
t.Error("PidsLimit did not preserve the value from spec")
}
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "someconfig" {
t.Error("CredentialSpec.Config field was stripped from spec")
}
@ -72,6 +80,10 @@ func TestAdjustForAPIVersion(t *testing.T) {
t.Error("Sysctls was not stripped from spec")
}
if spec.TaskTemplate.ContainerSpec.PidsLimit != 0 {
t.Error("PidsLimit was not stripped from spec")
}
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "" {
t.Error("CredentialSpec.Config field was not stripped from spec")
}

View file

@ -2967,6 +2967,13 @@ definitions:
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."
type: "boolean"
x-nullable: true
PidsLimit:
description: |
Tune a container's PIDs limit. Set `0` for unlimited.
type: "integer"
format: "int64"
default: 0
example: 100
Sysctls:
description: |
Set kernel namedspaced parameters (sysctls) in the container.

View file

@ -72,6 +72,7 @@ type ContainerSpec struct {
Secrets []*SecretReference `json:",omitempty"`
Configs []*ConfigReference `json:",omitempty"`
Isolation container.Isolation `json:",omitempty"`
PidsLimit int64 `json:",omitempty"`
Sysctls map[string]string `json:",omitempty"`
Capabilities []string `json:",omitempty"`
}

View file

@ -36,6 +36,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
Configs: configReferencesFromGRPC(c.Configs),
Isolation: IsolationFromGRPC(c.Isolation),
Init: initFromGRPC(c.Init),
PidsLimit: c.PidsLimit,
Sysctls: c.Sysctls,
Capabilities: c.Capabilities,
}
@ -263,6 +264,7 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
Secrets: secretReferencesToGRPC(c.Secrets),
Isolation: isolationToGRPC(c.Isolation),
Init: initToGRPC(c.Init),
PidsLimit: c.PidsLimit,
Sysctls: c.Sysctls,
Capabilities: c.Capabilities,
}

View file

@ -431,6 +431,12 @@ func (c *containerConfig) volumeCreateRequest(mount *api.Mount) *volumetypes.Vol
func (c *containerConfig) resources() enginecontainer.Resources {
resources := enginecontainer.Resources{}
// set pids limit
pidsLimit := c.spec().PidsLimit
if pidsLimit > 0 {
resources.PidsLimit = &pidsLimit
}
// If no limits are specified let the engine use its defaults.
//
// TODO(aluzzardi): We might want to set some limits anyway otherwise

View file

@ -30,6 +30,12 @@ keywords: "API, Docker, rcli, REST, documentation"
* `POST /services/{id}/update` now accepts `Capabilities` as part of the `ContainerSpec`.
* `GET /tasks` now returns `Capabilities` as part of the `ContainerSpec`.
* `GET /tasks/{id}` now returns `Capabilities` as part of the `ContainerSpec`.
* `GET /services` now returns `PidsLimit` as part of the `ContainerSpec`.
* `GET /services/{id}` now returns `PidsLimit` as part of the `ContainerSpec`.
* `POST /services/create` now accepts `PidsLimit` as part of the `ContainerSpec`.
* `POST /services/{id}/update` now accepts `PidsLimit` as part of the `ContainerSpec`.
* `GET /tasks` now returns `PidsLimit` as part of the `ContainerSpec`.
* `GET /tasks/{id}` now returns `PidsLimit` as part of the `ContainerSpec`.
* `POST /containers/create` on Linux now accepts the `HostConfig.CgroupnsMode` property.
Set the property to `host` to create the container in the daemon's cgroup namespace, or
`private` to create the container in its own private cgroup namespace. The per-daemon

View file

@ -196,6 +196,14 @@ func ServiceWithCapabilities(Capabilities []string) ServiceSpecOpt {
}
}
// ServiceWithPidsLimit sets the PidsLimit option of the service's ContainerSpec.
func ServiceWithPidsLimit(limit int64) ServiceSpecOpt {
return func(spec *swarmtypes.ServiceSpec) {
ensureContainerSpec(spec)
spec.TaskTemplate.ContainerSpec.PidsLimit = limit
}
}
// GetRunningTasks gets the list of running tasks for a service
func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
t.Helper()

View file

@ -5,7 +5,9 @@ import (
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
swarmtypes "github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/docker/docker/client"
"github.com/docker/docker/integration/internal/network"
"github.com/docker/docker/integration/internal/swarm"
@ -248,6 +250,91 @@ func TestServiceUpdateNetwork(t *testing.T) {
assert.NilError(t, err)
}
// TestServiceUpdatePidsLimit tests creating and updating a service with PidsLimit
func TestServiceUpdatePidsLimit(t *testing.T) {
skip.If(
t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
"setting pidslimit for services is not supported before api v1.41",
)
skip.If(t, testEnv.DaemonInfo.OSType != "linux")
tests := []struct {
name string
pidsLimit int64
expected int64
}{
{
name: "create service with PidsLimit 300",
pidsLimit: 300,
expected: 300,
},
{
name: "unset PidsLimit to 0",
pidsLimit: 0,
expected: 0,
},
{
name: "update PidsLimit to 100",
pidsLimit: 100,
expected: 100,
},
}
defer setupTest(t)()
d := swarm.NewSwarm(t, testEnv)
defer d.Stop(t)
cli := d.NewClientT(t)
defer func() { _ = cli.Close() }()
ctx := context.Background()
var (
serviceID string
service swarmtypes.Service
)
for i, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if i == 0 {
serviceID = swarm.CreateService(t, d, swarm.ServiceWithPidsLimit(tc.pidsLimit))
} else {
service = getService(t, cli, serviceID)
service.Spec.TaskTemplate.ContainerSpec.PidsLimit = tc.pidsLimit
_, err := cli.ServiceUpdate(ctx, serviceID, service.Version, service.Spec, types.ServiceUpdateOptions{})
assert.NilError(t, err)
poll.WaitOn(t, serviceIsUpdated(cli, serviceID), swarm.ServicePoll)
}
poll.WaitOn(t, swarm.RunningTasksCount(cli, serviceID, 1), swarm.ServicePoll)
service = getService(t, cli, serviceID)
container := getServiceTaskContainer(ctx, t, cli, serviceID)
assert.Equal(t, service.Spec.TaskTemplate.ContainerSpec.PidsLimit, tc.expected)
if tc.expected == 0 {
if container.HostConfig.Resources.PidsLimit != nil {
t.Fatalf("Expected container.HostConfig.Resources.PidsLimit to be nil")
}
} else {
assert.Assert(t, container.HostConfig.Resources.PidsLimit != nil)
assert.Equal(t, *container.HostConfig.Resources.PidsLimit, tc.expected)
}
})
}
err := cli.ServiceRemove(ctx, serviceID)
assert.NilError(t, err)
}
func getServiceTaskContainer(ctx context.Context, t *testing.T, cli client.APIClient, serviceID string) types.ContainerJSON {
t.Helper()
filter := filters.NewArgs()
filter.Add("service", serviceID)
filter.Add("desired-state", "running")
tasks, err := cli.TaskList(ctx, types.TaskListOptions{Filters: filter})
assert.NilError(t, err)
assert.Assert(t, len(tasks) > 0)
ctr, err := cli.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
assert.NilError(t, err)
assert.Equal(t, ctr.State.Running, true)
return ctr
}
func getService(t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service {
t.Helper()
service, _, err := cli.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})