mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
Add support for sysctl options in services
Adds support for sysctl options in docker services. * Adds API plumbing for creating services with sysctl options set. * Adds swagger.yaml documentation for new API field. * Updates the API version history document. * Changes executor package to make use of the Sysctls field on objects * Includes integration test to verify that new behavior works. Essentially, everything needed to support the equivalent of docker run's `--sysctl` option except the CLI. Includes a vendoring of swarmkit for proto changes to support the new behavior. Signed-off-by: Drew Erny <drew.erny@docker.com>
This commit is contained in:
parent
d6a7c22f7b
commit
14da20f5e7
8 changed files with 149 additions and 4 deletions
|
@ -182,8 +182,17 @@ func (sr *swarmRouter) createService(ctx context.Context, w http.ResponseWriter,
|
|||
encodedAuth := r.Header.Get("X-Registry-Auth")
|
||||
cliVersion := r.Header.Get("version")
|
||||
queryRegistry := false
|
||||
if cliVersion != "" && versions.LessThan(cliVersion, "1.30") {
|
||||
queryRegistry = true
|
||||
if cliVersion != "" {
|
||||
if versions.LessThan(cliVersion, "1.30") {
|
||||
queryRegistry = true
|
||||
}
|
||||
if versions.LessThan(cliVersion, "1.39") {
|
||||
if service.TaskTemplate.ContainerSpec != nil {
|
||||
// Sysctls for docker swarm services weren't supported before
|
||||
// API version 1.39
|
||||
service.TaskTemplate.ContainerSpec.Sysctls = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := sr.backend.CreateService(service, encodedAuth, queryRegistry)
|
||||
|
@ -216,8 +225,17 @@ func (sr *swarmRouter) updateService(ctx context.Context, w http.ResponseWriter,
|
|||
flags.Rollback = r.URL.Query().Get("rollback")
|
||||
cliVersion := r.Header.Get("version")
|
||||
queryRegistry := false
|
||||
if cliVersion != "" && versions.LessThan(cliVersion, "1.30") {
|
||||
queryRegistry = true
|
||||
if cliVersion != "" {
|
||||
if versions.LessThan(cliVersion, "1.30") {
|
||||
queryRegistry = true
|
||||
}
|
||||
if versions.LessThan(cliVersion, "1.39") {
|
||||
if service.TaskTemplate.ContainerSpec != nil {
|
||||
// Sysctls for docker swarm services weren't supported before
|
||||
// API version 1.39
|
||||
service.TaskTemplate.ContainerSpec.Sysctls = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := sr.backend.UpdateService(vars["id"], version, service, flags, queryRegistry)
|
||||
|
|
|
@ -2750,6 +2750,18 @@ 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
|
||||
Sysctls:
|
||||
description: |
|
||||
Set kernel namedspaced parameters (sysctls) in the container.
|
||||
The Sysctls option on services accepts the same sysctls as the
|
||||
are supported on containers. Note that while the same sysctls are
|
||||
supported, no guarantees or checks are made about their
|
||||
suitability for a clustered environment, and it's up to the user
|
||||
to determine whether a given sysctl will work properly in a
|
||||
Service.
|
||||
type: "object"
|
||||
additionalProperties:
|
||||
type: "string"
|
||||
NetworkAttachmentSpec:
|
||||
description: |
|
||||
Read-only spec type for non-swarm containers attached to swarm overlay
|
||||
|
|
|
@ -71,4 +71,5 @@ type ContainerSpec struct {
|
|||
Secrets []*SecretReference `json:",omitempty"`
|
||||
Configs []*ConfigReference `json:",omitempty"`
|
||||
Isolation container.Isolation `json:",omitempty"`
|
||||
Sysctls map[string]string `json:",omitempty"`
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
|
|||
Configs: configReferencesFromGRPC(c.Configs),
|
||||
Isolation: IsolationFromGRPC(c.Isolation),
|
||||
Init: initFromGRPC(c.Init),
|
||||
Sysctls: c.Sysctls,
|
||||
}
|
||||
|
||||
if c.DNSConfig != nil {
|
||||
|
@ -251,6 +252,7 @@ func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
|
|||
Configs: configReferencesToGRPC(c.Configs),
|
||||
Isolation: isolationToGRPC(c.Isolation),
|
||||
Init: initToGRPC(c.Init),
|
||||
Sysctls: c.Sysctls,
|
||||
}
|
||||
|
||||
if c.DNSConfig != nil {
|
||||
|
|
|
@ -364,6 +364,7 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
|
|||
ReadonlyRootfs: c.spec().ReadOnly,
|
||||
Isolation: c.isolation(),
|
||||
Init: c.init(),
|
||||
Sysctls: c.spec().Sysctls,
|
||||
}
|
||||
|
||||
if c.spec().DNSConfig != nil {
|
||||
|
|
|
@ -30,6 +30,12 @@ keywords: "API, Docker, rcli, REST, documentation"
|
|||
on the node.label. The format of the label filter is `node.label=<key>`/`node.label=<key>=<value>`
|
||||
to return those with the specified labels, or `node.label!=<key>`/`node.label!=<key>=<value>`
|
||||
to return those without the specified labels.
|
||||
* `GET /services` now returns `Sysctls` as part of the `ContainerSpec`.
|
||||
* `GET /services/{id}` now returns `Sysctls` as part of the `ContainerSpec`.
|
||||
* `POST /services/create` now accepts `Sysctls` as part of the `ContainerSpec`.
|
||||
* `POST /services/{id}/update` now accepts `Sysctls` as part of the `ContainerSpec`.
|
||||
* `GET /tasks` now returns `Sysctls` as part of the `ContainerSpec`.
|
||||
* `GET /tasks/{id}` now returns `Sysctls` as part of the `ContainerSpec`.
|
||||
|
||||
## V1.38 API changes
|
||||
|
||||
|
|
|
@ -159,6 +159,14 @@ func ServiceWithEndpoint(endpoint *swarmtypes.EndpointSpec) ServiceSpecOpt {
|
|||
}
|
||||
}
|
||||
|
||||
// ServiceWithSysctls sets the Sysctls option of the service's ContainerSpec.
|
||||
func ServiceWithSysctls(sysctls map[string]string) ServiceSpecOpt {
|
||||
return func(spec *swarmtypes.ServiceSpec) {
|
||||
ensureContainerSpec(spec)
|
||||
spec.TaskTemplate.ContainerSpec.Sysctls = sysctls
|
||||
}
|
||||
}
|
||||
|
||||
// GetRunningTasks gets the list of running tasks for a service
|
||||
func GetRunningTasks(t *testing.T, d *daemon.Daemon, serviceID string) []swarmtypes.Task {
|
||||
t.Helper()
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"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"
|
||||
|
@ -17,6 +18,7 @@ import (
|
|||
"gotest.tools/assert"
|
||||
is "gotest.tools/assert/cmp"
|
||||
"gotest.tools/poll"
|
||||
"gotest.tools/skip"
|
||||
)
|
||||
|
||||
func TestServiceCreateInit(t *testing.T) {
|
||||
|
@ -309,6 +311,101 @@ func TestCreateServiceConfigFileMode(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
}
|
||||
|
||||
// TestServiceCreateSysctls tests that a service created with sysctl options in
|
||||
// the ContainerSpec correctly applies those options.
|
||||
//
|
||||
// To test this, we're going to create a service with the sysctl option
|
||||
//
|
||||
// {"net.ipv4.ip_nonlocal_bind": "0"}
|
||||
//
|
||||
// We'll get the service's tasks to get the container ID, and then we'll
|
||||
// inspect the container. If the output of the container inspect contains the
|
||||
// sysctl option with the correct value, we can assume that the sysctl has been
|
||||
// plumbed correctly.
|
||||
//
|
||||
// Next, we'll remove that service and create a new service with that option
|
||||
// set to 1. This means that no matter what the default is, we can be confident
|
||||
// that the sysctl option is applying as intended.
|
||||
//
|
||||
// Additionally, we'll do service and task inspects to verify that the inspect
|
||||
// output includes the desired sysctl option.
|
||||
//
|
||||
// We're using net.ipv4.ip_nonlocal_bind because it's something that I'm fairly
|
||||
// confident won't be modified by the container runtime, and won't blow
|
||||
// anything up in the test environment
|
||||
func TestCreateServiceSysctls(t *testing.T) {
|
||||
skip.If(
|
||||
t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.39"),
|
||||
"setting service sysctls is unsupported before api v1.39",
|
||||
)
|
||||
|
||||
defer setupTest(t)()
|
||||
d := swarm.NewSwarm(t, testEnv)
|
||||
defer d.Stop(t)
|
||||
client := d.NewClientT(t)
|
||||
defer client.Close()
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// run thie block twice, so that no matter what the default value of
|
||||
// net.ipv4.ip_nonlocal_bind is, we can verify that setting the sysctl
|
||||
// options works
|
||||
for _, expected := range []string{"0", "1"} {
|
||||
|
||||
// store the map we're going to be using everywhere.
|
||||
expectedSysctls := map[string]string{"net.ipv4.ip_nonlocal_bind": expected}
|
||||
|
||||
// Create the service with the sysctl options
|
||||
var instances uint64 = 1
|
||||
serviceID := swarm.CreateService(t, d,
|
||||
swarm.ServiceWithSysctls(expectedSysctls),
|
||||
)
|
||||
|
||||
// wait for the service to converge to 1 running task as expected
|
||||
poll.WaitOn(t, serviceRunningTasksCount(client, serviceID, instances))
|
||||
|
||||
// we're going to check 3 things:
|
||||
//
|
||||
// 1. Does the container, when inspected, have the sysctl option set?
|
||||
// 2. Does the task have the sysctl in the spec?
|
||||
// 3. Does the service have the sysctl in the spec?
|
||||
//
|
||||
// if all 3 of these things are true, we know that the sysctl has been
|
||||
// plumbed correctly through the engine.
|
||||
//
|
||||
// We don't actually have to get inside the container and check its
|
||||
// logs or anything. If we see the sysctl set on the container inspect,
|
||||
// we know that the sysctl is plumbed correctly. everything below that
|
||||
// level has been tested elsewhere. (thanks @thaJeztah, because an
|
||||
// earlier version of this test had to get container logs and was much
|
||||
// more complex)
|
||||
|
||||
// get all of the tasks of the service, so we can get the container
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("service", serviceID)
|
||||
tasks, err := client.TaskList(ctx, types.TaskListOptions{
|
||||
Filters: filter,
|
||||
})
|
||||
assert.NilError(t, err)
|
||||
assert.Check(t, is.Equal(len(tasks), 1))
|
||||
|
||||
// verify that the container has the sysctl option set
|
||||
ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t, ctnr.HostConfig.Sysctls, expectedSysctls)
|
||||
|
||||
// verify that the task has the sysctl option set in the task object
|
||||
assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Sysctls, expectedSysctls)
|
||||
|
||||
// verify that the service also has the sysctl set in the spec.
|
||||
service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
|
||||
assert.NilError(t, err)
|
||||
assert.DeepEqual(t,
|
||||
service.Spec.TaskTemplate.ContainerSpec.Sysctls, expectedSysctls,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func serviceRunningTasksCount(client client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result {
|
||||
return func(log poll.LogT) poll.Result {
|
||||
filter := filters.NewArgs()
|
||||
|
|
Loading…
Reference in a new issue