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:
commit
c8e31dc2f2
9 changed files with 134 additions and 4 deletions
|
@ -97,9 +97,10 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
|
||||||
}
|
}
|
||||||
if versions.LessThan(cliVersion, "1.41") {
|
if versions.LessThan(cliVersion, "1.41") {
|
||||||
if service.TaskTemplate.ContainerSpec != nil {
|
if service.TaskTemplate.ContainerSpec != nil {
|
||||||
// Capabilities for docker swarm services weren't supported before
|
// Capabilities and PidsLimit for docker swarm services weren't
|
||||||
// API version 1.41
|
// supported before API version 1.41
|
||||||
service.TaskTemplate.ContainerSpec.Capabilities = nil
|
service.TaskTemplate.ContainerSpec.Capabilities = nil
|
||||||
|
service.TaskTemplate.ContainerSpec.PidsLimit = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// jobs were only introduced in API version 1.41. Nil out both Job
|
// jobs were only introduced in API version 1.41. Nil out both Job
|
||||||
|
|
|
@ -17,7 +17,8 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
||||||
spec := &swarm.ServiceSpec{
|
spec := &swarm.ServiceSpec{
|
||||||
TaskTemplate: swarm.TaskSpec{
|
TaskTemplate: swarm.TaskSpec{
|
||||||
ContainerSpec: &swarm.ContainerSpec{
|
ContainerSpec: &swarm.ContainerSpec{
|
||||||
Sysctls: expectedSysctls,
|
Sysctls: expectedSysctls,
|
||||||
|
PidsLimit: 300,
|
||||||
Privileges: &swarm.Privileges{
|
Privileges: &swarm.Privileges{
|
||||||
CredentialSpec: &swarm.CredentialSpec{
|
CredentialSpec: &swarm.CredentialSpec{
|
||||||
Config: "someconfig",
|
Config: "someconfig",
|
||||||
|
@ -49,11 +50,18 @@ func TestAdjustForAPIVersion(t *testing.T) {
|
||||||
// first, does calling this with a later version correctly NOT strip
|
// 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
|
// fields? do the later version first, so we can reuse this spec in the
|
||||||
// next test.
|
// next test.
|
||||||
adjustForAPIVersion("1.40", spec)
|
adjustForAPIVersion("1.41", spec)
|
||||||
if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls) {
|
if !reflect.DeepEqual(spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls) {
|
||||||
t.Error("Sysctls was stripped from spec")
|
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" {
|
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "someconfig" {
|
||||||
t.Error("CredentialSpec.Config field was stripped from spec")
|
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")
|
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 != "" {
|
if spec.TaskTemplate.ContainerSpec.Privileges.CredentialSpec.Config != "" {
|
||||||
t.Error("CredentialSpec.Config field was not stripped from spec")
|
t.Error("CredentialSpec.Config field was not stripped from spec")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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."
|
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"
|
type: "boolean"
|
||||||
x-nullable: true
|
x-nullable: true
|
||||||
|
PidsLimit:
|
||||||
|
description: |
|
||||||
|
Tune a container's PIDs limit. Set `0` for unlimited.
|
||||||
|
type: "integer"
|
||||||
|
format: "int64"
|
||||||
|
default: 0
|
||||||
|
example: 100
|
||||||
Sysctls:
|
Sysctls:
|
||||||
description: |
|
description: |
|
||||||
Set kernel namedspaced parameters (sysctls) in the container.
|
Set kernel namedspaced parameters (sysctls) in the container.
|
||||||
|
|
|
@ -72,6 +72,7 @@ type ContainerSpec struct {
|
||||||
Secrets []*SecretReference `json:",omitempty"`
|
Secrets []*SecretReference `json:",omitempty"`
|
||||||
Configs []*ConfigReference `json:",omitempty"`
|
Configs []*ConfigReference `json:",omitempty"`
|
||||||
Isolation container.Isolation `json:",omitempty"`
|
Isolation container.Isolation `json:",omitempty"`
|
||||||
|
PidsLimit int64 `json:",omitempty"`
|
||||||
Sysctls map[string]string `json:",omitempty"`
|
Sysctls map[string]string `json:",omitempty"`
|
||||||
Capabilities []string `json:",omitempty"`
|
Capabilities []string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
|
||||||
Configs: configReferencesFromGRPC(c.Configs),
|
Configs: configReferencesFromGRPC(c.Configs),
|
||||||
Isolation: IsolationFromGRPC(c.Isolation),
|
Isolation: IsolationFromGRPC(c.Isolation),
|
||||||
Init: initFromGRPC(c.Init),
|
Init: initFromGRPC(c.Init),
|
||||||
|
PidsLimit: c.PidsLimit,
|
||||||
Sysctls: c.Sysctls,
|
Sysctls: c.Sysctls,
|
||||||
Capabilities: c.Capabilities,
|
Capabilities: c.Capabilities,
|
||||||
}
|
}
|
||||||
|
@ -263,6 +264,7 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
||||||
Secrets: secretReferencesToGRPC(c.Secrets),
|
Secrets: secretReferencesToGRPC(c.Secrets),
|
||||||
Isolation: isolationToGRPC(c.Isolation),
|
Isolation: isolationToGRPC(c.Isolation),
|
||||||
Init: initToGRPC(c.Init),
|
Init: initToGRPC(c.Init),
|
||||||
|
PidsLimit: c.PidsLimit,
|
||||||
Sysctls: c.Sysctls,
|
Sysctls: c.Sysctls,
|
||||||
Capabilities: c.Capabilities,
|
Capabilities: c.Capabilities,
|
||||||
}
|
}
|
||||||
|
|
|
@ -431,6 +431,12 @@ func (c *containerConfig) volumeCreateRequest(mount *api.Mount) *volumetypes.Vol
|
||||||
func (c *containerConfig) resources() enginecontainer.Resources {
|
func (c *containerConfig) resources() enginecontainer.Resources {
|
||||||
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.
|
// If no limits are specified let the engine use its defaults.
|
||||||
//
|
//
|
||||||
// TODO(aluzzardi): We might want to set some limits anyway otherwise
|
// TODO(aluzzardi): We might want to set some limits anyway otherwise
|
||||||
|
|
|
@ -30,6 +30,12 @@ keywords: "API, Docker, rcli, REST, documentation"
|
||||||
* `POST /services/{id}/update` now accepts `Capabilities` as part of the `ContainerSpec`.
|
* `POST /services/{id}/update` now accepts `Capabilities` as part of the `ContainerSpec`.
|
||||||
* `GET /tasks` now returns `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 /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.
|
* `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
|
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
|
`private` to create the container in its own private cgroup namespace. The per-daemon
|
||||||
|
|
|
@ -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
|
// GetRunningTasks gets the list of running tasks for a service
|
||||||
func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
|
func GetRunningTasks(t *testing.T, c client.ServiceAPIClient, serviceID string) []swarmtypes.Task {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
|
@ -5,7 +5,9 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
swarmtypes "github.com/docker/docker/api/types/swarm"
|
swarmtypes "github.com/docker/docker/api/types/swarm"
|
||||||
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/docker/docker/integration/internal/network"
|
"github.com/docker/docker/integration/internal/network"
|
||||||
"github.com/docker/docker/integration/internal/swarm"
|
"github.com/docker/docker/integration/internal/swarm"
|
||||||
|
@ -248,6 +250,91 @@ func TestServiceUpdateNetwork(t *testing.T) {
|
||||||
assert.NilError(t, err)
|
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 {
|
func getService(t *testing.T, cli client.ServiceAPIClient, serviceID string) swarmtypes.Service {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
service, _, err := cli.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|
service, _, err := cli.ServiceInspectWithRaw(context.Background(), serviceID, types.ServiceInspectOptions{})
|
||||||
|
|
Loading…
Add table
Reference in a new issue