Przeglądaj źródła

Merge pull request #39173 from olljanat/25885-capabilities-swarm

Add support for capabilities options in services
Kirill Kolyshkin 6 lat temu
rodzic
commit
1d5748d975

+ 7 - 0
api/server/router/swarm/helpers.go

@@ -95,4 +95,11 @@ func adjustForAPIVersion(cliVersion string, service *swarm.ServiceSpec) {
 			service.TaskTemplate.Placement.MaxReplicas = 0
 		}
 	}
+	if versions.LessThan(cliVersion, "1.41") {
+		if service.TaskTemplate.ContainerSpec != nil {
+			// Capabilities for docker swarm services weren't supported before
+			// API version 1.41
+			service.TaskTemplate.ContainerSpec.Capabilities = nil
+		}
+	}
 }

+ 12 - 0
api/swagger.yaml

@@ -2882,6 +2882,18 @@ definitions:
             type: "object"
             additionalProperties:
               type: "string"
+          # This option is not used by Windows containers
+          Capabilities:
+            type: "array"
+            description: |
+              A list of kernel capabilities to be available for container (this overrides the default set).
+            items:
+              type: "string"
+            example:
+              - "CAP_NET_RAW"
+              - "CAP_SYS_ADMIN"
+              - "CAP_SYS_CHROOT"
+              - "CAP_SYSLOG"
       NetworkAttachmentSpec:
         description: |
           Read-only spec type for non-swarm containers attached to swarm overlay

+ 7 - 6
api/types/swarm/container.go

@@ -67,10 +67,11 @@ type ContainerSpec struct {
 	// The format of extra hosts on swarmkit is specified in:
 	// http://man7.org/linux/man-pages/man5/hosts.5.html
 	//    IP_address canonical_hostname [aliases...]
-	Hosts     []string            `json:",omitempty"`
-	DNSConfig *DNSConfig          `json:",omitempty"`
-	Secrets   []*SecretReference  `json:",omitempty"`
-	Configs   []*ConfigReference  `json:",omitempty"`
-	Isolation container.Isolation `json:",omitempty"`
-	Sysctls   map[string]string   `json:",omitempty"`
+	Hosts        []string            `json:",omitempty"`
+	DNSConfig    *DNSConfig          `json:",omitempty"`
+	Secrets      []*SecretReference  `json:",omitempty"`
+	Configs      []*ConfigReference  `json:",omitempty"`
+	Isolation    container.Isolation `json:",omitempty"`
+	Sysctls      map[string]string   `json:",omitempty"`
+	Capabilities []string            `json:",omitempty"`
 }

+ 39 - 37
daemon/cluster/convert/container.go

@@ -18,25 +18,26 @@ func containerSpecFromGRPC(c *swarmapi.ContainerSpec) *types.ContainerSpec {
 		return nil
 	}
 	containerSpec := &types.ContainerSpec{
-		Image:      c.Image,
-		Labels:     c.Labels,
-		Command:    c.Command,
-		Args:       c.Args,
-		Hostname:   c.Hostname,
-		Env:        c.Env,
-		Dir:        c.Dir,
-		User:       c.User,
-		Groups:     c.Groups,
-		StopSignal: c.StopSignal,
-		TTY:        c.TTY,
-		OpenStdin:  c.OpenStdin,
-		ReadOnly:   c.ReadOnly,
-		Hosts:      c.Hosts,
-		Secrets:    secretReferencesFromGRPC(c.Secrets),
-		Configs:    configReferencesFromGRPC(c.Configs),
-		Isolation:  IsolationFromGRPC(c.Isolation),
-		Init:       initFromGRPC(c.Init),
-		Sysctls:    c.Sysctls,
+		Image:        c.Image,
+		Labels:       c.Labels,
+		Command:      c.Command,
+		Args:         c.Args,
+		Hostname:     c.Hostname,
+		Env:          c.Env,
+		Dir:          c.Dir,
+		User:         c.User,
+		Groups:       c.Groups,
+		StopSignal:   c.StopSignal,
+		TTY:          c.TTY,
+		OpenStdin:    c.OpenStdin,
+		ReadOnly:     c.ReadOnly,
+		Hosts:        c.Hosts,
+		Secrets:      secretReferencesFromGRPC(c.Secrets),
+		Configs:      configReferencesFromGRPC(c.Configs),
+		Isolation:    IsolationFromGRPC(c.Isolation),
+		Init:         initFromGRPC(c.Init),
+		Sysctls:      c.Sysctls,
+		Capabilities: c.Capabilities,
 	}
 
 	if c.DNSConfig != nil {
@@ -244,24 +245,25 @@ func configReferencesFromGRPC(sr []*swarmapi.ConfigReference) []*types.ConfigRef
 
 func containerToGRPC(c *types.ContainerSpec) (*swarmapi.ContainerSpec, error) {
 	containerSpec := &swarmapi.ContainerSpec{
-		Image:      c.Image,
-		Labels:     c.Labels,
-		Command:    c.Command,
-		Args:       c.Args,
-		Hostname:   c.Hostname,
-		Env:        c.Env,
-		Dir:        c.Dir,
-		User:       c.User,
-		Groups:     c.Groups,
-		StopSignal: c.StopSignal,
-		TTY:        c.TTY,
-		OpenStdin:  c.OpenStdin,
-		ReadOnly:   c.ReadOnly,
-		Hosts:      c.Hosts,
-		Secrets:    secretReferencesToGRPC(c.Secrets),
-		Isolation:  isolationToGRPC(c.Isolation),
-		Init:       initToGRPC(c.Init),
-		Sysctls:    c.Sysctls,
+		Image:        c.Image,
+		Labels:       c.Labels,
+		Command:      c.Command,
+		Args:         c.Args,
+		Hostname:     c.Hostname,
+		Env:          c.Env,
+		Dir:          c.Dir,
+		User:         c.User,
+		Groups:       c.Groups,
+		StopSignal:   c.StopSignal,
+		TTY:          c.TTY,
+		OpenStdin:    c.OpenStdin,
+		ReadOnly:     c.ReadOnly,
+		Hosts:        c.Hosts,
+		Secrets:      secretReferencesToGRPC(c.Secrets),
+		Isolation:    isolationToGRPC(c.Isolation),
+		Init:         initToGRPC(c.Init),
+		Sysctls:      c.Sysctls,
+		Capabilities: c.Capabilities,
 	}
 
 	if c.DNSConfig != nil {

+ 1 - 0
daemon/cluster/executor/container/container.go

@@ -362,6 +362,7 @@ func (c *containerConfig) hostConfig() *enginecontainer.HostConfig {
 		Isolation:      c.isolation(),
 		Init:           c.init(),
 		Sysctls:        c.spec().Sysctls,
+		Capabilities:   c.spec().Capabilities,
 	}
 
 	if c.spec().DNSConfig != nil {

+ 6 - 0
docs/api/version-history.md

@@ -17,6 +17,12 @@ keywords: "API, Docker, rcli, REST, documentation"
 
 [Docker Engine API v1.41](https://docs.docker.com/engine/api/v1.41/) documentation
 
+* `GET /services` now returns `Capabilities` as part of the `ContainerSpec`.
+* `GET /services/{id}` now returns `Capabilities` as part of the `ContainerSpec`.
+* `POST /services/create` 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/{id}` now  returns `Capabilities` 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

+ 8 - 0
integration/internal/swarm/service.go

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

+ 76 - 0
integration/service/create_test.go

@@ -440,3 +440,79 @@ func TestCreateServiceSysctls(t *testing.T) {
 		)
 	}
 }
+
+// TestServiceCreateCapabilities tests that a service created with capabilities options in
+// the ContainerSpec correctly applies those options.
+//
+// To test this, we're going to create a service with the capabilities option
+//
+//   []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
+//
+// 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
+// capabilities option with the correct value, we can assume that the capabilities has been
+// plumbed correctly.
+func TestCreateServiceCapabilities(t *testing.T) {
+	skip.If(
+		t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.41"),
+		"setting service capabilities is unsupported before api v1.41",
+	)
+
+	defer setupTest(t)()
+	d := swarm.NewSwarm(t, testEnv)
+	defer d.Stop(t)
+	client := d.NewClientT(t)
+	defer client.Close()
+
+	ctx := context.Background()
+
+	// store the map we're going to be using everywhere.
+	expectedCapabilities := []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"}
+
+	// Create the service with the capabilities options
+	var instances uint64 = 1
+	serviceID := swarm.CreateService(t, d,
+		swarm.ServiceWithCapabilities(expectedCapabilities),
+	)
+
+	// wait for the service to converge to 1 running task as expected
+	poll.WaitOn(t, swarm.RunningTasksCount(client, serviceID, instances))
+
+	// we're going to check 3 things:
+	//
+	//   1. Does the container, when inspected, have the capabilities option set?
+	//   2. Does the task have the capabilities in the spec?
+	//   3. Does the service have the capabilities in the spec?
+	//
+	// if all 3 of these things are true, we know that the capabilities 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 capabilities set on the container inspect,
+	// we know that the capabilities is plumbed correctly. everything below that
+	// level has been tested elsewhere.
+
+	// 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 capabilities option set
+	ctnr, err := client.ContainerInspect(ctx, tasks[0].Status.ContainerStatus.ContainerID)
+	assert.NilError(t, err)
+	assert.DeepEqual(t, ctnr.HostConfig.Capabilities, expectedCapabilities)
+
+	// verify that the task has the capabilities option set in the task object
+	assert.DeepEqual(t, tasks[0].Spec.ContainerSpec.Capabilities, expectedCapabilities)
+
+	// verify that the service also has the capabilities set in the spec.
+	service, _, err := client.ServiceInspectWithRaw(ctx, serviceID, types.ServiceInspectOptions{})
+	assert.NilError(t, err)
+	assert.DeepEqual(t,
+		service.Spec.TaskTemplate.ContainerSpec.Capabilities, expectedCapabilities,
+	)
+}